|
使用Platformio平台的libopencm3开发框架来开发STM32G0,以下为多通道ADC与DMA的使用。
1 S. c; ?' {( t 1 新建项目建立adc_dma项目 / s, y! x% [* L% B
在PIO的Home页面新建项目,项目名称adc_dma,选择开发板为 MonkeyPi_STM32_G070RB,开发框架选择libopencm3; - 1upload_protocol = cmsis-dap& v+ r, K) a0 e, q
- 2debug_tool = cmsis-dap
复制代码 4 m' w* Y+ r4 B% E
3 _5 M$ @' |+ N2 编写程序2.1 ADC 设置这里设置PA0、PA1、PA2、PA3四个引脚为ADC: - 1static void adc_setup(void)5 l2 f- j6 B5 x8 t$ _
- 2{
; h" c; y: X! g( k7 Z/ F9 d, T; w - 3 rcc_periph_clock_enable(RCC_ADC);
- q" F: w* [1 O2 J* d" F - 4 rcc_periph_clock_enable(RCC_GPIOA);& s& ]' {+ y1 Z% C5 B; y
- 5
. x) R! D0 s- ^7 L4 z - 6 gpio_mode_setup(GPIOA, GPIO_MODE_ANALOG, GPIO_PUPD_NONE, GPIO0); [7 q2 J& o( e& E
- 7 gpio_mode_setup(GPIOA, GPIO_MODE_ANALOG, GPIO_PUPD_NONE, GPIO1);" h( t- A7 n* I" ]+ a
- 8 gpio_mode_setup(GPIOA, GPIO_MODE_ANALOG, GPIO_PUPD_NONE, GPIO2);
; G* Z: Q2 W7 | h9 u/ f - 9 gpio_mode_setup(GPIOA, GPIO_MODE_ANALOG, GPIO_PUPD_NONE, GPIO3);
0 b, D. a# X: a5 v6 L. m* z" b/ a - 10$ u, i4 U0 u2 S7 H% s
- 11 adc_power_off(ADC1);
4 Z5 x) E/ H8 Y8 c% o - 12 adc_set_clk_prescale(ADC1, ADC_CCR_PRESC_DIV2);
* E+ B" ]$ d( k' {! h4 {! `/ z - 13 adc_set_single_conversion_mode(ADC1);( ]9 i& I5 R, ]3 ^; Q
- 14 adc_set_right_aligned(ADC1);3 {7 I$ L c p$ `
- 15 adc_set_sample_time_on_all_channels(ADC1, ADC_SMPTIME_160DOT5);. t5 S4 n9 m) X' s) U6 }0 w
- 16: y, l, e9 d9 A0 n/ w+ n0 D% r
- 17 uint8_t channel_array[16] = {0};6 }6 T+ L1 v8 g2 U, f+ S7 Y; c& y5 [
- 18 channel_array[0] = 0;
' ]4 M5 t! L# v/ ]& V7 k [ - 19 channel_array[1] = 1;
* E6 r6 i4 s, f" d - 20 channel_array[2] = 2;
~# \9 u) A" t2 O6 P - 21 channel_array[3] = 3;) `3 M" {% T2 P
- 22 adc_set_regular_sequence(ADC1, ADC_CHAN_CNT, channel_array);/ ?" x$ J2 ]8 b- d0 c; P* W/ K3 C/ C* x
- 23 adc_enable_dma_circular_mode(ADC1);1 Y. p6 D( | M* a/ }
- 24 adc_set_resolution(ADC1, ADC_CFGR1_RES_12_BIT);" Y+ g3 h1 o9 r' M q/ U; i
- 25 adc_power_on(ADC1);# \* s" ]1 G8 c2 m4 d. y
- 26/ o6 c# h8 ?2 f/ H% A8 R
- 27 /* Wait for ADC starting up. */
" u8 b( V& B$ v% D! g - 28 delay_ms(10); w' P% L; Z: ~' @* T# w( @# o! H
- 29}
复制代码
' v5 M+ e8 q9 J i. {2.2 DMA配置 - 1static void dma_setup(void *data, int size)& U: }: p, r1 `9 _, K. E9 V
- 2{
& H1 D* b$ U6 T% D ?0 g& w# G - 3 dma_channel_reset(DMA1, DMA_CHANNEL1);( q8 Z1 U) W7 c) V1 R, I+ Z! S
- 4 dma_set_peripheral_address(DMA1, DMA_CHANNEL1, (uint32_t)&ADC_DR(ADC1));
( F0 h. [1 l7 q - 5 dma_set_memory_address(DMA1, DMA_CHANNEL1, (uint32_t)data);' j/ |" U1 l' N3 _2 S: t1 i
- 6 dma_set_number_of_data(DMA1, DMA_CHANNEL1, size);
7 a7 b$ [* }, E5 D2 A - 7 dma_set_read_from_peripheral(DMA1, DMA_CHANNEL1);4 B3 d& B: O- I7 I6 \# Y
- 8 dma_enable_memory_increment_mode(DMA1, DMA_CHANNEL1);
8 a1 |- y: g/ U6 J' A - 9 dma_set_peripheral_size(DMA1, DMA_CHANNEL1, DMA_CCR_PSIZE_16BIT);
) w: a/ Q/ o. d# Q+ U - 10 dma_set_memory_size(DMA1, DMA_CHANNEL1, DMA_CCR_MSIZE_16BIT);
, S% l4 l# y- c- e1 G8 f - 11 dma_enable_circular_mode(DMA1, DMA_CHANNEL1);; ?7 Q$ M) B# f! c
- 12 dma_enable_transfer_complete_interrupt(DMA1, DMA_CHANNEL1);
# q! c% R6 r! l9 `7 \) k2 l - 13 dma_enable_channel(DMA1, DMA_CHANNEL1);
# m2 W) U) ?; U' S - 14
: h6 [7 b& m0 D" d/ X/ ]5 U2 l- H; X - 15 dmamux_reset_dma_channel(DMAMUX1, DMA_CHANNEL1);
( d- g1 S! Q0 n* `# \" ? - 16 dmamux_set_dma_channel_request(DMAMUX1, DMA_CHANNEL1, DMAMUX_CxCR_DMAREQ_ID_ADC);+ T5 K$ |1 |3 X/ I% e* u( z* Q
- 17}
复制代码 / I% q6 G& i: g7 V; R
! l! H. C- m( V9 d5 x7 s! m3 I2 ?$ o
主要是设置DMA的外设地址为ADC数据寄存器 ADC_DR;并设置内存地址为定义的buff,size为需要缓存的数据大小: - 1#define ADC_CHAN_CNT 4
! G1 [' x0 J5 Z1 S. ^ - 2#define ADC_FILETER_SIZE 32# A( n! T( E1 ~
- 3# V: e+ V( G3 {6 a
- 4int16_t adc_values[ADC_FILETER_SIZE*ADC_CHAN_CNT];
复制代码
! F, w7 k7 ]7 J; q2.3 ADC配置为DMA读取和Timer触发定时器设置
) H4 s B9 @( S# A9 `3 D
- 1void tim3_setup(void)3 E W" E0 g6 \. B4 }
- 2{
& t6 l% t# X7 f5 r - 3 /* Enable TIM3 clock. */
2 x. r- P2 K( G: d% f: G1 i - 4 rcc_periph_clock_enable(RCC_TIM3);8 s" M/ r4 W5 Z6 u. M* {$ p
- 5, j! c, Z7 c5 Z. ]+ P! s
- 6 /* Enable TIM3 interrupt. */; O+ c; c4 _" J0 {( w) N
- 7 nvic_enable_irq(NVIC_TIM3_IRQ);
' p( _+ ?9 M# R2 W. S - 8
1 H4 W0 V: h9 k4 b9 s% o - 9 /* Reset TIM3 peripheral to defaults. */$ E: |; T2 L3 U( ]
- 10 rcc_periph_reset_pulse(RST_TIM3);
1 \1 e9 t# s0 i1 t - 11( h( @4 d. r/ o/ k& L
- 12 /* Timer global mode:
7 m9 h. O7 ]8 a - 13 * - No divider$ E x2 l/ F: J" I3 T3 }0 S+ _
- 14 * - Alignment edge
7 ~$ ^8 K* }6 y* d% S7 I5 i2 h7 Y - 15 * - Direction up
5 R0 U' ?- c( A% a( p/ t S - 16 * (These are actually default values after reset above, so this call( q) L' \1 N, B: E
- 17 * is strictly unnecessary, but demos the api for alternative settings)1 o* x$ v ?/ m( d7 s3 }
- 18 */
3 C: I c/ V; r( u" L+ ? F - 19 timer_set_mode(TIM3, TIM_CR1_CKD_CK_INT," O+ j( K6 `0 B
- 20 TIM_CR1_CMS_EDGE, TIM_CR1_DIR_UP);
9 Z _8 ~% O. d0 a+ L - 21
6 [: h+ L" R/ S; H4 X& o' D J - 22 /*
9 x8 A( ~2 I$ ^ - 23 * Please take note that the clock source for STM32 timers
* j2 i; | g! U' A - 24 * might not be the raw APB1/APB2 clocks. In various conditions they7 W5 K7 {1 i' x
- 25 * are doubled. See the Reference Manual for full details!7 M1 n6 m4 B* S- U5 z4 [1 t( ]
- 26 * In our case, TIM3 on APB1 is running at double frequency, so this+ n1 Z. c: C( ?
- 27 * sets the prescaler to have the timer run at 5kHz, _9 ~+ f3 |# b3 p3 {1 t
- 28 */0 w% {% t) f$ Y8 E4 y: i( u
- 29 timer_set_prescaler(TIM3, 64-1);
2 q! G, W' V3 F P' E9 N - 30
! B, n: K/ Y* } - 31 /* Disable preload. */2 H* Y4 o$ E0 H8 c: B% }" G s! m$ W
- 32 timer_disable_preload(TIM3);
, C( C2 m, n* R! N( ^, L/ U& Q - 33 timer_continuous_mode(TIM3);
8 D5 A. q; t2 W& M) u/ t" B( B/ x - 340 q+ T- P) v [+ V+ R4 Y$ }+ W" Q
- 35 timer_set_period(TIM3, 20000-1); //100Hz
1 z0 g w7 z1 t u6 K# ` - 360 O9 _; v1 X; {3 `. ~" k
- 37 timer_set_master_mode(TIM3, TIM_CR2_MMS_UPDATE);6 b. _! M& y& I. z
- 38! p) k9 y* W! d5 Y( v
- 39 timer_enable_irq(TIM3, TIM_DIER_UIE);
O4 j% M1 ~$ K$ b+ c - 40}
5 J Y- O" M! q1 h4 f8 f - 41- S" n- w0 v$ `6 @ o/ y( g
- 42void tim3_enable_counter(bool en)
4 W& i1 v5 a+ J( V/ p2 a - 43{
- ^( T; j- M5 v, k7 W l: A - 44 if(en){
$ _" u- M$ ]1 W/ R! }& m: C; q8 K! m - 45 timer_enable_counter(TIM3);
* O* F4 I/ j q: V) j d - 46 }else{1 ^: F/ U* l+ G. ~5 q" @- e
- 47 timer_disable_counter(TIM3);
# T, i( v) H, T$ v' s, k4 I) V. ~ - 48 }
* b- r- `3 N# l - 49}
2 Z' q6 V6 U, s4 x - 50" V+ L% x$ A0 z. U6 ?7 D
- 51void tim3_isr(void); u7 B9 }2 L* d8 }( J" u1 m
- 52{
- `. _& W4 C& x: ^; \( J/ |: s% S - 53 if (timer_get_flag(TIM3, TIM_SR_UIF)) {
/ A4 c- q/ Y/ N8 I+ K5 m' D Z - 54
, E. c. }# {6 `$ S' x) s& Y3 P - 55 /* Clear compare interrupt flag. */
$ D4 H9 C3 S: }( c - 56 timer_clear_flag(TIM3, TIM_SR_UIF);3 a& P$ W" V9 q& X% J9 p$ @3 ^7 G
- 57 }
4 B$ j- E2 s- ^6 P* ?, d! O - 58}
复制代码 7 Z! r3 j2 G' p1 J% P6 m4 h7 f4 b
' t: J; m( L1 C @DMA设置和Timer触发 7 c' s: C" n3 c' P8 N
- 1 rcc_periph_clock_enable(RCC_DMA);
) f- p: d1 X, S/ ?8 \% | - 2 nvic_set_priority(NVIC_DMA1_CHANNEL1_IRQ, 3);
$ Z; W3 j. K ~, b) f9 L! q/ x - 3 nvic_enable_irq(NVIC_DMA1_CHANNEL1_IRQ);/ g0 X/ c0 a, Q( M& n- k" W
- 4
( s: J# B, R4 j5 k; S3 g - 5 adc_setup();' `9 o$ G& J1 U' p
- 6" S& V+ ~9 V4 l+ Z0 t/ ?9 s
- 7 dma_setup(adc_values, ADC_CHAN_CNT*ADC_FILETER_SIZE);$ o% L& S1 Z- B& N1 t0 z5 r
- 8
9 k g' m G; y+ D" M - 9 adc_enable_overrun_interrupt(ADC1);3 v% V' Q" g' I1 m: p$ h8 B5 w. K# l
- 105 w; M5 z* M) Y# S+ I
- 11 adc_enable_dma(ADC1);
, E8 C' }. r/ Q; ^, o0 X9 w9 ^ - 12# F$ Y' C" g+ E" d) ~8 o# ?
- 13 ADC_CFGR1(ADC1) = (ADC_CFGR1(ADC1) & ~(0x3<<10)) | (0x1<<10); // Hardware trigger detection on the rising edge; P+ t ~) E* q2 T& ?( G" ~4 L
- 14 ADC_CFGR1(ADC1) = (ADC_CFGR1(ADC1) & ~ADC_CFGR1_EXTSEL) | (3<<ADC_CFGR1_EXTSEL_SHIFT); // toggle by tim3
, a/ n! l; s/ z0 d1 W/ a1 v# w" O - 15
$ ?* N3 A$ l3 u2 ^( j - 16 tim3_setup();5 c D) K4 I8 |9 a" p# M9 |2 Z
- 179 e$ O; ]. Y P z+ g9 @- O4 g
- 18 adc_start_conversion_regular(ADC1);
# F6 R! G: i% w9 G - 19
% K5 b9 w7 Y5 _2 z - 20; j* J# X" s' a" ]1 r
- 21 tim3_enable_counter(true);
( w- f9 K, k- p - 22
$ [% p! u4 k$ z' u1 \ - 23 delay_ms(100);
复制代码
- p$ L3 d5 d+ U: @" L1 t4 ~8 v4 k6 T
DMA中断时候即准备好读取ADC数据,因此在DMA中断中先把定时器关闭,读取数据后再次打开: - 1void dma1_channel1_isr(void)
* M% o' T' B6 i5 R8 i& w9 @ - 2{1 l! l% k# c$ g+ v# ^2 _$ U! ]6 p
- 35 @( F# F; S& ^& f+ q+ Y
- 4 if ((DMA1_ISR &DMA_ISR_TCIF1) != 0) {% P2 C# ~. a; K9 G) \% ^# O
- 5 DMA1_IFCR |= DMA_IFCR_CTCIF1;* D0 Y4 h, z _( H% m! ?
- 6 }
3 j3 f2 v! ~% z" w$ K - 7/ k/ Q' R1 R" q9 T0 s8 a$ _/ D
- 8 tim3_enable_counter(false); F% o! b* n( q6 n
- 90 E3 I( l' L' Q, J* Q: [
- 10}
复制代码
6 c0 X: o) f, k: Q6 x2.4 ADC读取 D/ y' p4 [! {; \ J1 A
- 1void adc_sample(void)/ W( V9 {( F+ K1 I$ V, b5 P
- 2{
$ c7 h/ g; p$ X - 3 uint32_t sum_val1 = 0;+ I; x. ?* ^2 }2 l, e
- 4 uint32_t sum_val2 = 0;/ W3 {- r1 Q5 e0 y/ J8 Q2 P
- 5 uint32_t sum_val3 = 0;
7 t& y* d+ p0 d$ ]. l - 6 uint32_t sum_val4 = 0; p ~& p, _. |. z" K6 _ Z
- 7) b) E& t* {0 Y- T [0 U+ ?
- 8 for(int i=0; i<ADC_FILETER_SIZE; i++){
) {5 ?4 E2 M* h) Z - 9 sum_val1 += adc_values[ADC_CHAN_CNT*i + 0];
, j% h: b/ Z( W8 G" Y - 10 sum_val2 += adc_values[ADC_CHAN_CNT*i + 1];/ A% M8 h# a0 Q$ I( o* e
- 11 sum_val3 += adc_values[ADC_CHAN_CNT*i + 2];
- N% p2 C1 s7 O9 H9 n! z' v9 Y - 12 sum_val4 += adc_values[ADC_CHAN_CNT*i + 3];
( r. Q; S, T6 e - 13 }
; i: F1 D+ `0 o5 J& r - 14
- X$ b4 A& u$ ]% n" v. F - 15 uint32_t filter_val1 = sum_val1/ADC_FILETER_SIZE;5 I7 K& N" v4 v+ `6 A0 C
- 16 uint32_t filter_val2 = sum_val2/ADC_FILETER_SIZE;# ~: [+ I* v8 H1 V2 X
- 17 uint32_t filter_val3 = sum_val3/ADC_FILETER_SIZE;
) u& n' I6 w9 ` D0 B - 18 uint32_t filter_val4 = sum_val4/ADC_FILETER_SIZE;/ s2 v, o8 R; e7 {# a) N# a
- 19
8 _9 E* E$ V$ Z& [# p& G% K - 20 printf("adc:%d %d %d %d\r\n", filter_val1, filter_val2, filter_val3, filter_val4);$ A. T2 W9 X# Q+ R7 G
- 210 V- S( I$ z5 V) @: o
- 22 tim3_enable_counter(true);; Y! s! s$ X: N$ ]& i' E: C; T' ~
- 23}
复制代码
# F8 e+ F0 G% a- o" Z4 h. S! B5 |) S
读取时候按照通道的顺序从buff中取出,这里做了简单的过滤; ) X! {+ b% _) N; T* D( V
3 烧写测试将程序烧写到开发板,然后打开串口可以看到四个ADC通道的数据,在PA0/PA1/PA3/PA4四个引脚连接不同电压可以看到变化:
9 g. z+ B% ~( {' \" W7 Z; ~' a& ]$ [$ A; b2 H3 J
& R: j+ r6 C& h' H
|