前面做了一帖关于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
# u& j/ e' }# `
2 V' P1 s( S/ t
比较偷懒的做法,初始化AD采集外设的代码可以放在main.c和main.h中,这样程序在系统初始化时就会自动初始化AD采集外设,并可以在程序的任何位置做AD采集操作:5 s3 w$ p( @: t$ { T: |
- ADC_HandleTypeDef g_adc_handle;
2 I- K! J" i+ ~/ n4 M- q2 a. |
5 B& m. l- o% o( \1 G5 }% p) m- void ADC_PB0_Init(void)7 l- D9 N2 P+ P* M, X
- {) t' ~/ B9 K; A' C. l
+ [. i/ j W! N. q i$ T/ ^% Q- __HAL_RCC_GPIOB_CLK_ENABLE();. V4 x3 ^9 t8 T- h
- __HAL_RCC_ADC12_CLK_ENABLE();
; s! c8 q+ S) {6 j0 q5 ^( w - % m1 [' B6 M# ^# Z2 Q
- RCC_PeriphCLKInitTypeDef rcc_periph_clk_init_struct = {0};; H0 u2 R& _8 F" S* T: Q" a
- GPIO_InitTypeDef gpio_init_struct = {0};
1 U. m+ e/ o; c9 ?$ x
0 o1 s9 C9 ?' \9 j- rcc_periph_clk_init_struct.PeriphClockSelection = RCC_PERIPHCLK_ADC;% c+ S" l t; H# S" S
- rcc_periph_clk_init_struct.AdcClockSelection = RCC_ADCCLKSOURCE_PLL2P;
" Q( L3 z5 Z9 I, A# j* u1 E0 k - HAL_RCCEx_PeriphCLKConfig(&rcc_periph_clk_init_struct);9 t3 \/ t2 h: l8 W
0 y$ M* n' V6 n. d( J' Q1 a) j- gpio_init_struct.Mode = GPIO_MODE_ANALOG;2 B% { }3 w1 e
- gpio_init_struct.Pull = GPIO_NOPULL;9 D: ?% I% J, Z' N& Y h
- gpio_init_struct.Pin = GPIO_PIN_0;
5 a, S) P+ k8 W) a& v. F% R4 S) b - HAL_GPIO_Init(GPIOB , &gpio_init_struct);
) Z/ r% Z* o" a& f - ) l( K( F! m+ p
- g_adc_handle.Instance = ADC1;
5 }' }5 J/ h9 R3 G8 f - g_adc_handle.Init.ClockPrescaler = ADC_CLOCK_ASYNC_DIV8;% h/ N5 {4 J4 m F/ R
- g_adc_handle.Init.Resolution = ADC_RESOLUTION_12B;
8 D; g% P& Z% }" f! p1 h - g_adc_handle.Init.DataAlign = ADC_DATAALIGN_RIGHT;
# U) V ^# t! E+ d2 W1 A) n - g_adc_handle.Init.ScanConvMode = ADC_SCAN_DISABLE;- V0 O2 ^) r% Y
- g_adc_handle.Init.EOCSelection = ADC_EOC_SINGLE_CONV;' x" ~4 v: m8 K; r
- g_adc_handle.Init.LowPowerAutoWait = DISABLE;: \1 N; a0 p2 j$ u R
- g_adc_handle.Init.ContinuousConvMode = DISABLE;) e) f/ ]* M" W8 n: N1 F
- g_adc_handle.Init.NbrOfConversion = 1;4 Q# C( ~1 \' H% A4 Y& }2 R
- g_adc_handle.Init.DiscontinuousConvMode = DISABLE;
" N9 S* i: C& w( w. V& V) b+ | - g_adc_handle.Init.NbrOfDiscConversion = 1;
$ U% ?% v( k- @: Y l - g_adc_handle.Init.ExternalTrigConv = ADC_SOFTWARE_START;# V1 V" |1 e1 m9 v2 C
- g_adc_handle.Init.ExternalTrigConvEdge = ADC_EXTERNALTRIGCONVEDGE_NONE;
3 x8 _7 h2 u% a d+ L" A - g_adc_handle.Init.SamplingMode = ADC_SAMPLING_MODE_NORMAL;
U: A1 W& b% l: l$ X% P+ h t2 ~ - g_adc_handle.Init.ConversionDataManagement = ADC_CONVERSIONDATA_DR;/ E$ W. T0 i j: i3 S5 I& o0 W
- g_adc_handle.Init.Overrun = ADC_OVR_DATA_OVERWRITTEN;2 K7 c$ N, F' E# F
- g_adc_handle.Init.OversamplingMode = DISABLE;
$ F+ z1 S% C0 H0 M" w - HAL_ADC_Init(&g_adc_handle);
( ~, V% L; ] S/ Y4 s0 O+ k - 1 V' p. d# g$ Q( L' H2 o
- HAL_ADCEx_Calibration_Start(&g_adc_handle, ADC_SINGLE_ENDED);
/ g6 _8 Q7 y% N$ ~* ]1 z3 \" J - }0 ^7 k H! z3 \+ G+ \( s
- 9 F/ T; a6 d' @3 p* D8 u; k8 w
- uint32_t adc_get_result(uint32_t channel)8 J' }( ~* t/ F8 G, n; R
- {& @8 v. t/ }5 m& J" N4 h
- ADC_ChannelConfTypeDef adc_channel_conf_struct = {0};. A$ J/ ^0 c, [+ C! n" K) L, Z6 P
- uint32_t result;, [- b# G* M# E
- 3 a8 A' x% [6 U, X* s$ Z
- adc_channel_conf_struct.Channel = channel;
, d! e) _) }" o4 ^! i9 h - adc_channel_conf_struct.Rank = ADC_REGULAR_RANK_1;6 h, `5 ?6 H f& H
- adc_channel_conf_struct.SamplingTime = ADC_SAMPLETIME_640CYCLES_5;
+ E7 e) p- P- F( U. U' s; j - adc_channel_conf_struct.SingleDiff = ADC_SINGLE_ENDED;/ h) g2 ]+ d" Q3 W# f+ D
- adc_channel_conf_struct.OffsetNumber = ADC_OFFSET_NONE;
8 F4 u3 L4 ^: a - adc_channel_conf_struct.Offset = 0;/ D" j) P% w ]2 O- T+ l, u
- adc_channel_conf_struct.OffsetSign = ADC_OFFSET_SIGN_NEGATIVE;3 g. e+ E, s/ a
- HAL_ADC_ConfigChannel(&g_adc_handle, &adc_channel_conf_struct);
3 H6 W: q; U/ u X8 h" V0 F - 9 q% M$ x _9 H0 D! ^+ u3 f& D
- HAL_ADC_Start(&g_adc_handle);
4 r- p8 o. E! z. o$ w - HAL_ADC_PollForConversion(&g_adc_handle, HAL_MAX_DELAY);: g+ B1 k) b. F' F2 a: R
- result = HAL_ADC_GetValue(&g_adc_handle);
! ^0 j+ E+ \: O+ `$ w! m3 U1 c! x5 t - HAL_ADC_Stop(&g_adc_handle); c7 t$ T% Z. Q! y ]1 W+ ]
) `* t9 q% M" h, h, c4 R" c0 Q0 |- c- return result;
$ Q- e4 A3 Q C8 _( c# j - }' R9 x) |( n% w, [+ F3 G+ U
- H+ P# h# `2 [* g# X- uint32_t adc_get_result_average(uint32_t channel, uint8_t times)
5 ^4 {4 y; U! ?" _: W - {
6 f# y0 h2 D; X* e% |8 p3 c1 e - uint32_t sum_result = 0;2 n1 z8 O/ U5 W4 v+ L" H* s
- uint8_t index;3 @1 j& h8 j5 L
- uint32_t result;
# V/ A9 y/ F% @; q8 m0 h - 9 y7 l! A9 D1 U' @7 f z; }" o
- for (index=0; index<times; index++)
3 T. |0 \! |! h& W) B - {
- F3 J/ _$ ?3 V0 _ - sum_result += adc_get_result(channel);
" _. {3 `, S/ y - }
; r+ {! }3 S" V# r0 b3 A) W
( b" F! j& Q8 _! o. r' X( j- P- result = sum_result / times;
& _. }# [4 S+ b* K; C- y! M; H6 z
, `# o" \3 X% m w. E" f; d- return result;
( J( n+ L! {$ ~- G5 }5 R7 q( C - }
复制代码
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 \
/ 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! `
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
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 @
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
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
) h& ^% G* A0 t' ]& [* O
' R( R9 \4 t- n
看看效果,两个按钮分别控制绿灯和红灯的开关:
" M: {) b* O2 u
5 M- l( o! u4 }6 `+ ~% n8 q" V9 Y7 n3 p
旋转电位器旋钮改变AD输出值:& m* A* Y$ ?7 u% o2 }
, G3 P9 n+ E1 r
|