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

【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采集值并显示到屏幕上。
6 I! {) e/ F" k7 l: G) \" Q/ N 37.jpg # u& j/ e' }# `
2 V' P1 s( S/ t
比较偷懒的做法,初始化AD采集外设的代码可以放在main.c和main.h中,这样程序在系统初始化时就会自动初始化AD采集外设,并可以在程序的任何位置做AD采集操作:5 s3 w$ p( @: t$ {  T: |
  1. ADC_HandleTypeDef g_adc_handle;
    2 I- K! J" i+ ~/ n4 M- q2 a. |

  2. 5 B& m. l- o% o( \1 G5 }% p) m
  3. void ADC_PB0_Init(void)7 l- D9 N2 P+ P* M, X
  4. {) t' ~/ B9 K; A' C. l

  5. + [. i/ j  W! N. q  i$ T/ ^% Q
  6. __HAL_RCC_GPIOB_CLK_ENABLE();. V4 x3 ^9 t8 T- h
  7. __HAL_RCC_ADC12_CLK_ENABLE();
    ; s! c8 q+ S) {6 j0 q5 ^( w
  8. % m1 [' B6 M# ^# Z2 Q
  9. RCC_PeriphCLKInitTypeDef rcc_periph_clk_init_struct = {0};; H0 u2 R& _8 F" S* T: Q" a
  10. GPIO_InitTypeDef gpio_init_struct = {0};
    1 U. m+ e/ o; c9 ?$ x

  11. 0 o1 s9 C9 ?' \9 j
  12. rcc_periph_clk_init_struct.PeriphClockSelection = RCC_PERIPHCLK_ADC;% c+ S" l  t; H# S" S
  13. rcc_periph_clk_init_struct.AdcClockSelection = RCC_ADCCLKSOURCE_PLL2P;
    " Q( L3 z5 Z9 I, A# j* u1 E0 k
  14. HAL_RCCEx_PeriphCLKConfig(&rcc_periph_clk_init_struct);9 t3 \/ t2 h: l8 W

  15. 0 y$ M* n' V6 n. d( J' Q1 a) j
  16. gpio_init_struct.Mode = GPIO_MODE_ANALOG;2 B% {  }3 w1 e
  17. gpio_init_struct.Pull = GPIO_NOPULL;9 D: ?% I% J, Z' N& Y  h
  18. gpio_init_struct.Pin = GPIO_PIN_0;
    5 a, S) P+ k8 W) a& v. F% R4 S) b
  19. HAL_GPIO_Init(GPIOB , &gpio_init_struct);
    ) Z/ r% Z* o" a& f
  20. ) l( K( F! m+ p
  21. g_adc_handle.Instance = ADC1;
    5 }' }5 J/ h9 R3 G8 f
  22. g_adc_handle.Init.ClockPrescaler = ADC_CLOCK_ASYNC_DIV8;% h/ N5 {4 J4 m  F/ R
  23. g_adc_handle.Init.Resolution = ADC_RESOLUTION_12B;
    8 D; g% P& Z% }" f! p1 h
  24. g_adc_handle.Init.DataAlign = ADC_DATAALIGN_RIGHT;
    # U) V  ^# t! E+ d2 W1 A) n
  25. g_adc_handle.Init.ScanConvMode = ADC_SCAN_DISABLE;- V0 O2 ^) r% Y
  26. g_adc_handle.Init.EOCSelection = ADC_EOC_SINGLE_CONV;' x" ~4 v: m8 K; r
  27. g_adc_handle.Init.LowPowerAutoWait = DISABLE;: \1 N; a0 p2 j$ u  R
  28. g_adc_handle.Init.ContinuousConvMode = DISABLE;) e) f/ ]* M" W8 n: N1 F
  29. g_adc_handle.Init.NbrOfConversion = 1;4 Q# C( ~1 \' H% A4 Y& }2 R
  30. g_adc_handle.Init.DiscontinuousConvMode = DISABLE;
    " N9 S* i: C& w( w. V& V) b+ |
  31. g_adc_handle.Init.NbrOfDiscConversion = 1;
    $ U% ?% v( k- @: Y  l
  32. g_adc_handle.Init.ExternalTrigConv = ADC_SOFTWARE_START;# V1 V" |1 e1 m9 v2 C
  33. g_adc_handle.Init.ExternalTrigConvEdge = ADC_EXTERNALTRIGCONVEDGE_NONE;
    3 x8 _7 h2 u% a  d+ L" A
  34. g_adc_handle.Init.SamplingMode = ADC_SAMPLING_MODE_NORMAL;
      U: A1 W& b% l: l$ X% P+ h  t2 ~
  35. g_adc_handle.Init.ConversionDataManagement = ADC_CONVERSIONDATA_DR;/ E$ W. T0 i  j: i3 S5 I& o0 W
  36. g_adc_handle.Init.Overrun = ADC_OVR_DATA_OVERWRITTEN;2 K7 c$ N, F' E# F
  37. g_adc_handle.Init.OversamplingMode = DISABLE;
    $ F+ z1 S% C0 H0 M" w
  38. HAL_ADC_Init(&g_adc_handle);
    ( ~, V% L; ]  S/ Y4 s0 O+ k
  39. 1 V' p. d# g$ Q( L' H2 o
  40. HAL_ADCEx_Calibration_Start(&g_adc_handle, ADC_SINGLE_ENDED);
    / g6 _8 Q7 y% N$ ~* ]1 z3 \" J
  41. }0 ^7 k  H! z3 \+ G+ \( s
  42. 9 F/ T; a6 d' @3 p* D8 u; k8 w
  43. uint32_t adc_get_result(uint32_t channel)8 J' }( ~* t/ F8 G, n; R
  44. {& @8 v. t/ }5 m& J" N4 h
  45. ADC_ChannelConfTypeDef adc_channel_conf_struct = {0};. A$ J/ ^0 c, [+ C! n" K) L, Z6 P
  46. uint32_t result;, [- b# G* M# E
  47. 3 a8 A' x% [6 U, X* s$ Z
  48. adc_channel_conf_struct.Channel = channel;
    , d! e) _) }" o4 ^! i9 h
  49. adc_channel_conf_struct.Rank = ADC_REGULAR_RANK_1;6 h, `5 ?6 H  f& H
  50. adc_channel_conf_struct.SamplingTime = ADC_SAMPLETIME_640CYCLES_5;
    + E7 e) p- P- F( U. U' s; j
  51. adc_channel_conf_struct.SingleDiff = ADC_SINGLE_ENDED;/ h) g2 ]+ d" Q3 W# f+ D
  52. adc_channel_conf_struct.OffsetNumber = ADC_OFFSET_NONE;
    8 F4 u3 L4 ^: a
  53. adc_channel_conf_struct.Offset = 0;/ D" j) P% w  ]2 O- T+ l, u
  54. adc_channel_conf_struct.OffsetSign = ADC_OFFSET_SIGN_NEGATIVE;3 g. e+ E, s/ a
  55. HAL_ADC_ConfigChannel(&g_adc_handle, &adc_channel_conf_struct);
    3 H6 W: q; U/ u  X8 h" V0 F
  56. 9 q% M$ x  _9 H0 D! ^+ u3 f& D
  57. HAL_ADC_Start(&g_adc_handle);
    4 r- p8 o. E! z. o$ w
  58. HAL_ADC_PollForConversion(&g_adc_handle, HAL_MAX_DELAY);: g+ B1 k) b. F' F2 a: R
  59. result = HAL_ADC_GetValue(&g_adc_handle);
    ! ^0 j+ E+ \: O+ `$ w! m3 U1 c! x5 t
  60. HAL_ADC_Stop(&g_adc_handle);  c7 t$ T% Z. Q! y  ]1 W+ ]

  61. ) `* t9 q% M" h, h, c4 R" c0 Q0 |- c
  62. return result;
    $ Q- e4 A3 Q  C8 _( c# j
  63. }' R9 x) |( n% w, [+ F3 G+ U

  64. - H+ P# h# `2 [* g# X
  65. uint32_t adc_get_result_average(uint32_t channel, uint8_t times)
    5 ^4 {4 y; U! ?" _: W
  66. {
    6 f# y0 h2 D; X* e% |8 p3 c1 e
  67. uint32_t sum_result = 0;2 n1 z8 O/ U5 W4 v+ L" H* s
  68. uint8_t index;3 @1 j& h8 j5 L
  69. uint32_t result;
    # V/ A9 y/ F% @; q8 m0 h
  70. 9 y7 l! A9 D1 U' @7 f  z; }" o
  71. for (index=0; index<times; index++)
    3 T. |0 \! |! h& W) B
  72. {
    - F3 J/ _$ ?3 V0 _
  73.     sum_result += adc_get_result(channel);
    " _. {3 `, S/ y
  74. }
    ; r+ {! }3 S" V# r0 b3 A) W

  75. ( b" F! j& Q8 _! o. r' X( j- P
  76. result = sum_result / times;
    & _. }# [4 S+ b* K; C- y! M; H6 z

  77. , `# o" \3 X% m  w. E" f; d
  78. return result;
    ( J( n+ L! {$ ~- G5 }5 R7 q( C
  79. }
复制代码
38.JPG 39.JPG   B& {' Q' F% @0 a, m0 _% T4 E

# k* w3 ]  m% V6 R然后再Model.cpp的tick成员函数里做AD采集(实际上在Screen1View.cpp的tick回调事件函数里面做也是可以的,就是采集间隔会大很多):
$ `. t9 z, A" K% s9 f# ^1 \ 40.JPG 41.JPG / V0 G+ D9 E5 [" e" T0 a8 Q
1 Q/ R4 I2 U1 e$ W  x
然后最关键的来了,涉及到C++基础知识,多态性与虚函数,需要在ModelListener类里面注册一个虚函数的实现,随便起个名就可以,我起名为Notify_ADC_Value_Changed:7 p5 [) {6 ?! X6 e* N! `
42.JPG 9 N6 ?$ d: s6 y# @2 L) ]7 Y
) N0 l3 X3 Y1 a" w
为什么要在ModelListener类里面注册虚函数的实现呢,因为ModelListener类是Screen1Presenter类的父类,是Screen1Presenter类是继承而来的,因此Screen1Presenter类也要注册Notify_ADC_Value_Changed函数,在Screen1Presenter类中的Notify_ADC_Value_Changed函数就要实现具体功能了,也就是将Model类对象的值赋给Screen1View类对象的值:
9 Z6 t& u3 F. Y( H: `; L 43.JPG 45.JPG 46.JPG 1 ]" }) b; l! D1 T5 _; s: U* D

6 |3 i2 Z% J  @# c, a  L
同时ModelListener类对象又是Model类的成员,Model类有个公开成员叫adc_pb0_value,可以由别的类进行调用,Screen1View类也有同名的公开成员adc_pb0_value,两者可以相互赋值:
2 i* `4 h( X8 d  @ 44.JPG 9 U3 K6 ?! D6 l6 l% a+ s

# E) o5 Z  H1 S5 t$ P* f) A
在多态性与虚函数知识点中,可以得知,父类的同一个虚函数可以由不同子类继承并实现不同功能,以实现多态。9 T& i/ \- z  Y0 g0 P* n1 A* Z1 b

' X6 ~( z6 \& O# }4 I2 p, d8 c
最后在Screen1View类的Tick事件回调函数中调用Notify_ADC_Value_Changed函进行上述操作,在Screen1View类的adc_pb0_value成员获得值并显示,完成了一次从Model->Present->View的信息传递,实现了将硬件外设的值传递给图形界面。
. {, x7 _8 _2 S: N" C1 E: r 47.JPG
1 g7 ^/ L# X$ e( O. C! s* w) Z, U# D/ r# K
然后是通过界面控制硬件,这个就简单多了,直接在TouchGFX里面创建两个开关按钮,分别命名为Tb_led1 Tb_led2,写两个不同的事件回调函数即可:9 E$ R9 b5 A1 G# Q- c) m7 u" `" N
48.JPG 49.JPG ) h& ^% G* A0 t' ]& [* O
' R( R9 \4 t- n
看看效果,两个按钮分别控制绿灯和红灯的开关:
" M: {) b* O2 u IMG_20250521_235717.jpg IMG_20250521_235719.jpg IMG_20250521_235722.jpg IMG_20250521_235726.jpg
5 M- l( o! u4 }6 `+ ~% n8 q" V9 Y7 n3 p
旋转电位器旋钮改变AD输出值:& m* A* Y$ ?7 u% o2 }
IMG_20250521_235804.jpg IMG_20250521_235806.jpg IMG_20250521_235808.jpg , G3 P9 n+ E1 r
收藏 评论0 发布时间:2025-5-22 00:22

举报

0个回答

所属标签

相似分享

官网相关资源

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