使用Platformio平台的libopencm3开发框架来开发STM32G0,以下为多通道ADC与DMA的使用。

1 新建项目

  • 建立adc_dma项目

在PIO的Home页面新建项目,项目名称adc_dma,选择开发板为 MonkeyPi_STM32_G070RB,开发框架选择libopencm3;

  • 项目建立完成后在src目录下新建main.c主程序文件;
  • 修改下载和调试方式,这里开发板使用的是DAPLink仿真器,因此修改platformio.ini文件如下:
1
2
upload_protocol = cmsis-dap
debug_tool = cmsis-dap

2 编写程序

2.1 ADC 设置

这里设置PA0、PA1、PA2、PA3四个引脚为ADC:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
static void adc_setup(void)
{
rcc_periph_clock_enable(RCC_ADC);
rcc_periph_clock_enable(RCC_GPIOA);

gpio_mode_setup(GPIOA, GPIO_MODE_ANALOG, GPIO_PUPD_NONE, GPIO0);
gpio_mode_setup(GPIOA, GPIO_MODE_ANALOG, GPIO_PUPD_NONE, GPIO1);
gpio_mode_setup(GPIOA, GPIO_MODE_ANALOG, GPIO_PUPD_NONE, GPIO2);
gpio_mode_setup(GPIOA, GPIO_MODE_ANALOG, GPIO_PUPD_NONE, GPIO3);

adc_power_off(ADC1);
adc_set_clk_prescale(ADC1, ADC_CCR_PRESC_DIV2);
adc_set_single_conversion_mode(ADC1);
adc_set_right_aligned(ADC1);
adc_set_sample_time_on_all_channels(ADC1, ADC_SMPTIME_160DOT5);

uint8_t channel_array[16] = {0};
channel_array[0] = 0;
channel_array[1] = 1;
channel_array[2] = 2;
channel_array[3] = 3;
adc_set_regular_sequence(ADC1, ADC_CHAN_CNT, channel_array);
adc_enable_dma_circular_mode(ADC1);
adc_set_resolution(ADC1, ADC_CFGR1_RES_12_BIT);
adc_power_on(ADC1);

/* Wait for ADC starting up. */
delay_ms(10);
}
2.2 DMA配置
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
static void dma_setup(void *data, int size)
{
dma_channel_reset(DMA1, DMA_CHANNEL1);
dma_set_peripheral_address(DMA1, DMA_CHANNEL1, (uint32_t)&ADC_DR(ADC1));
dma_set_memory_address(DMA1, DMA_CHANNEL1, (uint32_t)data);
dma_set_number_of_data(DMA1, DMA_CHANNEL1, size);
dma_set_read_from_peripheral(DMA1, DMA_CHANNEL1);
dma_enable_memory_increment_mode(DMA1, DMA_CHANNEL1);
dma_set_peripheral_size(DMA1, DMA_CHANNEL1, DMA_CCR_PSIZE_16BIT);
dma_set_memory_size(DMA1, DMA_CHANNEL1, DMA_CCR_MSIZE_16BIT);
dma_enable_circular_mode(DMA1, DMA_CHANNEL1);
dma_enable_transfer_complete_interrupt(DMA1, DMA_CHANNEL1);
dma_enable_channel(DMA1, DMA_CHANNEL1);

dmamux_reset_dma_channel(DMAMUX1, DMA_CHANNEL1);
dmamux_set_dma_channel_request(DMAMUX1, DMA_CHANNEL1, DMAMUX_CxCR_DMAREQ_ID_ADC);
}

主要是设置DMA的外设地址为ADC数据寄存器 ADC_DR;并设置内存地址为定义的buff,size为需要缓存的数据大小:

1
2
3
4
#define ADC_CHAN_CNT        4
#define ADC_FILETER_SIZE 32

int16_t adc_values[ADC_FILETER_SIZE*ADC_CHAN_CNT];
2.3 ADC配置为DMA读取和Timer触发
  • 定时器设置
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
void tim3_setup(void)
{
/* Enable TIM3 clock. */
rcc_periph_clock_enable(RCC_TIM3);

/* Enable TIM3 interrupt. */
nvic_enable_irq(NVIC_TIM3_IRQ);

/* Reset TIM3 peripheral to defaults. */
rcc_periph_reset_pulse(RST_TIM3);

/* Timer global mode:
* - No divider
* - Alignment edge
* - Direction up
* (These are actually default values after reset above, so this call
* is strictly unnecessary, but demos the api for alternative settings)
*/
timer_set_mode(TIM3, TIM_CR1_CKD_CK_INT,
TIM_CR1_CMS_EDGE, TIM_CR1_DIR_UP);

/*
* Please take note that the clock source for STM32 timers
* might not be the raw APB1/APB2 clocks. In various conditions they
* are doubled. See the Reference Manual for full details!
* In our case, TIM3 on APB1 is running at double frequency, so this
* sets the prescaler to have the timer run at 5kHz
*/
timer_set_prescaler(TIM3, 64-1);

/* Disable preload. */
timer_disable_preload(TIM3);
timer_continuous_mode(TIM3);

timer_set_period(TIM3, 20000-1); //100Hz

timer_set_master_mode(TIM3, TIM_CR2_MMS_UPDATE);

timer_enable_irq(TIM3, TIM_DIER_UIE);
}

void tim3_enable_counter(bool en)
{
if(en){
timer_enable_counter(TIM3);
}else{
timer_disable_counter(TIM3);
}
}

void tim3_isr(void)
{
if (timer_get_flag(TIM3, TIM_SR_UIF)) {

/* Clear compare interrupt flag. */
timer_clear_flag(TIM3, TIM_SR_UIF);
}
}
  • DMA设置和Timer触发
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
rcc_periph_clock_enable(RCC_DMA);
nvic_set_priority(NVIC_DMA1_CHANNEL1_IRQ, 3);
nvic_enable_irq(NVIC_DMA1_CHANNEL1_IRQ);

adc_setup();

dma_setup(adc_values, ADC_CHAN_CNT*ADC_FILETER_SIZE);

adc_enable_overrun_interrupt(ADC1);

adc_enable_dma(ADC1);

ADC_CFGR1(ADC1) = (ADC_CFGR1(ADC1) & ~(0x3<<10)) | (0x1<<10); // Hardware trigger detection on the rising edge
ADC_CFGR1(ADC1) = (ADC_CFGR1(ADC1) & ~ADC_CFGR1_EXTSEL) | (3<<ADC_CFGR1_EXTSEL_SHIFT); // toggle by tim3

tim3_setup();

adc_start_conversion_regular(ADC1);


tim3_enable_counter(true);

delay_ms(100);

DMA中断时候即准备好读取ADC数据,因此在DMA中断中先把定时器关闭,读取数据后再次打开:

1
2
3
4
5
6
7
8
9
10
void dma1_channel1_isr(void)
{

if ((DMA1_ISR &DMA_ISR_TCIF1) != 0) {
DMA1_IFCR |= DMA_IFCR_CTCIF1;
}

tim3_enable_counter(false);

}
2.4 ADC读取
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
void adc_sample(void)
{
uint32_t sum_val1 = 0;
uint32_t sum_val2 = 0;
uint32_t sum_val3 = 0;
uint32_t sum_val4 = 0;

for(int i=0; i<ADC_FILETER_SIZE; i++){
sum_val1 += adc_values[ADC_CHAN_CNT*i + 0];
sum_val2 += adc_values[ADC_CHAN_CNT*i + 1];
sum_val3 += adc_values[ADC_CHAN_CNT*i + 2];
sum_val4 += adc_values[ADC_CHAN_CNT*i + 3];
}

uint32_t filter_val1 = sum_val1/ADC_FILETER_SIZE;
uint32_t filter_val2 = sum_val2/ADC_FILETER_SIZE;
uint32_t filter_val3 = sum_val3/ADC_FILETER_SIZE;
uint32_t filter_val4 = sum_val4/ADC_FILETER_SIZE;

printf("adc:%d %d %d %d\r\n", filter_val1, filter_val2, filter_val3, filter_val4);

tim3_enable_counter(true);
}

读取时候按照通道的顺序从buff中取出,这里做了简单的过滤;

3 烧写测试

将程序烧写到开发板,然后打开串口可以看到四个ADC通道的数据,在PA0/PA1/PA3/PA4四个引脚连接不同电压可以看到变化: