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

【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采集值并显示到屏幕上。
! {2 f" i/ ^* d- k/ D/ P+ } 37.jpg * N- ~: K4 O0 G' L! f3 h
- G+ k- v& Y  U% S8 W
比较偷懒的做法,初始化AD采集外设的代码可以放在main.c和main.h中,这样程序在系统初始化时就会自动初始化AD采集外设,并可以在程序的任何位置做AD采集操作:/ Y5 P5 A+ h- |7 T% |" l. G
  1. ADC_HandleTypeDef g_adc_handle;
    - |- \/ N7 u; E8 C4 b& t( u
  2. 2 Q- b) X9 U; F3 Q& z
  3. void ADC_PB0_Init(void); n9 f) A& f0 B9 i
  4. {
      |9 b' V, N( R2 E+ a, Z

  5. ! x0 {6 X( C: T  r& D
  6. __HAL_RCC_GPIOB_CLK_ENABLE();
    9 [# }* E, U; ^/ c8 ?
  7. __HAL_RCC_ADC12_CLK_ENABLE();. {% {. B3 X3 g* Y% q. O
  8. 7 x$ N! j7 r7 s7 p
  9. RCC_PeriphCLKInitTypeDef rcc_periph_clk_init_struct = {0};1 Q! h2 T- E+ M% m- `) u
  10. GPIO_InitTypeDef gpio_init_struct = {0};
    ( l+ Y( g5 n* U: s! B: `( C
  11. / Z$ Q, B. [! c" e+ m8 M5 h
  12. rcc_periph_clk_init_struct.PeriphClockSelection = RCC_PERIPHCLK_ADC;
    ) n! q, B8 e# s6 C
  13. rcc_periph_clk_init_struct.AdcClockSelection = RCC_ADCCLKSOURCE_PLL2P;
    0 y' u& J1 x2 [) Y( j5 {1 g# y
  14. HAL_RCCEx_PeriphCLKConfig(&rcc_periph_clk_init_struct);5 M/ v" H/ D/ p4 L& o
  15. 1 @$ {' D8 d0 L9 R
  16. gpio_init_struct.Mode = GPIO_MODE_ANALOG;
    - J" B+ J9 }- n" f* s% D
  17. gpio_init_struct.Pull = GPIO_NOPULL;
    2 d3 K, ?/ X2 A' v5 n' U
  18. gpio_init_struct.Pin = GPIO_PIN_0;; h$ E4 s6 V8 O9 D. V- o+ k
  19. HAL_GPIO_Init(GPIOB , &gpio_init_struct);
    $ H9 s# F6 z. ~2 R1 h8 \% q
  20. 2 i1 x3 [* c6 N9 r9 O& @8 S
  21. g_adc_handle.Instance = ADC1;
    % r& F6 G# M, M3 O7 z
  22. g_adc_handle.Init.ClockPrescaler = ADC_CLOCK_ASYNC_DIV8;' N' n5 M, u6 M
  23. g_adc_handle.Init.Resolution = ADC_RESOLUTION_12B;  [8 |8 k. e3 T4 u+ A
  24. g_adc_handle.Init.DataAlign = ADC_DATAALIGN_RIGHT;
    " Z! |# z3 V% Y+ H5 i" `& ?
  25. g_adc_handle.Init.ScanConvMode = ADC_SCAN_DISABLE;
    : g* H" Z, l  ^/ j! O+ r2 E
  26. g_adc_handle.Init.EOCSelection = ADC_EOC_SINGLE_CONV;
    ( {& a+ E$ |0 E! g, b/ u
  27. g_adc_handle.Init.LowPowerAutoWait = DISABLE;4 e! z1 k4 C5 f6 N# D
  28. g_adc_handle.Init.ContinuousConvMode = DISABLE;
    3 s  k& m! u6 |; u0 M/ o# W
  29. g_adc_handle.Init.NbrOfConversion = 1;
    4 p: T* l6 @) z" m4 d5 I/ |
  30. g_adc_handle.Init.DiscontinuousConvMode = DISABLE;- }/ K+ @% y) F, E( h+ G
  31. g_adc_handle.Init.NbrOfDiscConversion = 1;
    / {  {+ c* L* I0 _+ v
  32. g_adc_handle.Init.ExternalTrigConv = ADC_SOFTWARE_START;/ E( j6 f7 \. P
  33. g_adc_handle.Init.ExternalTrigConvEdge = ADC_EXTERNALTRIGCONVEDGE_NONE;( M3 W9 Y. S$ J+ x% P
  34. g_adc_handle.Init.SamplingMode = ADC_SAMPLING_MODE_NORMAL;
    ) D0 h2 X2 d8 L) |9 v% f! N: ]
  35. g_adc_handle.Init.ConversionDataManagement = ADC_CONVERSIONDATA_DR;) S3 u/ o0 y* W
  36. g_adc_handle.Init.Overrun = ADC_OVR_DATA_OVERWRITTEN;
    5 l3 q6 b3 ~& k1 z, V; P# f, \& q$ ?
  37. g_adc_handle.Init.OversamplingMode = DISABLE;
    % s+ P' O1 k# Q! }
  38. HAL_ADC_Init(&g_adc_handle);" m6 v# B  g: Z0 h
  39. 6 V5 _" }! B7 v3 V/ m2 k
  40. HAL_ADCEx_Calibration_Start(&g_adc_handle, ADC_SINGLE_ENDED);0 v+ \7 W: _" [
  41. }
    . R# U5 ~, G( |$ a% R+ r' N
  42. 8 s% {) R" J0 R  C
  43. uint32_t adc_get_result(uint32_t channel)
    + g' [; H7 y( Q4 w& G! O
  44. {
    7 {3 I4 Y5 d: l
  45. ADC_ChannelConfTypeDef adc_channel_conf_struct = {0};: n0 b0 \" f! a1 W' K
  46. uint32_t result;
    ) d0 L4 f! \  c8 i: X. F. B
  47. $ d7 E: `0 ?2 c0 c2 p
  48. adc_channel_conf_struct.Channel = channel;/ k- ?) o7 L4 B3 D
  49. adc_channel_conf_struct.Rank = ADC_REGULAR_RANK_1;* ?+ V9 C' V  H' Z7 ]0 s( _
  50. adc_channel_conf_struct.SamplingTime = ADC_SAMPLETIME_640CYCLES_5;
    , n8 y0 Z0 R4 W) u
  51. adc_channel_conf_struct.SingleDiff = ADC_SINGLE_ENDED;* `: S* i& P% x. G: u/ U9 n
  52. adc_channel_conf_struct.OffsetNumber = ADC_OFFSET_NONE;
    ) w; f2 A" i5 v4 K; p  x$ T
  53. adc_channel_conf_struct.Offset = 0;$ R; ^, q/ J# M# y' A0 @: e6 @$ `
  54. adc_channel_conf_struct.OffsetSign = ADC_OFFSET_SIGN_NEGATIVE;
    3 K  p7 [, K$ T; d' |) ^. a
  55. HAL_ADC_ConfigChannel(&g_adc_handle, &adc_channel_conf_struct);
    % l) G% ]& x8 Z8 Y& h: N" |' {

  56. 0 h4 H# q5 o2 ^; p, U
  57. HAL_ADC_Start(&g_adc_handle);
    . o. M1 S  n$ o4 Q8 Z4 D/ S6 e
  58. HAL_ADC_PollForConversion(&g_adc_handle, HAL_MAX_DELAY);
    : j  o5 Q! ]% b+ D, F
  59. result = HAL_ADC_GetValue(&g_adc_handle);1 }( `, H. Q8 }- K6 b
  60. HAL_ADC_Stop(&g_adc_handle);' X& `! ~/ U+ m' C2 x- N5 W

  61. % R, K; A' ?0 D* C4 U8 {3 E
  62. return result;5 ]: U% _% h# R! F: S6 V! d1 b
  63. }+ V: V, k2 C: e8 |$ ^
  64. $ O& o* T' D) n- P8 X
  65. uint32_t adc_get_result_average(uint32_t channel, uint8_t times)7 x1 N6 {! v$ J( J
  66. {
    8 h; p" A% k3 ~4 }$ O1 L
  67. uint32_t sum_result = 0;
    " }$ y- A$ @2 t5 F% w
  68. uint8_t index;
    , p- D; ]) G  Y! S/ P- b, R
  69. uint32_t result;5 E5 u1 ]/ Y3 X" s

  70. 3 y0 l4 i4 x$ J* d1 ]& M
  71. for (index=0; index<times; index++), D4 J( W# b' m8 G
  72. {. z% r: R) g0 N( t( W
  73.     sum_result += adc_get_result(channel);) l( p- i# _: o
  74. }
    : a- [+ s( ^' J$ E
  75. - g- X" Q- S& A: z
  76. result = sum_result / times;& Y! i1 q8 T5 Z7 a, x3 I

  77. - l( s+ H. ~  G5 N" N$ p
  78. return result;4 h% F0 v" T# E! ~/ _/ l
  79. }
复制代码
38.JPG 39.JPG
/ S2 ?+ P5 i; n# k5 G
' u$ V* h! Y4 D% }8 S4 r然后再Model.cpp的tick成员函数里做AD采集(实际上在Screen1View.cpp的tick回调事件函数里面做也是可以的,就是采集间隔会大很多):
* G! C7 O4 t, i 40.JPG 41.JPG
4 y0 d2 M6 P+ c1 S: f- v
! b! l: U- x1 h3 P: P6 x5 O; L
然后最关键的来了,涉及到C++基础知识,多态性与虚函数,需要在ModelListener类里面注册一个虚函数的实现,随便起个名就可以,我起名为Notify_ADC_Value_Changed:
6 D! `3 C% ?8 T: e0 b 42.JPG * k. D7 H& u; c9 x
9 `: q8 h# I% q. m2 b0 ~
为什么要在ModelListener类里面注册虚函数的实现呢,因为ModelListener类是Screen1Presenter类的父类,是Screen1Presenter类是继承而来的,因此Screen1Presenter类也要注册Notify_ADC_Value_Changed函数,在Screen1Presenter类中的Notify_ADC_Value_Changed函数就要实现具体功能了,也就是将Model类对象的值赋给Screen1View类对象的值:
$ a4 y  V9 N0 e5 A) s4 g' I: D 43.JPG 45.JPG 46.JPG
# [* K* I% R( ^2 H
, L4 s- c1 {3 h$ a/ p# p7 Q5 `
同时ModelListener类对象又是Model类的成员,Model类有个公开成员叫adc_pb0_value,可以由别的类进行调用,Screen1View类也有同名的公开成员adc_pb0_value,两者可以相互赋值:
7 ~) @4 n# H/ h( E+ z/ Z 44.JPG
# [) d; J3 j( }2 |
( J6 A0 f2 r  ]$ Y% M
在多态性与虚函数知识点中,可以得知,父类的同一个虚函数可以由不同子类继承并实现不同功能,以实现多态。
" q+ T/ L8 H  L  N/ s% v
" r4 W- H( O: _9 n
最后在Screen1View类的Tick事件回调函数中调用Notify_ADC_Value_Changed函进行上述操作,在Screen1View类的adc_pb0_value成员获得值并显示,完成了一次从Model->Present->View的信息传递,实现了将硬件外设的值传递给图形界面。
- m( M8 {7 Y3 e+ V5 y% L2 y) \9 ? 47.JPG
* ]8 @' c* F% X. @
- w* G& w6 Z/ Z然后是通过界面控制硬件,这个就简单多了,直接在TouchGFX里面创建两个开关按钮,分别命名为Tb_led1 Tb_led2,写两个不同的事件回调函数即可:
( i! b+ H; q2 `* o+ G" A
48.JPG 49.JPG ' O- ?: Y! F! h0 m. q

5 J% u2 D& z4 A4 j: @0 k看看效果,两个按钮分别控制绿灯和红灯的开关:& H: P: V+ S4 b
IMG_20250521_235717.jpg IMG_20250521_235719.jpg IMG_20250521_235722.jpg IMG_20250521_235726.jpg
# m$ Y" B5 B, m5 Z' u
% N; ^# R% j$ X
旋转电位器旋钮改变AD输出值:
3 b! k) T: H7 m. f IMG_20250521_235804.jpg IMG_20250521_235806.jpg IMG_20250521_235808.jpg
! M9 B2 W7 m1 w
赞 收藏 评论0 发布时间:2025-5-22 00:22

举报

0个回答

所属标签

相似技术帖

官网相关资源

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