前面做了一帖关于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采集值并显示到屏幕上。* }* s* \/ @8 s
* g5 j# E' _4 @( ~, r2 S$ W& [( I( D8 ^
比较偷懒的做法,初始化AD采集外设的代码可以放在main.c和main.h中,这样程序在系统初始化时就会自动初始化AD采集外设,并可以在程序的任何位置做AD采集操作:
; ^. z1 g& {+ _# g2 }3 D- ADC_HandleTypeDef g_adc_handle;3 r% k1 Z7 ~7 m. p/ I+ X% H
- 8 p8 i: a7 L/ Z( M" N
- void ADC_PB0_Init(void)* m! B% q: i- K/ r6 ?. C+ j( k2 R
- {: _8 k( o& u5 ^" ] e* |
% k! \( U1 l4 N0 ~! W5 M- __HAL_RCC_GPIOB_CLK_ENABLE();
( C1 j! |5 p8 u0 `4 f - __HAL_RCC_ADC12_CLK_ENABLE();
+ f; F n9 f( R% K$ l! @ - % k3 b# y+ _ e0 T5 `6 O
- RCC_PeriphCLKInitTypeDef rcc_periph_clk_init_struct = {0};
4 M/ }, b7 y& L9 c& d - GPIO_InitTypeDef gpio_init_struct = {0};' S: z* {" Q& m( I$ k" g0 L3 o
3 n% x( `# y! ^; O/ Z0 Q- rcc_periph_clk_init_struct.PeriphClockSelection = RCC_PERIPHCLK_ADC;; @- Y. z2 W) ^- ^+ N" d
- rcc_periph_clk_init_struct.AdcClockSelection = RCC_ADCCLKSOURCE_PLL2P;5 R K: ^2 W; T& O( C8 D% `2 |
- HAL_RCCEx_PeriphCLKConfig(&rcc_periph_clk_init_struct);
2 l8 o" B8 X$ z3 S' v; g) _
! t5 B! Y9 ?/ [5 p9 K6 Y% ?7 G: K, X- gpio_init_struct.Mode = GPIO_MODE_ANALOG;( q' b0 v; Q& r( x! b: ^2 J& b
- gpio_init_struct.Pull = GPIO_NOPULL;
4 e! R5 G- v3 h6 e - gpio_init_struct.Pin = GPIO_PIN_0;2 s6 E# t0 r* b4 b0 k
- HAL_GPIO_Init(GPIOB , &gpio_init_struct);
- a* ~; ^# O& t# t( x; h - & v- g6 B! p7 z3 \# b- ?3 t
- g_adc_handle.Instance = ADC1;
& ?' D4 t4 t0 o - g_adc_handle.Init.ClockPrescaler = ADC_CLOCK_ASYNC_DIV8;
; P" x! L, ^" u - g_adc_handle.Init.Resolution = ADC_RESOLUTION_12B;; T2 x5 [# Q9 J
- g_adc_handle.Init.DataAlign = ADC_DATAALIGN_RIGHT;1 ]% t( x, d/ B5 v @9 ]
- g_adc_handle.Init.ScanConvMode = ADC_SCAN_DISABLE;0 ~* L2 a" ?# t" _
- g_adc_handle.Init.EOCSelection = ADC_EOC_SINGLE_CONV;% c$ O( W" g. d
- g_adc_handle.Init.LowPowerAutoWait = DISABLE;, l! ^8 w6 g% [7 e5 Y! ~3 k, E7 s
- g_adc_handle.Init.ContinuousConvMode = DISABLE;
! J: u/ m- C) ^9 G/ L: T% T - g_adc_handle.Init.NbrOfConversion = 1;
4 |1 q! X; Y0 E, e: K - g_adc_handle.Init.DiscontinuousConvMode = DISABLE;
+ |2 u; x6 I; p r - g_adc_handle.Init.NbrOfDiscConversion = 1;
4 r% F4 z, N* R1 z! _* y5 v - g_adc_handle.Init.ExternalTrigConv = ADC_SOFTWARE_START;2 c* T* L d! u j, v$ F' z+ l3 V
- g_adc_handle.Init.ExternalTrigConvEdge = ADC_EXTERNALTRIGCONVEDGE_NONE;
' [) e! w" s4 { - g_adc_handle.Init.SamplingMode = ADC_SAMPLING_MODE_NORMAL;( m! B& M+ {* k) X
- g_adc_handle.Init.ConversionDataManagement = ADC_CONVERSIONDATA_DR;
h; |# l( t7 [" ]; ? - g_adc_handle.Init.Overrun = ADC_OVR_DATA_OVERWRITTEN;- J' l8 |) G5 {9 w5 x
- g_adc_handle.Init.OversamplingMode = DISABLE;
, D/ p2 x4 t" _3 i - HAL_ADC_Init(&g_adc_handle);
# [2 y2 {0 ~1 ?. b, K6 K0 \9 J - 9 [$ c" N$ ]8 `$ T; i8 D& Z2 r
- HAL_ADCEx_Calibration_Start(&g_adc_handle, ADC_SINGLE_ENDED);; }/ V& _- \1 c4 ~
- }
+ ?' A% M5 }0 N( K+ s$ q - ' @/ L' W: o; r. k: k( I2 m. Z
- uint32_t adc_get_result(uint32_t channel)* P3 a: M- R% b2 A( y% c
- {" N; f- D8 [0 |# \9 C* I4 k
- ADC_ChannelConfTypeDef adc_channel_conf_struct = {0};
# H! f! } e1 w$ }& }% | - uint32_t result;' e+ E. R7 E1 F @+ x5 i- [ M
- . a+ I9 B' _' I t
- adc_channel_conf_struct.Channel = channel;9 h/ j$ o: o+ X' ?+ C( y
- adc_channel_conf_struct.Rank = ADC_REGULAR_RANK_1;: ~0 O( E* L& @( s0 X
- adc_channel_conf_struct.SamplingTime = ADC_SAMPLETIME_640CYCLES_5;: K9 F; q0 a* }" L
- adc_channel_conf_struct.SingleDiff = ADC_SINGLE_ENDED;/ Y' ?" K9 O0 N9 O, N# J4 ^1 N
- adc_channel_conf_struct.OffsetNumber = ADC_OFFSET_NONE;
1 T6 ?$ W- e8 W8 b& u; _- [ - adc_channel_conf_struct.Offset = 0;
$ ]& R9 ^3 g6 E& f# H7 ^2 H - adc_channel_conf_struct.OffsetSign = ADC_OFFSET_SIGN_NEGATIVE;$ o. {) X7 ~' i
- HAL_ADC_ConfigChannel(&g_adc_handle, &adc_channel_conf_struct);$ L" x8 r4 I1 A
- 7 v: t' n9 E) t; k1 S! x9 h
- HAL_ADC_Start(&g_adc_handle);
* {1 `8 o, j' R7 l( D) r - HAL_ADC_PollForConversion(&g_adc_handle, HAL_MAX_DELAY);
4 S0 Y0 f* T7 {8 k/ M' _$ q, e - result = HAL_ADC_GetValue(&g_adc_handle);
( g; _ T' e# v% Q - HAL_ADC_Stop(&g_adc_handle);
1 {- g; p" j8 }( x - 4 d8 ^# n) y& B+ j
- return result;
. V) o- r7 R: I/ Z7 ~9 n3 e6 x - }
J+ z& ^ O0 A# {
+ r6 _4 \% H- t0 k* F- uint32_t adc_get_result_average(uint32_t channel, uint8_t times)
2 P4 i' M1 E! b' F5 [ - {
) m7 \6 C' N- T r/ `3 j - uint32_t sum_result = 0;
( B2 |/ U8 s- v: C# q1 H4 W - uint8_t index;
4 H2 ?7 M" C+ o7 h: i, o - uint32_t result;, K/ g2 u' h6 [5 }1 {
( A8 z" v4 I, Q' p/ o- for (index=0; index<times; index++)( C: m, x3 T* s/ X$ X7 y
- {( d2 G% B! o" p
- sum_result += adc_get_result(channel); |1 {% n1 r0 K! u% o& \
- }
1 J, k& v& e% ^! S5 m
2 K4 u) z9 k1 f& z- result = sum_result / times;, t/ }7 f+ D6 U8 b! ]
1 t8 t* }4 ~% d% C0 A- return result;! x2 V" b1 K' n: [. k
- }
复制代码
$ [# I1 B- x7 w$ O* `( R- Z: E6 D4 _) J, D M- [
然后再Model.cpp的tick成员函数里做AD采集(实际上在Screen1View.cpp的tick回调事件函数里面做也是可以的,就是采集间隔会大很多):. B. N0 g+ W9 [6 s& f) D
- b8 f; b3 j$ C4 Q) W" w
7 y# \+ b: i! Y- B然后最关键的来了,涉及到C++基础知识,多态性与虚函数,需要在ModelListener类里面注册一个虚函数的实现,随便起个名就可以,我起名为Notify_ADC_Value_Changed:+ n$ ^! {- R* D2 p# o
% s; }( V/ H9 Z2 L! ?3 E+ Q0 z# M3 e/ O3 y: m
为什么要在ModelListener类里面注册虚函数的实现呢,因为ModelListener类是Screen1Presenter类的父类,是Screen1Presenter类是继承而来的,因此Screen1Presenter类也要注册Notify_ADC_Value_Changed函数,在Screen1Presenter类中的Notify_ADC_Value_Changed函数就要实现具体功能了,也就是将Model类对象的值赋给Screen1View类对象的值:
" H% N1 p+ o- @7 M
* Y. X. ~4 o! a
: c; N; |1 r- v1 f' O6 z5 C+ O
同时ModelListener类对象又是Model类的成员,Model类有个公开成员叫adc_pb0_value,可以由别的类进行调用,Screen1View类也有同名的公开成员adc_pb0_value,两者可以相互赋值:- F* Z, {. v' t9 T
! H; Q% A& I t/ f7 P
6 h! T5 e: z, G" o
在多态性与虚函数知识点中,可以得知,父类的同一个虚函数可以由不同子类继承并实现不同功能,以实现多态。
0 z- r: @. r. y! o% P2 f; m% q4 y9 O3 u9 }7 b f
最后在Screen1View类的Tick事件回调函数中调用Notify_ADC_Value_Changed函进行上述操作,在Screen1View类的adc_pb0_value成员获得值并显示,完成了一次从Model->Present->View的信息传递,实现了将硬件外设的值传递给图形界面。- L, B1 |6 F) _$ u' H
+ S1 F/ S b3 H7 c& M! m
& `) I$ k) O& a0 O3 _2 b1 Y然后是通过界面控制硬件,这个就简单多了,直接在TouchGFX里面创建两个开关按钮,分别命名为Tb_led1 Tb_led2,写两个不同的事件回调函数即可:6 ^% j8 Y% k- o
2 b1 ^. r7 |' _/ H( \9 m' \( ~8 k$ ?& R4 A& J- N C. O: G
看看效果,两个按钮分别控制绿灯和红灯的开关:
, s% }/ @% u4 a1 S; V
% B; J8 g, y2 ~/ d/ M1 T
4 [' e& F/ v; s旋转电位器旋钮改变AD输出值:
. ?6 O& m2 M% F# O; B- l/ \5 A/ H
: q4 A' d2 k, }! M& y |