使用Platformio平台的libopencm3开发框架来开发STM32G0,以下为多通道ADC与DMA的使用。
1 新建项目在PIO的Home页面新建项目,项目名称adc_dma,选择开发板为 MonkeyPi_STM32_G070RB,开发框架选择libopencm3; - 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}
复制代码
- 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<<ADC_CFGR1_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; i<ADC_FILETER_SIZE; i++){
- 9 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\r\n", filter_val1, filter_val2, filter_val3, filter_val4);
- 21
- 22 tim3_enable_counter(true);
- 23}
复制代码
读取时候按照通道的顺序从buff中取出,这里做了简单的过滤;
3 烧写测试将程序烧写到开发板,然后打开串口可以看到四个ADC通道的数据,在PA0/PA1/PA3/PA4四个引脚连接不同电压可以看到变化:
|