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

【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采集值并显示到屏幕上。
* {7 B* N! c2 d6 p 37.jpg
% j, P$ k* E- d* k; Q
6 I' ^$ L0 I8 M9 _) F. O! v# l比较偷懒的做法,初始化AD采集外设的代码可以放在main.c和main.h中,这样程序在系统初始化时就会自动初始化AD采集外设,并可以在程序的任何位置做AD采集操作:0 G% Z; [- `) }$ h7 O
  1. ADC_HandleTypeDef g_adc_handle;/ y4 d  W% \  F# m( ]

  2. & o+ ^( W$ Y& S8 ]4 z2 Y+ Y7 L$ {
  3. void ADC_PB0_Init(void)
    ; w$ t( ]3 W1 S1 n7 y' y/ s! ~* A
  4. {
    " N) A( d) r+ s, p" N1 B
  5. 3 S0 s0 J+ f, D  l
  6. __HAL_RCC_GPIOB_CLK_ENABLE();7 B1 d3 u6 D* q- |3 D/ H
  7. __HAL_RCC_ADC12_CLK_ENABLE();4 `: o0 {/ b( Y
  8. . w9 h/ M+ O6 V) }' c+ x4 {
  9. RCC_PeriphCLKInitTypeDef rcc_periph_clk_init_struct = {0};
    6 [+ ?& [" `/ i% `! |
  10. GPIO_InitTypeDef gpio_init_struct = {0};
    6 N2 d4 L: r% O
  11. 6 [& \% D5 C! m: G7 _
  12. rcc_periph_clk_init_struct.PeriphClockSelection = RCC_PERIPHCLK_ADC;/ |4 c5 K+ i; r3 J
  13. rcc_periph_clk_init_struct.AdcClockSelection = RCC_ADCCLKSOURCE_PLL2P;  h% a+ P( L. ]; n" Y" ?
  14. HAL_RCCEx_PeriphCLKConfig(&rcc_periph_clk_init_struct);/ @# R. f+ y( e  A& ~
  15. % E% o  F  s8 N
  16. gpio_init_struct.Mode = GPIO_MODE_ANALOG;
    & D. U9 X2 }& r) R' i$ l6 B1 D) A
  17. gpio_init_struct.Pull = GPIO_NOPULL;' |3 \/ d7 f+ c% z$ Z3 ?
  18. gpio_init_struct.Pin = GPIO_PIN_0;; S5 m& J4 u+ V
  19. HAL_GPIO_Init(GPIOB , &gpio_init_struct);
    ; o/ F* ^, n- G

  20. 2 m" n2 g. z0 c/ s* y" K
  21. g_adc_handle.Instance = ADC1;
    4 J4 ]: @7 q6 M
  22. g_adc_handle.Init.ClockPrescaler = ADC_CLOCK_ASYNC_DIV8;7 U/ G& \% p8 B* y% E6 T# k
  23. g_adc_handle.Init.Resolution = ADC_RESOLUTION_12B;: ?% I2 }7 O+ p; ^2 o
  24. g_adc_handle.Init.DataAlign = ADC_DATAALIGN_RIGHT;5 E' Q2 r4 k, W& c3 S) C8 J6 o
  25. g_adc_handle.Init.ScanConvMode = ADC_SCAN_DISABLE;
    ( G& c' f# Y, k( |
  26. g_adc_handle.Init.EOCSelection = ADC_EOC_SINGLE_CONV;
    : o/ n! T( n1 H' [/ x8 j
  27. g_adc_handle.Init.LowPowerAutoWait = DISABLE;6 c7 y' q$ V8 r
  28. g_adc_handle.Init.ContinuousConvMode = DISABLE;& q1 V; a* v7 Q" }0 l, l
  29. g_adc_handle.Init.NbrOfConversion = 1;
    * e# ]& b# N$ _" {8 A; U7 p/ [
  30. g_adc_handle.Init.DiscontinuousConvMode = DISABLE;
    : g+ c* ~, O7 L+ P
  31. g_adc_handle.Init.NbrOfDiscConversion = 1;
    & }, ]. Q- n$ E' J! W" @4 ^
  32. g_adc_handle.Init.ExternalTrigConv = ADC_SOFTWARE_START;
    . b+ V" K1 [  K' f
  33. g_adc_handle.Init.ExternalTrigConvEdge = ADC_EXTERNALTRIGCONVEDGE_NONE;7 k5 D' v2 A" {. Q8 G
  34. g_adc_handle.Init.SamplingMode = ADC_SAMPLING_MODE_NORMAL;
    . L3 v, ^& n+ O1 @& D# P
  35. g_adc_handle.Init.ConversionDataManagement = ADC_CONVERSIONDATA_DR;
    8 n) x, q+ I5 i5 y1 e
  36. g_adc_handle.Init.Overrun = ADC_OVR_DATA_OVERWRITTEN;
    * @" k3 M) W) O0 }
  37. g_adc_handle.Init.OversamplingMode = DISABLE;$ G  y% O/ f2 D5 [3 C3 U2 b' j
  38. HAL_ADC_Init(&g_adc_handle);0 e( k8 U6 m- i" P* q' M" w. O" N

  39. 0 R. J7 Z$ V0 m  V/ ~/ l9 ?9 K
  40. HAL_ADCEx_Calibration_Start(&g_adc_handle, ADC_SINGLE_ENDED);
    8 L) J5 u! S' P
  41. }
    - k) q3 o' A1 Y9 d

  42. ) n3 G7 S; B% D( a# q
  43. uint32_t adc_get_result(uint32_t channel)0 s+ w7 ^+ L7 D7 w! j( b1 t
  44. {  X; n) M8 T4 D1 _7 K/ d2 ]! l
  45. ADC_ChannelConfTypeDef adc_channel_conf_struct = {0};$ f5 l1 n' L) n+ p& A
  46. uint32_t result;
    & q( T4 u2 Q& _9 }. v

  47. ' w6 f- S1 b, i( q8 b" }/ e) s
  48. adc_channel_conf_struct.Channel = channel;
    : p; u4 j; z* Q7 _* Y
  49. adc_channel_conf_struct.Rank = ADC_REGULAR_RANK_1;" y0 z, u4 H9 n7 Y$ E" g1 C5 `0 _0 d( M
  50. adc_channel_conf_struct.SamplingTime = ADC_SAMPLETIME_640CYCLES_5;' L9 [! L0 y& X+ f
  51. adc_channel_conf_struct.SingleDiff = ADC_SINGLE_ENDED;
    1 f2 K1 X  n, {) H. E
  52. adc_channel_conf_struct.OffsetNumber = ADC_OFFSET_NONE;  T4 W3 p9 S9 C  E" `
  53. adc_channel_conf_struct.Offset = 0;5 L% N7 f. Z* H, T4 V. y# K
  54. adc_channel_conf_struct.OffsetSign = ADC_OFFSET_SIGN_NEGATIVE;
    ' e1 U/ h7 k1 U  `2 K
  55. HAL_ADC_ConfigChannel(&g_adc_handle, &adc_channel_conf_struct);
    ; Y  \! _: W. @: p* M' S! b7 }

  56. 8 I% z# u# M% G) V5 H
  57. HAL_ADC_Start(&g_adc_handle);0 ]; `0 E# t# G
  58. HAL_ADC_PollForConversion(&g_adc_handle, HAL_MAX_DELAY);# k) A8 L9 K. K  H3 ?
  59. result = HAL_ADC_GetValue(&g_adc_handle);& p8 e3 U0 L' H4 a1 M) s
  60. HAL_ADC_Stop(&g_adc_handle);; I8 _6 ^  L& d9 t9 G3 b4 m9 x8 T

  61. ; z) e( W+ ]3 ]
  62. return result;: N5 ~4 j" O& _+ M
  63. }
    " n8 O) Y8 `0 `
  64.   z( E# L( K/ j0 a% I* E
  65. uint32_t adc_get_result_average(uint32_t channel, uint8_t times)5 i; g, ^* q' }5 e1 R
  66. {
    6 Q  \* v: E& e
  67. uint32_t sum_result = 0;
    $ k4 ?, R  j1 f# n* m1 a8 R1 T
  68. uint8_t index;1 ^2 i) W' L; c( O/ U9 j
  69. uint32_t result;* n/ `1 q1 H* F$ {5 J
  70. 1 n4 ?+ ~( `$ W# f. K
  71. for (index=0; index<times; index++)
    8 n% w6 T- N/ {; c
  72. {
    2 E* }" G7 @1 q% m
  73.     sum_result += adc_get_result(channel);! Q* \5 G5 S) _- V( Q
  74. }: K' [% y5 `2 H1 ]: J
  75. 1 k+ G2 Q0 ^9 Q6 V- M
  76. result = sum_result / times;% l6 \5 J' N: R7 K) z' a
  77. 2 y7 W3 ?9 X+ H
  78. return result;
    + _" X: S* H2 B
  79. }
复制代码
38.JPG 39.JPG
  v* K; [3 c, k, I7 l8 K% H
7 }  \; Z, N+ w% t! ~4 z然后再Model.cpp的tick成员函数里做AD采集(实际上在Screen1View.cpp的tick回调事件函数里面做也是可以的,就是采集间隔会大很多):
& C1 Z  }" X; n) P1 E$ z 40.JPG 41.JPG 3 Z! S) ~" K) j  ~# I" G! U& J, I2 ]
: B( W8 {4 P! _: m
然后最关键的来了,涉及到C++基础知识,多态性与虚函数,需要在ModelListener类里面注册一个虚函数的实现,随便起个名就可以,我起名为Notify_ADC_Value_Changed:
% V3 a3 s) S# `0 {% z# q, g- H/ ?7 C1 k 42.JPG
% ]' z! }/ Y' k0 R) F6 s9 |
1 K( F' H: c' f+ L3 O- {7 k
为什么要在ModelListener类里面注册虚函数的实现呢,因为ModelListener类是Screen1Presenter类的父类,是Screen1Presenter类是继承而来的,因此Screen1Presenter类也要注册Notify_ADC_Value_Changed函数,在Screen1Presenter类中的Notify_ADC_Value_Changed函数就要实现具体功能了,也就是将Model类对象的值赋给Screen1View类对象的值:. `7 D' F. l9 S+ ]1 P. [
43.JPG 45.JPG 46.JPG 2 ]6 Y: W) p1 J' B0 @
9 z5 c5 ]9 @( s( ^# n/ o2 B
同时ModelListener类对象又是Model类的成员,Model类有个公开成员叫adc_pb0_value,可以由别的类进行调用,Screen1View类也有同名的公开成员adc_pb0_value,两者可以相互赋值:& I% R, v+ \- z( w% I& S! }0 Y. s
44.JPG / O3 Z( v7 C9 l$ h  {+ e2 u# L; H
: c, i7 S$ B& U7 F/ P' o% _
在多态性与虚函数知识点中,可以得知,父类的同一个虚函数可以由不同子类继承并实现不同功能,以实现多态。8 w0 e) ~/ j" J6 Y1 T0 O$ E0 X
( n$ \, O8 m2 `) Q0 L- ^
最后在Screen1View类的Tick事件回调函数中调用Notify_ADC_Value_Changed函进行上述操作,在Screen1View类的adc_pb0_value成员获得值并显示,完成了一次从Model->Present->View的信息传递,实现了将硬件外设的值传递给图形界面。3 x# N( l/ w& v
47.JPG 8 j+ B# }) {+ ^1 |6 W" }4 m9 z

% ^% W& U2 |- v& u然后是通过界面控制硬件,这个就简单多了,直接在TouchGFX里面创建两个开关按钮,分别命名为Tb_led1 Tb_led2,写两个不同的事件回调函数即可:! g3 }. z3 {) M% }$ Y
48.JPG 49.JPG
4 ?+ @# D8 X+ [, x7 }8 B9 F6 ^5 _7 W" N- Y  H% u$ Q
看看效果,两个按钮分别控制绿灯和红灯的开关:4 V+ X- B7 ~: A+ B3 V+ D$ }3 @" V" J
IMG_20250521_235717.jpg IMG_20250521_235719.jpg IMG_20250521_235722.jpg IMG_20250521_235726.jpg
4 R4 i' g) R. P1 r; h; i, f: C& L: @* [/ e4 d
旋转电位器旋钮改变AD输出值:8 }9 c8 }+ t- X+ i3 ~
IMG_20250521_235804.jpg IMG_20250521_235806.jpg IMG_20250521_235808.jpg $ o( o: ]* P4 L0 `' ^( P
收藏 评论0 发布时间:2025-5-22 00:22

举报

0个回答

所属标签

相似技术帖

官网相关资源

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