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

【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采集值并显示到屏幕上。* }* s* \/ @8 s
37.jpg
* g5 j# E' _4 @( ~, r2 S$ W& [( I( D8 ^
比较偷懒的做法,初始化AD采集外设的代码可以放在main.c和main.h中,这样程序在系统初始化时就会自动初始化AD采集外设,并可以在程序的任何位置做AD采集操作:
; ^. z1 g& {+ _# g2 }3 D
  1. ADC_HandleTypeDef g_adc_handle;3 r% k1 Z7 ~7 m. p/ I+ X% H
  2. 8 p8 i: a7 L/ Z( M" N
  3. void ADC_PB0_Init(void)* m! B% q: i- K/ r6 ?. C+ j( k2 R
  4. {: _8 k( o& u5 ^" ]  e* |

  5. % k! \( U1 l4 N0 ~! W5 M
  6. __HAL_RCC_GPIOB_CLK_ENABLE();
    ( C1 j! |5 p8 u0 `4 f
  7. __HAL_RCC_ADC12_CLK_ENABLE();
    + f; F  n9 f( R% K$ l! @
  8. % k3 b# y+ _  e0 T5 `6 O
  9. RCC_PeriphCLKInitTypeDef rcc_periph_clk_init_struct = {0};
    4 M/ }, b7 y& L9 c& d
  10. GPIO_InitTypeDef gpio_init_struct = {0};' S: z* {" Q& m( I$ k" g0 L3 o

  11. 3 n% x( `# y! ^; O/ Z0 Q
  12. rcc_periph_clk_init_struct.PeriphClockSelection = RCC_PERIPHCLK_ADC;; @- Y. z2 W) ^- ^+ N" d
  13. rcc_periph_clk_init_struct.AdcClockSelection = RCC_ADCCLKSOURCE_PLL2P;5 R  K: ^2 W; T& O( C8 D% `2 |
  14. HAL_RCCEx_PeriphCLKConfig(&rcc_periph_clk_init_struct);
    2 l8 o" B8 X$ z3 S' v; g) _

  15. ! t5 B! Y9 ?/ [5 p9 K6 Y% ?7 G: K, X
  16. gpio_init_struct.Mode = GPIO_MODE_ANALOG;( q' b0 v; Q& r( x! b: ^2 J& b
  17. gpio_init_struct.Pull = GPIO_NOPULL;
    4 e! R5 G- v3 h6 e
  18. gpio_init_struct.Pin = GPIO_PIN_0;2 s6 E# t0 r* b4 b0 k
  19. HAL_GPIO_Init(GPIOB , &gpio_init_struct);
    - a* ~; ^# O& t# t( x; h
  20. & v- g6 B! p7 z3 \# b- ?3 t
  21. g_adc_handle.Instance = ADC1;
    & ?' D4 t4 t0 o
  22. g_adc_handle.Init.ClockPrescaler = ADC_CLOCK_ASYNC_DIV8;
    ; P" x! L, ^" u
  23. g_adc_handle.Init.Resolution = ADC_RESOLUTION_12B;; T2 x5 [# Q9 J
  24. g_adc_handle.Init.DataAlign = ADC_DATAALIGN_RIGHT;1 ]% t( x, d/ B5 v  @9 ]
  25. g_adc_handle.Init.ScanConvMode = ADC_SCAN_DISABLE;0 ~* L2 a" ?# t" _
  26. g_adc_handle.Init.EOCSelection = ADC_EOC_SINGLE_CONV;% c$ O( W" g. d
  27. g_adc_handle.Init.LowPowerAutoWait = DISABLE;, l! ^8 w6 g% [7 e5 Y! ~3 k, E7 s
  28. g_adc_handle.Init.ContinuousConvMode = DISABLE;
    ! J: u/ m- C) ^9 G/ L: T% T
  29. g_adc_handle.Init.NbrOfConversion = 1;
    4 |1 q! X; Y0 E, e: K
  30. g_adc_handle.Init.DiscontinuousConvMode = DISABLE;
    + |2 u; x6 I; p  r
  31. g_adc_handle.Init.NbrOfDiscConversion = 1;
    4 r% F4 z, N* R1 z! _* y5 v
  32. g_adc_handle.Init.ExternalTrigConv = ADC_SOFTWARE_START;2 c* T* L  d! u  j, v$ F' z+ l3 V
  33. g_adc_handle.Init.ExternalTrigConvEdge = ADC_EXTERNALTRIGCONVEDGE_NONE;
    ' [) e! w" s4 {
  34. g_adc_handle.Init.SamplingMode = ADC_SAMPLING_MODE_NORMAL;( m! B& M+ {* k) X
  35. g_adc_handle.Init.ConversionDataManagement = ADC_CONVERSIONDATA_DR;
      h; |# l( t7 [" ]; ?
  36. g_adc_handle.Init.Overrun = ADC_OVR_DATA_OVERWRITTEN;- J' l8 |) G5 {9 w5 x
  37. g_adc_handle.Init.OversamplingMode = DISABLE;
    , D/ p2 x4 t" _3 i
  38. HAL_ADC_Init(&g_adc_handle);
    # [2 y2 {0 ~1 ?. b, K6 K0 \9 J
  39. 9 [$ c" N$ ]8 `$ T; i8 D& Z2 r
  40. HAL_ADCEx_Calibration_Start(&g_adc_handle, ADC_SINGLE_ENDED);; }/ V& _- \1 c4 ~
  41. }
    + ?' A% M5 }0 N( K+ s$ q
  42. ' @/ L' W: o; r. k: k( I2 m. Z
  43. uint32_t adc_get_result(uint32_t channel)* P3 a: M- R% b2 A( y% c
  44. {" N; f- D8 [0 |# \9 C* I4 k
  45. ADC_ChannelConfTypeDef adc_channel_conf_struct = {0};
    # H! f! }  e1 w$ }& }% |
  46. uint32_t result;' e+ E. R7 E1 F  @+ x5 i- [  M
  47. . a+ I9 B' _' I  t
  48. adc_channel_conf_struct.Channel = channel;9 h/ j$ o: o+ X' ?+ C( y
  49. adc_channel_conf_struct.Rank = ADC_REGULAR_RANK_1;: ~0 O( E* L& @( s0 X
  50. adc_channel_conf_struct.SamplingTime = ADC_SAMPLETIME_640CYCLES_5;: K9 F; q0 a* }" L
  51. adc_channel_conf_struct.SingleDiff = ADC_SINGLE_ENDED;/ Y' ?" K9 O0 N9 O, N# J4 ^1 N
  52. adc_channel_conf_struct.OffsetNumber = ADC_OFFSET_NONE;
    1 T6 ?$ W- e8 W8 b& u; _- [
  53. adc_channel_conf_struct.Offset = 0;
    $ ]& R9 ^3 g6 E& f# H7 ^2 H
  54. adc_channel_conf_struct.OffsetSign = ADC_OFFSET_SIGN_NEGATIVE;$ o. {) X7 ~' i
  55. HAL_ADC_ConfigChannel(&g_adc_handle, &adc_channel_conf_struct);$ L" x8 r4 I1 A
  56. 7 v: t' n9 E) t; k1 S! x9 h
  57. HAL_ADC_Start(&g_adc_handle);
    * {1 `8 o, j' R7 l( D) r
  58. HAL_ADC_PollForConversion(&g_adc_handle, HAL_MAX_DELAY);
    4 S0 Y0 f* T7 {8 k/ M' _$ q, e
  59. result = HAL_ADC_GetValue(&g_adc_handle);
    ( g; _  T' e# v% Q
  60. HAL_ADC_Stop(&g_adc_handle);
    1 {- g; p" j8 }( x
  61. 4 d8 ^# n) y& B+ j
  62. return result;
    . V) o- r7 R: I/ Z7 ~9 n3 e6 x
  63. }
      J+ z& ^  O0 A# {

  64. + r6 _4 \% H- t0 k* F
  65. uint32_t adc_get_result_average(uint32_t channel, uint8_t times)
    2 P4 i' M1 E! b' F5 [
  66. {
    ) m7 \6 C' N- T  r/ `3 j
  67. uint32_t sum_result = 0;
    ( B2 |/ U8 s- v: C# q1 H4 W
  68. uint8_t index;
    4 H2 ?7 M" C+ o7 h: i, o
  69. uint32_t result;, K/ g2 u' h6 [5 }1 {

  70. ( A8 z" v4 I, Q' p/ o
  71. for (index=0; index<times; index++)( C: m, x3 T* s/ X$ X7 y
  72. {( d2 G% B! o" p
  73.     sum_result += adc_get_result(channel);  |1 {% n1 r0 K! u% o& \
  74. }
    1 J, k& v& e% ^! S5 m

  75. 2 K4 u) z9 k1 f& z
  76. result = sum_result / times;, t/ }7 f+ D6 U8 b! ]

  77. 1 t8 t* }4 ~% d% C0 A
  78. return result;! x2 V" b1 K' n: [. k
  79. }
复制代码
38.JPG 39.JPG
$ [# I1 B- x7 w$ O* `( R- Z: E6 D4 _) J, D  M- [
然后再Model.cpp的tick成员函数里做AD采集(实际上在Screen1View.cpp的tick回调事件函数里面做也是可以的,就是采集间隔会大很多):. B. N0 g+ W9 [6 s& f) D
40.JPG 41.JPG
- b8 f; b3 j$ C4 Q) W" w
7 y# \+ b: i! Y- B
然后最关键的来了,涉及到C++基础知识,多态性与虚函数,需要在ModelListener类里面注册一个虚函数的实现,随便起个名就可以,我起名为Notify_ADC_Value_Changed:+ n$ ^! {- R* D2 p# o
42.JPG
% s; }( V/ H9 Z2 L! ?3 E+ Q0 z# M3 e/ O3 y: m
为什么要在ModelListener类里面注册虚函数的实现呢,因为ModelListener类是Screen1Presenter类的父类,是Screen1Presenter类是继承而来的,因此Screen1Presenter类也要注册Notify_ADC_Value_Changed函数,在Screen1Presenter类中的Notify_ADC_Value_Changed函数就要实现具体功能了,也就是将Model类对象的值赋给Screen1View类对象的值:
" H% N1 p+ o- @7 M 43.JPG 45.JPG 46.JPG * Y. X. ~4 o! a
: c; N; |1 r- v1 f' O6 z5 C+ O
同时ModelListener类对象又是Model类的成员,Model类有个公开成员叫adc_pb0_value,可以由别的类进行调用,Screen1View类也有同名的公开成员adc_pb0_value,两者可以相互赋值:- F* Z, {. v' t9 T
44.JPG ! H; Q% A& I  t/ f7 P
6 h! T5 e: z, G" o
在多态性与虚函数知识点中,可以得知,父类的同一个虚函数可以由不同子类继承并实现不同功能,以实现多态。
0 z- r: @. r. y! o% P2 f; m% q4 y9 O3 u9 }7 b  f
最后在Screen1View类的Tick事件回调函数中调用Notify_ADC_Value_Changed函进行上述操作,在Screen1View类的adc_pb0_value成员获得值并显示,完成了一次从Model->Present->View的信息传递,实现了将硬件外设的值传递给图形界面。- L, B1 |6 F) _$ u' H
47.JPG
+ S1 F/ S  b3 H7 c& M! m
& `) I$ k) O& a0 O3 _2 b1 Y然后是通过界面控制硬件,这个就简单多了,直接在TouchGFX里面创建两个开关按钮,分别命名为Tb_led1 Tb_led2,写两个不同的事件回调函数即可:6 ^% j8 Y% k- o
48.JPG 49.JPG
2 b1 ^. r7 |' _/ H( \9 m' \( ~8 k$ ?& R4 A& J- N  C. O: G
看看效果,两个按钮分别控制绿灯和红灯的开关:
, s% }/ @% u4 a1 S; V IMG_20250521_235717.jpg IMG_20250521_235719.jpg IMG_20250521_235722.jpg IMG_20250521_235726.jpg
% B; J8 g, y2 ~/ d/ M1 T
4 [' e& F/ v; s
旋转电位器旋钮改变AD输出值:
. ?6 O& m2 M% F# O; B- l/ \5 A/ H IMG_20250521_235804.jpg IMG_20250521_235806.jpg IMG_20250521_235808.jpg
: q4 A' d2 k, }! M& y
赞 收藏 评论0 发布时间:2025-5-22 00:22

举报

0个回答

所属标签

相似技术帖

官网相关资源

关于
我们是谁
投资者关系
意法半导体可持续发展举措
创新与技术
意法半导体官网
联系我们
联系ST分支机构
寻找销售人员和分销渠道
社区
媒体中心
活动与培训
隐私策略
隐私策略
Cookies管理
行使您的权利
官方最新发布
人形机器人运动控制、感知与智能配电
半导体创新技术与应用方向
EE架构与软件定义汽车
12V/48V 汽车智能配电(SPD)
区域控制单元(ZCU)与分区架构
关注我们
st-img 微信公众号
st-img 手机版