|
LVGL默认是使用回调函数进行事件触发的,比如按下屏幕中的按键,或者是某个控件的值发生变化之类的,而如果想要使用LVGL去连续不断地显示某样东西,那就需要用到lv_timer即定时器控件,这帖我将用lv_timer来实现对BME680模块的温度、湿度、气压数据读取。' g. N. A7 o& v! B. \2 [, F5 ?* Y* h 首先需要在LVGL程序开始的时候注册定时器回调函数:+ A9 @7 R9 E/ W" D1 O
void lv_example_tileview(void). k: P" d0 g; i* K5 N& z# @ {& I. Y0 q5 L+ E! j( |& N" Q4 M4 @ lv_obj_t *tileview = lv_tileview_create(lv_scr_act());+ ?+ I1 G$ x L+ c9 g/ r" u lv_timer_t * timer1; `# l/ A/ |3 z) V timer1 = lv_timer_create(my_timer1 , 100000 , NULL); 。。。, y- j+ R7 F0 }: {0 Q; g0 [! [ }# u! {) U5 Y: A0 Z) l7 z" y ' f' i" ]: Q7 W* D 第二个参数为定时器溢出时间间隔,数字越大间隔越大。然后需要在定时器回调函数里面写上具体的实现:! R5 `8 b$ H ^% C2 d static void my_timer1(lv_timer_t * timer)% z2 ?0 o& `: @1 W { 。。。 }9 ~ h* P) A( o: D0 w; `1 X ; p! y% g7 J( l4 |" e( w! W 3 \; i* A) D: \7 t A+ _4 S/ a6 E BME680是一个使用I2C通信总线的模块,非常典型也非常常用,采集的大气压数据非常准确,跟气象台数据基本一致,算是BMP280的上位替代,BME680的初始化代码比较特别,是使用回调函数注册相关参数的,比如I2C读,I2C写,Delay函数,器件地址等,我手上这个模块的器件地址是0x77:
#define BME680_I2C_ADDR_SECONDARY UINT8_C(0x77) 9 a% n/ G' A% G- f( q6 Y gas_sensor.dev_id = BME680_I2C_ADDR_SECONDARY;1 }+ k6 a- V* g$ X% u gas_sensor.intf = BME680_I2C_INTF; gas_sensor.read = user_i2c_read;: b @6 z2 K2 u P' q gas_sensor.write = user_i2c_write; gas_sensor.delay_ms = Delay_ms;8 r9 e0 V7 d$ H $ `7 S! n- S& \ if(bme680_init(&gas_sensor) != BME680_OK)/ N; k/ M! K3 B4 _6 C# D/ v" s {5 G: T' L. c+ h3 r7 R printf("Error al inicializar el sensor.\n"); }7 i# n4 @. d9 y( t7 k6 } printf("bme680_init.\n"); : c* F8 t4 O" D9 O2 {% b( H2 y 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);: H( I* S: ~! M$ p: e/ I Fake_I2C_Wait_Ack();% X k+ B. P* E L! O2 v; j& F Fake_I2C_Send_Byte(reg);$ J$ u% j/ b/ [' d7 f! E1 @8 R- O Fake_I2C_Wait_Ack();8 ^- J5 I. y' B3 N d6 S Fake_I2C_Start();0 @) @! z9 A0 r+ i! J2 J Fake_I2C_Send_Byte((dev_addr << 1) | 1); Fake_I2C_Wait_Ack(); res = Fake_I2C_Read_Byte(0); Fake_I2C_Stop();. ], R% G* t- Z: l return res; } " L7 y, P$ H& k, J, b% A0 z 6 x3 H) m$ w9 V; H: o void Fake_I2C_Read_Reg_Datas(uint8_t dev_addr , uint8_t reg , uint8_t data_len , uint8_t data[])# u6 V1 G n- B$ r; s { while(data_len)9 f* p3 ?% f) B$ J* i { *data = Fake_I2C_Read_Reg_Addr(dev_addr , reg++);8 `' {$ m0 R# ]8 w data ++; data_len --; } Delay_ms(1); }8 i: E% t0 u; Y. w1 r! ~/ n void Fake_I2C_Write_Reg_Addr_Data(uint8_t dev_addr , uint8_t reg , uint8_t data)2 h' j: e# L; z {; o: K/ y5 p% {6 w, E Fake_I2C_Start();0 ~* Z4 g( P3 `0 Q, r Fake_I2C_Send_Byte((dev_addr << 1) | 0);' r, r# e6 g& _: y% k$ D1 w' B Fake_I2C_Wait_Ack(); Fake_I2C_Send_Byte(reg); ( n9 {2 ?1 t6 a& r0 D Fake_I2C_Wait_Ack();4 r& D, [- I: l Fake_I2C_Send_Byte(data); 1 Y9 D! t0 ?+ @; V% p( k4 y Fake_I2C_Wait_Ack();7 _, D$ R! o g" t Fake_I2C_Stop(); } void Fake_I2C_Write_Reg_Addr_Datas(uint8_t dev_addr , uint8_t reg , uint8_t data_len , uint8_t data[]) {- v; `' W5 f+ m$ |: C# Q int i;: y v# h/ q! F& p5 m& ^! x2 v Fake_I2C_Start();$ z( n% ?& ~8 P9 @7 k8 v2 z/ X Fake_I2C_Send_Byte((dev_addr << 1) | 0); Fake_I2C_Wait_Ack();: v9 |* K5 z- \5 `, M# P. e Fake_I2C_Send_Byte(reg); 1 O) y5 O0 u" }. U) G3 V: i Fake_I2C_Wait_Ack(); for(i = 0 ; i < data_len ; i++) { Fake_I2C_Send_Byte(data); & K6 j+ Y) \$ A+ @; ] Fake_I2C_Wait_Ack();, B; t. s; j2 Y* h } Fake_I2C_Stop();; I! \1 Q, @) }! W) g: I5 i Delay_ms(1); } 2 R W" w, E: H, I + r& d8 C5 w2 [, C int8_t user_i2c_write(uint8_t dev_id , uint8_t reg_addr , uint8_t *reg_data , uint16_t len) {( d8 E# s! z+ i3 D Fake_I2C_Write_Reg_Addr_Datas(0x77 , reg_addr , len , reg_data);( j# @) ?. V* \ a8 K return 0;) Q- {1 q8 S N6 E$ ~' D } 9 D: z& I, R( S6 d8 I' n0 E int8_t user_i2c_read(uint8_t dev_id , uint8_t reg_addr , uint8_t *reg_data , uint16_t len)! b$ r' M- c# X( G { Fake_I2C_Read_Reg_Datas(0x77 , reg_addr , len , reg_data);# B' ?- `3 V' j- N4 Y return 0; }& B* x: ? h2 B2 L6 C # J) B* [: I3 R) t! T 在主界面中添加tileview,可以实现类似智能手机的滑屏效果: ! N) r, J0 V$ ] lv_obj_t *tileview = lv_tileview_create(lv_scr_act());: U- U! |( _: | 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 ); - E9 z; D8 g7 z+ Y. i % o4 w) ~+ l, p" @3 J2 D 这样,就可以单独用一个页面去显示BME680的传感器读数,而其它页面实现别的功能了,非常方便。用Bar(进度条)和Label去动态显示读数: # o7 G0 N7 I* y font = &lv_font_montserrat_24;3 ^6 Q! q/ b0 Q8 k 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);, j# t2 m8 @* B. v9 J lv_obj_set_size(bar_bme680_temp , 260 , 20); label_bme680_pres = lv_label_create(tile_1);# c7 j7 A5 v5 ]" [ lv_obj_set_style_text_font(label_bme680_pres , font , LV_STATE_DEFAULT); lv_obj_align(label_bme680_pres , LV_ALIGN_CENTER, 0 , -120);5 A! P$ z" v8 R' W + w( B: G: T' Z8 N; H8 a bar_bme680_pres = lv_bar_create(tile_1);0 I, w7 S; j3 D# v/ E+ U lv_obj_align(bar_bme680_pres , LV_ALIGN_CENTER, 0 , -80);: D7 u5 R& l+ J+ c+ H& E/ r lv_obj_set_size(bar_bme680_pres , 260 , 20);# F8 V* q: D7 T2 S0 ?7 B ' }/ B6 k+ V, X- ~5 v 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);# c# v' q: \7 S6 d& Q 6 L* N5 N' o' S. Y! S8 Y; L" ^6 h" \ bar_bme680_humi = lv_bar_create(tile_1);3 n; o$ b2 r3 }2 x# f2 g# I lv_obj_align(bar_bme680_humi , LV_ALIGN_CENTER, 0 , 20); lv_obj_set_size(bar_bme680_humi , 260 , 20);. |" J" r# W1 A6 [ 9 K2 T% P6 _9 ^9 y, \& ^# O 看看演示效果:. [3 M; `/ F* p& x: d8 e5 V 用手捂着BME680,会看到温度读数升高,松开温度则降下来。 ) j I$ C: ~; z 除了使用LVGL界面以及lv_timer定时器定时采集BME680数据并显示以外,我还用STM32H743VI的GPIO串口功能接一个串口语音模块,结合LVGL界面实现语音控制,涉及到中断跳转以及lv_timer定时器回调机制。- ^2 r. d) M' E1 Z 串口语音模块直接买的天问模块,接开发板的UART2即PA2和PA3接口:: M5 N R2 R# z6 P
串口的空闲中断及DMA实现如下:& \ o* ~5 C1 t2 y8 d. D UART_HandleTypeDef g_uart2_handle;. W; n O( `9 _% D' o7 z 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;. h8 A1 ]1 n' {& O" k void UART2_Init(uint32_t baud) {! K4 d/ V) [ s) o4 t GPIO_InitTypeDef gpio_init_struct; / M3 d. F7 l$ {$ V! ^+ c' I% p: r! y __HAL_RCC_GPIOA_CLK_ENABLE(); __HAL_RCC_USART2_CLK_ENABLE();+ q% M* s0 {, |7 F1 K9 O __HAL_RCC_DMA1_CLK_ENABLE(); : m) [8 h+ Y* g gpio_init_struct.Mode = GPIO_MODE_AF_PP; gpio_init_struct.Pull = GPIO_PULLUP;/ k, G/ D. O) G7 i0 O: o0 N gpio_init_struct.Speed = GPIO_SPEED_FREQ_HIGH; gpio_init_struct.Alternate = GPIO_AF7_USART2; 1 p4 O# G0 Y. a gpio_init_struct.Pin = GPIO_PIN_2 | GPIO_PIN_3; 9 Q; y4 U4 j" h- P* y: }/ Q HAL_GPIO_Init(GPIOA , &gpio_init_struct); $ {3 j" C) Y; o" M/ i; P4 e g_uart2_handle.Instance = USART2; g_uart2_handle.Init.BaudRate = baud;: {8 l0 [% t$ Y9 S7 v g_uart2_handle.Init.WordLength = UART_WORDLENGTH_8B; g_uart2_handle.Init.StopBits = UART_STOPBITS_1; g_uart2_handle.Init.Parity = UART_PARITY_NONE;3 D# S7 o) O0 C6 y* r- m g_uart2_handle.Init.HwFlowCtl = UART_HWCONTROL_NONE;4 D% k! R1 N: h, B8 e3 M g_uart2_handle.Init.Mode = UART_MODE_TX_RX; 5 j) L% T2 V7 \% Z, E* F, z HAL_UART_Init(&g_uart2_handle); __HAL_UART_ENABLE_IT(&g_uart2_handle , UART_IT_IDLE);- ~# \9 b m* s8 ]' n. i7 z% ]7 I C HAL_NVIC_SetPriority(USART2_IRQn , 0 , 0);) @8 G1 J) V6 G q& p" r+ \4 O HAL_NVIC_EnableIRQ(USART2_IRQn); ) W2 W% D" f9 y' Q+ a8 _% p o& N8 V hdma_uart2_rx.Instance = DMA1_Stream0; ~/ z9 W, X8 _+ t$ ^# r2 i 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;1 k) L, n! ~. J* _1 A* U hdma_uart2_rx.Init.MemDataAlignment = DMA_MDATAALIGN_BYTE;+ M0 ^# |( `8 C& z) d% E2 V hdma_uart2_rx.Init.Mode = DMA_CIRCULAR; hdma_uart2_rx.Init.Priority = DMA_PRIORITY_LOW; hdma_uart2_rx.Init.FIFOMode = DMA_FIFOMODE_DISABLE;: D3 `- A1 |1 [: P: V2 | HAL_DMA_Init(&hdma_uart2_rx); __HAL_LINKDMA(&g_uart2_handle , hdmarx , hdma_uart2_rx); } STM32H743没有将DMA通道和特定外设绑定,因此DMA通道选择别的也是可以的(但最好是选择DMA1)。 " {2 J; ~$ i2 D3 b 串口中断服务函数实现:- P8 n- V& z$ Z. W) \& T void USART2_IRQHandler() {4 J- C/ A! e6 e if(__HAL_UART_GET_FLAG(&g_uart2_handle, UART_FLAG_IDLE)) { __HAL_UART_CLEAR_IDLEFLAG(&g_uart2_handle);: q3 z8 z2 J8 ~' J8 N3 D9 F5 d ; C% h& B+ a& }3 c1 ~. F- A# @) T // g_uart2_handle.Instance->ISR;% {' F% V; T) \ V // g_uart2_handle.Instance->RDR;8 a4 t/ j. E8 J8 [! [0 j // printf("USART2_IRQHandler\n"); HAL_UART_DMAStop(&g_uart2_handle);# u5 I7 B+ _6 n : o5 n; W( q. N8 H6 D: H' V SCB_InvalidateDCache_by_Addr((uint32_t*)rx2_buf , BUFFERSIZE);: Q! X8 L, d- J/ G G# N7 [+ E rx2_len = BUFFERSIZE - __HAL_DMA_GET_COUNTER(&hdma_uart2_rx); uart2_recv_end_flag = 1; rx2_buf[rx2_len] = '\0';* n0 S( ]. u/ ~2 A H( ? // printf("USART2_IRQHandler rx2_buf = %s rx2_len = %d.\n" , rx2_buf , rx2_len); if(strstr(rx2_buf , "打开一号灯") != NULL)' G( {/ b3 U, _: _1 Q- y/ v5 p {1 l, T& A3 h$ V+ Q% V printf("USART2_IRQHandler 打开一号灯.\n" , rx2_buf , rx2_len); uart2_flag1 = true;! r1 ~" y5 E, {! G; N% D) y) j } else if(strstr(rx2_buf , "关闭一号灯") != NULL) { printf("USART2_IRQHandler 关闭一号灯.\n" , rx2_buf , rx2_len);) n$ P0 W, P4 O) ]' s# ~ uart2_flag1 = false; } else if(strstr(rx2_buf , "打开二号灯") != NULL) {, w# t6 s0 u3 W2 O- ~ printf("USART2_IRQHandler 打开二号灯.\n" , rx2_buf , rx2_len);% p! d' x5 |8 _ uart2_flag2 = true;6 d- w1 X1 q' p } else if(strstr(rx2_buf , "关闭二号灯") != NULL) {) S7 Q7 X* }8 ~+ d" x printf("USART2_IRQHandler 关闭二号灯.\n" , rx2_buf , rx2_len); uart2_flag2 = false; }8 a7 ~( c2 R0 M* Y( b A. c0 I HAL_UART_Receive_DMA(&g_uart2_handle , (unsigned char*)rx2_buf , BUFFERSIZE);! Q3 F, X" N% G& k6 C- [, y }: ]+ K6 E3 c+ ~& j) D }& h0 s" S, y/ f: T0 Z( i% l5 z ; P- O. U% q% j, N 在这里实现相关的标志位控制,以便在lv_timer定时器回调函数中控制相应组件或者外设。lv_timer定时器回调函数:4 _ Z# g$ W, b' e4 ] x8 N, T- N * {: I' i% y) j' Y" t/ r static void my_timer1(lv_timer_t * timer)9 K/ Y2 Z4 G: g9 O {: n- \# K @, u1 o: c, V9 ~. ?) b 。。。 if(uart2_flag1)3 a4 W6 |6 p$ f0 | {) U% J3 i9 E0 ^/ r/ Z- P% w2 C lv_led_on(lv_led1);% w* `8 V. ?; t+ P2 \ }' K1 |1 K# z, g9 b: t+ x9 ^5 F else { lv_led_off(lv_led1); }% N# d) U# T0 i7 e7 ^ p6 a7 U if(uart2_flag2). s% P p' x3 P i. m( J {& Z9 v( {$ j0 {. t* ]8 P3 G7 C lv_led_on(lv_led2); Y) e) Y a2 ?5 ?. M* F }6 c: m+ a) z1 m# ~: z# j J' ? else { lv_led_off(lv_led2); }# b4 e, [. M2 K/ t. I } 视频演示效果: I9 d0 Q& u* h8 P' [ |
STM32C543RC开发板Stop、Sleep和standby模式测试
【STM32C5教程】(10)LPUART DMA 通信实验
【STM32C5教程】(9)DAC 模拟量信号输出实验
【STM32C5教程】(8)ADC 模拟量数据采集实验
【STM32C5教程】(7)TIM1 互补 PWM 输出实验
【STM32C5教程】(6)定时器 PWM 输出实验
【STM32C5教程】(5)定时器周期中断实验
【STM32C5教程】(4)GPIO 外部中断实验
【STM32C5教程】(3)GPIO 按键输入实验
STM32C542RC开发板PWM呼吸灯及多功能按键实验
微信公众号
手机版