你的浏览器版本过低,可能导致网站不能正常访问!
为了你能正常使用网站功能,请使用这些浏览器。

【2025·STM32峰会】GUI解决方案实训分享4-使用MVP架构从硬件外设读取数据并显示到图形界面、从图形界面发送指令控制硬件外设

[复制链接]
donatello1996 发布时间:2025-5-22 00:22
前面做了一帖关于TouchGFX搭建工程的铺垫,但是最终还是要实现与硬件外设进行交互的,TouchGFX的架构中,与硬件外设进行交互的架构称之为MVP架构,即Model类(Modlistener类)、ScreenView类以及ScreenPresenter类,其中Model类和ScreenView类可以直接通过include "main.h"的方式直接控制HAL库的硬件外设,也就是可以在ScreenView类的控件回调函数中直接调用HAL库控制语句,达到控制硬件外设的目的。那么,怎么从硬件外设处循环读取某些信息,比如读取AD采集值,读取SPI接口的2.4G无线模块接收到的信息呢?方法有二,第一是在Model类的tick成员函数中进行读取,第二是在TouchGFX Disigner中创建tick回调事件之后,在ScreenView类的tick回调事件函数中进行读取,前者读取较为频繁,后者读取节拍慢一些,根据需要选择不同的方式,本例中我将使用Model类的tick成员函数读取AD采集值并显示到屏幕上。( f1 C" ~# K6 I- W
37.jpg 1 J8 Q$ Z6 ?8 k+ h
& v) e2 ~7 M2 c8 ]+ l3 Z6 b
比较偷懒的做法,初始化AD采集外设的代码可以放在main.c和main.h中,这样程序在系统初始化时就会自动初始化AD采集外设,并可以在程序的任何位置做AD采集操作:
  o6 c( Y6 x- }# J; b( b' H
  1. ADC_HandleTypeDef g_adc_handle;  a" M8 Q1 _' V

  2. 3 U8 b$ L" E4 `) V& o9 d/ C
  3. void ADC_PB0_Init(void)$ o3 X1 b6 ?5 I; N% O9 k% k
  4. {
    6 f( Y$ Y) x: ^# W* z. r

  5. 4 V# P# [& ^, U9 X# f
  6. __HAL_RCC_GPIOB_CLK_ENABLE();& J) m7 D8 H+ L- Q) W+ ~. L- ?
  7. __HAL_RCC_ADC12_CLK_ENABLE();& E2 l) p- R0 n& [: q5 ?

  8. ) ^/ A& q4 S/ v# r; l
  9. RCC_PeriphCLKInitTypeDef rcc_periph_clk_init_struct = {0};4 ]4 L9 d8 p0 [2 A% U, i
  10. GPIO_InitTypeDef gpio_init_struct = {0};
    7 X% z+ B, A$ m: D' g' W# t
  11. ' `* ?  G1 P7 h! R0 T$ C
  12. rcc_periph_clk_init_struct.PeriphClockSelection = RCC_PERIPHCLK_ADC;
    ; \5 P( x" A: G# S) ]
  13. rcc_periph_clk_init_struct.AdcClockSelection = RCC_ADCCLKSOURCE_PLL2P;' b8 x7 C7 }3 u$ z' k
  14. HAL_RCCEx_PeriphCLKConfig(&rcc_periph_clk_init_struct);. k% q" ~; z* u8 n

  15. 6 E2 P& m7 Z% k2 v9 A" _
  16. gpio_init_struct.Mode = GPIO_MODE_ANALOG;  P+ V: G7 D: J! ?/ X
  17. gpio_init_struct.Pull = GPIO_NOPULL;
    ) u2 L, \/ o  n/ y! @3 H( x( ]
  18. gpio_init_struct.Pin = GPIO_PIN_0;& t* m- n$ H& a$ [: c1 O$ ~
  19. HAL_GPIO_Init(GPIOB , &gpio_init_struct);5 }* _) `, _2 O% g7 b
  20. , Y$ q6 c/ \# B2 Q/ y
  21. g_adc_handle.Instance = ADC1;  C& {$ j. e0 \% S( a6 h! B
  22. g_adc_handle.Init.ClockPrescaler = ADC_CLOCK_ASYNC_DIV8;
    7 _. G2 `" M+ V. e; A% B: d/ m
  23. g_adc_handle.Init.Resolution = ADC_RESOLUTION_12B;8 f( }4 L4 g$ a# G! x2 h2 `+ D
  24. g_adc_handle.Init.DataAlign = ADC_DATAALIGN_RIGHT;$ d6 g; A$ f6 Y! a5 b$ `* y
  25. g_adc_handle.Init.ScanConvMode = ADC_SCAN_DISABLE;. s: \" {/ @( F4 c$ }6 E
  26. g_adc_handle.Init.EOCSelection = ADC_EOC_SINGLE_CONV;0 J; O9 ]5 k) W+ w2 e0 x- q8 o
  27. g_adc_handle.Init.LowPowerAutoWait = DISABLE;
    5 y: C3 ~/ H5 @
  28. g_adc_handle.Init.ContinuousConvMode = DISABLE;
    * h0 h% V- X& |' l
  29. g_adc_handle.Init.NbrOfConversion = 1;
    ! X$ f( J. ]6 j. n
  30. g_adc_handle.Init.DiscontinuousConvMode = DISABLE;4 F, |$ m7 c  Z4 W
  31. g_adc_handle.Init.NbrOfDiscConversion = 1;
    ( L9 i: }& C5 U/ K
  32. g_adc_handle.Init.ExternalTrigConv = ADC_SOFTWARE_START;3 Q  Y  w' t2 A" N
  33. g_adc_handle.Init.ExternalTrigConvEdge = ADC_EXTERNALTRIGCONVEDGE_NONE;* X8 N8 r" N" L. d% [
  34. g_adc_handle.Init.SamplingMode = ADC_SAMPLING_MODE_NORMAL;: Q& v0 ?- v. K
  35. g_adc_handle.Init.ConversionDataManagement = ADC_CONVERSIONDATA_DR;
    $ ~* [. _3 e& e9 e. r7 A
  36. g_adc_handle.Init.Overrun = ADC_OVR_DATA_OVERWRITTEN;
    ( Q2 E1 K7 |0 U% ~0 N+ t; C
  37. g_adc_handle.Init.OversamplingMode = DISABLE;
    : n( S5 m* Q; Y7 Y( K% T: M9 j% {* x
  38. HAL_ADC_Init(&g_adc_handle);
    ( e1 o0 ]( ^( K* B
  39. ) K. o: b) l1 @5 o8 g$ H) `; s
  40. HAL_ADCEx_Calibration_Start(&g_adc_handle, ADC_SINGLE_ENDED);
    . z! Q% K: _; C+ p; V- R& x
  41. }
    + [( u: }0 h# {6 O7 h
  42.   H( Y5 P9 m( `# O# d
  43. uint32_t adc_get_result(uint32_t channel)
    ( D1 H' e. S( C6 G" f* w( {* K) `: Y
  44. {7 X3 n( K9 H9 T/ P% s. g
  45. ADC_ChannelConfTypeDef adc_channel_conf_struct = {0};
    / i  u9 t2 K$ B1 O0 @, D
  46. uint32_t result;
    - Y/ _" e1 s: ?' t7 Q9 {: M4 L& M
  47. ; e$ v9 N1 f/ a( V, Z6 [. q+ x7 D
  48. adc_channel_conf_struct.Channel = channel;9 b9 u4 \% A# v/ {  ?" v
  49. adc_channel_conf_struct.Rank = ADC_REGULAR_RANK_1;% a$ o, m" g' m4 T  d, E9 v
  50. adc_channel_conf_struct.SamplingTime = ADC_SAMPLETIME_640CYCLES_5;0 d. a+ D1 C( O6 ^) O
  51. adc_channel_conf_struct.SingleDiff = ADC_SINGLE_ENDED;" ~. Y4 x" {5 M' A; f, o
  52. adc_channel_conf_struct.OffsetNumber = ADC_OFFSET_NONE;9 r; E: f4 U- L8 ?
  53. adc_channel_conf_struct.Offset = 0;4 l1 A: S. u- T/ r
  54. adc_channel_conf_struct.OffsetSign = ADC_OFFSET_SIGN_NEGATIVE;
    $ ?  N. D! _. g% B6 @9 ~6 `
  55. HAL_ADC_ConfigChannel(&g_adc_handle, &adc_channel_conf_struct);
    & ?$ r/ p* q$ @8 A6 B" ?
  56. 1 o4 X; O" s4 @8 c7 w0 m
  57. HAL_ADC_Start(&g_adc_handle);$ X1 W! w+ H% I3 @, B( S
  58. HAL_ADC_PollForConversion(&g_adc_handle, HAL_MAX_DELAY);
    2 s1 b. `% R) @6 p- F
  59. result = HAL_ADC_GetValue(&g_adc_handle);
    + a1 E& ]8 @2 G
  60. HAL_ADC_Stop(&g_adc_handle);3 \7 s% a- G* s  `! i
  61. : l0 g) P3 |0 g2 `/ X$ I: E; M  e
  62. return result;' t! Q4 C5 D% z. Z( F3 L
  63. }: a1 _" V  w( S# w
  64. * }. [  D& Z/ S* Q( R+ q! z
  65. uint32_t adc_get_result_average(uint32_t channel, uint8_t times)- Z1 |0 w8 i2 |; v: U/ U# v' M
  66. {
    1 ?7 A% L& x- r
  67. uint32_t sum_result = 0;5 \0 B7 ?6 p8 r
  68. uint8_t index;' D+ {8 t; K6 z# B  `
  69. uint32_t result;
    5 ^! Y! P3 _0 J. s" g

  70. - ?$ ]6 G  P4 a, n  p* l& B( H6 f
  71. for (index=0; index<times; index++)6 j  r. v& u2 c  I/ U1 L+ J# Q
  72. {6 I0 A( i" G7 O- U3 k0 a3 y
  73.     sum_result += adc_get_result(channel);
      Z5 Q/ J5 [4 K  |! E. k
  74. }2 N7 ]3 \# z# v: W- N/ _
  75. - s# D3 \6 q9 ~; D# Y7 J
  76. result = sum_result / times;  l) v- U9 c% k7 k
  77. : \6 l4 \" ?: w7 P
  78. return result;
    5 D. l$ v+ G# }6 D, O/ j- x4 F) }
  79. }
复制代码
38.JPG 39.JPG ; l: H6 `3 }$ \1 q6 l4 I5 w5 R

! q2 h0 _* D) D& C: O! |然后再Model.cpp的tick成员函数里做AD采集(实际上在Screen1View.cpp的tick回调事件函数里面做也是可以的,就是采集间隔会大很多):
8 }% _4 B4 S& v5 R  T/ ?. f! u 40.JPG 41.JPG
2 K9 ?3 i" U* K! E0 r
8 x$ G4 l/ e2 `5 E
然后最关键的来了,涉及到C++基础知识,多态性与虚函数,需要在ModelListener类里面注册一个虚函数的实现,随便起个名就可以,我起名为Notify_ADC_Value_Changed:* N: D) C5 s% v/ L; U7 z% b
42.JPG
7 Z/ K( Q+ S# ]2 c) ~, i0 m+ w" }9 ^2 U( L" R" D2 L
为什么要在ModelListener类里面注册虚函数的实现呢,因为ModelListener类是Screen1Presenter类的父类,是Screen1Presenter类是继承而来的,因此Screen1Presenter类也要注册Notify_ADC_Value_Changed函数,在Screen1Presenter类中的Notify_ADC_Value_Changed函数就要实现具体功能了,也就是将Model类对象的值赋给Screen1View类对象的值:; J3 X1 O& W7 O3 L) t6 J
43.JPG 45.JPG 46.JPG
# t! A  K* y5 n/ Q
5 q2 V$ X  L) V7 O
同时ModelListener类对象又是Model类的成员,Model类有个公开成员叫adc_pb0_value,可以由别的类进行调用,Screen1View类也有同名的公开成员adc_pb0_value,两者可以相互赋值:: T( z# h2 I: _2 k$ h) P% x, C
44.JPG ' g- c% Q9 T8 T$ T( K; u' s; Y
+ y/ |# J) `% x% M) I
在多态性与虚函数知识点中,可以得知,父类的同一个虚函数可以由不同子类继承并实现不同功能,以实现多态。" T. C/ U1 Z% z) H7 l6 s
3 z4 c$ j+ ]5 p" D
最后在Screen1View类的Tick事件回调函数中调用Notify_ADC_Value_Changed函进行上述操作,在Screen1View类的adc_pb0_value成员获得值并显示,完成了一次从Model->Present->View的信息传递,实现了将硬件外设的值传递给图形界面。* R" d1 D, @8 ^1 x5 f! R0 s, v
47.JPG
1 B. |' o/ u6 {) {+ }( ?
4 l" `! ~9 O5 `  I, W6 t4 i然后是通过界面控制硬件,这个就简单多了,直接在TouchGFX里面创建两个开关按钮,分别命名为Tb_led1 Tb_led2,写两个不同的事件回调函数即可:/ c8 e; N/ [3 @0 M  h
48.JPG 49.JPG : e1 o( Z5 v: l/ w+ l

6 g( F6 p) y6 U7 F% B+ x看看效果,两个按钮分别控制绿灯和红灯的开关:2 Y5 a9 j6 w0 X, p9 R
IMG_20250521_235717.jpg IMG_20250521_235719.jpg IMG_20250521_235722.jpg IMG_20250521_235726.jpg ' L* m( v& L) p/ m

/ Q, A, ?6 o4 g; P; m, N5 B
旋转电位器旋钮改变AD输出值:
: G) A2 ^  z3 @ IMG_20250521_235804.jpg IMG_20250521_235806.jpg IMG_20250521_235808.jpg
4 \: a% F- S/ x4 m& `2 K
收藏 评论0 发布时间:2025-5-22 00:22

举报

0个回答

所属标签

相似技术帖

官网相关资源

关于
我们是谁
投资者关系
意法半导体可持续发展举措
创新与技术
意法半导体官网
联系我们
联系ST分支机构
寻找销售人员和分销渠道
社区
媒体中心
活动与培训
隐私策略
隐私策略
Cookies管理
行使您的权利
官方最新发布
STM32N6 AI生态系统
STM32MCU,MPU高性能GUI
ST ACEPACK电源模块
意法半导体生物传感器
STM32Cube扩展软件包
关注我们
st-img 微信公众号
st-img 手机版