|
LVGL默认是使用回调函数进行事件触发的,比如按下屏幕中的按键,或者是某个控件的值发生变化之类的,而如果想要使用LVGL去连续不断地显示某样东西,那就需要用到lv_timer即定时器控件,这帖我将用lv_timer来实现对BME680模块的温度、湿度、气压数据读取。/ H6 W% B+ U- ^/ V! T. d, I 首先需要在LVGL程序开始的时候注册定时器回调函数:; d$ r" T" z" v& @% j5 u
void lv_example_tileview(void) { lv_obj_t *tileview = lv_tileview_create(lv_scr_act()); lv_timer_t * timer1;; U0 g6 L: j" N& b) ? timer1 = lv_timer_create(my_timer1 , 100000 , NULL); 。。。 }6 N- n, @" ~( b 3 Q( O$ Y6 @0 S$ d* }( m 第二个参数为定时器溢出时间间隔,数字越大间隔越大。然后需要在定时器回调函数里面写上具体的实现: static void my_timer1(lv_timer_t * timer) {3 F5 X* G4 c. k6 P! l- n5 N* A 。。。" _5 ]; W7 @+ B2 a6 G* }. w# f }+ M; a, r! ]4 w/ j1 ]' \ $ C& s* s5 G X! r BME680是一个使用I2C通信总线的模块,非常典型也非常常用,采集的大气压数据非常准确,跟气象台数据基本一致,算是BMP280的上位替代,BME680的初始化代码比较特别,是使用回调函数注册相关参数的,比如I2C读,I2C写,Delay函数,器件地址等,我手上这个模块的器件地址是0x77:
#define BME680_I2C_ADDR_SECONDARY UINT8_C(0x77)& f9 ^' I3 u! I% L - Q" c9 ~: E) s1 J; ~6 B3 G gas_sensor.dev_id = BME680_I2C_ADDR_SECONDARY;, j" X4 B6 A, j" M9 d9 C K/ V% V gas_sensor.intf = BME680_I2C_INTF;7 n2 R( X. V1 t. V& n, y b2 B' ? gas_sensor.read = user_i2c_read;3 i" t4 L- y! g gas_sensor.write = user_i2c_write;9 y3 c6 S! \" Y" A gas_sensor.delay_ms = Delay_ms; if(bme680_init(&gas_sensor) != BME680_OK) { printf("Error al inicializar el sensor.\n"); }6 i5 H4 l2 P1 z. \. B4 a% F printf("bme680_init.\n");* I5 G8 N" K2 t I2C就用现成的模拟I2C代码即可:
uint8_t Fake_I2C_Read_Reg_Addr(uint8_t dev_addr , uint8_t reg)) _9 m) M, n' q3 p { uint8_t res;$ y: b: X% z9 B& o Fake_I2C_Start(); - H( E, s3 |3 }9 V2 W Fake_I2C_Send_Byte((dev_addr << 1) | 0);0 b- Q7 m" D; X7 m! E3 f- `3 }8 j7 G Fake_I2C_Wait_Ack();' M) A, x! w9 D9 A" `1 O$ y* Y5 k Fake_I2C_Send_Byte(reg);3 @) A5 e9 @$ F' g Fake_I2C_Wait_Ack();9 Y# o6 H* E" t- C, v% `, {: }+ P& x Fake_I2C_Start(); Fake_I2C_Send_Byte((dev_addr << 1) | 1); Fake_I2C_Wait_Ack();0 I" ^% b& I9 L( [9 } res = Fake_I2C_Read_Byte(0);8 F/ W0 a, E1 E9 O4 Q7 ^ Fake_I2C_Stop(); return res; }8 i% b9 C6 B- x+ } h6 W" x2 D% y! _8 X6 { void Fake_I2C_Read_Reg_Datas(uint8_t dev_addr , uint8_t reg , uint8_t data_len , uint8_t data[]) {" Y+ j0 I& X3 ?) {' f6 Y& _5 N* P while(data_len)1 H# z% x" |$ b/ |, d1 H1 z* H! a {* v0 W4 C. y" ]: h: Y/ }6 S2 A *data = Fake_I2C_Read_Reg_Addr(dev_addr , reg++);: j7 D6 c+ ?6 H% W8 z# h. _ data ++; data_len --; } Delay_ms(1);3 E: a+ L/ \- e( _ }; b5 x& ?0 U$ H# Y5 N4 v% L$ ?6 ^ 7 Q0 E' f9 X7 M1 u. ? 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);4 E; J/ l. O9 X* h6 ~ Fake_I2C_Wait_Ack(); Fake_I2C_Send_Byte(reg); Fake_I2C_Wait_Ack(); Fake_I2C_Send_Byte(data); 1 \; m O& m8 y0 O+ O4 S Fake_I2C_Wait_Ack();- z3 p7 N/ X0 X" y. ~! ^$ F Fake_I2C_Stop();, w7 `3 `( A0 _4 t+ z } - \3 S# A2 K/ @6 J void Fake_I2C_Write_Reg_Addr_Datas(uint8_t dev_addr , uint8_t reg , uint8_t data_len , uint8_t data[])1 q0 p& U# W- b {: w$ O" j$ U6 B; b) n" F9 s! j int i; Fake_I2C_Start();2 h# u8 a7 @ b7 [: a' n W Fake_I2C_Send_Byte((dev_addr << 1) | 0);8 U& F r c8 K2 w6 _+ a) }: U Fake_I2C_Wait_Ack(); Fake_I2C_Send_Byte(reg); 3 ^% _1 q) Q' u/ j" _8 o( u' D$ @ Fake_I2C_Wait_Ack();& w$ z: k- x h" B! ^# d% P for(i = 0 ; i < data_len ; i++)8 S) Z: w3 i* }8 Q { Fake_I2C_Send_Byte(data); Fake_I2C_Wait_Ack();1 I; s4 p0 B: F! e2 p$ q }! R" N' K4 J, a$ ^2 g Fake_I2C_Stop(); Delay_ms(1); } 0 k9 C5 |- I9 M8 w( q# F% ? 8 q% P& e/ a' t! { int8_t user_i2c_write(uint8_t dev_id , uint8_t reg_addr , uint8_t *reg_data , uint16_t len)7 D p& Z( T9 V { Fake_I2C_Write_Reg_Addr_Datas(0x77 , reg_addr , len , reg_data);5 n E+ j; D7 F& C! N) k- j9 U return 0;2 J0 Y/ h7 J0 U% ~9 \1 K' `4 r }0 D Y4 I$ H5 _ & H8 u# j, M( L/ |5 k" v 8 z0 C. y, ^4 [6 v int8_t user_i2c_read(uint8_t dev_id , uint8_t reg_addr , uint8_t *reg_data , uint16_t len)9 r) U b: R: F2 y5 v; ` { Fake_I2C_Read_Reg_Datas(0x77 , reg_addr , len , reg_data); return 0;! j! Q8 T+ A8 ?4 Z1 |/ [ }5 y5 n6 O% }$ F! t/ P( N ; `7 g/ Q+ u' P; O% S" | 在主界面中添加tileview,可以实现类似智能手机的滑屏效果:2 V6 f/ W, d8 \1 l' [$ s+ {# d% V+ D1 w lv_obj_t *tileview = lv_tileview_create(lv_scr_act());# n2 u( d! @" e " v1 R* f0 R1 E ( y* \# |) e* Q" G 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 );; Y5 z' g+ S& ^/ I! q0 ]& s C4 ]4 x: i" {+ K$ y 这样,就可以单独用一个页面去显示BME680的传感器读数,而其它页面实现别的功能了,非常方便。用Bar(进度条)和Label去动态显示读数: 6 x5 v% A7 @) d1 j) D5 m font = &lv_font_montserrat_24;# R, Y* Z, y3 P! ?4 f' ` lv_obj_t* label = lv_label_create(btn_speed_up);1 \+ D \- z9 G$ @ 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);; q6 L- d1 Y; \; m5 r1 A+ h. d4 a 9 L/ U6 B: }8 @ bar_bme680_temp = lv_bar_create(tile_1);7 `9 F8 B$ \! ^ lv_obj_align(bar_bme680_temp , LV_ALIGN_CENTER, 0 , -180); lv_obj_set_size(bar_bme680_temp , 260 , 20); 9 g6 N1 h1 L/ Y* Z label_bme680_pres = lv_label_create(tile_1);0 n: v6 [5 ~+ U9 z+ g2 B& h lv_obj_set_style_text_font(label_bme680_pres , font , LV_STATE_DEFAULT);8 {' g2 x8 W, h lv_obj_align(label_bme680_pres , LV_ALIGN_CENTER, 0 , -120);3 P! O$ R8 ?, q' h4 M' Q, G: ` 7 o/ ]. }( C; x; N3 m+ H" c: J/ Y bar_bme680_pres = lv_bar_create(tile_1);& F; E' x" w; c( D8 f8 H8 M8 C) r lv_obj_align(bar_bme680_pres , LV_ALIGN_CENTER, 0 , -80); lv_obj_set_size(bar_bme680_pres , 260 , 20);& b0 O8 S( a( m 3 @3 }/ P5 ]3 p7 E- X8 B4 `# _# `& t label_bme680_humi = lv_label_create(tile_1); lv_obj_set_style_text_font(label_bme680_humi , font , LV_STATE_DEFAULT);7 B5 F& ]; l1 \- ]4 r, U lv_obj_align(label_bme680_humi , LV_ALIGN_CENTER, 0 , -20); 0 D8 X* M! ?, A bar_bme680_humi = lv_bar_create(tile_1);9 P. N$ c1 j {/ W+ ` lv_obj_align(bar_bme680_humi , LV_ALIGN_CENTER, 0 , 20); lv_obj_set_size(bar_bme680_humi , 260 , 20);1 V0 D* `7 O% U% D 看看演示效果: 用手捂着BME680,会看到温度读数升高,松开温度则降下来。 - C' J( k4 d+ i5 p$ n U% x: N) y# ^4 L 除了使用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];( F! I9 c* Y9 Q1 n; i% ?: l6 r bool uart2_flag1 = false; bool uart2_flag2 = false;" W4 A) H" W9 V; J" P void UART2_Init(uint32_t baud) { GPIO_InitTypeDef gpio_init_struct; __HAL_RCC_GPIOA_CLK_ENABLE();+ X3 h6 T8 H; i: N# A6 a __HAL_RCC_USART2_CLK_ENABLE(); __HAL_RCC_DMA1_CLK_ENABLE();7 X8 b2 j4 d8 `5 I; r7 h + q) A9 _! @$ g1 b' Q o! z gpio_init_struct.Mode = GPIO_MODE_AF_PP;2 f" e4 a/ _. Q r8 ~ gpio_init_struct.Pull = GPIO_PULLUP;! `3 H) K. E; ~- q( {8 ]) L# j gpio_init_struct.Speed = GPIO_SPEED_FREQ_HIGH; gpio_init_struct.Alternate = GPIO_AF7_USART2; 0 x' W0 H5 o& Q gpio_init_struct.Pin = GPIO_PIN_2 | GPIO_PIN_3; , a# F1 M) m0 Q i, x4 C HAL_GPIO_Init(GPIOA , &gpio_init_struct);# r* u+ c' G6 e# L7 ?* X9 R - R" K+ q* m# R( X g_uart2_handle.Instance = USART2;2 Q" e K D3 H( L H g_uart2_handle.Init.BaudRate = baud;- B% ~ E$ }& ?; Y g_uart2_handle.Init.WordLength = UART_WORDLENGTH_8B;- P( {9 V, U! n( \ g_uart2_handle.Init.StopBits = UART_STOPBITS_1;( t4 Z6 s+ r. H4 i1 M# a$ \) G g_uart2_handle.Init.Parity = UART_PARITY_NONE;2 L7 ^+ E! V3 j: E8 L4 t g_uart2_handle.Init.HwFlowCtl = UART_HWCONTROL_NONE;: e% b: \" L: g# ] E) s- \- [ g_uart2_handle.Init.Mode = UART_MODE_TX_RX; * b. E& G" d4 ~- X HAL_UART_Init(&g_uart2_handle);, Z: [, H7 h7 I6 s __HAL_UART_ENABLE_IT(&g_uart2_handle , UART_IT_IDLE);4 H. a+ T K6 y HAL_NVIC_SetPriority(USART2_IRQn , 0 , 0); HAL_NVIC_EnableIRQ(USART2_IRQn);& I$ _8 ]8 ?, Q0 L : K6 j& o2 S! y3 r! O. I2 ]: b hdma_uart2_rx.Instance = DMA1_Stream0;: N# E5 ]$ L7 \( |1 h% @ hdma_uart2_rx.Init.Request = DMA_REQUEST_USART2_RX; hdma_uart2_rx.Init.Direction = DMA_PERIPH_TO_MEMORY;5 G5 u+ k0 u6 ~( q0 E hdma_uart2_rx.Init.PeriphInc = DMA_PINC_DISABLE; hdma_uart2_rx.Init.MemInc = DMA_MINC_ENABLE;9 t% b' N7 q% W Y2 `0 z4 s hdma_uart2_rx.Init.PeriphDataAlignment = DMA_PDATAALIGN_BYTE; hdma_uart2_rx.Init.MemDataAlignment = DMA_MDATAALIGN_BYTE;2 b6 d5 F$ i" P/ j) B. g hdma_uart2_rx.Init.Mode = DMA_CIRCULAR; hdma_uart2_rx.Init.Priority = DMA_PRIORITY_LOW;" J3 R- E) X4 z6 O' |$ z hdma_uart2_rx.Init.FIFOMode = DMA_FIFOMODE_DISABLE;: H* k* P- n% R HAL_DMA_Init(&hdma_uart2_rx); __HAL_LINKDMA(&g_uart2_handle , hdmarx , hdma_uart2_rx);9 h" s! O9 _0 g9 V6 ] }4 o/ ? `3 j# A7 X# ~* ^( ~ X! E1 k 6 c) `0 a4 F- r$ N STM32H743没有将DMA通道和特定外设绑定,因此DMA通道选择别的也是可以的(但最好是选择DMA1)。 串口中断服务函数实现: void USART2_IRQHandler()$ q; W: l! m3 _2 O0 U* H { if(__HAL_UART_GET_FLAG(&g_uart2_handle, UART_FLAG_IDLE)) {' E8 f2 g3 ^) c% i8 o- V. R __HAL_UART_CLEAR_IDLEFLAG(&g_uart2_handle);' E/ C) T, L7 g: m0 c' r& \% Z // g_uart2_handle.Instance->ISR; // g_uart2_handle.Instance->RDR;- i1 L3 Y/ @7 Z, r: y- T6 z) P // printf("USART2_IRQHandler\n");' c- ^( r3 k. Q HAL_UART_DMAStop(&g_uart2_handle);+ A# U& z1 P' b8 t! | # }+ I% [6 J3 ~8 f+ r1 E8 U SCB_InvalidateDCache_by_Addr((uint32_t*)rx2_buf , BUFFERSIZE);0 Q: s1 i2 }2 x- ? Z) k, s4 t rx2_len = BUFFERSIZE - __HAL_DMA_GET_COUNTER(&hdma_uart2_rx); uart2_recv_end_flag = 1; rx2_buf[rx2_len] = '\0';) z" y# M& \& U/ p0 z // printf("USART2_IRQHandler rx2_buf = %s rx2_len = %d.\n" , rx2_buf , rx2_len);* D) d' ^9 j9 i if(strstr(rx2_buf , "打开一号灯") != NULL)& S( M! p6 c; v" x; r3 p8 i6 C8 t9 Z { printf("USART2_IRQHandler 打开一号灯.\n" , rx2_buf , rx2_len);) d: s( q: N6 Q( a uart2_flag1 = true;8 ^; w% p- @) r1 g } else if(strstr(rx2_buf , "关闭一号灯") != NULL): g F' l& j9 Y/ {. n$ X" n: a6 \ {4 }! S" V% ?% A r' B7 P' c. l( A printf("USART2_IRQHandler 关闭一号灯.\n" , rx2_buf , rx2_len);/ b: t- A$ c4 ^( U& y+ \2 ^0 D6 O uart2_flag1 = false; }% T+ i9 S& L0 _, W else if(strstr(rx2_buf , "打开二号灯") != NULL) { printf("USART2_IRQHandler 打开二号灯.\n" , rx2_buf , rx2_len);0 D- I9 C$ k8 I6 ?1 w uart2_flag2 = true; } else if(strstr(rx2_buf , "关闭二号灯") != NULL) { printf("USART2_IRQHandler 关闭二号灯.\n" , rx2_buf , rx2_len);# F6 Z; u! E! r' r uart2_flag2 = false; }/ ~( o% q6 S" z: h9 d% C HAL_UART_Receive_DMA(&g_uart2_handle , (unsigned char*)rx2_buf , BUFFERSIZE);" N) c1 D0 H7 O, i! J } }6 e& S$ f. j3 a1 C 在这里实现相关的标志位控制,以便在lv_timer定时器回调函数中控制相应组件或者外设。lv_timer定时器回调函数: # q& }, x% }( V$ T static void my_timer1(lv_timer_t * timer) { 。。。 if(uart2_flag1) {1 d. m/ |9 U8 k& H6 Q" V% e8 l( D lv_led_on(lv_led1);8 H' V' b5 r( s1 [: Q: _: ^0 b }- q7 J) ~' h: }: w2 z6 B/ h r else * T9 r, T. P& t! A8 D7 n5 j P8 I { lv_led_off(lv_led1);1 B+ x7 b5 y } } if(uart2_flag2) {1 a( A2 u m% f3 @. J5 U: [! g lv_led_on(lv_led2); } else 5 I0 n) T8 P5 Q9 X2 w3 I9 N, ]; U { lv_led_off(lv_led2); } }4 N* d5 ]8 K4 n4 e) r d 视频演示效果: |
STM32H743基于CubeMx5.0配置手册
STM32L562DK探索板——串口通讯测试成功
【STM32H735G-DK测评】lvgl移植
【STM32H735G-DK 测评】STM32H735G-DK External Loder 编译和测试
M24LR-discovery测试小结
STM32L562DK探索板——串口通讯的再测试
STM32L562DK探索板——串口通讯的测试
STM32L562DK探索板——运动传感器测试
STM32L562DK探索板——继续深入了解Audio Play的工作流程
STM32L562DK探索板——触摸菜单实验之触摸测试
微信公众号
手机版