淘宝上买了一块STM32F103C8T6开发板,调试的时候发现是国产山寨版,更换了芯片包后正常运行,但是最近调试ADC的时候发现怎么都不出结果,网上找了一堆解决方案,都不行,网上得来终觉浅,绝知此事要躬行。自己研究自己学,干。。
目录
1资料收集
stm32f1x系列芯片中文参考手册见: https://download.csdn.net/download/weixin_51248645/87704176
1.1STM32F103C8T6闪存大小?
64K的,属于中容量产品。
1.2ADC转换时间
1.3ADC转换框图
1.4引脚连接
(STM32F103C8T6不用关注,已经连好的)
1.5通道选择
1.6转换时序图
1.7DMA设置
1.8ADC通道
// STM32F103C8T6 的ADC1 通道有10路,分别是:
// 10 脚PA0 ADC12_IN0
// 11 脚PA1 ADC12_IN1
// 12 脚PA2 ADC12_IN2
// 13 脚PA3 ADC12_IN3
// 14 脚PA4 ADC12_IN4
// 15 脚PA5 ADC12_IN5
// 16 脚PA6 ADC12_IN6
// 17 脚PA7 ADC12_IN7
// 18 脚PA8 ADC12_IN8
// 19 脚PA9 ADC12_IN9
// ADC12 意为可配成ADC1或ADC2,不是有ADC12
2心态搞崩
。。。。。经过实验测试,TMD国产山寨货实物是可以模数转换的,但是仿真时proteus识别不了,所以一直是0,暂时没找到解决办法。
3ADC采集
直接采用ADC通道采集,然后将采集结果获取到即可。代码如下。
void ADCx_Init()
{
GPIO_InitTypeDef GPIO_InitStructure;
ADC_InitTypeDef ADC_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA|RCC_APB2Periph_ADC1,ENABLE);
RCC_ADCCLKConfig(RCC_PCLK2_Div6);
GPIO_InitStructure.GPIO_Pin=GPIO_Pin_6;
GPIO_InitStructure.GPIO_Mode=GPIO_Mode_AIN;
GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;
GPIO_Init(GPIOA,&GPIO_InitStructure);
ADC_InitStructure.ADC_Mode=ADC_Mode_Independent;
ADC_InitStructure.ADC_ScanConvMode=DISABLE;
ADC_InitStructure.ADC_ContinuousConvMode=DISABLE;
ADC_InitStructure.ADC_ExternalTrigConv=ADC_ExternalTrigConv_T2_CC2;
//ADC_InitStructure.ADC_ExternalTrigConv=ADC_ExternalTrigConv_None;
ADC_InitStructure.ADC_DataAlign=ADC_DataAlign_Right;
ADC_InitStructure.ADC_NbrOfChannel=1;
ADC_Init(ADC1,&ADC_InitStructure);
ADC_Cmd(ADC1,ENABLE);
//ADC_DMACmd(ADC1, ENABLE); //ADCµÄDMA¹¦ÄÜʹÄÜ
ADC_ResetCalibration(ADC1);
//ADC_RegularChannelConfig(ADC1, ADC_Channel_6, 1, ADC_SampleTime_1Cycles5 );//ADC1ͨµÀ6,²ÉÑùʱ¼äΪ239.5ÖÜÆÚ
//ADC_ResetCalibration(ADC1);
while(ADC_GetResetCalibrationStatus(ADC1)){}
ADC_StartCalibration(ADC1);
while(ADC_GetCalibrationStatus(ADC1))
{
}
ADC_SoftwareStartConvCmd(ADC1,ENABLE);
}
u16 Get_ADC_Value(u8 ch,u8 times)
{
u8 t;
u16 out;
u16 temp_val=0;
//float vol=0;
ADC_RegularChannelConfig(ADC1,ch,1,ADC_SampleTime_239Cycles5);
for(t=0;t<times;t++)
{
ADC_SoftwareStartConvCmd(ADC1,ENABLE);
while(!ADC_GetFlagStatus(ADC1,ADC_FLAG_EOC))
{
}
temp_val+=ADC_GetConversionValue(ADC1);
//temp_val=ADC_GetConversionValue(ADC1);
delay_ms(5);
}
out=temp_val/times;
//vol=(float)(temp_val/times)*(3.3/4096);
//printf("¼ì²âADֵΪ£º%d \r\n",temp_val/times);
//printf("¼ì²âµçѹֵΪ£º%.2f V \r\n",vol);
//return temp_val/times;
return out;
}
main函数里面调用Get_ADC_Value(ADC_Channel_1,10)即可。
4ADC+DMA采集
DMA作为数据自动搬运模块,所以只需要配置好搬运的方式即可,分两部分,首先ADC设置里将DMA使能,然后DMA里对各个参数进行配置。(DMA数据搬运完成可以采用中断方式输出,也可以直接读取,此次采用中断输出的方式)
初始化和调用分为4个步骤:
1ADC初始化
void ADCx_Init()
{
GPIO_InitTypeDef GPIO_InitStructure;
ADC_InitTypeDef ADC_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA|RCC_APB2Periph_ADC1,ENABLE);
RCC_ADCCLKConfig(RCC_PCLK2_Div6);
GPIO_InitStructure.GPIO_Pin=GPIO_Pin_6;
GPIO_InitStructure.GPIO_Mode=GPIO_Mode_AIN;
GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;
GPIO_Init(GPIOA,&GPIO_InitStructure);
ADC_InitStructure.ADC_Mode=ADC_Mode_Independent;
ADC_InitStructure.ADC_ScanConvMode=DISABLE;
ADC_InitStructure.ADC_ContinuousConvMode=DISABLE;
ADC_InitStructure.ADC_ExternalTrigConv=ADC_ExternalTrigConv_T2_CC2;
//ADC_InitStructure.ADC_ExternalTrigConv=ADC_ExternalTrigConv_None;
ADC_InitStructure.ADC_DataAlign=ADC_DataAlign_Right;
ADC_InitStructure.ADC_NbrOfChannel=1;
ADC_Init(ADC1,&ADC_InitStructure);
ADC_Cmd(ADC1,ENABLE);
ADC_DMACmd(ADC1, ENABLE); //ADCµÄDMA¹¦ÄÜʹÄÜ
ADC_ResetCalibration(ADC1);
//ADC_RegularChannelConfig(ADC1, ADC_Channel_6, 1, ADC_SampleTime_1Cycles5 );
//ADC_ResetCalibration(ADC1);
while(ADC_GetResetCalibrationStatus(ADC1)){}
ADC_StartCalibration(ADC1);
while(ADC_GetCalibrationStatus(ADC1))
{
}
ADC_SoftwareStartConvCmd(ADC1,ENABLE);
}
2DMA初始化
DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord; //数据宽度为16位
DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord; //数据宽度为16位
这两个要设置为16位的,如果是8位,则输出最大为255.
DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte;//外设数据长度:8位
DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte;//存储器数据长度:8位
#include "dma.h"
#include "usart.h"
/******************************************************************
函数名称:MYDMA1_Config()
函数功能:DMA1初始化配置
参数说明:DMA_CHx:DMA通道选择
cpar:DMA外设ADC基地址
cmar:DMA内存基地址
cndtrDMA通道的DMA缓存的大小
备 注:
*******************************************************************/
void MYDMA1_Config(DMA_Channel_TypeDef* DMA_CHx,u32 cpar,u32 cmar,u16 cndtr)
{
DMA_InitTypeDef DMA_InitStructure;
NVIC_InitTypeDef NVIC_InitStructure;
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE); //使能DMA传输
DMA_DeInit(DMA_CHx); //将DMA的通道1寄存器重设为缺省值
DMA_InitStructure.DMA_PeripheralBaseAddr = cpar; //DMA外设ADC基地址
DMA_InitStructure.DMA_MemoryBaseAddr = cmar; //DMA内存基地址
DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC; //数据传输方向,从外设读取发送到内存//
DMA_InitStructure.DMA_BufferSize = cndtr; //DMA通道的DMA缓存的大小
DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable; //外设地址寄存器不变
DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable; //内存地址寄存器递增
DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord; //数据宽度为16位
DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord; //数据宽度为16位
DMA_InitStructure.DMA_Mode = DMA_Mode_Circular; //工作在循环模式
DMA_InitStructure.DMA_Priority = DMA_Priority_High; //DMA通道 x拥有高优先级
DMA_InitStructure.DMA_M2M = DMA_M2M_Disable; //DMA通道x没有设置为内存到内存传输
DMA_Init(DMA_CHx, &DMA_InitStructure); //ADC1匹配DMA通道1
DMA_ITConfig(DMA1_Channel1,DMA1_IT_TC1,ENABLE); //使能DMA传输中断
//配置中断优先级
NVIC_InitStructure.NVIC_IRQChannel = DMA1_Channel1_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=0 ;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
DMA_Cmd(DMA1_Channel1,ENABLE);//使能DMA通道
}
初始化调用如下:
MYDMA1_Config(DMA1_Channel1,(u32)&ADC1->DR,(u32)¤tadc,1);
currentadc为最后数据搬运到的地址,可以设置一个全局变量,然后读取这个数据即可。
3中断控制
/******************************************************************
简介:DMA中断用于完整采样一次(采样1024次),
并将其存储于adcx[]缓存数组中,等待后续数据处理
*******************************************************************/
void DMA1_Channel1_IRQHandler(void)
{
if(DMA_GetITStatus(DMA1_IT_TC1)!=RESET)
{
adcx[t]=currentadc;
t++;
if(t==NPT)
{
t=0;
adc_flag=1;
DMA_Cmd(DMA1_Channel1, DISABLE); //失能DMA
}
}
DMA_ClearITPendingBit(DMA1_IT_TC1);
}
4主函数while里面还要每次都打开ADC
while(1)
{
ADC_RegularChannelConfig(ADC1,ADC_Channel_1,1,ADC_SampleTime_239Cycles5);
ADC_SoftwareStartConvCmd(ADC1,ENABLE);
v_get=currentadc;
OLED_ShowNum(20,30,v_get,5,16);//oled????
OLED_Refresh_Gram();
}
5定时器+ADC+DMA采集
加入定时器,通过定时器控制ADC进行采集。作用是将上面ADC+DMA里面,主函数中while每次都要重新打开ADC才能开始采集,所以通过定时器的方式,时间到了就可以自动采集,然后触发DMA进行处理。
1ADC初始化
void ADCx_Init()
{
GPIO_InitTypeDef GPIO_InitStructure;
ADC_InitTypeDef ADC_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA|RCC_APB2Periph_ADC1,ENABLE);
RCC_ADCCLKConfig(RCC_PCLK2_Div6);
GPIO_InitStructure.GPIO_Pin=GPIO_Pin_6;
GPIO_InitStructure.GPIO_Mode=GPIO_Mode_AIN;
GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;
GPIO_Init(GPIOA,&GPIO_InitStructure);
ADC_InitStructure.ADC_Mode=ADC_Mode_Independent;
ADC_InitStructure.ADC_ScanConvMode=DISABLE;
ADC_InitStructure.ADC_ContinuousConvMode=DISABLE;
ADC_InitStructure.ADC_ExternalTrigConv=ADC_ExternalTrigConv_T2_CC2;
//ADC_InitStructure.ADC_ExternalTrigConv=ADC_ExternalTrigConv_None;
ADC_InitStructure.ADC_DataAlign=ADC_DataAlign_Right;
ADC_InitStructure.ADC_NbrOfChannel=1;
ADC_Init(ADC1,&ADC_InitStructure);
ADC_ExternalTrigConvCmd(ADC1, ENABLE);
ADC_Cmd(ADC1,ENABLE);
ADC_DMACmd(ADC1, ENABLE); //ADCµÄDMA¹¦ÄÜʹÄÜ
ADC_ResetCalibration(ADC1);
ADC_RegularChannelConfig(ADC1, ADC_Channel_6, 1, ADC_SampleTime_1Cycles5 );//ADC1ͨµÀ6,²ÉÑùʱ¼äΪ1.5ÖÜÆÚ
ADC_ResetCalibration(ADC1);
while(ADC_GetResetCalibrationStatus(ADC1)){}
ADC_StartCalibration(ADC1);
while(ADC_GetCalibrationStatus(ADC1))
{
}
ADC_SoftwareStartConvCmd(ADC1,ENABLE);
TIM_Cmd(TIM2, ENABLE);
DMA_Cmd(DMA1_Channel1, ENABLE);
}
2DMA初始化
/******************************************************************
函数名称:MYDMA1_Config()
函数功能:DMA1初始化配置
参数说明:DMA_CHx:DMA通道选择
cpar:DMA外设ADC基地址
cmar:DMA内存基地址
cndtrDMA通道的DMA缓存的大小
备 注:
*******************************************************************/
void MYDMA1_Config(DMA_Channel_TypeDef* DMA_CHx,u32 cpar,u32 cmar,u16 cndtr)
{
DMA_InitTypeDef DMA_InitStructure;
NVIC_InitTypeDef NVIC_InitStructure;
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE); //使能DMA传输
DMA_DeInit(DMA_CHx); //将DMA的通道1寄存器重设为缺省值
DMA_InitStructure.DMA_PeripheralBaseAddr = cpar; //DMA外设ADC基地址
DMA_InitStructure.DMA_MemoryBaseAddr = cmar; //DMA内存基地址
DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC; //数据传输方向,从外设读取发送到内存//
DMA_InitStructure.DMA_BufferSize = cndtr; //DMA通道的DMA缓存的大小
DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable; //外设地址寄存器不变
DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable; //内存地址寄存器递增
DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord; //数据宽度为16位
DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord; //数据宽度为16位
DMA_InitStructure.DMA_Mode = DMA_Mode_Circular; //工作在循环模式
DMA_InitStructure.DMA_Priority = DMA_Priority_High; //DMA通道 x拥有高优先级
DMA_InitStructure.DMA_M2M = DMA_M2M_Disable; //DMA通道x没有设置为内存到内存传输
DMA_Init(DMA_CHx, &DMA_InitStructure); //ADC1匹配DMA通道1
DMA_ITConfig(DMA1_Channel1,DMA1_IT_TC1,ENABLE); //使能DMA传输中断
//配置中断优先级
NVIC_InitStructure.NVIC_IRQChannel = DMA1_Channel1_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=0 ;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
DMA_Cmd(DMA1_Channel1,ENABLE);//使能DMA通道
}
3定时器初始化
/******************************************************************
函数名称:TIM2_PWM_Init(u16 arr,u16 psc)
函数功能:定时器2,PWM输出模式初始化函数
参数说明:arr:重装载值
psc:预分频值
备 注:通过TIM2-CH2的PWM输出触发ADC采样
*******************************************************************/
void TIM2_PWM_Init(u16 arr,u16 psc)
{
GPIO_InitTypeDef GPIO_InitStructure;
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
TIM_OCInitTypeDef TIM_OCInitStructure;
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE); //使能定时器2时钟
/*
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_AFIO, ENABLE); //使能GPIO外设和AFIO复用功能模块时钟
//设置该引脚为复用输出功能,输出TIM2 CH2的PWM脉冲波形 GPIOA.1
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1; //TIM_CH2
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; //复用推挽输出
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);//初始化GPIO
*/
//初始化TIM3
TIM_TimeBaseStructure.TIM_Period = arr; //设置在下一个更新事件装入活动的自动重装载寄存器周期的值
TIM_TimeBaseStructure.TIM_Prescaler =psc; //设置用来作为TIMx时钟频率除数的预分频值
TIM_TimeBaseStructure.TIM_ClockDivision = 0; //设置时钟分割:TDTS = Tck_tim
TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up; //TIM向上计数模式
TIM_TimeBaseInit(TIM2, &TIM_TimeBaseStructure); //根据TIM_TimeBaseInitStruct中指定的参数初始化TIMx的时间基数单位
//初始化TIM2 Channel2 PWM模式
TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1; //选择定时器模式:TIM脉冲宽度调制模式2
TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable; //比较输出使能
TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_Low; //输出极性:TIM输出比较极性高
TIM_OCInitStructure.TIM_Pulse=1000;
TIM_OC2Init(TIM2, &TIM_OCInitStructure); //根据T指定的参数初始化外设TIM2
TIM_CtrlPWMOutputs(TIM2, ENABLE);//使能PWM输出
TIM_Cmd(TIM2, ENABLE); //使能TIM2
TIM_InternalClockConfig(TIM2);
TIM_OC2PreloadConfig(TIM2, TIM_OCPreload_Enable);
TIM_UpdateDisableConfig(TIM2, DISABLE);
}
4初始化调用
u16 T=2000;//定时器2重载值,不能小于PWM的Pluse值
u16 pre=36;//定时器2预分频值
u16 currentadc;//实时采样数据
void Init()
{
RCC_HSE_Config(RCC_PLLSource_HSE_Div1,RCC_PLLMul_9); //初始化时钟72M
SysTick_Init(72);//初始化定时器72M
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); //中断分组
//USART1_Init(115200);//初始化串口1
LED_Init();
Ext_interrupts_Init();//初始化中断
OLED_Init(); //OLED
MYDMA1_Config(DMA1_Channel1,(u32)&ADC1->DR,(u32)¤tadc,1);
//BEEP_Init();
//TIM4_Init(1000,36000-1);
TIM4_Init(1,35); //三角波和噪声频率控制,
//TIM3_Int_Init(39,71); //72MHz/40/72=25kHz 25kHz/1024≈25Hz 正弦波频率约为24.5Hz
TIM2_PWM_Init(T-1,pre-1); //最大频率72000000/1/2000=3.6KHz
}
5DMA中断配置
/******************************************************************
简介:DMA中断用于完整采样一次(采样1024次),
并将其存储于adcx[]缓存数组中,等待后续数据处理
*******************************************************************/
void DMA1_Channel1_IRQHandler(void)
{
if(DMA_GetITStatus(DMA1_IT_TC1)!=RESET)
{
adcx[t]=currentadc;
t++;
if(t==NPT)
{
t=0;
adc_flag=1;
DMA_Cmd(DMA1_Channel1, DISABLE); //失能DMA
}
}
DMA_ClearITPendingBit(DMA1_IT_TC1);
}
6DMA使能
。。。。特别重要。
主函数while中,要加入DMA使能,否则DMA搬运完一次后就停止了。
while(1)
{
v_get=currentadc;
OLED_ShowNum(20,30,v_get,5,16);
OLED_Refresh_Gram();
led0=!led0;
DMA_Cmd(DMA1_Channel1,ENABLE);//使能DMA1-CH1
}