前面做了一帖关于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采集值并显示到屏幕上。) l% H1 L6 E: J! }. `
& h5 u& C" c0 `& Q6 d, z
4 ~9 p0 [- ~5 L% z" d比较偷懒的做法,初始化AD采集外设的代码可以放在main.c和main.h中,这样程序在系统初始化时就会自动初始化AD采集外设,并可以在程序的任何位置做AD采集操作:( ~. j; z) @ E8 E
- ADC_HandleTypeDef g_adc_handle;
6 N9 X6 K `2 E# o/ | - + j. r& O6 f! R. ^' E# g( G9 b' j
- void ADC_PB0_Init(void)
$ Q% N: }$ l/ O( K3 U - {
X6 H" t8 u4 l
' K$ g8 _* m! D$ W- P" g( {* W9 @- __HAL_RCC_GPIOB_CLK_ENABLE();
4 u) v+ v4 i {1 r - __HAL_RCC_ADC12_CLK_ENABLE();
) V, l$ N3 v' f - 4 B h5 r/ l' e+ c
- RCC_PeriphCLKInitTypeDef rcc_periph_clk_init_struct = {0};3 I& ~7 M0 {" \
- GPIO_InitTypeDef gpio_init_struct = {0};/ k8 l& `) S' q
D1 d& a& [6 M6 n- rcc_periph_clk_init_struct.PeriphClockSelection = RCC_PERIPHCLK_ADC;' H+ l8 V( {+ ^3 Z2 B' Q* k4 w
- rcc_periph_clk_init_struct.AdcClockSelection = RCC_ADCCLKSOURCE_PLL2P;
2 x' ?0 L7 d: [0 K - HAL_RCCEx_PeriphCLKConfig(&rcc_periph_clk_init_struct);
, @3 g* X, L8 B
9 @- j6 i) k- E7 v) w* S2 G( _- gpio_init_struct.Mode = GPIO_MODE_ANALOG;
: k8 v: Z. ?" e, @ - gpio_init_struct.Pull = GPIO_NOPULL;( r7 T+ U' y$ H: N" M) |/ A
- gpio_init_struct.Pin = GPIO_PIN_0;
, K$ u: o/ f/ Z - HAL_GPIO_Init(GPIOB , &gpio_init_struct);
( i6 q8 t% e) H/ X - 0 T4 w. [8 T6 n6 b2 g5 u' f
- g_adc_handle.Instance = ADC1;& M) V- K. Y! E) K C# h
- g_adc_handle.Init.ClockPrescaler = ADC_CLOCK_ASYNC_DIV8;
$ A$ _4 t7 V# p8 c7 I, g - g_adc_handle.Init.Resolution = ADC_RESOLUTION_12B;
% S& Q e! n& z0 v) V" h - g_adc_handle.Init.DataAlign = ADC_DATAALIGN_RIGHT;' T; { V& ]) `) `& k
- g_adc_handle.Init.ScanConvMode = ADC_SCAN_DISABLE;7 t2 c; G4 B" m
- g_adc_handle.Init.EOCSelection = ADC_EOC_SINGLE_CONV;8 p9 K* A: v( j2 Q' v* \* V) @ B1 w
- g_adc_handle.Init.LowPowerAutoWait = DISABLE;5 H% C! o5 I# r* S) y
- g_adc_handle.Init.ContinuousConvMode = DISABLE;
& x5 v% g$ i% i- O$ M; U - g_adc_handle.Init.NbrOfConversion = 1;
- ^ y0 Y! e9 ~3 r4 {* o, c6 \( a - g_adc_handle.Init.DiscontinuousConvMode = DISABLE;2 l) d+ a- d" f
- g_adc_handle.Init.NbrOfDiscConversion = 1;
! t, E3 `( G" w4 o* n - g_adc_handle.Init.ExternalTrigConv = ADC_SOFTWARE_START;2 G' ?+ H8 C, \; m' Z) {
- g_adc_handle.Init.ExternalTrigConvEdge = ADC_EXTERNALTRIGCONVEDGE_NONE;
: `9 U! d3 Y- l1 [ - g_adc_handle.Init.SamplingMode = ADC_SAMPLING_MODE_NORMAL;
; {9 [4 w/ s! @4 b - g_adc_handle.Init.ConversionDataManagement = ADC_CONVERSIONDATA_DR;
! f" _3 p3 ]" W3 x - g_adc_handle.Init.Overrun = ADC_OVR_DATA_OVERWRITTEN;
; D! M. ?* y1 V# ~7 Q - g_adc_handle.Init.OversamplingMode = DISABLE;
6 \3 W8 [' R+ b - HAL_ADC_Init(&g_adc_handle);" O1 L! o/ s3 X+ W! a# N8 t+ m8 ~
2 I2 O$ Z; W5 i- G- U- HAL_ADCEx_Calibration_Start(&g_adc_handle, ADC_SINGLE_ENDED);0 {. k; Y) e1 M# k9 d1 e! ^. k
- }
5 H& B% v) b& u2 c; K5 V; O" i D) C
" ^4 j ~+ B, G$ q# k6 [- uint32_t adc_get_result(uint32_t channel)
3 j# j: \3 n- e/ b; q" h - {5 W$ ~1 b" M4 v* `6 f
- ADC_ChannelConfTypeDef adc_channel_conf_struct = {0};+ r; q9 c6 w: E6 L- f$ r
- uint32_t result;' y( T# U8 C* s. N9 T. r
8 Q Q( g/ v! K* A7 P1 F& _* m- adc_channel_conf_struct.Channel = channel;5 ~7 E+ t( X. t/ ?2 L5 g/ I
- adc_channel_conf_struct.Rank = ADC_REGULAR_RANK_1;1 w# f! O3 Y" @; ]6 ~; E( Y- A
- adc_channel_conf_struct.SamplingTime = ADC_SAMPLETIME_640CYCLES_5;
. @. X! V0 l3 m; J2 g, ^+ J9 B - adc_channel_conf_struct.SingleDiff = ADC_SINGLE_ENDED;( u7 h# J) V( \# n# j- m
- adc_channel_conf_struct.OffsetNumber = ADC_OFFSET_NONE;
8 \) r- R4 g7 a% R0 c: Z1 K% ?7 M1 X - adc_channel_conf_struct.Offset = 0;( |# M& O0 d% b+ n+ B" E1 _
- adc_channel_conf_struct.OffsetSign = ADC_OFFSET_SIGN_NEGATIVE;
F3 E* @/ f8 b! k8 I - HAL_ADC_ConfigChannel(&g_adc_handle, &adc_channel_conf_struct);
+ h8 T( |, [" D+ h4 O - 1 P. h& @* S T- k- m* ]5 J! ^+ T
- HAL_ADC_Start(&g_adc_handle); g' Q8 ^6 {% F1 g5 i
- HAL_ADC_PollForConversion(&g_adc_handle, HAL_MAX_DELAY);
* l+ h2 B# [+ w& C - result = HAL_ADC_GetValue(&g_adc_handle);
& K( k6 Q& h7 Q. n - HAL_ADC_Stop(&g_adc_handle);" T* ^% t& G$ `9 A3 X ~$ `
p$ _; y/ M6 n0 H- return result;
5 [& g% D% d5 s( a - }( X* l# U/ z( X$ g. G
- + }: `; i2 G6 ?/ M9 V. _* w
- uint32_t adc_get_result_average(uint32_t channel, uint8_t times)
, n+ W& _: Q* i$ ^6 t. v7 I# c - {8 A, D! G6 e8 V, \( P8 U
- uint32_t sum_result = 0;
3 J' M' G/ d i9 h2 s - uint8_t index;7 A$ _4 [! {) Y% J7 A
- uint32_t result;8 a2 _. ?; z! A. u: \
- 0 u4 E; @2 a! b) d1 |& n
- for (index=0; index<times; index++)/ N! t6 l; @! z' Z: |3 F* s# b6 W$ q
- {! N$ |0 b4 V( x6 t9 u
- sum_result += adc_get_result(channel);4 e7 I. h; {- K% ^! w: R
- }& w1 U1 Z3 ]: L
: u+ S! h1 }: ?, g# K& T# ?3 K- result = sum_result / times;
" W- m8 |4 K" E- S' |" k - 9 O7 N% r' F- X( B
- return result;/ v; r2 q0 _1 F3 C! z! L
- }
复制代码
; Y$ P6 t& [8 T
, X5 R6 m5 f) t' x# {3 n3 n+ ~9 L然后再Model.cpp的tick成员函数里做AD采集(实际上在Screen1View.cpp的tick回调事件函数里面做也是可以的,就是采集间隔会大很多):4 [, b6 S) M. L, p4 I( O8 B$ X e
% L/ L$ P3 s, {# x; @% B
) _" n, y6 I, t' W% I" Z# K然后最关键的来了,涉及到C++基础知识,多态性与虚函数,需要在ModelListener类里面注册一个虚函数的实现,随便起个名就可以,我起名为Notify_ADC_Value_Changed:8 y% X8 j$ E- u/ H" E( f" o8 A
6 j% v+ G1 b8 Z8 F; S0 q
7 a) N$ u, Z7 X; m为什么要在ModelListener类里面注册虚函数的实现呢,因为ModelListener类是Screen1Presenter类的父类,是Screen1Presenter类是继承而来的,因此Screen1Presenter类也要注册Notify_ADC_Value_Changed函数,在Screen1Presenter类中的Notify_ADC_Value_Changed函数就要实现具体功能了,也就是将Model类对象的值赋给Screen1View类对象的值:
" o, @, [- o) M4 m- Q5 G
+ K! D3 s5 L8 T! m \, T
: I& S9 t5 L* N' D% J
同时ModelListener类对象又是Model类的成员,Model类有个公开成员叫adc_pb0_value,可以由别的类进行调用,Screen1View类也有同名的公开成员adc_pb0_value,两者可以相互赋值:0 Z# E( k& U% b
% {; D% o: P6 f) ]0 ~0 ~
& F2 z& f# O7 l- l- x3 f1 s, q
在多态性与虚函数知识点中,可以得知,父类的同一个虚函数可以由不同子类继承并实现不同功能,以实现多态。7 l9 e2 d$ H& R$ r! e3 g3 ~
5 {5 U! u7 q7 d# X7 o N; G
最后在Screen1View类的Tick事件回调函数中调用Notify_ADC_Value_Changed函进行上述操作,在Screen1View类的adc_pb0_value成员获得值并显示,完成了一次从Model->Present->View的信息传递,实现了将硬件外设的值传递给图形界面。
6 a: }3 Y* r$ Q7 G
2 h' n" Y5 [1 x; S7 Y+ k ^5 ~) H# w: R7 p, t9 v5 |- s
然后是通过界面控制硬件,这个就简单多了,直接在TouchGFX里面创建两个开关按钮,分别命名为Tb_led1 Tb_led2,写两个不同的事件回调函数即可:4 b, T! t9 v$ I+ T" z7 ?5 |
# h3 F% S& e) m/ [/ m
* R4 c5 e1 n1 k5 k看看效果,两个按钮分别控制绿灯和红灯的开关:: A$ K! k- y; a6 j7 h+ d
" B' e' P r0 D* x- l# a( L
, n4 H$ F) T% C3 ?5 K) v# F
旋转电位器旋钮改变AD输出值:
$ c# K7 B8 M/ G" c/ b: y" {# p
8 l/ q* A5 w3 i+ I |