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

【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采集值并显示到屏幕上。$ @; _- x9 r. ^0 Z# Y
37.jpg % k3 l5 e5 M% d7 `$ r: w" X
: A1 q* a. v1 J  x# L
比较偷懒的做法,初始化AD采集外设的代码可以放在main.c和main.h中,这样程序在系统初始化时就会自动初始化AD采集外设,并可以在程序的任何位置做AD采集操作:' n# z- W, H$ V
  1. ADC_HandleTypeDef g_adc_handle;
    - {3 _' F. M8 ~

  2. 0 Q+ E: s( M( _5 S3 S8 P, I' ]+ r
  3. void ADC_PB0_Init(void)# I; m# L3 U9 k1 Z& V: q
  4. {
    . B7 M3 G, V' L7 C# V# s( T& ]

  5. % s4 B1 z* U  B+ t* }2 _
  6. __HAL_RCC_GPIOB_CLK_ENABLE();
    ! `4 B6 \1 t9 k2 x' U3 o8 q
  7. __HAL_RCC_ADC12_CLK_ENABLE();1 g, x" ?0 }. z+ b

  8. + C* j* c1 }, w$ Q/ u7 m! i% K5 s& K3 [
  9. RCC_PeriphCLKInitTypeDef rcc_periph_clk_init_struct = {0};3 K, @8 V+ g9 f: V7 w/ O! O
  10. GPIO_InitTypeDef gpio_init_struct = {0};5 C3 _0 e# Q! B& D" V2 V8 `
  11. $ h/ q9 W' W4 i* f0 Y- k
  12. rcc_periph_clk_init_struct.PeriphClockSelection = RCC_PERIPHCLK_ADC;
    ' n! k. I/ A7 c7 H
  13. rcc_periph_clk_init_struct.AdcClockSelection = RCC_ADCCLKSOURCE_PLL2P;0 k! V" ]. w! Q, r/ ^
  14. HAL_RCCEx_PeriphCLKConfig(&rcc_periph_clk_init_struct);
    0 }% r- g' F. V) Y6 N, h! ?( n3 F

  15. $ e3 }( I& |0 y. f7 h3 W6 Q
  16. gpio_init_struct.Mode = GPIO_MODE_ANALOG;- O- ]2 o, }. [& E
  17. gpio_init_struct.Pull = GPIO_NOPULL;
    ; D% w# X' P) x9 n7 |: N3 F# ^
  18. gpio_init_struct.Pin = GPIO_PIN_0;" r$ g  g0 L. D6 a  @9 \( v2 M
  19. HAL_GPIO_Init(GPIOB , &gpio_init_struct);5 Y& T% p/ P( F. F5 s: y

  20. : s7 ~  V; h% x1 ?# ]( r, T" r
  21. g_adc_handle.Instance = ADC1;6 q8 U/ O3 y& \+ r
  22. g_adc_handle.Init.ClockPrescaler = ADC_CLOCK_ASYNC_DIV8;
    + [6 Y* B) q7 ]$ Y! T2 x
  23. g_adc_handle.Init.Resolution = ADC_RESOLUTION_12B;
    & l2 o$ y+ F8 X* n5 }& |- ^
  24. g_adc_handle.Init.DataAlign = ADC_DATAALIGN_RIGHT;. r2 z1 p" N3 C
  25. g_adc_handle.Init.ScanConvMode = ADC_SCAN_DISABLE;
    9 S% f5 c' q$ }% B; N
  26. g_adc_handle.Init.EOCSelection = ADC_EOC_SINGLE_CONV;
    ( \# O& C8 s+ o# ~
  27. g_adc_handle.Init.LowPowerAutoWait = DISABLE;8 B! F( v5 a+ ]# i9 Z8 b
  28. g_adc_handle.Init.ContinuousConvMode = DISABLE;. \- [7 |* E/ e2 M
  29. g_adc_handle.Init.NbrOfConversion = 1;
    : n6 A7 s8 [; {1 ]
  30. g_adc_handle.Init.DiscontinuousConvMode = DISABLE;
    6 a5 S6 ]7 w' y3 m4 u
  31. g_adc_handle.Init.NbrOfDiscConversion = 1;
    " R7 p8 M$ M9 e# F# ?7 j* g3 m; Z  ^; p
  32. g_adc_handle.Init.ExternalTrigConv = ADC_SOFTWARE_START;
    ' w% t- t7 G$ c* M
  33. g_adc_handle.Init.ExternalTrigConvEdge = ADC_EXTERNALTRIGCONVEDGE_NONE;$ q2 O' `; f2 @) H/ o
  34. g_adc_handle.Init.SamplingMode = ADC_SAMPLING_MODE_NORMAL;& P# J5 f4 B$ P; j4 t. d
  35. g_adc_handle.Init.ConversionDataManagement = ADC_CONVERSIONDATA_DR;  J4 q. P$ l, `* J2 K9 ?' E: {
  36. g_adc_handle.Init.Overrun = ADC_OVR_DATA_OVERWRITTEN;
    $ V4 c5 m1 {" _5 j( S4 ~8 h
  37. g_adc_handle.Init.OversamplingMode = DISABLE;
    3 V3 y3 t  l  [" n
  38. HAL_ADC_Init(&g_adc_handle);/ J9 c' N- w: m0 X1 A: n# j" D, E
  39. . s6 H' X( C+ q( r9 j6 q# p
  40. HAL_ADCEx_Calibration_Start(&g_adc_handle, ADC_SINGLE_ENDED);
    1 U, {( z( S0 V% Y
  41. }. _4 J6 D3 ~; ^
  42. " i! h$ r  ~6 L- L  e2 G( @
  43. uint32_t adc_get_result(uint32_t channel)
    ) M: U: B1 j  {
  44. {3 l" r& W: x) K2 Y$ ~/ h% v' j& `
  45. ADC_ChannelConfTypeDef adc_channel_conf_struct = {0};" @7 b. d# j" g2 B  i
  46. uint32_t result;3 }1 A* j1 u9 t+ c$ j5 z( \

  47. ( H4 }& E; b; o6 b9 j
  48. adc_channel_conf_struct.Channel = channel;+ R( U; E& d1 p3 b) m' z6 G) K
  49. adc_channel_conf_struct.Rank = ADC_REGULAR_RANK_1;) r' g3 S8 K  V9 E4 ^4 k/ n
  50. adc_channel_conf_struct.SamplingTime = ADC_SAMPLETIME_640CYCLES_5;
    ' ^  K& |6 t: i
  51. adc_channel_conf_struct.SingleDiff = ADC_SINGLE_ENDED;
    0 A. f# u$ z' a6 @* f
  52. adc_channel_conf_struct.OffsetNumber = ADC_OFFSET_NONE;; j, m0 W( [; n, d6 Z+ q$ O
  53. adc_channel_conf_struct.Offset = 0;2 g# m2 D' m2 o  D2 ^9 Q
  54. adc_channel_conf_struct.OffsetSign = ADC_OFFSET_SIGN_NEGATIVE;4 o' O2 F. U" S, Y
  55. HAL_ADC_ConfigChannel(&g_adc_handle, &adc_channel_conf_struct);* j4 {6 n# ?) i* @7 _5 B* {; H

  56. 5 N# o& l' E; s/ I) w& b
  57. HAL_ADC_Start(&g_adc_handle);4 d) S( S0 `  u  S: o& E4 o
  58. HAL_ADC_PollForConversion(&g_adc_handle, HAL_MAX_DELAY);
    ' ~# a/ v/ S8 P
  59. result = HAL_ADC_GetValue(&g_adc_handle);
    + u8 X  e4 A# }1 t1 o% J
  60. HAL_ADC_Stop(&g_adc_handle);
    7 c' s4 Z+ C8 A' [; E$ X8 t
  61. & K* g+ ~; L5 K) h+ K
  62. return result;3 f* P3 [/ U$ l9 x4 q" n1 g, D
  63. }' k. H6 p, b$ p" h( L
  64. & f9 A2 b! T. l
  65. uint32_t adc_get_result_average(uint32_t channel, uint8_t times)
    + s( o0 S7 X  R. d: h$ G$ S
  66. {
    9 z- T5 |; F% s1 ^& g
  67. uint32_t sum_result = 0;7 i5 a3 R+ b! S1 W4 }! ?+ a7 E  |
  68. uint8_t index;
    / f( @/ r# {+ H. u% W$ ^
  69. uint32_t result;
    ' k, t4 t: ^# g1 n: p9 I
  70. ' t* b1 {+ l  I, r+ n  ~
  71. for (index=0; index<times; index++)3 r1 ]: Z7 N/ ~- b2 i( [: w% w8 A
  72. {
    3 c' {4 u+ i. Q! v
  73.     sum_result += adc_get_result(channel);
    5 _, V" z- ?$ i: u4 _: J8 a* k- B
  74. }
    $ [) Z' M, e2 q
  75. : w7 K. s0 U" `$ C$ h; k
  76. result = sum_result / times;9 _4 X) B6 U  N0 p- T
  77. 6 n8 H. [: [% ]: f5 _4 R1 e
  78. return result;
    3 }; w" r# K4 n8 B4 ^/ W2 K! Y
  79. }
复制代码
38.JPG 39.JPG 1 I3 ^) k  A: m, y

" P9 R$ p+ X2 Z3 w' B然后再Model.cpp的tick成员函数里做AD采集(实际上在Screen1View.cpp的tick回调事件函数里面做也是可以的,就是采集间隔会大很多):
1 O! b, z, c4 x. t  B 40.JPG 41.JPG
+ K$ f1 _& P) h! p, F# S3 O
7 h  j2 f3 p- o4 X6 `! @
然后最关键的来了,涉及到C++基础知识,多态性与虚函数,需要在ModelListener类里面注册一个虚函数的实现,随便起个名就可以,我起名为Notify_ADC_Value_Changed:
, L- W% \$ q$ _) G) e 42.JPG ' h" w' O* x! T4 y$ ?! M, M

' T3 T8 g$ U; x8 b4 O) {
为什么要在ModelListener类里面注册虚函数的实现呢,因为ModelListener类是Screen1Presenter类的父类,是Screen1Presenter类是继承而来的,因此Screen1Presenter类也要注册Notify_ADC_Value_Changed函数,在Screen1Presenter类中的Notify_ADC_Value_Changed函数就要实现具体功能了,也就是将Model类对象的值赋给Screen1View类对象的值:
# e! Y: |3 `$ k+ {0 W$ I* u) } 43.JPG 45.JPG 46.JPG
; C# E# _2 |. f2 M
/ ?1 s0 r0 c( g8 M# A0 W8 T  g8 P
同时ModelListener类对象又是Model类的成员,Model类有个公开成员叫adc_pb0_value,可以由别的类进行调用,Screen1View类也有同名的公开成员adc_pb0_value,两者可以相互赋值:
( e( X* u5 r1 D" |  X; x4 z 44.JPG
9 ~4 g) u7 K; R* z6 a" g% P6 H4 C+ M4 i5 h  P% M( i
在多态性与虚函数知识点中,可以得知,父类的同一个虚函数可以由不同子类继承并实现不同功能,以实现多态。$ k, A+ |2 p2 x1 L! C& v: x; Z% E
& r, w' P! f  |) z9 k9 n
最后在Screen1View类的Tick事件回调函数中调用Notify_ADC_Value_Changed函进行上述操作,在Screen1View类的adc_pb0_value成员获得值并显示,完成了一次从Model->Present->View的信息传递,实现了将硬件外设的值传递给图形界面。+ o* a+ P0 ~# @( t+ U+ l
47.JPG
1 @: f: L/ c0 A# m9 P' D; r( y7 k# K  R% N1 @
然后是通过界面控制硬件,这个就简单多了,直接在TouchGFX里面创建两个开关按钮,分别命名为Tb_led1 Tb_led2,写两个不同的事件回调函数即可:6 V. g- B0 f, h8 d+ J
48.JPG 49.JPG ) \: O# ]. U$ K

$ o$ s! N2 G9 @% }) i( ]* H6 y看看效果,两个按钮分别控制绿灯和红灯的开关:7 j5 r% _: u$ u5 ?9 v
IMG_20250521_235717.jpg IMG_20250521_235719.jpg IMG_20250521_235722.jpg IMG_20250521_235726.jpg # V2 J$ r% X5 y: U" v% e! h: G* o

3 T" ]. f6 ^6 I5 ^1 J: H
旋转电位器旋钮改变AD输出值:
; ^' s  I& Y7 W* c IMG_20250521_235804.jpg IMG_20250521_235806.jpg IMG_20250521_235808.jpg
' f8 h6 F. K& D* T* L
收藏 评论0 发布时间:2025-5-22 00:22

举报

0个回答

所属标签

相似分享

官网相关资源

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