忙完了比赛,我还是好好继续来研究这个平衡车。上次更新到OLED的学习。有了显示,我们就可以进行更多的操作了。OLED的显示,能方便我们了解更多的内容!比如,采集的电压值,小车编码器的值都能清楚明了的显示在小车上,就不需要再接串口将数据传输到电脑上观察。
先来了解了解STM32F103C8T6的ADC。
STM32 的 ADC 是 12 位逐次逼近型的模拟数字转换器。它有18个通道,可测量16个外部和2个内部信号源,ADC_IN16为内部温度采集。各通道的 A/D 转换可以单次、连续、扫描或间断模式执行。 ADC 的结果可以左对齐或右对齐方式存储在 16 位数据寄存器中。
这个小车所使用的32芯片有2个ADC。STM32 的 ADC 最大的转换速率为 1Mhz,也就是转换时间为 1us(在 ADCCLK=14M,采样周期为1.5个 ADC 时钟下得到),不要让 ADC 的时钟超过 14M,否则将导致结果准确度下降。
根据原理图,
我们可以知道,对应的电压值采集的STM32引脚为PA4。所以采用ADC1的通道4。
那么在配置ADC的初始化时候,就需要配置GPIOA_4。
还是常规要求,在使用32的某个功能时,需要开启某部分的时钟,这里就需要开启ADC时钟,和GPIOA的时钟
第一步:使能GPIOA和ADC1通道时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA
| RCC_APB2Periph_ADC1 , ENABLE ); 接下来初始化ADC引脚。
GPIO_InitStructure.GPIO_Pin =
GPIO_Pin_4;
GPIO_InitStructure.GPIO_Mode =
GPIO_Mode_AIN; //输入模拟信号:模式为模拟输入
GPIO_Init(GPIOA,&GPIO_InitStructure);
第二步;复位ADC1,设置分频因子
由于使用的库函数,需要将
stm32f10x_adc.c这个文件添加进工程,
接下来就可以直接调用函数
ADC_DeInit(ADC1); //复位ADC1,将外设 ADC1 的全部寄存器重设为缺省值
RCC_ADCCLKConfig(RCC_PCLK2_Div6); //设置ADC分频因子6
72M/6=12, ADC最大时间不能超过14M
第三步,初始化ADC1参数
ADC_InitStructure.ADC_Mode = ADC_Mode_Independent; //独立工作模式
ADC_InitStructure.ADC_ScanConvMode = DISABLE; //单通道模式
ADC_InitStructure.ADC_ContinuousConvMode = DISABLE; //单次转换模式
ADC_InitStructure.ADC_ExternalTrigConv =
ADC_ExternalTrigConv_None; //软件而不是外部触发启动
ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right; //ADC数据右对齐
ADC_InitStructure.ADC_NbrOfChannel = 1; //顺序进行规则转换的ADC通道的数目
ADC_Init(ADC1,&ADC_InitStructure); //根据ADC_InitStruct中指定的参数初始化外设ADCx的寄存器
第四步:使能ADC并校准
在完成上面的操作就可以使能和校准ADC。为了得到精准的AD转换结果,都必须要校准ADC。但是要注意的是,每次进行校准之后要等待校准结束。这里是通过获取校准状态来判断是否校准是否结束。
下面是ADC使能和校准的方法:
ADC_Cmd(ADC1, ENABLE); //使能指定的ADC1
ADC_ResetCalibration(ADC1); //使能复位校准
while(ADC_GetResetCalibrationStatus(ADC1)); //等待复位校准结束
ADC_StartCalibration(ADC1); //开启AD校准
while(ADC_GetCalibrationStatus(ADC1)); //等待校准结束
第五步:读取AD转换值
上面校准完成之后,就可以设置规则序列 1 里面的通道,采样顺序,以及通道的采样周期,然后启动 ADC 转换。在转换结束后,读取 ADC 转换结果值就是了。
设置规则序列通道以及采样周期的函数:
ADC_RegularChannelConfig(ADC1, ch, 1, ADC_SampleTime_239Cycles5
); //ADC1,ADC通道,序列1,采样时间为239.5周期
设置软件开启 ADC 转换的方法是:
ADC_SoftwareStartConvCmd(ADC1, ENABLE); //使能指定的ADC1的软件转换启动功能
我们要判断 ADC1的转换是否结束。所以需要 进行以下操作:
while(!ADC_GetFlagStatus(ADC1, ADC_FLAG_EOC )); //等待转换结束
最后再获取ADC转换结果
ADC_GetConversionValue(ADC1); //返回最近一次ADC1规则组的转换结果。
这里可以将获取ADC值得程序编写为一个子程序
/*************************************************************************
函数功能:获得ADC值
入口参数:ADC1 的通道
返回 值:AD转换结果
*************************************************************************/
u16 Get_Adc(u8 ch)
{
ADC_RegularChannelConfig(ADC1,
ch, 1, ADC_SampleTime_239Cycles5 );
ADC_SoftwareStartConvCmd(ADC1,
ENABLE);
while(!ADC_GetFlagStatus(ADC1,
ADC_FLAG_EOC ));
return
ADC_GetConversionValue(ADC1);
}
在实际的程序中,一般都是多次采集AD值,然后取平均值,这样能得到较为准确的值。
所以我编写下面的函数来实现
/*************************************************************************
函数功能:获得多次ADC平均值
入口参数:ADC1 的通道,采样次数
返回 值:AD转换结果
*************************************************************************/
u16 Get_Adc_Average(u8 ch,u8 times)
{
u32
temp_val=0;
u8 t;
for(t=0;t
{
temp_val+=Get_Adc(ch);
delay_ms(5);
}
return
temp_val/times;
}
通过这种方式获取ADC的值为0-4096;我们还需要进一步转换,才能得到电压值。由于STM32没有可设置的外部电压,所以使用内部3.3V为参考电压,需要采集的电压值大于3.3v,所以在硬件设计将设置AD采样电阻,查看原理图,可以看到,最终AD采集的电压为R2上面的电压。
下面是读取电压值的子程序
/*************************************************************************
函数功能:读取电池电压
入口参数:无
返回 值:无
*************************************************************************/
void Get_battery_volt(void)
{
Voltage=Get_Adc(4)*3.3*11.5*100/1.5/4096;
}
这里多乘了100是方便小数部分的显示。