基于LiteOS的多任务串行通信和LCD显示程序Demo

举报
Jasonchenbj 发表于 2020/07/27 21:35:32 2020/07/27
【摘要】 通常在基于系统的应用程序开发时,通信程序和显示程序会不同的线程中来运行,以避免由于通信程序的问题而导致界面程序也一同挂掉的现象。基于LiteOS这种轻量级的系统上的应用开发也会遇到类似的情况。本文使用小熊派开发板,参考IoT Link中的Demo程序,基于LiteOS系统之上,使用LiteOS的多任务管理功能,编写实现了两个任务之间串行通信的Demo程序,并在每个任务中将收发相关的信息使用L...

通常在基于系统的应用程序开发时,通信程序和显示程序会不同的线程中来运行,以避免由于通信程序的问题而导致界面程序也一同挂掉的现象。基于LiteOS这种轻量级的系统上的应用开发也会遇到类似的情况。本文使用小熊派开发板,参考IoT Link中的Demo程序,基于LiteOS系统之上,使用LiteOS的多任务管理功能,编写实现了两个任务之间串行通信的Demo程序,并在每个任务中将收发相关的信息使用LCD显示出来。

1. 硬件设计

本文使用了小熊派智慧农业的开发主板。在这个开发主板上,以STM32L431RTC6为主控制器。开发板上连接E53的接口中有UART3的连接端子,如下图中的18和19两个引脚。其中18引脚对应的是STM32L431RTC6的PC4引脚,即UART3_Tx; 19引脚对应的是STM32L431RTC6的PC5引脚,即UART3_Rx。

1595856040856053709.png

在其P5端子排中预留了UART2的连接端子,如下图中的5和6引脚。其中5引脚对应的是STM32L431RTC6的PA2引脚,即UART2_Tx;6引脚对应的是STM32L431RTC6的PA3引脚,即UART2_Rx。

image.png

Demo程序使用UART2发送数据,UART3接收数据。因此将5引脚和19引脚连接,6引脚和18引脚连接。注意这里的引脚编号是指小熊派开发主板上的相关编号。连接好的硬件如下图所示。

image.png


2. Demo程序设计

2.1 Demo程序的工程创建

基于IoT Link Studio进行设计开发。在VS Code中,使用IoT Link Studio,创建基于STM32L431_BearPi_OS_Func的硬件平台的示例工程osal_task_demo。如下图所示。

image.png2.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文件中的说明部分能看到如下使用提示信息

image.png

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中编译程序,编译成功后的结果如下

image.png

编译成功后,下载程序到开发板,运行验证,一些效果图片如下。

image.png  image.png  image.png  image.png

image.png

4. 总结

通过这个Demo程序,在LiteOS系统基础上,实现了多任务的串行数据收发及显示功能。总结Demo程序,可以看出,LiteOS提供了很好的任务管理功能。每个任务的具体功能的编写时,除了使用LiteOS的一些API函数外,还可以使用很多STM32的库函数。本文的Demo可以说是需要综合应用了LiteOS的一些API和STM32的库函数,实现了串行口的收发和信息显示功能。

个人的思路难免存在疏漏,有不正确或者不合适的地方,欢迎大家提出讨论,共同学习共同进步。


【版权声明】本文为华为云社区用户原创内容,转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息, 否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。