目录
前情提要:
最近在做一个项目,用到了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