|
LVGL默认是使用回调函数进行事件触发的,比如按下屏幕中的按键,或者是某个控件的值发生变化之类的,而如果想要使用LVGL去连续不断地显示某样东西,那就需要用到lv_timer即定时器控件,这帖我将用lv_timer来实现对BME680模块的温度、湿度、气压数据读取。 首先需要在LVGL程序开始的时候注册定时器回调函数:
void lv_example_tileview(void) { lv_obj_t *tileview = lv_tileview_create(lv_scr_act()); lv_timer_t * timer1; timer1 = lv_timer_create(my_timer1 , 100000 , NULL); 。。。 } 第二个参数为定时器溢出时间间隔,数字越大间隔越大。然后需要在定时器回调函数里面写上具体的实现: static void my_timer1(lv_timer_t * timer) { 。。。 } BME680是一个使用I2C通信总线的模块,非常典型也非常常用,采集的大气压数据非常准确,跟气象台数据基本一致,算是BMP280的上位替代,BME680的初始化代码比较特别,是使用回调函数注册相关参数的,比如I2C读,I2C写,Delay函数,器件地址等,我手上这个模块的器件地址是0x77:
#define BME680_I2C_ADDR_SECONDARY UINT8_C(0x77) gas_sensor.dev_id = BME680_I2C_ADDR_SECONDARY; gas_sensor.intf = BME680_I2C_INTF; gas_sensor.read = user_i2c_read; gas_sensor.write = user_i2c_write; gas_sensor.delay_ms = Delay_ms; if(bme680_init(&gas_sensor) != BME680_OK) { printf("Error al inicializar el sensor.\n"); } printf("bme680_init.\n"); I2C就用现成的模拟I2C代码即可:
uint8_t Fake_I2C_Read_Reg_Addr(uint8_t dev_addr , uint8_t reg) { uint8_t res; Fake_I2C_Start(); Fake_I2C_Send_Byte((dev_addr << 1) | 0); Fake_I2C_Wait_Ack(); Fake_I2C_Send_Byte(reg); Fake_I2C_Wait_Ack(); Fake_I2C_Start(); Fake_I2C_Send_Byte((dev_addr << 1) | 1); Fake_I2C_Wait_Ack(); res = Fake_I2C_Read_Byte(0); Fake_I2C_Stop(); return res; } void Fake_I2C_Read_Reg_Datas(uint8_t dev_addr , uint8_t reg , uint8_t data_len , uint8_t data[]) { while(data_len) { *data = Fake_I2C_Read_Reg_Addr(dev_addr , reg++); data ++; data_len --; } Delay_ms(1); } void Fake_I2C_Write_Reg_Addr_Data(uint8_t dev_addr , uint8_t reg , uint8_t data) { Fake_I2C_Start(); Fake_I2C_Send_Byte((dev_addr << 1) | 0); Fake_I2C_Wait_Ack(); Fake_I2C_Send_Byte(reg); Fake_I2C_Wait_Ack(); Fake_I2C_Send_Byte(data); Fake_I2C_Wait_Ack(); Fake_I2C_Stop(); } void Fake_I2C_Write_Reg_Addr_Datas(uint8_t dev_addr , uint8_t reg , uint8_t data_len , uint8_t data[]) { int i; Fake_I2C_Start(); Fake_I2C_Send_Byte((dev_addr << 1) | 0); Fake_I2C_Wait_Ack(); Fake_I2C_Send_Byte(reg); Fake_I2C_Wait_Ack(); for(i = 0 ; i < data_len ; i++) { Fake_I2C_Send_Byte(data); Fake_I2C_Wait_Ack(); } Fake_I2C_Stop(); Delay_ms(1); } int8_t user_i2c_write(uint8_t dev_id , uint8_t reg_addr , uint8_t *reg_data , uint16_t len) { Fake_I2C_Write_Reg_Addr_Datas(0x77 , reg_addr , len , reg_data); return 0; } int8_t user_i2c_read(uint8_t dev_id , uint8_t reg_addr , uint8_t *reg_data , uint16_t len) { Fake_I2C_Read_Reg_Datas(0x77 , reg_addr , len , reg_data); return 0; } 在主界面中添加tileview,可以实现类似智能手机的滑屏效果: lv_obj_t *tileview = lv_tileview_create(lv_scr_act()); lv_obj_t *tile_1 = lv_tileview_add_tile( tileview, 0 , 0, LV_DIR_LEFT | LV_DIR_RIGHT ); lv_obj_t *tile_2 = lv_tileview_add_tile( tileview, 1 , 0, LV_DIR_LEFT | LV_DIR_RIGHT ); 这样,就可以单独用一个页面去显示BME680的传感器读数,而其它页面实现别的功能了,非常方便。用Bar(进度条)和Label去动态显示读数: font = &lv_font_montserrat_24; lv_obj_t* label = lv_label_create(btn_speed_up); lv_obj_set_style_text_font(label , font , LV_PART_MAIN); label_bme680_temp = lv_label_create(tile_1); lv_obj_set_style_text_font(label_bme680_temp , font , LV_STATE_DEFAULT); lv_obj_align(label_bme680_temp , LV_ALIGN_CENTER, 0 , -220); bar_bme680_temp = lv_bar_create(tile_1); lv_obj_align(bar_bme680_temp , LV_ALIGN_CENTER, 0 , -180); lv_obj_set_size(bar_bme680_temp , 260 , 20); label_bme680_pres = lv_label_create(tile_1); lv_obj_set_style_text_font(label_bme680_pres , font , LV_STATE_DEFAULT); lv_obj_align(label_bme680_pres , LV_ALIGN_CENTER, 0 , -120); bar_bme680_pres = lv_bar_create(tile_1); lv_obj_align(bar_bme680_pres , LV_ALIGN_CENTER, 0 , -80); lv_obj_set_size(bar_bme680_pres , 260 , 20); label_bme680_humi = lv_label_create(tile_1); lv_obj_set_style_text_font(label_bme680_humi , font , LV_STATE_DEFAULT); lv_obj_align(label_bme680_humi , LV_ALIGN_CENTER, 0 , -20); bar_bme680_humi = lv_bar_create(tile_1); lv_obj_align(bar_bme680_humi , LV_ALIGN_CENTER, 0 , 20); lv_obj_set_size(bar_bme680_humi , 260 , 20); 看看演示效果: 用手捂着BME680,会看到温度读数升高,松开温度则降下来。 除了使用LVGL界面以及lv_timer定时器定时采集BME680数据并显示以外,我还用STM32H743VI的GPIO串口功能接一个串口语音模块,结合LVGL界面实现语音控制,涉及到中断跳转以及lv_timer定时器回调机制。 串口语音模块直接买的天问模块,接开发板的UART2即PA2和PA3接口:
串口的空闲中断及DMA实现如下: UART_HandleTypeDef g_uart2_handle; DMA_HandleTypeDef hdma_uart2_rx; uint8_t uart2_recv_end_flag; uint32_t rx2_len = 0; unsigned char rx2_buf[BUFFERSIZE]; bool uart2_flag1 = false; bool uart2_flag2 = false; void UART2_Init(uint32_t baud) { GPIO_InitTypeDef gpio_init_struct; __HAL_RCC_GPIOA_CLK_ENABLE(); __HAL_RCC_USART2_CLK_ENABLE(); __HAL_RCC_DMA1_CLK_ENABLE(); gpio_init_struct.Mode = GPIO_MODE_AF_PP; gpio_init_struct.Pull = GPIO_PULLUP; gpio_init_struct.Speed = GPIO_SPEED_FREQ_HIGH; gpio_init_struct.Alternate = GPIO_AF7_USART2; gpio_init_struct.Pin = GPIO_PIN_2 | GPIO_PIN_3; HAL_GPIO_Init(GPIOA , &gpio_init_struct); g_uart2_handle.Instance = USART2; g_uart2_handle.Init.BaudRate = baud; g_uart2_handle.Init.WordLength = UART_WORDLENGTH_8B; g_uart2_handle.Init.StopBits = UART_STOPBITS_1; g_uart2_handle.Init.Parity = UART_PARITY_NONE; g_uart2_handle.Init.HwFlowCtl = UART_HWCONTROL_NONE; g_uart2_handle.Init.Mode = UART_MODE_TX_RX; HAL_UART_Init(&g_uart2_handle); __HAL_UART_ENABLE_IT(&g_uart2_handle , UART_IT_IDLE); HAL_NVIC_SetPriority(USART2_IRQn , 0 , 0); HAL_NVIC_EnableIRQ(USART2_IRQn); hdma_uart2_rx.Instance = DMA1_Stream0; hdma_uart2_rx.Init.Request = DMA_REQUEST_USART2_RX; hdma_uart2_rx.Init.Direction = DMA_PERIPH_TO_MEMORY; hdma_uart2_rx.Init.PeriphInc = DMA_PINC_DISABLE; hdma_uart2_rx.Init.MemInc = DMA_MINC_ENABLE; hdma_uart2_rx.Init.PeriphDataAlignment = DMA_PDATAALIGN_BYTE; hdma_uart2_rx.Init.MemDataAlignment = DMA_MDATAALIGN_BYTE; hdma_uart2_rx.Init.Mode = DMA_CIRCULAR; hdma_uart2_rx.Init.Priority = DMA_PRIORITY_LOW; hdma_uart2_rx.Init.FIFOMode = DMA_FIFOMODE_DISABLE; HAL_DMA_Init(&hdma_uart2_rx); __HAL_LINKDMA(&g_uart2_handle , hdmarx , hdma_uart2_rx); } STM32H743没有将DMA通道和特定外设绑定,因此DMA通道选择别的也是可以的(但最好是选择DMA1)。 串口中断服务函数实现: void USART2_IRQHandler() { if(__HAL_UART_GET_FLAG(&g_uart2_handle, UART_FLAG_IDLE)) { __HAL_UART_CLEAR_IDLEFLAG(&g_uart2_handle); // g_uart2_handle.Instance->ISR; // g_uart2_handle.Instance->RDR; // printf("USART2_IRQHandler\n"); HAL_UART_DMAStop(&g_uart2_handle); SCB_InvalidateDCache_by_Addr((uint32_t*)rx2_buf , BUFFERSIZE); rx2_len = BUFFERSIZE - __HAL_DMA_GET_COUNTER(&hdma_uart2_rx); uart2_recv_end_flag = 1; rx2_buf[rx2_len] = '\0'; // printf("USART2_IRQHandler rx2_buf = %s rx2_len = %d.\n" , rx2_buf , rx2_len); if(strstr(rx2_buf , "打开一号灯") != NULL) { printf("USART2_IRQHandler 打开一号灯.\n" , rx2_buf , rx2_len); uart2_flag1 = true; } else if(strstr(rx2_buf , "关闭一号灯") != NULL) { printf("USART2_IRQHandler 关闭一号灯.\n" , rx2_buf , rx2_len); uart2_flag1 = false; } else if(strstr(rx2_buf , "打开二号灯") != NULL) { printf("USART2_IRQHandler 打开二号灯.\n" , rx2_buf , rx2_len); uart2_flag2 = true; } else if(strstr(rx2_buf , "关闭二号灯") != NULL) { printf("USART2_IRQHandler 关闭二号灯.\n" , rx2_buf , rx2_len); uart2_flag2 = false; } HAL_UART_Receive_DMA(&g_uart2_handle , (unsigned char*)rx2_buf , BUFFERSIZE); } } 在这里实现相关的标志位控制,以便在lv_timer定时器回调函数中控制相应组件或者外设。lv_timer定时器回调函数: static void my_timer1(lv_timer_t * timer) { 。。。 if(uart2_flag1) { lv_led_on(lv_led1); } else { lv_led_off(lv_led1); } if(uart2_flag2) { lv_led_on(lv_led2); } else { lv_led_off(lv_led2); } } 视频演示效果: |
STM32L562DK探索板——简单的显示变量函数
STM32F746G-DISCO移植lvgl9.0性能基准测试
【福利三:逢7发帖赢大礼】使用MPU6050模块、img_dsc、img_btn机制实现一些简单有趣的动画效果
STM32L562DK探索板——建立演示菜单
STM32L562DK探索板——字符显示功能的剖析
【福利三:逢7发帖赢大礼】使用arc弧形拉条控件控制PWM舵机及使用SPI3接口操作NRF24L01模块进行无线通信
1-趋势痛点篇:嵌入式 GUI 爆发,传统外挂存储架构的多重工程瓶颈
告别外置存储!大容量内存单芯片MCU,重塑新一代嵌入式GUI
STM32L562DK探索板——继续测试Audio
请教下STM32N6570 怎么把分辨率改成320x240并可以全屏显示
微信公众号
手机版