循着上一次的设置,我们需要完成ADC的端口的初始化和调试准备。
修改外设配置函数
添加ADC的初始化函数,基本步骤完成GPIO的设置,完成ADC的排序器设计(之后实现),进行ADC使能和adc的校准。
引脚编号
引脚名称
端口分类
引脚复用
复用分类
说明
6
PA0-WKUP
I/O
ADC_IN0
ADC
*I DC bus
7
PA1
I/O
ADC_IN1
ADC
*Uu
8
PA2
I/O
ADC_IN2
ADC
Uv
9
PA3
I/O
ADC_IN3
ADC
Uw
10
PA4
I/O
ADC_IN4
ADC
Iu
11
PA5
I/O
ADC_IN5
ADC
Iv
12
PA6
I/O
ADC_IN6
ADC
Iw
备注:目前仅考虑电流采样,因为电压采样接口存在冲突。
修改之后的测试用外设配置函数为:
#define _ADC_TEST
uint16_t adc_i[3]; // ADC result for current.
dma_parameter_struct dma_adc;
void peripheral_init()
{
// RCU:
rcu_periph_clock_enable(RCU_GPIOA); // GPIO A
rcu_periph_clock_enable(RCU_CFGCMP); // CFGCMP clock
rcu_periph_clock_enable(RCU_ADC); // ADC
rcu_adc_clock_config(RCU_ADCCK_APB2_DIV6);
rcu_periph_clock_enable(RCU_DMA); // DMA
// System tick
systick_config();
// GPIO config
gpio_mode_set(GPIOA, GPIO_MODE_INPUT, GPIO_PUPD_PULLUP, GPIO_PIN_0); // Input Key
gpio_mode_set(GPIOA, GPIO_MODE_OUTPUT, GPIO_PUPD_NONE, GPIO_PIN_1); // Output Key
gpio_output_options_set(GPIOA, GPIO_OTYPE_PP, GPIO_OSPEED_MAX, GPIO_PIN_1);
gpio_bit_reset(GPIOA, GPIO_PIN_1);
// NVIC - EXTI
nvic_irq_enable(EXTI0_1_IRQn, 2U, 0U); // GPIO A0 -> EXTI 0
syscfg_exti_line_config(EXTI_SOURCE_GPIOA, EXTI_SOURCE_PIN0); // GPIO A0 Interrupt enable
exti_init(EXTI_0, EXTI_INTERRUPT, EXTI_TRIG_FALLING);
exti_interrupt_flag_clear(EXTI_0);
// ADC GPIO
gpio_mode_set(GPIOA,GPIO_MODE_ANALOG,GPIO_PUPD_NONE,GPIO_PIN_4); // Iu
gpio_mode_set(GPIOA,GPIO_MODE_ANALOG,GPIO_PUPD_NONE,GPIO_PIN_5); // Iv
gpio_mode_set(GPIOA,GPIO_MODE_ANALOG,GPIO_PUPD_NONE,GPIO_PIN_6); // Iw
// ADC channel config
adc_channel_length_config(ADC_REGULAR_CHANNEL, 3);
adc_regular_channel_config(0,ADC_CHANNEL_4,ADC_SAMPLETIME_1POINT5); // 1.5 cycle
adc_regular_channel_config(1,ADC_CHANNEL_5,ADC_SAMPLETIME_1POINT5); // 1.5
adc_regular_channel_config(2,ADC_CHANNEL_6,ADC_SAMPLETIME_1POINT5); // 1.5
adc_data_alignment_config(ADC_DATAALIGN_RIGHT); // Right align
adc_resolution_config(ADC_RESOLUTION_12B);
adc_special_function_config(ADC_SCAN_MODE,ENABLE); // Scan mode
adc_dma_mode_enable(); // Setup dma mode
adc_external_trigger_config(ADC_REGULAR_CHANNEL, ENABLE);
#ifdef _ADC_TEST
// Case I: for now, the timer has not setup. So just test ADC
adc_external_trigger_source_config(ADC_REGULAR_CHANNEL,ADC_EXTTRIG_REGULAR_NONE); // software trigger FOR DEBUG
#else
// Case II: If Timer has been setup
adc_external_trigger_source_config(ADC_REGULAR_CHANNEL,ADC_EXTTRIG_REGULAR_T1_CH1);
// NOTE: Three channel of Timer0 is setup for generate PWM, so T1 may be a reasonable solution.
// This part may be changed, during debugging process.
#endif
// DMA for ADC
dma_struct_para_init(&dma_adc);
dma_adc.periph_addr = ADC_BASE;
dma_adc.periph_width = DMA_PERIPHERAL_WIDTH_16BIT; // 2 byte(s)
dma_adc.memory_addr = (uint32_t)adc_i;
dma_adc.memory_width = DMA_MEMORY_WIDTH_16BIT;
dma_adc.memory_inc = (uint8_t)DMA_MEMORY_INCREASE_ENABLE;
dma_adc.number = 3;
dma_adc.priority = (uint32_t)DMA_PRIORITY_HIGH;
dma_circulation_enable(DMA_CH0); // ADC DMA setup
dma_init(DMA_CH0, &dma_adc); // inti DMA for ADC(1)
dma_periph_address_config(DMA_CH0,ADC_BASE);
dma_transfer_direction_config(DMA_CH0,DMA_PERIPHERAL_TO_MEMORY);
dma_channel_enable(DMA_CH0);
// ADC setup
adc_enable();
delay_1ms(10);
adc_calibration_enable(); // calibration
#ifdef _ADC_TEST
// This only for ADC debug.
adc_software_trigger_enable(ADC_REGULAR_CHANNEL); // First time read ADC
#endif
}
采样方法
为了实现采样,我们应当尝试扫描转换模式,在引脚定义章节我们已经计划需要读取7个通道的ADC信息。在数据手册11.4.5节介绍的“扫描转换模式”,其驱动时序如下图所示:
理论上电机驱动过程中应当关注采样的时间点,在11.4节给出了ADC的模块框图如下:
在10.4.10节给出了触发信号的规则:
规则通道的触发
ETSRC[2:0]
触发源
000
TIMER0_CH0
001
TIMER0_CH1
010
TIMER0_CH2
011
TIMER1_CH1
100
TIMER2_TRGO
101
TIMER14_CH0
110
EXTI_11(外部通道输入)
111
SWRCST(软件触发)
注入通道的触发
ETSIC[2:0]
触发源
000
TIMER0_TRGO
001
TIMER0_CH3
010
TIMER1_TRGO
011
TIMER1_CH0
100
TIMER2_CH3
101
TIMER14_TRGO
110
EXTI_15(外部通道输入)
111
SWRCST(软件触发)
对于芯片ADC的使用有如下注意事项,需要注意,否则可能导致ADC并未正常启动:
时钟使能(ADC,ADC总线时钟分频器,DMA时钟)
ADC需要用到的GPIO通道复用
配置排序器(rank)
配置数据长度和数据对齐方式
启用扫描模式和DMA
允许外部触发,并选择外部触发源(为了测试通常选用软件触发源)
配置ADC的DMA通道,我们此处使用了通道0,注意外设地址是ADC_BASE
而不是要读取的目标位置RDATA
启用ADC并进行ADC校准
通过软件源触发ADC转换(测试用)
这一部分的代码已经从上面的初始化代码中得到了体现。
实际应用场景
在ADC的实际应用中,实际上是为了采集电机的反电动势和电机的电流,它们配置的关键如下:
对于电流采样为了规避开关信号的冲击,应当在下溢中断发生的时候开始转换。而Timer0通常设置为中间对齐模式,下溢出中断可以通过TIMER0_TRGO
来选择。找张网图来说明一下位置关系。
电压采样应当在三相桥关断的情况下进行,此时可以读取到电机的反电动势,但是此时可以暂不做考虑,无论是在实际电机的驱动中,或者是在仿真中,由于电压是由发生器(脉冲)给定的,所以可以直接用调制值来代替采样值。目前局限于时间问题,考虑对于直流电机的驱动,暂且也不考虑。
ADC测试代码中可以使用软件触发来实现ADC的读取,可以通过如下测试代码实现:
while(adc_flag_get(ADC_FLAG_EOC) != SET); // pending until convertion cmpt.
while(dma_flag_get(DMA_CH0,DMA_FLAG_FTF) != SET); // pending until DMA transfer cmpt.
// read ADC result here.
delay_1ms(1); // break point here.
#ifdef _ADC_TEST
// This only for ADC debug.
adc_software_trigger_enable(ADC_REGULAR_CHANNEL);
#endif
将这一段代码加入到项目中即可实现ADC测试效果。上图中标记出了可以通过全局变量监测的方式读取ADC的位置。
总结,GD32的手感总体还是相当棒的,由于之前被ST养出了懒病还没有改好,导致实际的调试过程异常痛苦。时长出现忘记使能时钟、忘记打开标志位的情况,不过后来简单看了看上古时期自己的笔记,加上慢慢熟悉整个系统之后,调试还是比较容易的。