基于LiteOS的多任务串行通信和LCD显示程序Demo
通常在基于系统的应用程序开发时,通信程序和显示程序会不同的线程中来运行,以避免由于通信程序的问题而导致界面程序也一同挂掉的现象。基于LiteOS这种轻量级的系统上的应用开发也会遇到类似的情况。本文使用小熊派开发板,参考IoT Link中的Demo程序,基于LiteOS系统之上,使用LiteOS的多任务管理功能,编写实现了两个任务之间串行通信的Demo程序,并在每个任务中将收发相关的信息使用LCD显示出来。
1. 硬件设计
本文使用了小熊派智慧农业的开发主板。在这个开发主板上,以STM32L431RTC6为主控制器。开发板上连接E53的接口中有UART3的连接端子,如下图中的18和19两个引脚。其中18引脚对应的是STM32L431RTC6的PC4引脚,即UART3_Tx; 19引脚对应的是STM32L431RTC6的PC5引脚,即UART3_Rx。
在其P5端子排中预留了UART2的连接端子,如下图中的5和6引脚。其中5引脚对应的是STM32L431RTC6的PA2引脚,即UART2_Tx;6引脚对应的是STM32L431RTC6的PA3引脚,即UART2_Rx。
Demo程序使用UART2发送数据,UART3接收数据。因此将5引脚和19引脚连接,6引脚和18引脚连接。注意这里的引脚编号是指小熊派开发主板上的相关编号。连接好的硬件如下图所示。
2. Demo程序设计
2.1 Demo程序的工程创建
基于IoT Link Studio进行设计开发。在VS Code中,使用IoT Link Studio,创建基于STM32L431_BearPi_OS_Func的硬件平台的示例工程osal_task_demo。如下图所示。
2.2 程序设计思路
2.2.1 两个任务的创建
在程序中创建两个任务,分别称为任务1和任务2.在任务1中使用UART2发送数据,并将发送的数据显示在LCD上。在任务2中使用UART3接收数据,并将接收的数据显示在LCD上。如下图是创建两个任务的函数。在这个函数中调用了初始化uart2_3_init()函数。
//demo_main函数 //初始化UART2和UART3,并创建了两个任务。这样两个任务就会被LiteOS系统自动调用。 int standard_app_demo_main() { //printf("user task 1 exit!\r\n"); uart2_3_init(); user_task1_id = osal_task_create("user_task1",user_task1_entry,NULL,0x400,NULL,USER_TASK1_PRI); user_task2_id = osal_task_create("user_task2",user_task2_entry,NULL,0x400,NULL,USER_TASK2_PRI); return 0; }
2.2.2 UART2 和 UART3的初始化及相关中断的创建
为了使用UART2和UART3,自定义了初始化UART2和UART3的函数,即UART2_3_init(void)。如下图所示的代码。
//小熊派主板上的UART2和UART3的初始化以及相关中断的创建 //UART2是PA2_Tx,PA3_Rx; UART3是PC4_Tx,PC5_Rx void uart2_3_init(void) { MX_USART2_UART_Init(); //初始化串口 PC4 PC5 波特率9600 MX_USART3_UART_Init(); //初始化串口 PA2 PA3 波特率为9600 LOS_HwiCreate(USART2_IRQn, 6,0,USART2_IRQHandler,NULL); //创建中断 LOS_HwiCreate(USART3_IRQn, 7,0,USART3_IRQHandler,NULL); //创建中断 LiteOS默认的最小的中断优先级数值是7 }
在这个函数中首先调用usart.c中的两个初始化函数,来进行UART2和UART3的初始化。
串行口在通信时两端的波特率、停止位、奇偶校验位等参数设置需要相同。在初始化两个UART时,要注意修改usart.c中的代码确保两个串行口的相关参数是相同的,这样可以避免一些问题产生。如下是usart.c中初始化UART2和UART3的函数代码。
/* USART2 init function */ void MX_USART2_UART_Init(void) { huart2.Instance = USART2; huart2.Init.BaudRate = 9600; huart2.Init.WordLength = UART_WORDLENGTH_8B; huart2.Init.StopBits = UART_STOPBITS_1; huart2.Init.Parity = UART_PARITY_NONE; huart2.Init.Mode = UART_MODE_TX_RX; huart2.Init.HwFlowCtl = UART_HWCONTROL_NONE; huart2.Init.OverSampling = UART_OVERSAMPLING_16; huart2.Init.OneBitSampling = UART_ONE_BIT_SAMPLE_DISABLE; huart2.AdvancedInit.AdvFeatureInit = UART_ADVFEATURE_NO_INIT; if (HAL_UART_Init(&huart2) != HAL_OK) { _Error_Handler(__FILE__, __LINE__); } } /* USART3 init function */ void MX_USART3_UART_Init(void) { huart3.Instance = USART3; huart3.Init.BaudRate = 9600; //115200 huart3.Init.WordLength = UART_WORDLENGTH_8B; huart3.Init.StopBits = UART_STOPBITS_1; huart3.Init.Parity = UART_PARITY_NONE; huart3.Init.Mode = UART_MODE_TX_RX; huart3.Init.HwFlowCtl = UART_HWCONTROL_NONE; huart3.Init.OverSampling = UART_OVERSAMPLING_16; huart3.Init.OneBitSampling = UART_ONE_BIT_SAMPLE_DISABLE; huart3.AdvancedInit.AdvFeatureInit = UART_ADVFEATURE_NO_INIT; if (HAL_UART_Init(&huart3) != HAL_OK) { _Error_Handler(__FILE__, __LINE__); } }
然后调用
LOS_HwiCreate(USART2_IRQn, 6,0,USART2_IRQHandler,NULL); //创建中断 LOS_HwiCreate(USART3_IRQn, 7,0,USART3_IRQHandler,NULL); //创建中断 LiteOS默认的最小的中断优先级数值是7
创建UART2的全局中断USART2_IRQn,并将全局中断处理函数USART2_IRQHandler与这个中断挂接。
创建UART3的全局中断USART3_IRQn,并将全局中断处理函数USART3_IRQHandler与这个中断挂接。
上述两个中断处理函数都在stm32l4xx_it.c文件中,代码如下。
** * @brief This function handles USART2 global interrupt. */ void USART2_IRQHandler(void) { /* USER CODE BEGIN USART2_IRQn 0 */ /* USER CODE END USART2_IRQn 0 */ HAL_UART_IRQHandler(&huart2); /* USER CODE BEGIN USART2_IRQn 1 */ /* USER CODE END USART2_IRQn 1 */ } /** * @brief This function handles USART3 global interrupt. */ void USART3_IRQHandler(void) { /* USER CODE BEGIN USART3_IRQn 0 */ /* USER CODE END USART3_IRQn 0 */ HAL_UART_IRQHandler(&huart3); /* USER CODE BEGIN USART3_IRQn 1 */ /* USER CODE END USART3_IRQn 1 */ }
这两个中断服务主要调用了STM32L4xx_HAL_Driver(STM32L4系列的硬件驱动库)中的函数,在文件stm32l4xx_hal_uart.c中有它们的实现代码。这相当于最终使用了STM32L4库函数中的中断服务函数。在创建上述的中断时,要留意,中断优先级最好不要大于7,因为LiteOS中默认配置的中断优先级的最小数值是7。有了中断后,两个串口之间的通信就可以采用非阻塞的方式进行了。
2.2.3 采用非阻塞方式通信
主要定义了使用UART2发送数据的函数和使用UART3接收数据的函数。分别接收如下。
发送函数uart2_send,代码如下
static ssize_t uart2_send(const char *buf,size_t len, unsigned int timeout) { HAL_UART_Transmit(&huart2,(unsigned char *)buf,len,timeout); //HAL_UART_Transmit(&huart3, "$CCMSG,GGA,1,0,*19\r\n", 20, 200); HAL_Delay(200); return len; }
这个函数其实主要是调用HAL_UART_Transmit函数来通过UART2发送数据。这个HAL_UART_Transmit函数也属于STM32L4xx系列的硬件驱动库函数,也在文件stm32l4xx_hal_uart.c文件中。由于使用的是UART2发送数据,所以在调用这个函数的时候,第一个参数需要传递&huart2. huart2包含了UART2的很多配置信息,是在usart.c中定义的一个全局变量,可以在这里使用。
接收数据的函数uart3_receive,代码如下
//使用UART3接收数据的函数 void uart3_receive(void) { HAL_StatusTypeDef ret; ret=HAL_UART_Receive_IT(&huart3,g_revBuf,UART_INFO_MAX_LEN); }
这个函数其实主要是调用HAL_UART_Receive_IT函数来通过UART3接收数据。这个HAL_UART_Receive_IT函数也属于STM32L4xx系列的硬件驱动库函数,也在文件stm32l4xx_hal_uart.c文件中。由于使用的是UART3接收数据,所以在调用这个函数的时候,第一个参数需要传递&huart3. huart3包含了UART3的很多配置信息,是在usart.c中定义的一个全局变量,可以在这里使用。
上述两个函数的使用,可以在stm32l4xx_hal_uart.c文件中的说明部分能看到如下使用提示信息
2.2.4 两个任务的入口函数
LiteOS系统中,创建任务时,需要指定任务的入口函数。任务运行的时候,是从入口函数开始执行的。两个任务的入口函数分别说明如下。
任务1的入口函数user_task1_entry()
//任务1的入口函数 //任务1主要是通过UART2不断的发送数据,并将发送的数据在LCD上显示 static int user_task1_entry() { int i=1; while(i>0) { LCD_Clear(WHITE); POINT_COLOR = BLUE; switch(i) { case 1: uart2_send("abcdefg",UART_INFO_MAX_LEN,200); LCD_ShowString(10, 10, 240, 24, 16, "UART2_Sent Info:"); LCD_ShowString(10, 30, 240, 24, 16, "abcdefg"); break; case 2: uart2_send("0123456789",UART_INFO_MAX_LEN,200); LCD_ShowString(10, 10, 240, 24, 16, "UART2_Sent Info:"); LCD_ShowString(10, 30, 240, 24, 16, "0123456789"); break; case 3: uart2_send("ABCDEFG",UART_INFO_MAX_LEN,200); LCD_ShowString(10, 10, 240, 24, 16, "UART2_Sent Info:"); LCD_ShowString(10, 30, 240, 24, 16, "ABCDEFG"); break; case 4: uart2_send("I love China.",UART_INFO_MAX_LEN,200); LCD_ShowString(10, 10, 240, 24, 16, "UART2_Sent Info:"); LCD_ShowString(10, 30, 240, 24, 16, "I love China."); break; case 5: uart2_send("I like IOT.",UART_INFO_MAX_LEN,200); LCD_ShowString(10, 10, 240, 24, 16, "UART2_Sent Info:"); LCD_ShowString(10, 30, 240, 24, 16, "I like IOT."); break; } osal_task_sleep(2*500); i++; if(i>5) i=1; printf("task1-%ld: Send task is working.\r\n", user_task1_id); } printf("user task 1 exit!\r\n"); return 0; }
任务1入口函数主要是不断调用发送数据的函数发送一些示例的信息,同时将发送的信息显示在LCD上。逻辑比较简单,就不多做说明了。
任务2的入口函数user_task2_entry()
/任务2的入口函数 //任务2主要是通过UART3不断接收数据,并将接收到的数据显示在LCD上 static int user_task2_entry() { int i=5; while (1) { LCD_Clear(WHITE); POINT_COLOR = RED; uart3_receive(); LCD_ShowString(10, 50, 240, 24, 16, "UART3_Received Info:"); if(g_revBuf[0] != 0 ) { LCD_ShowString(10, 70, 240, 24, 16, g_revBuf); } printf("task 2-%ld: LCD show task is working.\r\n", user_task2_id); osal_task_sleep(2*600); } printf("user task 2 exit!\r\n"); return 0; }
任务2的入口函数主要是不断调用接收函数,接收数据,并将接收到的数据信息显示在LCD上。同样,逻辑比较简单,就不多做说明了。
2.2.5 头文件及一些全局变量
程序中使用的头文件及一些全局变量如下
#include <osal.h> #include <usart.h> #include <stm32l431xx.h> #include "gpio.h" #include "lcd.h" #include <stm32l4xx_it.h> #define USER_TASK1_PRI 12 #define USER_TASK2_PRI 11 #define UART_INFO_MAX_LEN 20 uint32_t user_task1_id = 0; uint32_t user_task2_id = 0; unsigned char g_revBuf[UART_INFO_MAX_LEN]={0};
注意在编译程序的时候,把相关的头文件包括在头文件路径中。
3. 编译运行验证
在IoT Link Studio中编译程序,编译成功后的结果如下
编译成功后,下载程序到开发板,运行验证,一些效果图片如下。
4. 总结
通过这个Demo程序,在LiteOS系统基础上,实现了多任务的串行数据收发及显示功能。总结Demo程序,可以看出,LiteOS提供了很好的任务管理功能。每个任务的具体功能的编写时,除了使用LiteOS的一些API函数外,还可以使用很多STM32的库函数。本文的Demo可以说是需要综合应用了LiteOS的一些API和STM32的库函数,实现了串行口的收发和信息显示功能。
个人的思路难免存在疏漏,有不正确或者不合适的地方,欢迎大家提出讨论,共同学习共同进步。
- 点赞
- 收藏
- 关注作者
评论(0)