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

【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采集值并显示到屏幕上。) l% H1 L6 E: J! }. `
37.jpg
& h5 u& C" c0 `& Q6 d, z
4 ~9 p0 [- ~5 L% z" d比较偷懒的做法,初始化AD采集外设的代码可以放在main.c和main.h中,这样程序在系统初始化时就会自动初始化AD采集外设,并可以在程序的任何位置做AD采集操作:( ~. j; z) @  E8 E
  1. ADC_HandleTypeDef g_adc_handle;
    6 N9 X6 K  `2 E# o/ |
  2. + j. r& O6 f! R. ^' E# g( G9 b' j
  3. void ADC_PB0_Init(void)
    $ Q% N: }$ l/ O( K3 U
  4. {
      X6 H" t8 u4 l

  5. ' K$ g8 _* m! D$ W- P" g( {* W9 @
  6. __HAL_RCC_GPIOB_CLK_ENABLE();
    4 u) v+ v4 i  {1 r
  7. __HAL_RCC_ADC12_CLK_ENABLE();
    ) V, l$ N3 v' f
  8. 4 B  h5 r/ l' e+ c
  9. RCC_PeriphCLKInitTypeDef rcc_periph_clk_init_struct = {0};3 I& ~7 M0 {" \
  10. GPIO_InitTypeDef gpio_init_struct = {0};/ k8 l& `) S' q

  11.   D1 d& a& [6 M6 n
  12. rcc_periph_clk_init_struct.PeriphClockSelection = RCC_PERIPHCLK_ADC;' H+ l8 V( {+ ^3 Z2 B' Q* k4 w
  13. rcc_periph_clk_init_struct.AdcClockSelection = RCC_ADCCLKSOURCE_PLL2P;
    2 x' ?0 L7 d: [0 K
  14. HAL_RCCEx_PeriphCLKConfig(&rcc_periph_clk_init_struct);
    , @3 g* X, L8 B

  15. 9 @- j6 i) k- E7 v) w* S2 G( _
  16. gpio_init_struct.Mode = GPIO_MODE_ANALOG;
    : k8 v: Z. ?" e, @
  17. gpio_init_struct.Pull = GPIO_NOPULL;( r7 T+ U' y$ H: N" M) |/ A
  18. gpio_init_struct.Pin = GPIO_PIN_0;
    , K$ u: o/ f/ Z
  19. HAL_GPIO_Init(GPIOB , &gpio_init_struct);
    ( i6 q8 t% e) H/ X
  20. 0 T4 w. [8 T6 n6 b2 g5 u' f
  21. g_adc_handle.Instance = ADC1;& M) V- K. Y! E) K  C# h
  22. g_adc_handle.Init.ClockPrescaler = ADC_CLOCK_ASYNC_DIV8;
    $ A$ _4 t7 V# p8 c7 I, g
  23. g_adc_handle.Init.Resolution = ADC_RESOLUTION_12B;
    % S& Q  e! n& z0 v) V" h
  24. g_adc_handle.Init.DataAlign = ADC_DATAALIGN_RIGHT;' T; {  V& ]) `) `& k
  25. g_adc_handle.Init.ScanConvMode = ADC_SCAN_DISABLE;7 t2 c; G4 B" m
  26. g_adc_handle.Init.EOCSelection = ADC_EOC_SINGLE_CONV;8 p9 K* A: v( j2 Q' v* \* V) @  B1 w
  27. g_adc_handle.Init.LowPowerAutoWait = DISABLE;5 H% C! o5 I# r* S) y
  28. g_adc_handle.Init.ContinuousConvMode = DISABLE;
    & x5 v% g$ i% i- O$ M; U
  29. g_adc_handle.Init.NbrOfConversion = 1;
    - ^  y0 Y! e9 ~3 r4 {* o, c6 \( a
  30. g_adc_handle.Init.DiscontinuousConvMode = DISABLE;2 l) d+ a- d" f
  31. g_adc_handle.Init.NbrOfDiscConversion = 1;
    ! t, E3 `( G" w4 o* n
  32. g_adc_handle.Init.ExternalTrigConv = ADC_SOFTWARE_START;2 G' ?+ H8 C, \; m' Z) {
  33. g_adc_handle.Init.ExternalTrigConvEdge = ADC_EXTERNALTRIGCONVEDGE_NONE;
    : `9 U! d3 Y- l1 [
  34. g_adc_handle.Init.SamplingMode = ADC_SAMPLING_MODE_NORMAL;
    ; {9 [4 w/ s! @4 b
  35. g_adc_handle.Init.ConversionDataManagement = ADC_CONVERSIONDATA_DR;
    ! f" _3 p3 ]" W3 x
  36. g_adc_handle.Init.Overrun = ADC_OVR_DATA_OVERWRITTEN;
    ; D! M. ?* y1 V# ~7 Q
  37. g_adc_handle.Init.OversamplingMode = DISABLE;
    6 \3 W8 [' R+ b
  38. HAL_ADC_Init(&g_adc_handle);" O1 L! o/ s3 X+ W! a# N8 t+ m8 ~

  39. 2 I2 O$ Z; W5 i- G- U
  40. HAL_ADCEx_Calibration_Start(&g_adc_handle, ADC_SINGLE_ENDED);0 {. k; Y) e1 M# k9 d1 e! ^. k
  41. }
    5 H& B% v) b& u2 c; K5 V; O" i  D) C

  42. " ^4 j  ~+ B, G$ q# k6 [
  43. uint32_t adc_get_result(uint32_t channel)
    3 j# j: \3 n- e/ b; q" h
  44. {5 W$ ~1 b" M4 v* `6 f
  45. ADC_ChannelConfTypeDef adc_channel_conf_struct = {0};+ r; q9 c6 w: E6 L- f$ r
  46. uint32_t result;' y( T# U8 C* s. N9 T. r

  47. 8 Q  Q( g/ v! K* A7 P1 F& _* m
  48. adc_channel_conf_struct.Channel = channel;5 ~7 E+ t( X. t/ ?2 L5 g/ I
  49. adc_channel_conf_struct.Rank = ADC_REGULAR_RANK_1;1 w# f! O3 Y" @; ]6 ~; E( Y- A
  50. adc_channel_conf_struct.SamplingTime = ADC_SAMPLETIME_640CYCLES_5;
    . @. X! V0 l3 m; J2 g, ^+ J9 B
  51. adc_channel_conf_struct.SingleDiff = ADC_SINGLE_ENDED;( u7 h# J) V( \# n# j- m
  52. adc_channel_conf_struct.OffsetNumber = ADC_OFFSET_NONE;
    8 \) r- R4 g7 a% R0 c: Z1 K% ?7 M1 X
  53. adc_channel_conf_struct.Offset = 0;( |# M& O0 d% b+ n+ B" E1 _
  54. adc_channel_conf_struct.OffsetSign = ADC_OFFSET_SIGN_NEGATIVE;
      F3 E* @/ f8 b! k8 I
  55. HAL_ADC_ConfigChannel(&g_adc_handle, &adc_channel_conf_struct);
    + h8 T( |, [" D+ h4 O
  56. 1 P. h& @* S  T- k- m* ]5 J! ^+ T
  57. HAL_ADC_Start(&g_adc_handle);  g' Q8 ^6 {% F1 g5 i
  58. HAL_ADC_PollForConversion(&g_adc_handle, HAL_MAX_DELAY);
    * l+ h2 B# [+ w& C
  59. result = HAL_ADC_GetValue(&g_adc_handle);
    & K( k6 Q& h7 Q. n
  60. HAL_ADC_Stop(&g_adc_handle);" T* ^% t& G$ `9 A3 X  ~$ `

  61.   p$ _; y/ M6 n0 H
  62. return result;
    5 [& g% D% d5 s( a
  63. }( X* l# U/ z( X$ g. G
  64. + }: `; i2 G6 ?/ M9 V. _* w
  65. uint32_t adc_get_result_average(uint32_t channel, uint8_t times)
    , n+ W& _: Q* i$ ^6 t. v7 I# c
  66. {8 A, D! G6 e8 V, \( P8 U
  67. uint32_t sum_result = 0;
    3 J' M' G/ d  i9 h2 s
  68. uint8_t index;7 A$ _4 [! {) Y% J7 A
  69. uint32_t result;8 a2 _. ?; z! A. u: \
  70. 0 u4 E; @2 a! b) d1 |& n
  71. for (index=0; index<times; index++)/ N! t6 l; @! z' Z: |3 F* s# b6 W$ q
  72. {! N$ |0 b4 V( x6 t9 u
  73.     sum_result += adc_get_result(channel);4 e7 I. h; {- K% ^! w: R
  74. }& w1 U1 Z3 ]: L

  75. : u+ S! h1 }: ?, g# K& T# ?3 K
  76. result = sum_result / times;
    " W- m8 |4 K" E- S' |" k
  77. 9 O7 N% r' F- X( B
  78. return result;/ v; r2 q0 _1 F3 C! z! L
  79. }
复制代码
38.JPG 39.JPG ; Y$ P6 t& [8 T

, X5 R6 m5 f) t' x# {3 n3 n+ ~9 L然后再Model.cpp的tick成员函数里做AD采集(实际上在Screen1View.cpp的tick回调事件函数里面做也是可以的,就是采集间隔会大很多):4 [, b6 S) M. L, p4 I( O8 B$ X  e
40.JPG 41.JPG % L/ L$ P3 s, {# x; @% B

) _" n, y6 I, t' W% I" Z# K
然后最关键的来了,涉及到C++基础知识,多态性与虚函数,需要在ModelListener类里面注册一个虚函数的实现,随便起个名就可以,我起名为Notify_ADC_Value_Changed:8 y% X8 j$ E- u/ H" E( f" o8 A
42.JPG
6 j% v+ G1 b8 Z8 F; S0 q
7 a) N$ u, Z7 X; m
为什么要在ModelListener类里面注册虚函数的实现呢,因为ModelListener类是Screen1Presenter类的父类,是Screen1Presenter类是继承而来的,因此Screen1Presenter类也要注册Notify_ADC_Value_Changed函数,在Screen1Presenter类中的Notify_ADC_Value_Changed函数就要实现具体功能了,也就是将Model类对象的值赋给Screen1View类对象的值:
" o, @, [- o) M4 m- Q5 G 43.JPG 45.JPG 46.JPG + K! D3 s5 L8 T! m  \, T
: I& S9 t5 L* N' D% J
同时ModelListener类对象又是Model类的成员,Model类有个公开成员叫adc_pb0_value,可以由别的类进行调用,Screen1View类也有同名的公开成员adc_pb0_value,两者可以相互赋值:0 Z# E( k& U% b
44.JPG % {; D% o: P6 f) ]0 ~0 ~
& F2 z& f# O7 l- l- x3 f1 s, q
在多态性与虚函数知识点中,可以得知,父类的同一个虚函数可以由不同子类继承并实现不同功能,以实现多态。7 l9 e2 d$ H& R$ r! e3 g3 ~
5 {5 U! u7 q7 d# X7 o  N; G
最后在Screen1View类的Tick事件回调函数中调用Notify_ADC_Value_Changed函进行上述操作,在Screen1View类的adc_pb0_value成员获得值并显示,完成了一次从Model->Present->View的信息传递,实现了将硬件外设的值传递给图形界面。
6 a: }3 Y* r$ Q7 G 47.JPG
2 h' n" Y5 [1 x; S7 Y+ k  ^5 ~) H# w: R7 p, t9 v5 |- s
然后是通过界面控制硬件,这个就简单多了,直接在TouchGFX里面创建两个开关按钮,分别命名为Tb_led1 Tb_led2,写两个不同的事件回调函数即可:4 b, T! t9 v$ I+ T" z7 ?5 |
48.JPG 49.JPG
# h3 F% S& e) m/ [/ m
* R4 c5 e1 n1 k5 k看看效果,两个按钮分别控制绿灯和红灯的开关:: A$ K! k- y; a6 j7 h+ d
IMG_20250521_235717.jpg IMG_20250521_235719.jpg IMG_20250521_235722.jpg IMG_20250521_235726.jpg " B' e' P  r0 D* x- l# a( L
, n4 H$ F) T% C3 ?5 K) v# F
旋转电位器旋钮改变AD输出值:
$ c# K7 B8 M/ G" c/ b: y" {# p IMG_20250521_235804.jpg IMG_20250521_235806.jpg IMG_20250521_235808.jpg
8 l/ q* A5 w3 i+ I
赞 收藏 评论0 发布时间:2025-5-22 00:22

举报

0个回答

所属标签

相似技术帖

官网相关资源

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