|
LVGL默认是使用回调函数进行事件触发的,比如按下屏幕中的按键,或者是某个控件的值发生变化之类的,而如果想要使用LVGL去连续不断地显示某样东西,那就需要用到lv_timer即定时器控件,这帖我将用lv_timer来实现对BME680模块的温度、湿度、气压数据读取。 首先需要在LVGL程序开始的时候注册定时器回调函数:( d* [( y* C( y5 {7 {
void lv_example_tileview(void) {& C+ z4 S' \6 F8 s7 m. {' G lv_obj_t *tileview = lv_tileview_create(lv_scr_act()); A2 p( k+ b: s lv_timer_t * timer1; timer1 = lv_timer_create(my_timer1 , 100000 , NULL); 。。。 } $ N, W3 F4 h8 o% y$ R 第二个参数为定时器溢出时间间隔,数字越大间隔越大。然后需要在定时器回调函数里面写上具体的实现: static void my_timer1(lv_timer_t * timer); e' K' G: D1 m2 p! N' ` { 。。。$ c# |0 c/ R' |# H } & [9 l1 O* {9 I( y5 [% m) P, u/ f+ ] ) N! T* Q; y/ q& f! E" Z( n) `4 ~ BME680是一个使用I2C通信总线的模块,非常典型也非常常用,采集的大气压数据非常准确,跟气象台数据基本一致,算是BMP280的上位替代,BME680的初始化代码比较特别,是使用回调函数注册相关参数的,比如I2C读,I2C写,Delay函数,器件地址等,我手上这个模块的器件地址是0x77:
#define BME680_I2C_ADDR_SECONDARY UINT8_C(0x77) gas_sensor.dev_id = BME680_I2C_ADDR_SECONDARY;3 \4 P' ^0 }" A gas_sensor.intf = BME680_I2C_INTF; gas_sensor.read = user_i2c_read; gas_sensor.write = user_i2c_write;& F% S2 t- a o! Z5 {' i gas_sensor.delay_ms = Delay_ms; if(bme680_init(&gas_sensor) != BME680_OK) {6 W6 s0 z! H; r m+ _. }% R printf("Error al inicializar el sensor.\n"); } printf("bme680_init.\n"); ; L4 T3 e( E- F I2C就用现成的模拟I2C代码即可:
uint8_t Fake_I2C_Read_Reg_Addr(uint8_t dev_addr , uint8_t reg)& q3 N- l. ~5 Q0 b8 ]0 r9 |' e {( C! O4 X$ _6 Q uint8_t res; Fake_I2C_Start(); 2 f6 ~& R0 H+ F6 s Fake_I2C_Send_Byte((dev_addr << 1) | 0);& Y0 X( V% T/ x6 V Fake_I2C_Wait_Ack();+ X$ ~; ^ ^2 O( j/ P; i+ | Fake_I2C_Send_Byte(reg);7 Y2 Q, k% V3 d) j6 L Fake_I2C_Wait_Ack();7 v2 z$ A+ i3 a. T% I# \ Fake_I2C_Start(); Fake_I2C_Send_Byte((dev_addr << 1) | 1); Fake_I2C_Wait_Ack(); res = Fake_I2C_Read_Byte(0); Fake_I2C_Stop();% [$ ^# A# s! e/ V4 S7 N& Z# P return res; ( }# k8 p8 V4 W. K } + m. n* N. G2 J0 x- h* z- v void Fake_I2C_Read_Reg_Datas(uint8_t dev_addr , uint8_t reg , uint8_t data_len , uint8_t data[]) { while(data_len)7 ~8 H1 K8 g* l: A7 j p { *data = Fake_I2C_Read_Reg_Addr(dev_addr , reg++);6 o% o/ Z# ~4 f0 C9 o& J data ++;" y1 \) R9 w' [6 D# n1 H* I1 s/ x data_len --;( b% d7 i. u1 `! ^0 A } . f1 H" h+ D+ [6 n0 [2 J% B Delay_ms(1); } / e7 r, [1 \) O' A* k# l void Fake_I2C_Write_Reg_Addr_Data(uint8_t dev_addr , uint8_t reg , uint8_t data)4 j8 J. |) F+ H1 t! ^ {6 s7 t2 f9 d$ q* a4 o* L/ D' N Fake_I2C_Start();3 U+ W5 ^0 E7 { Fake_I2C_Send_Byte((dev_addr << 1) | 0); Fake_I2C_Wait_Ack(); Fake_I2C_Send_Byte(reg); Fake_I2C_Wait_Ack();, Z. F* u! K! C0 k- Z6 @ Fake_I2C_Send_Byte(data); / z( M% T$ o/ |+ _ T. l Fake_I2C_Wait_Ack(); Fake_I2C_Stop();9 Q6 M! u6 h. q. ~9 v+ I1 ?. [- B/ b }0 x }7 l/ k% t( x8 m* j7 I ' f1 Y$ l2 h* }, r# ]. ~1 E* p4 s void Fake_I2C_Write_Reg_Addr_Datas(uint8_t dev_addr , uint8_t reg , uint8_t data_len , uint8_t data[]) { int i;$ f' S" {) _, E/ l7 q7 U$ W# H Fake_I2C_Start(); Fake_I2C_Send_Byte((dev_addr << 1) | 0); Fake_I2C_Wait_Ack(); Fake_I2C_Send_Byte(reg); ( s* J0 E+ O; ~) K Fake_I2C_Wait_Ack(); for(i = 0 ; i < data_len ; i++) { Fake_I2C_Send_Byte(data); Fake_I2C_Wait_Ack();* H6 O) n( r% |! n0 X }' @5 s6 g3 h! c: t7 W. [- _9 r Fake_I2C_Stop();( `, x' W( F5 \9 X4 Z Delay_ms(1);; X; o0 t; e& V4 _ }0 a: z* F( d4 g8 q ( V. |6 z$ v. _$ D0 R$ N int8_t user_i2c_write(uint8_t dev_id , uint8_t reg_addr , uint8_t *reg_data , uint16_t len) {2 B9 m( i" W* B4 v1 i8 o7 O/ ] Fake_I2C_Write_Reg_Addr_Datas(0x77 , reg_addr , len , reg_data); return 0; }- B* Z1 s* Q$ n2 G& N1 q: u" Q+ [ 6 d" ~# h& j. i! ]4 [ int8_t user_i2c_read(uint8_t dev_id , uint8_t reg_addr , uint8_t *reg_data , uint16_t len)* ^" E5 j5 {! R { Fake_I2C_Read_Reg_Datas(0x77 , reg_addr , len , reg_data);% g! x, j' d! H$ A return 0; }. r. I5 O: T/ F* V" o9 w+ j $ I; U" R4 V6 M ; S; k6 D4 Z5 l" F5 u: l, x 在主界面中添加tileview,可以实现类似智能手机的滑屏效果: " m3 K' S. {" R5 m, j ! n$ @9 d$ k2 N1 l. M0 o4 u; t+ E L( k: \. R lv_obj_t *tileview = lv_tileview_create(lv_scr_act()); * A. N: g, D& i$ O# B1 { lv_obj_t *tile_1 = lv_tileview_add_tile( tileview, 0 , 0, LV_DIR_LEFT | LV_DIR_RIGHT );( v' ?% t7 |4 ^; M$ W! q3 s lv_obj_t *tile_2 = lv_tileview_add_tile( tileview, 1 , 0, LV_DIR_LEFT | LV_DIR_RIGHT ); 7 B q4 h( O2 l2 N' s* p0 J0 H 这样,就可以单独用一个页面去显示BME680的传感器读数,而其它页面实现别的功能了,非常方便。用Bar(进度条)和Label去动态显示读数: font = &lv_font_montserrat_24; lv_obj_t* label = lv_label_create(btn_speed_up);/ z+ I- R# A; M" }0 I" ^( P" l lv_obj_set_style_text_font(label , font , LV_PART_MAIN);: H% B; v- \2 W! W5 S, v label_bme680_temp = lv_label_create(tile_1);9 d3 W7 ?0 `# L 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);5 @; J' u. c$ S" n" t& d lv_obj_set_size(bar_bme680_temp , 260 , 20);& I- Q, m Z0 Y. M8 v4 g1 [) k 2 Y _: ~- [' R* m 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);7 ` s* }- K: K8 q5 G; h bar_bme680_pres = lv_bar_create(tile_1);, l# A: h0 n( n+ F6 H, D, f$ e lv_obj_align(bar_bme680_pres , LV_ALIGN_CENTER, 0 , -80); lv_obj_set_size(bar_bme680_pres , 260 , 20);2 t" b5 M' d5 J8 n 2 m, B" p4 X$ X$ I8 i label_bme680_humi = lv_label_create(tile_1);5 U& B: P' U% \! U2 b lv_obj_set_style_text_font(label_bme680_humi , font , LV_STATE_DEFAULT);6 o* Y$ N' t) a* O* A/ D; ? lv_obj_align(label_bme680_humi , LV_ALIGN_CENTER, 0 , -20);5 X: P. z- V2 p+ W6 d) G! B bar_bme680_humi = lv_bar_create(tile_1);& T/ R% L3 r) N* ^# h- S lv_obj_align(bar_bme680_humi , LV_ALIGN_CENTER, 0 , 20);4 m8 }8 G8 w6 b" H lv_obj_set_size(bar_bme680_humi , 260 , 20);: M. ^5 S0 E" w8 P; \, ^ 8 D3 J) r5 ~' |3 t5 p# u 1 n' ]+ J) S4 y 看看演示效果: 用手捂着BME680,会看到温度读数升高,松开温度则降下来。0 ^6 |/ Q: m/ L! ~ * p* `, y6 W5 |5 x+ C 除了使用LVGL界面以及lv_timer定时器定时采集BME680数据并显示以外,我还用STM32H743VI的GPIO串口功能接一个串口语音模块,结合LVGL界面实现语音控制,涉及到中断跳转以及lv_timer定时器回调机制。 串口语音模块直接买的天问模块,接开发板的UART2即PA2和PA3接口:
串口的空闲中断及DMA实现如下: UART_HandleTypeDef g_uart2_handle;2 f+ l% W1 w% b% i. }0 X DMA_HandleTypeDef hdma_uart2_rx; uint8_t uart2_recv_end_flag; uint32_t rx2_len = 0;; ^/ p0 x& h7 m% X# N2 ? unsigned char rx2_buf[BUFFERSIZE];$ i- y2 |. i2 d9 B0 {9 J bool uart2_flag1 = false; bool uart2_flag2 = false; . G; W/ g. E4 y8 f2 n0 v& y: ] void UART2_Init(uint32_t baud) {: C/ X3 [7 a. L5 H) c' F9 p# ? GPIO_InitTypeDef gpio_init_struct; : H* o2 Y; T# ? __HAL_RCC_GPIOA_CLK_ENABLE(); __HAL_RCC_USART2_CLK_ENABLE();: e& j6 X/ m1 g6 } __HAL_RCC_DMA1_CLK_ENABLE();- k* _9 p7 d4 W) ~ gpio_init_struct.Mode = GPIO_MODE_AF_PP; |! r6 q% `% \& e" E- J, v# m gpio_init_struct.Pull = GPIO_PULLUP; gpio_init_struct.Speed = GPIO_SPEED_FREQ_HIGH; gpio_init_struct.Alternate = GPIO_AF7_USART2;+ X V- Y0 e$ ? w9 f # ?8 q$ w) |) _) S gpio_init_struct.Pin = GPIO_PIN_2 | GPIO_PIN_3; HAL_GPIO_Init(GPIOA , &gpio_init_struct);7 W+ s* N4 j4 g6 X; Z0 h) v- M g_uart2_handle.Instance = USART2; k9 J F0 @! ?( O! w6 k g_uart2_handle.Init.BaudRate = baud; g_uart2_handle.Init.WordLength = UART_WORDLENGTH_8B;6 o; o. y. K% { g_uart2_handle.Init.StopBits = UART_STOPBITS_1;3 d) M, a3 {8 @ 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; " K" ]' h( S y, d HAL_UART_Init(&g_uart2_handle); 3 B$ o! y: U& g& [6 U, \& W __HAL_UART_ENABLE_IT(&g_uart2_handle , UART_IT_IDLE);, U; m: ^) j% y: R" P HAL_NVIC_SetPriority(USART2_IRQn , 0 , 0);+ p( I, c/ c* r- P# y9 Z& B% u HAL_NVIC_EnableIRQ(USART2_IRQn); hdma_uart2_rx.Instance = DMA1_Stream0;; L. C0 v1 d% K: L1 n. z hdma_uart2_rx.Init.Request = DMA_REQUEST_USART2_RX;5 b. @8 x4 o; w( O& X/ u hdma_uart2_rx.Init.Direction = DMA_PERIPH_TO_MEMORY;# V1 k! C2 h. {# O5 x hdma_uart2_rx.Init.PeriphInc = DMA_PINC_DISABLE; hdma_uart2_rx.Init.MemInc = DMA_MINC_ENABLE; hdma_uart2_rx.Init.PeriphDataAlignment = DMA_PDATAALIGN_BYTE;5 S# r4 q1 e" F2 l. ^. T" e hdma_uart2_rx.Init.MemDataAlignment = DMA_MDATAALIGN_BYTE; ]# m6 I, `2 _6 [; u hdma_uart2_rx.Init.Mode = DMA_CIRCULAR;# ~. I% i0 f6 f. c1 L6 J1 m( ]. r hdma_uart2_rx.Init.Priority = DMA_PRIORITY_LOW;6 d# N4 h; C: y9 p hdma_uart2_rx.Init.FIFOMode = DMA_FIFOMODE_DISABLE; HAL_DMA_Init(&hdma_uart2_rx);1 @+ o( _+ R/ Z6 w% Q; T* P3 ]/ @7 F* { & P B3 n& Z/ s8 n2 A2 u0 m __HAL_LINKDMA(&g_uart2_handle , hdmarx , hdma_uart2_rx); }+ o/ Z9 w* A2 I5 u & c) p, x0 [' U' r( c5 @ STM32H743没有将DMA通道和特定外设绑定,因此DMA通道选择别的也是可以的(但最好是选择DMA1)。 串口中断服务函数实现: void USART2_IRQHandler() { j, f5 [, ?3 D. V if(__HAL_UART_GET_FLAG(&g_uart2_handle, UART_FLAG_IDLE))- i1 C+ ]# M, M% ^( c S* z {3 y/ ^9 m/ [) o __HAL_UART_CLEAR_IDLEFLAG(&g_uart2_handle); // g_uart2_handle.Instance->ISR; // g_uart2_handle.Instance->RDR; // printf("USART2_IRQHandler\n"); & O$ H/ ~( \' @: \' f# M HAL_UART_DMAStop(&g_uart2_handle); SCB_InvalidateDCache_by_Addr((uint32_t*)rx2_buf , BUFFERSIZE);0 o& n/ i+ F$ o rx2_len = BUFFERSIZE - __HAL_DMA_GET_COUNTER(&hdma_uart2_rx);9 N; f" N) B/ o0 M: m uart2_recv_end_flag = 1;5 q& u9 M6 {6 T' N9 S. f4 X rx2_buf[rx2_len] = '\0';# M1 J+ X6 r7 c& L& Z * A& _& R7 i1 _4 u H+ |( ?& j // printf("USART2_IRQHandler rx2_buf = %s rx2_len = %d.\n" , rx2_buf , rx2_len); if(strstr(rx2_buf , "打开一号灯") != NULL)" R4 @8 y: ?+ J8 L( _1 x { printf("USART2_IRQHandler 打开一号灯.\n" , rx2_buf , rx2_len); uart2_flag1 = true;; V/ ~- c; W4 Y2 e. T }- }" Q2 t5 b' K' n2 Y else if(strstr(rx2_buf , "关闭一号灯") != NULL)! x$ ?# x2 h8 Y U+ Q+ I3 G( ~% s {# I4 T. l3 X. [$ i: j0 J8 v printf("USART2_IRQHandler 关闭一号灯.\n" , rx2_buf , rx2_len);4 ]% g' x( a+ n3 ~: r uart2_flag1 = false;$ g, v! N. l" J2 O }( `. U( i% n3 D% v else if(strstr(rx2_buf , "打开二号灯") != NULL)6 Y4 X0 p0 O6 ]* x) R { printf("USART2_IRQHandler 打开二号灯.\n" , rx2_buf , rx2_len);( p" l' o5 n5 E- X% s uart2_flag2 = true; } else if(strstr(rx2_buf , "关闭二号灯") != NULL)8 I9 Q7 V5 f6 @& h7 u {' e7 u8 O. k0 k& E0 i9 | printf("USART2_IRQHandler 关闭二号灯.\n" , rx2_buf , rx2_len);+ G @! E, H4 d( A1 F [ uart2_flag2 = false; } HAL_UART_Receive_DMA(&g_uart2_handle , (unsigned char*)rx2_buf , BUFFERSIZE);2 @3 n/ J8 O' \# N } }/ W; N! P: J ?! G# \0 H6 {% h % C { d) r* V9 I9 F! H" m 在这里实现相关的标志位控制,以便在lv_timer定时器回调函数中控制相应组件或者外设。lv_timer定时器回调函数: static void my_timer1(lv_timer_t * timer): b* ~* b$ {* h0 P* D { 。。。 if(uart2_flag1) { lv_led_on(lv_led1); } else % v0 ?& ~% Z4 i# ]" j2 p { ` r! T' U H2 x3 m3 ^7 T1 P+ ~$ M9 o lv_led_off(lv_led1); } if(uart2_flag2)1 P2 e8 w5 I3 c { lv_led_on(lv_led2); } else {2 e5 G- x; l* D; k, ^ lv_led_off(lv_led2);: Y8 f) I9 b: c7 ]* j }: S. g! V0 U$ D9 K }/ U* |# ] L* G* S( I 视频演示效果: |
STM32C542RC开发板实现长短按键识别功能
STM32C542RC开发板PWM呼吸灯实验
STM32C542RC开发板的串口不定长数据接收实验
《STM32CubeMX2 实战课程》学习笔记——STM32C562RE开发测试
STM32C542RC开发板的串口DMA发送及接收实验
终于完成了STM32C542RC开发板的串口发送实验
STM32C542RC开发板的按键控制LED闪烁实验
【 逢7发帖赢大礼】1、利用CubeMX生成正点原子H7R7开发板的STM32CubeIDE工程
《STM32CubeMX2 实战课程》学习笔记--课程1:STM32CubeMX2安装
【STM32H735G-DK测评】ADC3+TIMER1+DMA 采样模拟信号
微信公众号
手机版