首先我们先看一下一个标准的ADC框图
ADDA ADDB ADDC设置ADC的转换通道,设置完成后设置ALE进行锁存,此时就确定了ADC的转换通道
IN0 IN1 IN2 IN3 IN4 IN5 IN6 IN7为该ADC模块的8个通道
确定好通道后通过逐次逼近法进行转换,该转换通过二分法进行逐次比较,因此是一个耗时的过程,这也就是后期ADC转换有个固定的转换时间的来源(一般32位单片机为12.5个周期),转换完成后数据会到数据锁存缓冲器进行存储
CLOCK为ADC时钟信号
START为ADC的触发转换信号
EOC为ADC的转换完成信号
下面是一个32位单片机的ADC小结框图
最右边的 GPIO 温度 Vrefint 都是ADC的各个通道,其中GPIO为外部通道,另外两个为内部通道
ADC分为两个组,分别是规则组和注入组。可以将组看成是一个菜单,其中规则组是一个可以写更多序列菜的菜单但是它的数据存储器只有一行也就是厨师一次只有一个盘子(ADC规则组数据寄存器)最后写的那一道菜,也就是说如果使用规则组的菜单一次写了很多菜,但没有及时取出,最后都只能取出最后的那道菜。注入组这个菜单它可以写的菜单相对少一些(一般为最多4个序列,也就是最多一次写4道菜),但是它好在数据存储器与序列数相同也就是厨师看到注入组菜单有多少菜就做多少菜。
接着以AT32F403的ADC框图为例进行分析,其他32位单片机都类似
首先是时钟
APB2时钟经过ADC预分频系数后得到ADC时钟,注意ADC时钟有最大值限制,因此在配置预分频系数时要在ADC限制最大时钟内。
接着是ADC的规则组与注入组
接着是触发源,一般有定时器通道触发,软件触发,外部中断触发
接着就是ADC的各个通道,包括16个外部通道和2个内部通道(内部温度通道和内部参考电压通道)
接着就是ADC的逐次接近法转换器
接下来是转换完成的一些标志位
接着是ADC转换完成的数据存储部分
ADC的转换模式与扫描模式
转换模式有两种一是单次转换也就是接收到一次触发信号后触发一次转换。连续转换就是接收到一次触发信号后一次接一次的进行ADC转换。
这里需要注意的点是单次触发需要后需要等转换完成再取数据,而连续转换则一般初始化阶段就发触发信号,后期读取不需要进行触发在等待转换完成再读取而可以直接读取。
扫描模式是指厨师可设置看菜单里的菜的数量来做菜(但也与盘子(ADC数据寄存器)很相关),而非扫描模式厨师只会看菜单里的一个菜。这一点需要去菜单联系,在规则组菜单可以有多个菜但只有一个盘子(盘子指ADC规则组数据寄存器),也就是说如果使用规则组的多个通道进行填充到规则组菜单那就需要配合DMA来配合,不然由于规则组只有一个盘子,如果没有及时取出盘子中的菜就被后面做的菜覆盖了。而非扫描模式是厨师只看菜单里的第一个菜
数据对齐模式
ADC数据有右对齐和左对齐,一般选择右对齐就可以直接读出准确的ADC值
ADC的采样时间
ADC的采样时间由两部分组成,一部分是用户设置采样周期个数,采样周期越大滤波越大数据越稳定但耗时,相反耗时越短但数据更加可能存在波动。另一部分就是固定的ADC逐次逼近法所需的转换时长一般为12.5个周期。
最后完成配置需要进行一次校准来校准好ADC。
对于普通的ADC转换采用ADC+DMA连续转换是最合适的
uint16_t AD_Value[4];
void AD_Init(void)
{
RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1, ENABLE); /* ADC时钟使能 */
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); /* ADC的通道IO口时钟使能 */
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE); /* DMA使能 */
RCC_ADCCLKConfig(RCC_PCLK2_Div6); /* 预分频系数,注意ADC时钟频率有最大值限制 */
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0 | GPIO_Pin_1 | GPIO_Pin_2 | GPIO_Pin_3;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure); /* ADC通道的IO口配置 */
ADC_RegularChannelConfig(ADC1, ADC_Channel_0, 1, ADC_SampleTime_55Cycles5); /* ADC规则组菜单配置,分别为 ADC1 ADC的通道0 规则组菜单序列1(第一道菜) ADC采样时长为55.5个周期(在算实际时长还要加上12.5个周期的ADC逐次逼近时长) */
ADC_RegularChannelConfig(ADC1, ADC_Channel_1, 2, ADC_SampleTime_55Cycles5);
ADC_RegularChannelConfig(ADC1, ADC_Channel_2, 3, ADC_SampleTime_55Cycles5);
ADC_RegularChannelConfig(ADC1, ADC_Channel_3, 4, ADC_SampleTime_55Cycles5);
ADC_InitTypeDef ADC_InitStructure;
ADC_InitStructure.ADC_Mode = ADC_Mode_Independent; /* ADC独立模式(ADC1与ADC2不联动) */
ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right; /* 数据右对齐 */
ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None; /* 不启用外部触发模式也就是启用软件触发 */
ADC_InitStructure.ADC_ContinuousConvMode = ENABLE; /* 循环模式使能 */
ADC_InitStructure.ADC_ScanConvMode = ENABLE; /* 扫描模式使能 */
ADC_InitStructure.ADC_NbrOfChannel = 4; /* 扫描模式的扫描序列数(非扫描模式为1) */
ADC_Init(ADC1, &ADC_InitStructure); /* ADC配置 */
DMA_InitTypeDef DMA_InitStructure;
DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)&ADC1->DR; /* 外设地址为ADC数据寄存器 */
DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord; /* 外设数据长度16bit */
DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable; /* 外设地址递增关闭 */
DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)AD_Value; /* 内存地址 */
DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord; /* 内存数据长度 */
DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable; /* 内存地址递增开启 */
DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC; /* 外设作为源 外设到地址方向传输 */
DMA_InitStructure.DMA_BufferSize = 4; /* 缓冲器长度 */
DMA_InitStructure.DMA_Mode = DMA_Mode_Circular; /* DMA循环模式 */
DMA_InitStructure.DMA_M2M = DMA_M2M_Disable; /* DMA为硬件触发(外设触发) */
DMA_InitStructure.DMA_Priority = DMA_Priority_Medium; /* DMA优先级 */
DMA_Init(DMA1_Channel1, &DMA_InitStructure); /* DMA配置 */
DMA_Cmd(DMA1_Channel1, ENABLE); /* DMA1通道1使能 */
ADC_DMACmd(ADC1, ENABLE); /* ADC的DMA使能 */
ADC_Cmd(ADC1, ENABLE); /* ADC1使能 */
ADC_ResetCalibration(ADC1); /* ADC复位校准 */
while (ADC_GetResetCalibrationStatus(ADC1) == SET); /* 等待ADC复位校准完成 */
ADC_StartCalibration(ADC1); /* ADC启动校准 */
while (ADC_GetCalibrationStatus(ADC1) == SET); /* 等待ADC启动校准完成 */
ADC_SoftwareStartConvCmd(ADC1, ENABLE); /* 软件触发ADC1 */
}
而对于FOC控制而言,常常采用定时器触发采样加上注入组采样完成中断的方式,并且由于对采样实时性的要求一般采用注入组直接采三相电流
参考代码如下
#define ORDINARY_COUNT 2
static uint16_t adc_buffer[ORDINARY_COUNT] = {0}; /* 规则组数据缓冲区 */
void drv_adc_init(void)
{
adc_base_config_type adc_base_struct;
gpio_init_type gpio_initstructure;
dma_init_type dma_init_struct;
crm_periph_clock_enable(CRM_GPIOA_PERIPH_CLOCK, TRUE); /* 开启GPIO时钟 */
crm_periph_clock_enable(CRM_GPIOB_PERIPH_CLOCK, TRUE);
crm_periph_clock_enable(CRM_GPIOC_PERIPH_CLOCK, TRUE);
crm_periph_clock_enable(CRM_ADC1_PERIPH_CLOCK, TRUE); /* 开启ADC1时钟 */
crm_periph_clock_enable(CRM_DMA1_PERIPH_CLOCK, TRUE); /* 开启DMA1时钟 */
crm_adc_clock_div_set(CRM_ADC_DIV_6); /* ADC时钟来自APB2,且要求ADC时钟最大为28MHZ,当前APB2时钟为120MHZ,6分频得到ADC时钟频率为20MHZ */
gpio_default_para_init(&gpio_initstructure); /* ADC的IO口初始化 */
gpio_initstructure.gpio_mode = GPIO_MODE_ANALOG;
gpio_initstructure.gpio_pins = GPIO_PINS_6|GPIO_PINS_7;
gpio_init(GPIOA, &gpio_initstructure);
gpio_default_para_init(&gpio_initstructure);
gpio_initstructure.gpio_mode = GPIO_MODE_ANALOG;
gpio_initstructure.gpio_pins = GPIO_PINS_0;
gpio_init(GPIOB, &gpio_initstructure);
gpio_default_para_init(&gpio_initstructure);
gpio_initstructure.gpio_mode = GPIO_MODE_ANALOG;
gpio_initstructure.gpio_pins = GPIO_PINS_1|GPIO_PINS_4;
gpio_init(GPIOC, &gpio_initstructure);
adc_combine_mode_select(ADC_INDEPENDENT_MODE); /* 选择ADC组合模式为独立模式(有时由于引脚限制也会采样注入组同步模式,该模式可以在ADC1的注入组收到触发信号后让ADC2的注入组也同步转换) */
adc_ordinary_channel_set(ADC1, ADC_CHANNEL_8, 1, ADC_SAMPLETIME_239_5); /* 规则组通道设置,ADC1的通道8设置为规则组序列1,采样周期为239.5个采样周期(算真实采样周期需要再加上12.5个逐次逼近法所需要的周期) */
adc_ordinary_channel_set(ADC1, ADC_CHANNEL_11, 2, ADC_SAMPLETIME_239_5); /* 规则组通道设置,ADC1的通道11设置为规则组序列2,采样周期为239.5个采样周期(算真实采样周期需要再加上12.5个逐次逼近法所需要的周期) */
adc_base_default_para_init(&adc_base_struct); /* ADC基本结构体填入默认参数 */
adc_base_struct.sequence_mode = TRUE; /* 开启扫描模式 */
adc_base_struct.repeat_mode = TRUE; /* 开启重复模式 */
adc_base_struct.data_align = ADC_RIGHT_ALIGNMENT; /* 数据右对齐 */
adc_base_struct.ordinary_channel_length = 2; /* 规则组序列长度为2 */
adc_base_config(ADC1, &adc_base_struct); /* ADC1基本参数初始化 */
adc_ordinary_conversion_trigger_set(ADC1, ADC12_ORDINARY_TRIG_SOFTWARE, TRUE); /* ADC1规则组设置为软件触发 */
adc_dma_mode_enable(ADC1, TRUE); /* ADC1开启DMA模式 */
dma_reset(DMA1_CHANNEL1); /* 复位DMA1通道1 */
dma_default_para_init(&dma_init_struct); /* DMA结构体填入默认参数 */
dma_init_struct.buffer_size = ORDINARY_COUNT; /* 缓存区大小 */
dma_init_struct.direction = DMA_DIR_PERIPHERAL_TO_MEMORY; /* 传输方向为外设到内存 */
dma_init_struct.memory_base_addr = (uint32_t)adc_buffer; /* 内存地址 */
dma_init_struct.memory_data_width = DMA_MEMORY_DATA_WIDTH_HALFWORD; /* 数据宽度为16位 */
dma_init_struct.memory_inc_enable = TRUE; /* 内存递增开启 */
dma_init_struct.peripheral_base_addr = (uint32_t)&(ADC1->odt); /* 外设地址为ADC1的规则组数据寄存器 */
dma_init_struct.peripheral_data_width = DMA_PERIPHERAL_DATA_WIDTH_HALFWORD; /* 数据宽度为16位 */
dma_init_struct.peripheral_inc_enable = FALSE; /* 外设地址递增关闭 */
dma_init_struct.priority = DMA_PRIORITY_HIGH; /* 高优先级 */
dma_init_struct.loop_mode_enable = TRUE; /* 循环模式开启 */
dma_init(DMA1_CHANNEL1, &dma_init_struct); /* DMA1通道1初始化 */
dma_flexible_config(DMA1, FLEX_CHANNEL1, DMA_FLEXIBLE_ADC1); /* DMA1通道1用于ADC1传输 */
dma_channel_enable(DMA1_CHANNEL1, TRUE); /* DMA1通道1开启 */
adc_preempt_channel_length_set(ADC1, 3); /* 注入组序列长度为3 */
adc_preempt_channel_set(ADC1, ADC_CHANNEL_6, 1, ADC_SAMPLETIME_1_5); /* ADC1的通道6设为注入组序列为1,采样周期为1.5个周期 */
adc_preempt_channel_set(ADC1, ADC_CHANNEL_7, 2, ADC_SAMPLETIME_1_5); /* ADC1的通道7设为注入组序列为2,采样周期为1.5个周期 */
adc_preempt_channel_set(ADC1, ADC_CHANNEL_14, 3, ADC_SAMPLETIME_1_5); /* ADC1的通道14设为注入组序列为3,采样周期为1.5个周期 */
adc_preempt_auto_mode_enable(ADC1, FALSE); /* ADC1注入组自动模式关闭 */
adc_preempt_conversion_trigger_set(ADC1, ADC12_PREEMPT_TRIG_TMR1CH4, TRUE); /* ADC1注入组设为定时器1通道4触发 */
adc_enable(ADC1, TRUE); /* ADC1开启 */
adc_calibration_init(ADC1); /* ADC1初始化校准 */
while(adc_calibration_init_status_get(ADC1)); /* 等待ADC1初始化校准完成 */
adc_calibration_start(ADC1); /* ADC1启动校准 */
while(adc_calibration_status_get(ADC1)); /* 等待ADC1启动校准完成 */
nvic_irq_enable(ADC1_2_IRQn,0,0); /* 开启ADC中断,ADC中断主优先级与子优先级都设为0 */
adc_interrupt_enable(ADC1, ADC_PCCE_INT, TRUE); /* 开启ADC1注入组转换完成中断 */
adc_ordinary_software_trigger_enable(ADC1, TRUE); /* 发送ADC1规则组软件触发信号 */
}
uint16_t adc_data_dma_buffer_get(char seq)
{
return adc_buffer[seq < 0 ? 0 : (seq > (ORDINARY_COUNT-1) ? (ORDINARY_COUNT-1) : seq)]; /* 获取ADC1规则组的数据 */
}
void adc_data_get(uint16_t *adc_data)
{
adc_data[0] = adc_preempt_conversion_data_get(ADC1, ADC_PREEMPT_CHANNEL_1); /* 获取ADC1注入组序列1的数据 */
adc_data[1] = adc_preempt_conversion_data_get(ADC1, ADC_PREEMPT_CHANNEL_2); /* 获取ADC1注入组序列2的数据 */
adc_data[2] = adc_preempt_conversion_data_get(ADC1, ADC_PREEMPT_CHANNEL_3); /* 获取ADC1注入组序列3的数据 */
}
void ADC1_2_IRQHandler(void)
{
if (adc_interrupt_flag_get(ADC1, ADC_PCCE_FLAG) != RESET) {
adc_flag_clear(ADC1, ADC_PCCE_FLAG);
esc_controler();
}
}
最后补充的点是:
1.在由于引脚限制时常常会采用ADC的注入组同步模式,这个模式下ADC1作为主机ADC2作为从机,当ADC1的注入组收到定时器的触发信号时,就会同步给ADC2的注入组使得ADC1和ADC2的注入组同步进行数据转换
2.上面有提到通过定时器来触发,需要注入PWM那边的触发信号不能太快,常用PWM的频率为16KHZ~25KHZ,主要取决于MCU的性能