Bootstrap

STM32 FreeRTOS处理LVGL+串口双任务相关问题总结

目录

前情提要:

1、LVGL部分

2、串口部分

3 代码部分

3.1 串口代码部分:

3.2 LVGL代码部分

总结:


前情提要:

最近在做一个项目,用到了LVGL+串口来调,碰到了巨多坑,调了我两天才调出来,特此写该博文以作记录。

1、LVGL部分

这个我就不赘述了,可以参考我的这篇文章来移植;这次我没有用Gui-Guider来生成GUI代码,为啥?因为它是NXP家的软件,新版本的只支持他自己家的板子,其他板子很难移植...(难绷,只能自己手撸LVGL代码了,以后有空了可以用LVGL官方代码生成软件SquareLine Studio)。好了闲话不多说,记录一下所碰到的问题吧!

2、串口部分

stm32串口有一个好用的组合:空闲中断+DMA,可以接收不定长数据,非常方便。首先在CubeMX中开启异步串口:

然后开启中断,再配置中断优先级(我用了FreeRTOS,只能管理5~15的优先级,所以这里设置为7):

 紧接着开启发送和接收DMA:

接着去创建串口任务,我这里选择生成weak弱函数是为了在自定义的c文件处理任务,方便自己写代码,其实采用默认生成也可以:

然后创建一个二值信号量,当串口发生空闲中断时,释放二值信号量(如果不懂空闲中断的原理可以去网上查,如果不懂二值信号量的话建议先学一下FreeRTOS     (:D     ):

最后生成代码即可

3 代码部分

3.1 串口代码部分:

裸机情况下,一般可以重定向printf来作为发送函数,但是在RTOS情况下,有可能出现的问题是:你在处理或者发送的过程中,发生了上下文任务切换,就会导致发送出错(我踩的第一个坑);然后我上网找了很久,找到了如下版本的发送函数,只能说堪称完美:

#include "usart.h"
#include "stdarg.h"
#include "stdio.h"
#include "FreeRTOS.h"
#include "task.h"
#include "cmsis_os.h"

//若在中断__get_IPSR()返回1,否则返回0
static int inHandlerMode(void)
{
	return __get_IPSR();
}

//串口1DMA发送函数
void uart1_printf(const char *format,...)
{
	uint8_t _dbg_TXBuff[1024];
	uint32_t length;
	
	if(inHandlerMode() != 0) {
		taskDISABLE_INTERRUPTS();//如果在中断中则禁止所有中断
	}
	else {
		while(HAL_UART_GetState(&huart1) == HAL_UART_STATE_BUSY_TX) {
			taskYIELD();//如果串口忙则挂起该任务
		}
	}
	
	va_list args;
	va_start(args, format);
	length = vsnprintf((char*)_dbg_TXBuff, sizeof(_dbg_TXBuff)+1, (char*)format, args);
	va_end(args);
	HAL_UART_Transmit_DMA(&huart1,_dbg_TXBuff,length);
	
	if(inHandlerMode() != 0) {
		taskENABLE_INTERRUPTS();//使能中断
	}	
}

这段发送代码完美解决串口忙、中断、任务切换等问题(虽然是“借鉴”别人的,但是英雄不问出处,我get到了就分享给各位,嘿嘿嘿~)

我用一个结构体来存储了串口的相关变量,且在对应的c文件中创建该变量并赋值:

#ifndef __USART_TASK_H
#define __USART_TASK_H

#ifdef __cplusplus
extern "C" {
#endif

#include "main.h"


#define BUFFER_SIZE 1024  //Receive datas size
//Define struct of the receive data
typedef struct {
  volatile uint16_t rx_len;
  uint8_t* rx_buf;
}UART_RxData_t;
extern UART_RxData_t rx_datas;


void uart1_printf(const char *format,...);
void USART1_ReceiveIDLE(void);


#ifdef __cplusplus
}
#endif

#endif
//对应.c文件的变量
extern DMA_HandleTypeDef hdma_usart1_rx;
extern osThreadId_t usartRxTaskHandle;//usart任务句柄
extern osSemaphoreId_t usartRxBinarySemHandle;//二值信号量

static uint8_t rx_buffer[BUFFER_SIZE]={0};
UART_RxData_t rx_datas = {  //define receive data
   .rx_len = 0,
   .rx_buf = rx_buffer
};

接着定义了串口空闲中断处理函数:

//中断处理函数
void USART1_ReceiveIDLE(void)
{
	if(RESET != __HAL_UART_GET_FLAG(&huart1, UART_FLAG_IDLE)) {
		__HAL_UART_CLEAR_IDLEFLAG(&huart1);//清除标志位
		HAL_UART_DMAStop(&huart1);//停止DMA
		rx_datas.rx_len = BUFFER_SIZE - __HAL_DMA_GET_COUNTER(&hdma_usart1_rx);//数据长度
		HAL_UART_Receive_DMA(&huart1, rx_datas.rx_buf, BUFFER_SIZE);//开启DMA
		osSemaphoreRelease(usartRxBinarySemHandle);//释放二值信号量  
	}
}

然后在串口处理任务中等待二值信号量的到来,再将得到的数据发送回去:

//usart task
void App_usartRxTask(void *argument)
{
	(void)argument;  
	__HAL_UART_ENABLE_IT(&huart1, UART_IT_IDLE);
	HAL_UART_Receive_DMA(&huart1, rx_datas.rx_buf, BUFFER_SIZE);
	for(;;)
	{
		if(osOK == osSemaphoreAcquire(usartRxBinarySemHandle, osWaitForever)) {
			uart1_printf("%s", rx_datas.rx_buf);//收到的数据发送回去
			memset(rx_datas.rx_buf, 0, rx_datas.rx_len);//清空数组
			rx_datas.rx_len = 0;
		}
		vTaskDelay(pdMS_TO_TICKS(10));
	}
}
/* 
这就是As weak(弱函数)的好处了,让我可以在任意文件实现这个任务函数,而不是所有都挤在freertos.c中*/

接着在stm32f4xx_it.c中的USART1_IRQHandler串口1中断处理函数中进行空闲中断的判断了:

/**
  * @brief This function handles USART1 global interrupt.
  */
void USART1_IRQHandler(void)
{
  /* USER CODE BEGIN USART1_IRQn 0 */
	uint32_t v = taskENTER_CRITICAL_FROM_ISR();//禁止任务调度
  USART1_ReceiveIDLE();//空闲中断处理
  /* USER CODE END USART1_IRQn 0 */
  HAL_UART_IRQHandler(&huart1);
  /* USER CODE BEGIN USART1_IRQn 1 */
	taskEXIT_CRITICAL_FROM_ISR(v);//开启任务调度
  /* USER CODE END USART1_IRQn 1 */
}

3.2 LVGL代码部分

下面的是我的LVGL任务部分:

#include "main.h"
#include "cmsis_os.h"
#include "semphr.h"
#include "lvgl.h"
#include "lv_port_disp.h"
#include "lv_port_indev.h"
#include "touch.h"


extern osMutexId_t LVGL_MutexHandle;

static void lvgl_first_demo_start(void);
//lvgl task
void AppTask_LVGL(void *argument)
{
	(void)argument;
	lv_init();           // lvgl初始化
	lv_port_disp_init(); // 显示器初始化
	lv_port_indev_init();// 输入设备初始化
	lvgl_first_demo_start();
	for(;;)
	{
		if(pdTRUE == xSemaphoreTake(LVGL_MutexHandle, portMAX_DELAY)) {//获取互斥量
			lv_task_handler();	//lvgl的任务执行函数
			xSemaphoreGive(LVGL_MutexHandle);		//释放互斥量
		}
		vTaskDelay(pdMS_TO_TICKS(10));	//任务切换
	}
}

  LVGL----GUI   


static void btn_event_cb(lv_event_t * event)
{
    lv_obj_t *btn = lv_event_get_target(event);              //获得事件最初瞄准的对象。即使事件是冒泡的,也是一样的。
    if(event->code == LV_EVENT_CLICKED) 
    {
        static uint8_t cnt = 0;
        cnt++;
        /*Get the first child of the button which is the label and change its text*/
        lv_obj_t * label = lv_obj_get_child(btn, NULL);
        lv_label_set_text_fmt(label, "Button: %d", cnt);
    }
}

static void lvgl_first_demo_start(void)
{
    lv_obj_t * btn = lv_btn_create(lv_scr_act());           /*Add a button the current screen*/
    lv_obj_set_pos(btn, 10, 10);                            /*Set its position*/
    lv_obj_set_size(btn, 100, 50);                          /*Set its size*/
    lv_obj_add_event_cb(btn, (lv_event_cb_t)btn_event_cb, LV_EVENT_CLICKED, NULL);/*Assign a callback to the button*/

    lv_obj_t * label = lv_label_create(btn);                /*Add a label to the button*/
    lv_label_set_text(label, "Yeah");                       /*Set the labels text*/


    lv_obj_t * label1 = lv_label_create(lv_scr_act());
    lv_label_set_text(label1, "Hello world!"); 
    lv_obj_align(label1, LV_ALIGN_CENTER, 0, 0);
    lv_obj_align_to(btn, label1, LV_ALIGN_OUT_TOP_MID, 0, -10);
}

下面坑来了:我一开始写好这两个任务之后并下载到板子中,发现LVGL执行得好好的,但是串口就是收不到数据,搞得我都怀疑是不是串口部分写错了,但后面我新建了一个工程试一试串口部分,发现运行正常,然后我就懵了。。。调了一天多才偶然发现,如果把lv_init()函数(LVGL初始化函数)注释掉之后,串口就能正常运行,我:???接着继续debug,打断点进入lv_init(),一步一步调,终于发现了罪魁祸首:

 

原来是系统给LVGL分配内存的过程中出了问题,那出了啥问题?我也不知道,但是我在这一步走下去就串口出错了。难道是内存爆了?我去看了一下FreeRTOSConfig.h,查一下系统总的堆内存发现有15360个size_t(就是uint16_t,也就是30KB):

然后再去查一下给LVGL分配的字节数(去lv_conf.h中查看),我晕,特么的LVGL默认给它分配了48KB的内存,难怪搞崩了!我立马给它改低了,改到16KB,程序立马好好地跑起来了。。。

总结:

不管怎么说,这次调试(踩坑)经历丰富了我的经验,也算是不虚此行吧!

以上均为个人学习心得,如有错误,请不吝赐教~

THE END

;