前面的博客我们介绍了单通道的模式,现在我们就在此基础之上来讨论下多通道的外部信号触发启动模式。
一.外部触发源
首先,stm32的adc启动大致分为两种一种软件触发,一种外部事件触发。
我们以ADC1为例,线面我们来看看具体有哪些外部信号能触发ADC:
如图我们只要是用TIM3_TRGO信号来触发adc。
下面我们来介绍下TIM3_TRGO信号。
TIM3_TRGO实际上就是定时器触发的一个信号,具体怎么触发这个信号了,我们可以查看管官方函数: TIM_SelectOutputTrigger(TIM3,TIM_TRGOSource_Update); //选择TRGO触发源为计时器更新事件
这里我们选择TIM_TRGOSource_Update作为TIM3的TRGO信号的触发源,可以理解为每次计时器一更新那么TRGO这个信号就会变为有效信号、就能被其他需要这个信号的外设所识别,如果不指定触发源的话就是一个无效信号气的外设无法识别。
从这里可以看出,除了更新事件外还有其他的事件能够作为TRGO信号的触发源。。。
在使用外部触发源是要配置ADC_InitStruct.ADC_ExternalTrigConv=ADC_ExternalTrigConv_T3_TRGO ; //T3_TRGO(定时器3TRGO启动)外部触发。
二.dma配置的问题
u32 buffsize=5
DMA_Config(DMA1_Channel1,(u32)&ADC1->DR,(u32)(&a[2]),(u32)buffsize); //dma初始化
在我们配置多通道DMA搬运时候(u32)(&a[2])缓冲区首地址参数我们要尽可能的传入数组首地址,如果是循环搬运那么如果我们传入的是A[2]地址当搬运到a[4]的时候就会继续将值从a【2】开始存,A[0],A[1]就不会存入值。。。
还有一个问题就是buffsize的值最好是和通道数一致这样每个通道的的数据就能和数组实现一一对应关系,如果buffsize比通道数大那么两者对应不上不容易查找数据。
例如双通道模式下buffsize=3即是a[3];一通道3.3v,一通道0v
第一次搬运:a[0]=3.3 a[1]=0 a[2]=3.3
第二次搬运:a[0]=0 a[1]=3.3 a[2]=0
如此下去不利于查找。
还有一个问题就是:在我们 设置DMA外设数据宽度,内存数据宽度的时候要相互对应,也就是说你的 外设数据宽度=内存数据宽度,否则容易出现数据错误。(很重要)一般都是查看外设数据寄存器的位数,然后根据这个值定义相应大小的数组。
附上完整代码:
DMA部分:
int DMA_Config(DMA_Channel_TypeDef* CHx,u32 PBaseAddr,u32 MBaseAddr,u32 buffsize)
{
DMA_InitTypeDef DMA_InitStruct;
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1,ENABLE);
DMA_DeInit(CHx);
DMA_InitStruct.DMA_PeripheralBaseAddr=PBaseAddr; //外设基地址
DMA_InitStruct.DMA_MemoryBaseAddr=MBaseAddr; //内存寄存器
DMA_InitStruct.DMA_DIR=DMA_DIR_PeripheralSRC; //数据传输方向 外设到内存
DMA_InitStruct.DMA_BufferSize=buffsize; //通道缓存大小
DMA_InitStruct.DMA_PeripheralInc=DMA_PeripheralInc_Disable; //外设寄存器地址不递增
DMA_InitStruct.DMA_MemoryInc=DMA_MemoryInc_Enable; //开启内存寄存器地址递增
DMA_InitStruct.DMA_PeripheralDataSize=DMA_PeripheralDataSize_HalfWord; //外围数据宽度16位
DMA_InitStruct.DMA_MemoryDataSize=DMA_MemoryDataSize_HalfWord; //内存数据宽度
DMA_InitStruct.DMA_Mode=DMA_Mode_Circular; //循环传输模式
DMA_InitStruct.DMA_Priority=DMA_Priority_High; //优先级
DMA_InitStruct.DMA_M2M=DMA_M2M_Disable; //不开启内存到内存
DMA_Init(CHx ,&DMA_InitStruct); //初始化DMA;
DMA_Cmd(CHx ,ENABLE);
return 1;
}
adc部分:
void Adc_Init(void)
{
GPIO_InitTypeDef GPIO_InitStruct;
ADC_InitTypeDef ADC_InitStruct;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA|RCC_APB2Periph_ADC1,ENABLE);//使能时钟
//配置GPIOA_5口模拟输入
GPIO_InitStruct.GPIO_Mode=GPIO_Mode_AIN;
GPIO_InitStruct.GPIO_Pin=GPIO_Pin_5;
GPIO_Init(GPIOA,&GPIO_InitStruct);
//配置GPIOA_4口模拟输入
GPIO_InitStruct.GPIO_Mode=GPIO_Mode_AIN;
GPIO_InitStruct.GPIO_Pin=GPIO_Pin_4;
GPIO_Init(GPIOA,&GPIO_InitStruct);
//ADC时钟分频因子
RCC_ADCCLKConfig(RCC_PCLK2_Div6);
//重置ADC寄存器
ADC_DeInit(ADC1);
ADC_InitStruct.ADC_Mode= ADC_Mode_Independent;//独立模式
ADC_InitStruct.ADC_DataAlign=ADC_DataAlign_Right; //数据右对齐
ADC_InitStruct.ADC_ExternalTrigConv=ADC_ExternalTrigConv_T3_TRGO ; //T3_TRGO(定时器3TRGO启动)外部触发
ADC_InitStruct.ADC_ContinuousConvMode=ENABLE; //enable开启连续扫描
ADC_InitStruct.ADC_NbrOfChannel=2; //规则转换通道数目
ADC_InitStruct.ADC_ScanConvMode=ENABLE; //DISABLE单通道模式,enable多通道扫描模式
ADC_Init(ADC1,&ADC_InitStruct);
ADC_Cmd(ADC1,ENABLE); //使能adc 必须先使能才校准,不然校准不通过的
ADC_ResetCalibration(ADC1);//开启复位校准
while(ADC_GetResetCalibrationStatus(ADC1));
ADC_StartCalibration(ADC1); //开启ad校准
while(ADC_GetCalibrationStatus(ADC1));
}
定时器部分:
定时器部分主要就是加上TRGO出发的选择。
主函数:
const u16 buffsize=2;
int main(void)
{
u16 a[buffsize]={0};
LedInit();
delay_init(); //延时函数初始化
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); //设置NVIC中断分组2:2位抢占优先级,2位响应优先级
uart_init(9600); //串口初始化为9600
Adc_Init(); //adc初始化
DMA_Config(DMA1_Channel1,(u32)&ADC1->DR,(u32)(&a[0]),(u32)buffsize); //dma初始化
TIM3_Int_Init(4999,7199); //定时器3初始化
// ADC_SoftwareStartConvCmd(ADC1,ENABLE); //设置adc软件启动
ADC_ExternalTrigConvCmd(ADC1,ENABLE); //使能adc外部触发启动
ADC_DMACmd(ADC1,ENABLE); //使能adc的dma传输
ADC_RegularChannelConfig(ADC1,ADC_Channel_5,1,ADC_SampleTime_13Cycles5); //配置通道5的采样时间和规则序列表
ADC_RegularChannelConfig(ADC1,ADC_Channel_4,2,ADC_SampleTime_13Cycles5); //配置通道4的采样时间和规则序列表
while(1)
{
if(ADC_GetFlagStatus(ADC1,ADC_FLAG_EOC))
{
printf("a[0]=%.2f ",a[0]*3.3/4096);
printf("a[1]=%.2f \r\n",a[1]*3.3/4096);
delay_ms(1000);
}
}
}