前面做了一帖关于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
% 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
- ADC_HandleTypeDef g_adc_handle;
- {3 _' F. M8 ~
0 Q+ E: s( M( _5 S3 S8 P, I' ]+ r- void ADC_PB0_Init(void)# I; m# L3 U9 k1 Z& V: q
- {
. B7 M3 G, V' L7 C# V# s( T& ]
% s4 B1 z* U B+ t* }2 _- __HAL_RCC_GPIOB_CLK_ENABLE();
! `4 B6 \1 t9 k2 x' U3 o8 q - __HAL_RCC_ADC12_CLK_ENABLE();1 g, x" ?0 }. z+ b
+ C* j* c1 }, w$ Q/ u7 m! i% K5 s& K3 [- RCC_PeriphCLKInitTypeDef rcc_periph_clk_init_struct = {0};3 K, @8 V+ g9 f: V7 w/ O! O
- GPIO_InitTypeDef gpio_init_struct = {0};5 C3 _0 e# Q! B& D" V2 V8 `
- $ h/ q9 W' W4 i* f0 Y- k
- rcc_periph_clk_init_struct.PeriphClockSelection = RCC_PERIPHCLK_ADC;
' n! k. I/ A7 c7 H - rcc_periph_clk_init_struct.AdcClockSelection = RCC_ADCCLKSOURCE_PLL2P;0 k! V" ]. w! Q, r/ ^
- HAL_RCCEx_PeriphCLKConfig(&rcc_periph_clk_init_struct);
0 }% r- g' F. V) Y6 N, h! ?( n3 F
$ e3 }( I& |0 y. f7 h3 W6 Q- gpio_init_struct.Mode = GPIO_MODE_ANALOG;- O- ]2 o, }. [& E
- gpio_init_struct.Pull = GPIO_NOPULL;
; D% w# X' P) x9 n7 |: N3 F# ^ - gpio_init_struct.Pin = GPIO_PIN_0;" r$ g g0 L. D6 a @9 \( v2 M
- HAL_GPIO_Init(GPIOB , &gpio_init_struct);5 Y& T% p/ P( F. F5 s: y
: s7 ~ V; h% x1 ?# ]( r, T" r- g_adc_handle.Instance = ADC1;6 q8 U/ O3 y& \+ r
- g_adc_handle.Init.ClockPrescaler = ADC_CLOCK_ASYNC_DIV8;
+ [6 Y* B) q7 ]$ Y! T2 x - g_adc_handle.Init.Resolution = ADC_RESOLUTION_12B;
& l2 o$ y+ F8 X* n5 }& |- ^ - g_adc_handle.Init.DataAlign = ADC_DATAALIGN_RIGHT;. r2 z1 p" N3 C
- g_adc_handle.Init.ScanConvMode = ADC_SCAN_DISABLE;
9 S% f5 c' q$ }% B; N - g_adc_handle.Init.EOCSelection = ADC_EOC_SINGLE_CONV;
( \# O& C8 s+ o# ~ - g_adc_handle.Init.LowPowerAutoWait = DISABLE;8 B! F( v5 a+ ]# i9 Z8 b
- g_adc_handle.Init.ContinuousConvMode = DISABLE;. \- [7 |* E/ e2 M
- g_adc_handle.Init.NbrOfConversion = 1;
: n6 A7 s8 [; {1 ] - g_adc_handle.Init.DiscontinuousConvMode = DISABLE;
6 a5 S6 ]7 w' y3 m4 u - g_adc_handle.Init.NbrOfDiscConversion = 1;
" R7 p8 M$ M9 e# F# ?7 j* g3 m; Z ^; p - g_adc_handle.Init.ExternalTrigConv = ADC_SOFTWARE_START;
' w% t- t7 G$ c* M - g_adc_handle.Init.ExternalTrigConvEdge = ADC_EXTERNALTRIGCONVEDGE_NONE;$ q2 O' `; f2 @) H/ o
- g_adc_handle.Init.SamplingMode = ADC_SAMPLING_MODE_NORMAL;& P# J5 f4 B$ P; j4 t. d
- g_adc_handle.Init.ConversionDataManagement = ADC_CONVERSIONDATA_DR; J4 q. P$ l, `* J2 K9 ?' E: {
- g_adc_handle.Init.Overrun = ADC_OVR_DATA_OVERWRITTEN;
$ V4 c5 m1 {" _5 j( S4 ~8 h - g_adc_handle.Init.OversamplingMode = DISABLE;
3 V3 y3 t l [" n - HAL_ADC_Init(&g_adc_handle);/ J9 c' N- w: m0 X1 A: n# j" D, E
- . s6 H' X( C+ q( r9 j6 q# p
- HAL_ADCEx_Calibration_Start(&g_adc_handle, ADC_SINGLE_ENDED);
1 U, {( z( S0 V% Y - }. _4 J6 D3 ~; ^
- " i! h$ r ~6 L- L e2 G( @
- uint32_t adc_get_result(uint32_t channel)
) M: U: B1 j { - {3 l" r& W: x) K2 Y$ ~/ h% v' j& `
- ADC_ChannelConfTypeDef adc_channel_conf_struct = {0};" @7 b. d# j" g2 B i
- uint32_t result;3 }1 A* j1 u9 t+ c$ j5 z( \
( H4 }& E; b; o6 b9 j- adc_channel_conf_struct.Channel = channel;+ R( U; E& d1 p3 b) m' z6 G) K
- adc_channel_conf_struct.Rank = ADC_REGULAR_RANK_1;) r' g3 S8 K V9 E4 ^4 k/ n
- adc_channel_conf_struct.SamplingTime = ADC_SAMPLETIME_640CYCLES_5;
' ^ K& |6 t: i - adc_channel_conf_struct.SingleDiff = ADC_SINGLE_ENDED;
0 A. f# u$ z' a6 @* f - adc_channel_conf_struct.OffsetNumber = ADC_OFFSET_NONE;; j, m0 W( [; n, d6 Z+ q$ O
- adc_channel_conf_struct.Offset = 0;2 g# m2 D' m2 o D2 ^9 Q
- adc_channel_conf_struct.OffsetSign = ADC_OFFSET_SIGN_NEGATIVE;4 o' O2 F. U" S, Y
- HAL_ADC_ConfigChannel(&g_adc_handle, &adc_channel_conf_struct);* j4 {6 n# ?) i* @7 _5 B* {; H
5 N# o& l' E; s/ I) w& b- HAL_ADC_Start(&g_adc_handle);4 d) S( S0 ` u S: o& E4 o
- HAL_ADC_PollForConversion(&g_adc_handle, HAL_MAX_DELAY);
' ~# a/ v/ S8 P - result = HAL_ADC_GetValue(&g_adc_handle);
+ u8 X e4 A# }1 t1 o% J - HAL_ADC_Stop(&g_adc_handle);
7 c' s4 Z+ C8 A' [; E$ X8 t - & K* g+ ~; L5 K) h+ K
- return result;3 f* P3 [/ U$ l9 x4 q" n1 g, D
- }' k. H6 p, b$ p" h( L
- & f9 A2 b! T. l
- uint32_t adc_get_result_average(uint32_t channel, uint8_t times)
+ s( o0 S7 X R. d: h$ G$ S - {
9 z- T5 |; F% s1 ^& g - uint32_t sum_result = 0;7 i5 a3 R+ b! S1 W4 }! ?+ a7 E |
- uint8_t index;
/ f( @/ r# {+ H. u% W$ ^ - uint32_t result;
' k, t4 t: ^# g1 n: p9 I - ' t* b1 {+ l I, r+ n ~
- for (index=0; index<times; index++)3 r1 ]: Z7 N/ ~- b2 i( [: w% w8 A
- {
3 c' {4 u+ i. Q! v - sum_result += adc_get_result(channel);
5 _, V" z- ?$ i: u4 _: J8 a* k- B - }
$ [) Z' M, e2 q - : w7 K. s0 U" `$ C$ h; k
- result = sum_result / times;9 _4 X) B6 U N0 p- T
- 6 n8 H. [: [% ]: f5 _4 R1 e
- return result;
3 }; w" r# K4 n8 B4 ^/ W2 K! Y - }
复制代码
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
+ 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
' 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) }
; 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
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
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
) \: O# ]. U$ K
$ o$ s! N2 G9 @% }) i( ]* H6 y看看效果,两个按钮分别控制绿灯和红灯的开关:7 j5 r% _: u$ u5 ?9 v
# 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
' f8 h6 F. K& D* T* L |