|
LVGL默认是使用回调函数进行事件触发的,比如按下屏幕中的按键,或者是某个控件的值发生变化之类的,而如果想要使用LVGL去连续不断地显示某样东西,那就需要用到lv_timer即定时器控件,这帖我将用lv_timer来实现对BME680模块的温度、湿度、气压数据读取。# ]; Z) G* ^( s% }; u3 v$ h5 L 首先需要在LVGL程序开始的时候注册定时器回调函数:
void lv_example_tileview(void)1 J( b# k$ ~9 u! J* ?5 ]8 W) z$ c {0 S: m- Q: O. `- l8 t) r8 I4 o2 G lv_obj_t *tileview = lv_tileview_create(lv_scr_act()); lv_timer_t * timer1;& o( N: @7 U6 @& C" ^1 P6 G: K timer1 = lv_timer_create(my_timer1 , 100000 , NULL); 。。。. D- e' v8 Z8 ~( h" A* p" J+ [. K } 5 M! v ]2 i0 X% q / B1 f' j' [/ P3 \5 I! V- z 第二个参数为定时器溢出时间间隔,数字越大间隔越大。然后需要在定时器回调函数里面写上具体的实现:3 M" O) S' L/ j( ]1 L3 P( j4 @4 n static void my_timer1(lv_timer_t * timer) { 。。。$ Y$ Q$ i# \2 V2 @9 S) w5 J }* g d4 |) s$ g BME680是一个使用I2C通信总线的模块,非常典型也非常常用,采集的大气压数据非常准确,跟气象台数据基本一致,算是BMP280的上位替代,BME680的初始化代码比较特别,是使用回调函数注册相关参数的,比如I2C读,I2C写,Delay函数,器件地址等,我手上这个模块的器件地址是0x77:
#define BME680_I2C_ADDR_SECONDARY UINT8_C(0x77) ; \! x+ v4 u3 }4 M gas_sensor.dev_id = BME680_I2C_ADDR_SECONDARY;" l4 ]" X, E$ [- b% f+ ] C gas_sensor.intf = BME680_I2C_INTF;$ U1 s* p! g+ C7 T& R gas_sensor.read = user_i2c_read;- N1 ^* @# `* D5 P gas_sensor.write = user_i2c_write; gas_sensor.delay_ms = Delay_ms;2 a$ V( V/ X+ P8 t if(bme680_init(&gas_sensor) != BME680_OK) {) s5 V8 a/ t1 V5 ]# G4 z/ u printf("Error al inicializar el sensor.\n"); } printf("bme680_init.\n");8 j$ e1 C. y5 e . c/ k8 u, L& e, ] I2C就用现成的模拟I2C代码即可:
uint8_t Fake_I2C_Read_Reg_Addr(uint8_t dev_addr , uint8_t reg)* ?8 ~' M* x' c) ^2 G6 W { uint8_t res; Fake_I2C_Start(); & p6 n' l7 R( |0 y$ x Fake_I2C_Send_Byte((dev_addr << 1) | 0);( Y! Z& G6 I( M) @2 U$ ?- D Fake_I2C_Wait_Ack(); Fake_I2C_Send_Byte(reg);' J- m9 P2 ~7 W8 o0 t Fake_I2C_Wait_Ack();+ n" e; @, K/ m Fake_I2C_Start();7 M9 ?; ^$ V6 e8 A3 t! K Fake_I2C_Send_Byte((dev_addr << 1) | 1); Fake_I2C_Wait_Ack(); res = Fake_I2C_Read_Byte(0); Fake_I2C_Stop();9 w" p0 V$ W4 K! M* L* I return res; } void Fake_I2C_Read_Reg_Datas(uint8_t dev_addr , uint8_t reg , uint8_t data_len , uint8_t data[]) {) {' G# Q# ]) A3 ]( ` while(data_len) {/ R3 W# u ]" F6 t; f *data = Fake_I2C_Read_Reg_Addr(dev_addr , reg++);4 l4 J% b' P5 n% O( I: V data ++;( c: w" ^. _; P1 S y data_len --;. A$ m1 ?* z. K0 k, q. j } # c3 ?/ L7 } _' ?! j& n Delay_ms(1); }( L) K- P. P7 ?) J3 v/ z1 A 3 D+ V! |" g: P) i: a5 S) I 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();$ V* R! q+ R4 D$ {5 n# W Fake_I2C_Send_Byte(reg); . y: e/ ~8 B& V$ y Fake_I2C_Wait_Ack();, |6 P9 w3 S* h2 k Fake_I2C_Send_Byte(data); Fake_I2C_Wait_Ack();+ ?& P9 a8 f* N8 s. \8 |2 Y5 c. R Fake_I2C_Stop(); }$ [) [9 j5 g* X) m1 w; y9 | $ s+ f# f8 Z; @ void Fake_I2C_Write_Reg_Addr_Datas(uint8_t dev_addr , uint8_t reg , uint8_t data_len , uint8_t data[])3 p8 F" g+ r" g% A' t {+ \6 R; P5 A6 W1 q. X int i;7 a j: Y" y1 s# [ Fake_I2C_Start(); Fake_I2C_Send_Byte((dev_addr << 1) | 0);# a) l3 h" O9 x8 t9 n6 K6 t Fake_I2C_Wait_Ack();9 v3 B+ x4 f8 t% w2 u, {- ` Fake_I2C_Send_Byte(reg); 9 G6 I$ a( o% \ Fake_I2C_Wait_Ack(); for(i = 0 ; i < data_len ; i++)+ H# t* d+ m. I2 x& b. F( ]0 T/ O { Fake_I2C_Send_Byte(data); - _+ d x, R& B- r% W) _ 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)% I7 O5 ]' Q% L) z2 Z% Q { Fake_I2C_Write_Reg_Addr_Datas(0x77 , reg_addr , len , reg_data); return 0; l, J( l/ M( x7 y# k }9 _. h' o& d8 J C2 P. u) E; ^ $ ~- P$ k/ J" e+ q4 }" L3 R int8_t user_i2c_read(uint8_t dev_id , uint8_t reg_addr , uint8_t *reg_data , uint16_t len) {) y+ i" `' r/ `) G( o0 e3 i( g Fake_I2C_Read_Reg_Datas(0x77 , reg_addr , len , reg_data);" B" t# ?6 N; I, W- z( E return 0; }* R& H$ ]6 m; K6 ~ 在主界面中添加tileview,可以实现类似智能手机的滑屏效果:! `) D% S4 f. a8 j8 u+ v lv_obj_t *tileview = lv_tileview_create(lv_scr_act());% J. y9 D |/ Q. e6 ^* Z% X# Q " d7 o2 E7 w$ ?6 T, I4 m' y% |, \+ p - g7 n, p, ~3 }9 Z 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 );/ X8 `+ L9 k- v$ O. u! k& P 这样,就可以单独用一个页面去显示BME680的传感器读数,而其它页面实现别的功能了,非常方便。用Bar(进度条)和Label去动态显示读数:+ ?8 }- s& i0 |! ]7 r5 H # P4 F9 `) @2 n- |# |; N( ] font = &lv_font_montserrat_24; lv_obj_t* label = lv_label_create(btn_speed_up);" ^1 s; W& ]) ^ lv_obj_set_style_text_font(label , font , LV_PART_MAIN);- h7 n G0 ?- @0 I" F label_bme680_temp = lv_label_create(tile_1); lv_obj_set_style_text_font(label_bme680_temp , font , LV_STATE_DEFAULT);1 V' k# ~: [& Y/ K6 W lv_obj_align(label_bme680_temp , LV_ALIGN_CENTER, 0 , -220);: I4 f/ Q5 h$ \1 J) k3 u , y& \2 J9 I, D! h bar_bme680_temp = lv_bar_create(tile_1);. o+ E2 \. U- P0 `3 {! v/ x 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);: u% m: b/ @% s! W9 f& F lv_obj_set_style_text_font(label_bme680_pres , font , LV_STATE_DEFAULT); lv_obj_align(label_bme680_pres , LV_ALIGN_CENTER, 0 , -120); \, V5 i1 o6 i9 m" c' x bar_bme680_pres = lv_bar_create(tile_1); lv_obj_align(bar_bme680_pres , LV_ALIGN_CENTER, 0 , -80);. }3 ^, d- C9 T/ }6 R/ R5 ? ]/ { lv_obj_set_size(bar_bme680_pres , 260 , 20); * {/ n- Z8 Q& T: {5 G1 H. G label_bme680_humi = lv_label_create(tile_1);. g! ~2 p& _/ P' B9 `. y lv_obj_set_style_text_font(label_bme680_humi , font , LV_STATE_DEFAULT);+ p* O7 [0 F3 C/ k lv_obj_align(label_bme680_humi , LV_ALIGN_CENTER, 0 , -20);5 Z5 h! R" X, a" t+ f & {% T4 k8 G t& [; L5 A. R! \+ [ bar_bme680_humi = lv_bar_create(tile_1);% c# C" ]2 o* r7 e5 f- C8 ^1 Z lv_obj_align(bar_bme680_humi , LV_ALIGN_CENTER, 0 , 20); lv_obj_set_size(bar_bme680_humi , 260 , 20); ! a3 U7 g) Q7 z8 p& N( b* G 看看演示效果: 用手捂着BME680,会看到温度读数升高,松开温度则降下来。$ R4 v5 P- e+ x" h; a% q* u4 d/ ^ 2 t1 N. }8 G$ J: \* s5 W ) ]% P- z: Z' u 除了使用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;2 n- I" j4 C3 H( E# m uint32_t rx2_len = 0;3 }. ~4 y: M: O$ N5 ^; M: y. b unsigned char rx2_buf[BUFFERSIZE];4 u7 \: t: t" P& X! H( H: J$ ^4 w0 ? bool uart2_flag1 = false;" a5 F& u0 Y7 Z' n, f bool uart2_flag2 = false; # \/ M, b8 U& K1 y! M void UART2_Init(uint32_t baud)5 H* [$ I+ t! {% m5 {' V% Y+ a {! `8 c# `% F- L! { GPIO_InitTypeDef gpio_init_struct; __HAL_RCC_GPIOA_CLK_ENABLE(); __HAL_RCC_USART2_CLK_ENABLE();+ F9 B _- u0 x __HAL_RCC_DMA1_CLK_ENABLE(); , y* s2 _9 u' v; n. o$ g' U1 j gpio_init_struct.Mode = GPIO_MODE_AF_PP;8 \ W0 w6 r3 v. P0 I1 x8 | gpio_init_struct.Pull = GPIO_PULLUP; V5 z7 M/ K& D& _. } gpio_init_struct.Speed = GPIO_SPEED_FREQ_HIGH; gpio_init_struct.Alternate = GPIO_AF7_USART2;4 @/ x) G8 z& U gpio_init_struct.Pin = GPIO_PIN_2 | GPIO_PIN_3; HAL_GPIO_Init(GPIOA , &gpio_init_struct);2 l( P) X( l& Y0 L0 I g_uart2_handle.Instance = USART2;% D4 \2 U1 ^/ e5 H5 ]* `+ x g_uart2_handle.Init.BaudRate = baud;8 I1 `. _, o) q1 x6 \! e0 d g_uart2_handle.Init.WordLength = UART_WORDLENGTH_8B; g_uart2_handle.Init.StopBits = UART_STOPBITS_1;- k: g+ G, d% H& Z" t, F g_uart2_handle.Init.Parity = UART_PARITY_NONE; g_uart2_handle.Init.HwFlowCtl = UART_HWCONTROL_NONE;% \8 [7 V' {- N. ? g_uart2_handle.Init.Mode = UART_MODE_TX_RX; ( x8 }6 j4 D5 N' m HAL_UART_Init(&g_uart2_handle); __HAL_UART_ENABLE_IT(&g_uart2_handle , UART_IT_IDLE); \& S! V% a% k# U8 }7 Y HAL_NVIC_SetPriority(USART2_IRQn , 0 , 0);6 n" h. z# b7 Z; o5 O HAL_NVIC_EnableIRQ(USART2_IRQn);. j* L; c* L9 e2 Z7 o7 W, s # \3 k7 N# s" S: q, t; C hdma_uart2_rx.Instance = DMA1_Stream0; hdma_uart2_rx.Init.Request = DMA_REQUEST_USART2_RX; hdma_uart2_rx.Init.Direction = DMA_PERIPH_TO_MEMORY;+ D' r% l- M$ ]; s hdma_uart2_rx.Init.PeriphInc = DMA_PINC_DISABLE;1 p8 J6 U; x- E) R' V hdma_uart2_rx.Init.MemInc = DMA_MINC_ENABLE;2 P _0 e9 J" x 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);0 L4 E6 }+ @9 |# f } 0 ]3 W* H6 }% j9 `$ R& x* _& A* s, d8 b STM32H743没有将DMA通道和特定外设绑定,因此DMA通道选择别的也是可以的(但最好是选择DMA1)。4 L! ]# C( c& ~% o. L: ?& O 1 T, ^& h! n b2 w$ \, A 串口中断服务函数实现:, k5 h' M g- w9 C# g! e8 S void USART2_IRQHandler()* d) q7 J6 I D: ]2 J7 Q {% H9 g6 r( N+ M1 b3 R+ ~& S. q s if(__HAL_UART_GET_FLAG(&g_uart2_handle, UART_FLAG_IDLE))' {# ]; f. v8 M4 ^1 M- M {' g# L* O$ m* j3 M. w& I" I __HAL_UART_CLEAR_IDLEFLAG(&g_uart2_handle); ) _$ ^% E i/ H* h- w // g_uart2_handle.Instance->ISR; // g_uart2_handle.Instance->RDR; // printf("USART2_IRQHandler\n");3 | l8 i/ K" l0 |/ O" s & L* r- j$ e! o5 S4 B5 A, \5 ~0 \ HAL_UART_DMAStop(&g_uart2_handle);: D# o4 b: N3 f0 B& {, z \ SCB_InvalidateDCache_by_Addr((uint32_t*)rx2_buf , BUFFERSIZE);& ]* ]( c+ Q0 p7 H rx2_len = BUFFERSIZE - __HAL_DMA_GET_COUNTER(&hdma_uart2_rx); uart2_recv_end_flag = 1; rx2_buf[rx2_len] = '\0';' G+ n: e7 [/ [7 h7 Z8 u // printf("USART2_IRQHandler rx2_buf = %s rx2_len = %d.\n" , rx2_buf , rx2_len); if(strstr(rx2_buf , "打开一号灯") != NULL)9 {# U4 q# t4 I' Z { printf("USART2_IRQHandler 打开一号灯.\n" , rx2_buf , rx2_len); uart2_flag1 = true;) P9 |5 ^* T c& ?2 ~ } else if(strstr(rx2_buf , "关闭一号灯") != NULL) { printf("USART2_IRQHandler 关闭一号灯.\n" , rx2_buf , rx2_len);" U! [3 u5 |2 B5 D( D3 c uart2_flag1 = false;1 U7 H+ o; W* Y* Y6 [6 B" [ } ^; k# k% L P' j else if(strstr(rx2_buf , "打开二号灯") != NULL); r4 U! o p, T- t% Z5 v7 ^! V {; Z+ e L. j- P$ u( r: H/ q: H! K printf("USART2_IRQHandler 打开二号灯.\n" , rx2_buf , rx2_len);1 o- Q7 I( o7 R }) ~# ?( Z& R uart2_flag2 = true;3 r! {* W2 y! {* F: R2 A }! X1 ]8 T9 }3 ~" t0 M2 O& T5 Z else if(strstr(rx2_buf , "关闭二号灯") != NULL)9 G, P: e% L- A# Q; U% ^, S- B' K8 L {0 ] w' @, Q; ]5 A printf("USART2_IRQHandler 关闭二号灯.\n" , rx2_buf , rx2_len);) C% b1 S0 ]) j/ n a! z6 n6 V, \ uart2_flag2 = false; } HAL_UART_Receive_DMA(&g_uart2_handle , (unsigned char*)rx2_buf , BUFFERSIZE);4 y4 q% s! g' s0 R ?9 N }9 i, k. I% D0 a0 O2 B7 [ }/ ?& `, G" p; g$ _, v ; P& \* I- t5 Q* X 在这里实现相关的标志位控制,以便在lv_timer定时器回调函数中控制相应组件或者外设。lv_timer定时器回调函数: - y1 M3 n8 l, A static void my_timer1(lv_timer_t * timer)7 j3 f; I# G/ g% \7 f W$ G3 G, a { 。。。 if(uart2_flag1)$ K! ^7 L1 O3 J, m, J { lv_led_on(lv_led1);& g* Y! e* d6 E' R; ]! ^ }8 }" O+ t4 v7 a+ C& G) `- E else ; j: b8 Q6 Q" }5 U$ G# M {% G% |( B! h* c% t1 w lv_led_off(lv_led1);- D, V. Z, t% V5 t }, E. ]8 O2 r( T D. M0 N! l if(uart2_flag2)7 p: l8 d6 O/ |6 }$ s0 j- T {! P% q' d7 Q- S4 s lv_led_on(lv_led2); } else ( g& B: T. }" @0 p9 Y { lv_led_off(lv_led2);- |; F: Y& p# |4 ]2 S } } ; T2 K7 r1 n# q% ] 视频演示效果: |
【STM32H735G-DK测评】lvgl移植
【STM32H735G-DK 测评】STM32H735G-DK External Loder 编译和测试
M24LR-discovery测试小结
STM32L562DK探索板——串口通讯的再测试
STM32L562DK探索板——串口通讯的测试
STM32L562DK探索板——运动传感器测试
STM32L562DK探索板——继续深入了解Audio Play的工作流程
STM32L562DK探索板——触摸菜单实验之触摸测试
STM32L562DK探索板——触摸菜单实验之LED闪烁
STM32L562DK探索板——Audio Play的再了解
微信公众号
手机版