使用Platformio平台的libopencm3开发框架来开发STM32G0,以下为多通道ADC与DMA的使用。
1 新建项目
建立adc_dma项目
在PIO的Home页面新建项目,项目名称adc_dma,选择开发板为 MonkeyPi_STM32_G070RB,开发框架选择libopencm3;
项目建立完成后在src目录下新建main.c主程序文件;
修改下载和调试方式,这里开发板使用的是DAPLink仿真器,因此修改platformio.ini文件如下:
1upload_protocol = cmsis-dap
2debug_tool = cmsis-dap
2 编写程序
2.1 ADC 设置
这里设置PA0、PA1、PA2、PA3四个引脚为ADC:
1static void adc_setup(void)
2{
3 rcc_periph_clock_enable(RCC_ADC);
4 rcc_periph_clock_enable(RCC_GPIOA);
5
6 gpio_mode_setup(GPIOA, GPIO_MODE_ANALOG, GPIO_PUPD_NONE, GPIO0);
7 gpio_mode_setup(GPIOA, GPIO_MODE_ANALOG, GPIO_PUPD_NONE, GPIO1);
8 gpio_mode_setup(GPIOA, GPIO_MODE_ANALOG, GPIO_PUPD_NONE, GPIO2);
9 gpio_mode_setup(GPIOA, GPIO_MODE_ANALOG, GPIO_PUPD_NONE, GPIO3);
10
11 adc_power_off(ADC1);
12 adc_set_clk_prescale(ADC1, ADC_CCR_PRESC_DIV2);
13 adc_set_single_conversion_mode(ADC1);
14 adc_set_right_aligned(ADC1);
15 adc_set_sample_time_on_all_channels(ADC1, ADC_SMPTIME_160DOT5);
16
17 uint8_t channel_array[16] = {0};
18 channel_array[0] = 0;
19 channel_array[1] = 1;
20 channel_array[2] = 2;
21 channel_array[3] = 3;
22 adc_set_regular_sequence(ADC1, ADC_CHAN_CNT, channel_array);
23 adc_enable_dma_circular_mode(ADC1);
24 adc_set_resolution(ADC1, ADC_CFGR1_RES_12_BIT);
25 adc_power_on(ADC1);
26
27 /* Wait for ADC starting up. */
28 delay_ms(10);
29}
2.2 DMA配置
1static void dma_setup(void *data, int size)
2{
3 dma_channel_reset(DMA1, DMA_CHANNEL1);
4 dma_set_peripheral_address(DMA1, DMA_CHANNEL1, (uint32_t)&ADC_DR(ADC1));
5 dma_set_memory_address(DMA1, DMA_CHANNEL1, (uint32_t)data);
6 dma_set_number_of_data(DMA1, DMA_CHANNEL1, size);
7 dma_set_read_from_peripheral(DMA1, DMA_CHANNEL1);
8 dma_enable_memory_increment_mode(DMA1, DMA_CHANNEL1);
9 dma_set_peripheral_size(DMA1, DMA_CHANNEL1, DMA_CCR_PSIZE_16BIT);
10 dma_set_memory_size(DMA1, DMA_CHANNEL1, DMA_CCR_MSIZE_16BIT);
11 dma_enable_circular_mode(DMA1, DMA_CHANNEL1);
12 dma_enable_transfer_complete_interrupt(DMA1, DMA_CHANNEL1);
13 dma_enable_channel(DMA1, DMA_CHANNEL1);
14
15 dmamux_reset_dma_channel(DMAMUX1, DMA_CHANNEL1);
16 dmamux_set_dma_channel_request(DMAMUX1, DMA_CHANNEL1, DMAMUX_CxCR_DMAREQ_ID_ADC);
17}
主要是设置DMA的外设地址为ADC数据寄存器 ADC_DR;并设置内存地址为定义的buff,size为需要缓存的数据大小:
1#define ADC_CHAN_CNT 4
2#define ADC_FILETER_SIZE 32
3
4int16_t adc_values[ADC_FILETER_SIZE*ADC_CHAN_CNT];
2.3 ADC配置为DMA读取和Timer触发
定时器设置
1void tim3_setup(void)
2{
3 /* Enable TIM3 clock. */
4 rcc_periph_clock_enable(RCC_TIM3);
5
6 /* Enable TIM3 interrupt. */
7 nvic_enable_irq(NVIC_TIM3_IRQ);
8
9 /* Reset TIM3 peripheral to defaults. */
10 rcc_periph_reset_pulse(RST_TIM3);
11
12 /* Timer global mode:
13 * - No divider
14 * - Alignment edge
15 * - Direction up
16 * (These are actually default values after reset above, so this call
17 * is strictly unnecessary, but demos the api for alternative settings)
18 */
19 timer_set_mode(TIM3, TIM_CR1_CKD_CK_INT,
20 TIM_CR1_CMS_EDGE, TIM_CR1_DIR_UP);
21
22 /*
23 * Please take note that the clock source for STM32 timers
24 * might not be the raw APB1/APB2 clocks. In various conditions they
25 * are doubled. See the Reference Manual for full details!
26 * In our case, TIM3 on APB1 is running at double frequency, so this
27 * sets the prescaler to have the timer run at 5kHz
28 */
29 timer_set_prescaler(TIM3, 64-1);
30
31 /* Disable preload. */
32 timer_disable_preload(TIM3);
33 timer_continuous_mode(TIM3);
34
35 timer_set_period(TIM3, 20000-1); //100Hz
36
37 timer_set_master_mode(TIM3, TIM_CR2_MMS_UPDATE);
38
39 timer_enable_irq(TIM3, TIM_DIER_UIE);
40}
41
42void tim3_enable_counter(bool en)
43{
44 if(en){
45 timer_enable_counter(TIM3);
46 }else{
47 timer_disable_counter(TIM3);
48 }
49}
50
51void tim3_isr(void)
52{
53 if (timer_get_flag(TIM3, TIM_SR_UIF)) {
54
55 /* Clear compare interrupt flag. */
56 timer_clear_flag(TIM3, TIM_SR_UIF);
57 }
58}
DMA设置和Timer触发
1 rcc_periph_clock_enable(RCC_DMA);
2 nvic_set_priority(NVIC_DMA1_CHANNEL1_IRQ, 3);
3 nvic_enable_irq(NVIC_DMA1_CHANNEL1_IRQ);
4
5 adc_setup();
6
7 dma_setup(adc_values, ADC_CHAN_CNT*ADC_FILETER_SIZE);
8
9 adc_enable_overrun_interrupt(ADC1);
10
11 adc_enable_dma(ADC1);
12
13 ADC_CFGR1(ADC1) = (ADC_CFGR1(ADC1) & ~(0x3<<10)) | (0x1<<10); // Hardware trigger detection on the rising edge
14 ADC_CFGR1(ADC1) = (ADC_CFGR1(ADC1) & ~ADC_CFGR1_EXTSEL) | (3<1_EXTSEL_SHIFT); // toggle by tim3
15
16 tim3_setup();
17
18 adc_start_conversion_regular(ADC1);
19
20
21 tim3_enable_counter(true);
22
23 delay_ms(100);
DMA中断时候即准备好读取ADC数据,因此在DMA中断中先把定时器关闭,读取数据后再次打开:
1void dma1_channel1_isr(void)
2{
3
4 if ((DMA1_ISR &DMA_ISR_TCIF1) != 0) {
5 DMA1_IFCR |= DMA_IFCR_CTCIF1;
6 }
7
8 tim3_enable_counter(false);
9
10}
2.4 ADC读取
1void adc_sample(void)
2{
3 uint32_t sum_val1 = 0;
4 uint32_t sum_val2 = 0;
5 uint32_t sum_val3 = 0;
6 uint32_t sum_val4 = 0;
7
8 for(int i=0; i9 sum_val1 += adc_values[ADC_CHAN_CNT*i + 0];
10 sum_val2 += adc_values[ADC_CHAN_CNT*i + 1];
11 sum_val3 += adc_values[ADC_CHAN_CNT*i + 2];
12 sum_val4 += adc_values[ADC_CHAN_CNT*i + 3];
13 }
14
15 uint32_t filter_val1 = sum_val1/ADC_FILETER_SIZE;
16 uint32_t filter_val2 = sum_val2/ADC_FILETER_SIZE;
17 uint32_t filter_val3 = sum_val3/ADC_FILETER_SIZE;
18 uint32_t filter_val4 = sum_val4/ADC_FILETER_SIZE;
19
20 printf("adc:%d %d %d %d ", filter_val1, filter_val2, filter_val3, filter_val4);
21
22 tim3_enable_counter(true);
23}
读取时候按照通道的顺序从buff中取出,这里做了简单的过滤;
3 烧写测试
将程序烧写到开发板,然后打开串口可以看到四个ADC通道的数据,在PA0/PA1/PA3/PA4四个引脚连接不同电压可以看到变化: