前面做了一帖关于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
% 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
- ADC_HandleTypeDef g_adc_handle;/ y4 d W% \ F# m( ]
& o+ ^( W$ Y& S8 ]4 z2 Y+ Y7 L$ {- void ADC_PB0_Init(void)
; w$ t( ]3 W1 S1 n7 y' y/ s! ~* A - {
" N) A( d) r+ s, p" N1 B - 3 S0 s0 J+ f, D l
- __HAL_RCC_GPIOB_CLK_ENABLE();7 B1 d3 u6 D* q- |3 D/ H
- __HAL_RCC_ADC12_CLK_ENABLE();4 `: o0 {/ b( Y
- . w9 h/ M+ O6 V) }' c+ x4 {
- RCC_PeriphCLKInitTypeDef rcc_periph_clk_init_struct = {0};
6 [+ ?& [" `/ i% `! | - GPIO_InitTypeDef gpio_init_struct = {0};
6 N2 d4 L: r% O - 6 [& \% D5 C! m: G7 _
- rcc_periph_clk_init_struct.PeriphClockSelection = RCC_PERIPHCLK_ADC;/ |4 c5 K+ i; r3 J
- rcc_periph_clk_init_struct.AdcClockSelection = RCC_ADCCLKSOURCE_PLL2P; h% a+ P( L. ]; n" Y" ?
- HAL_RCCEx_PeriphCLKConfig(&rcc_periph_clk_init_struct);/ @# R. f+ y( e A& ~
- % E% o F s8 N
- gpio_init_struct.Mode = GPIO_MODE_ANALOG;
& D. U9 X2 }& r) R' i$ l6 B1 D) A - gpio_init_struct.Pull = GPIO_NOPULL;' |3 \/ d7 f+ c% z$ Z3 ?
- gpio_init_struct.Pin = GPIO_PIN_0;; S5 m& J4 u+ V
- HAL_GPIO_Init(GPIOB , &gpio_init_struct);
; o/ F* ^, n- G
2 m" n2 g. z0 c/ s* y" K- g_adc_handle.Instance = ADC1;
4 J4 ]: @7 q6 M - g_adc_handle.Init.ClockPrescaler = ADC_CLOCK_ASYNC_DIV8;7 U/ G& \% p8 B* y% E6 T# k
- g_adc_handle.Init.Resolution = ADC_RESOLUTION_12B;: ?% I2 }7 O+ p; ^2 o
- g_adc_handle.Init.DataAlign = ADC_DATAALIGN_RIGHT;5 E' Q2 r4 k, W& c3 S) C8 J6 o
- g_adc_handle.Init.ScanConvMode = ADC_SCAN_DISABLE;
( G& c' f# Y, k( | - g_adc_handle.Init.EOCSelection = ADC_EOC_SINGLE_CONV;
: o/ n! T( n1 H' [/ x8 j - g_adc_handle.Init.LowPowerAutoWait = DISABLE;6 c7 y' q$ V8 r
- g_adc_handle.Init.ContinuousConvMode = DISABLE;& q1 V; a* v7 Q" }0 l, l
- g_adc_handle.Init.NbrOfConversion = 1;
* e# ]& b# N$ _" {8 A; U7 p/ [ - g_adc_handle.Init.DiscontinuousConvMode = DISABLE;
: g+ c* ~, O7 L+ P - g_adc_handle.Init.NbrOfDiscConversion = 1;
& }, ]. Q- n$ E' J! W" @4 ^ - g_adc_handle.Init.ExternalTrigConv = ADC_SOFTWARE_START;
. b+ V" K1 [ K' f - g_adc_handle.Init.ExternalTrigConvEdge = ADC_EXTERNALTRIGCONVEDGE_NONE;7 k5 D' v2 A" {. Q8 G
- g_adc_handle.Init.SamplingMode = ADC_SAMPLING_MODE_NORMAL;
. L3 v, ^& n+ O1 @& D# P - g_adc_handle.Init.ConversionDataManagement = ADC_CONVERSIONDATA_DR;
8 n) x, q+ I5 i5 y1 e - g_adc_handle.Init.Overrun = ADC_OVR_DATA_OVERWRITTEN;
* @" k3 M) W) O0 } - g_adc_handle.Init.OversamplingMode = DISABLE;$ G y% O/ f2 D5 [3 C3 U2 b' j
- HAL_ADC_Init(&g_adc_handle);0 e( k8 U6 m- i" P* q' M" w. O" N
0 R. J7 Z$ V0 m V/ ~/ l9 ?9 K- HAL_ADCEx_Calibration_Start(&g_adc_handle, ADC_SINGLE_ENDED);
8 L) J5 u! S' P - }
- k) q3 o' A1 Y9 d
) n3 G7 S; B% D( a# q- uint32_t adc_get_result(uint32_t channel)0 s+ w7 ^+ L7 D7 w! j( b1 t
- { X; n) M8 T4 D1 _7 K/ d2 ]! l
- ADC_ChannelConfTypeDef adc_channel_conf_struct = {0};$ f5 l1 n' L) n+ p& A
- uint32_t result;
& q( T4 u2 Q& _9 }. v
' w6 f- S1 b, i( q8 b" }/ e) s- adc_channel_conf_struct.Channel = channel;
: p; u4 j; z* Q7 _* Y - adc_channel_conf_struct.Rank = ADC_REGULAR_RANK_1;" y0 z, u4 H9 n7 Y$ E" g1 C5 `0 _0 d( M
- adc_channel_conf_struct.SamplingTime = ADC_SAMPLETIME_640CYCLES_5;' L9 [! L0 y& X+ f
- adc_channel_conf_struct.SingleDiff = ADC_SINGLE_ENDED;
1 f2 K1 X n, {) H. E - adc_channel_conf_struct.OffsetNumber = ADC_OFFSET_NONE; T4 W3 p9 S9 C E" `
- adc_channel_conf_struct.Offset = 0;5 L% N7 f. Z* H, T4 V. y# K
- adc_channel_conf_struct.OffsetSign = ADC_OFFSET_SIGN_NEGATIVE;
' e1 U/ h7 k1 U `2 K - HAL_ADC_ConfigChannel(&g_adc_handle, &adc_channel_conf_struct);
; Y \! _: W. @: p* M' S! b7 }
8 I% z# u# M% G) V5 H- HAL_ADC_Start(&g_adc_handle);0 ]; `0 E# t# G
- HAL_ADC_PollForConversion(&g_adc_handle, HAL_MAX_DELAY);# k) A8 L9 K. K H3 ?
- result = HAL_ADC_GetValue(&g_adc_handle);& p8 e3 U0 L' H4 a1 M) s
- HAL_ADC_Stop(&g_adc_handle);; I8 _6 ^ L& d9 t9 G3 b4 m9 x8 T
; z) e( W+ ]3 ]- return result;: N5 ~4 j" O& _+ M
- }
" n8 O) Y8 `0 ` - z( E# L( K/ j0 a% I* E
- uint32_t adc_get_result_average(uint32_t channel, uint8_t times)5 i; g, ^* q' }5 e1 R
- {
6 Q \* v: E& e - uint32_t sum_result = 0;
$ k4 ?, R j1 f# n* m1 a8 R1 T - uint8_t index;1 ^2 i) W' L; c( O/ U9 j
- uint32_t result;* n/ `1 q1 H* F$ {5 J
- 1 n4 ?+ ~( `$ W# f. K
- for (index=0; index<times; index++)
8 n% w6 T- N/ {; c - {
2 E* }" G7 @1 q% m - sum_result += adc_get_result(channel);! Q* \5 G5 S) _- V( Q
- }: K' [% y5 `2 H1 ]: J
- 1 k+ G2 Q0 ^9 Q6 V- M
- result = sum_result / times;% l6 \5 J' N: R7 K) z' a
- 2 y7 W3 ?9 X+ H
- return result;
+ _" X: S* H2 B - }
复制代码
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
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
% ]' 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. [
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
/ 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
8 j+ B# }) {+ ^1 |6 W" }4 m9 z
% ^% W& U2 |- v& u然后是通过界面控制硬件,这个就简单多了,直接在TouchGFX里面创建两个开关按钮,分别命名为Tb_led1 Tb_led2,写两个不同的事件回调函数即可:! g3 }. z3 {) M% }$ Y
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
4 R4 i' g) R. P1 r; h; i, f: C& L: @* [/ e4 d
旋转电位器旋钮改变AD输出值:8 }9 c8 }+ t- X+ i3 ~
$ o( o: ]* P4 L0 `' ^( P
|