有一次我想在407上跑4096点的FFT,如果用软件触发ADC的方式(最简单)思路就是开定时器,在定时器中断中开触发,但是经过测试,实际效果的话,低速的情况下,准度还可以,但是速率一旦上去,如500K后,效果非常差。后来看了网上以及407的数据手册后知道了还有定时器触发ADC的方式,于是本着学习的思路,就想着用用。
因为是学习嘛,所以就从寄存器操作来做了,寄存器操作有几个好处,首先就是移植方便,最近升级了Keil,好多原本的库不能用了,因为Keil里面,编译器换成了AC6不是AC5,但是因为我是用的纯寄存器的写法,所以移植起来非常方便,改几个宏定义就行了,再一个就是对库的依赖非常低,我也不要移植库。
好了,废话这么多了,下面开始正文吧。
首先就是这次这个串联使用对程序要求非常高,因为全程走硬件,所以完全没有中断,当然,这有好处也有坏处,好处就是程序的速率非常高,而且全程就是芯片内部的问题,几乎完全脱手,但是也有坏处,就是如果驱动没写好那么是非常崩溃的,因为啥都没有,而且没有标志位,你都不知道卡在哪了,哪个环节出了问题。
除此之外,还有个问题需要注意,原来我们开启外设几乎可以说随便开的,但是这次不一样了,因为ADC和DMA关联,所以ADC和DMA开启是有先后顺序的。
关于速率的问题,有个非常好的地方,我们只要连上那个管脚,看一下PWM的波形,还有频率就能知道ADC的频率。实测后我们测一个500K 5次谐波也是能顺利测出来的,也就是说最高频率是1M的波,经过FFT后频谱也是OK的。
首先我们先了解一下哪些事件可以触发ADC
以上是407可以触发ADC的信号,从这张表我们可以看出定时器1-8几乎都可以触发,那么我这里就用TIM3->CH1触发规则通道举例了。至于规则通道和注入通道是什么,我这里就不讲了,网上有非常多的解释。
TIM3->CH1是什么相信能看到这里的盆友都清楚——定时器的PWM模式。至于它怎么触发的,手册上写的也非常清楚
这就有个非常好的地方,通过配置PWM的占空比我们就做到一些骚操作,比如0%触发一次30%再触发一下。不过这次的话我就简单了就直接上升沿触发,占空比配置成50%
我们最后再理一下思路
1.首先定时关联ADC,定时器PWM模式一次上升沿触发一次ADC,占空比是50%
2.ADC转换一次,就会生成一次DMA请求,DMA就会搬一次数据(这次我用的是DMA2 组0 通道0)
那么就有一些好学的童鞋就会问了,我假设就想要一个固定长度的数据,比如4096个数据呢?
好问题!我们再看看数据手册上写的
我们再理一下流程,当我想搬4096个数据的时候,DMA搬运结束后,ADC生成了第4097个数据,但DMA不再搬了,这时硬件会产生一个溢出标志位,我们只需要判断这个标志位就知道了。哦,DMA搬完了,现在有4096个数据了。而且当产生溢出标志位时ADC会不再转换了。
不过,大家在下一次再进ADC时记得清这个标志位,我嘛,直接图省事,用RCC->APB2RSTR然后重新初始化
以下的话我就挑几个和软件触发ADC有区别的寄存器说说
这里我说明一下如果是最开始我建议就先看一下定时器能不能触发ADC,可以先不配置8,9位,如果OK了再动8,9位。而8,9位如果要用DMA就都置1。第8位好理解,那第9位是啥意思呢?我们可以理解为DMA搬一次还是多次。所以如果大量数据的话就将第9位置1,如果是单次的话第9位置0也可以。
第8,9,10位
相信能用上这个功能的童鞋都会用基础的DMA功能了,我这里对这几位不详细解释,我就简单说一下我配置的是存储器递增模式,外设指针固定,非循环模式
void tim3_pwm(unsigned int arr3,unsigned int psc3)
{
RCC->APB1ENR|=1<<1; //TIM3时钟使能
GPIOC->MODER|=2<<12; //PC6 复用功能
GPIOC->MODER|=2<<14; //PC7 复用功能
GPIOC->MODER|=2<<16; //PC8 复用功能
GPIOC->MODER|=2<<18; //PC9 复用功能
GPIOC->OSPEEDR|=2<<12; //PC6速度配置为50kHZ
GPIOC->OSPEEDR|=2<<14; //PC7速度配置为50kHZ
GPIOC->OSPEEDR|=2<<16; //PC8速度配置为50kHZ
GPIOC->OSPEEDR|=2<<18; //PC9速度配置为50kHZ
GPIOC->AFR[0]|=2<<24; //PC6使用功能映射到TIM3
GPIOC->AFR[0]|=2<<28; //PC7使用功能映射到TIM3
GPIOC->AFR[1]|=2<<0; //PC8使用功能映射到TIM3
GPIOC->AFR[1]|=2<<4; //PC9使用功能映射到TIM3
TIM3->ARR=arr3; //设定计数器自动重装值
TIM3->PSC=psc3; //预分频器不分频
TIM3->CCMR2|=7<<12; //CH4 PWM2模式
TIM3->CCMR2|=7<<4; //CH3 PWM2模式
TIM3->CCMR1|=7<<12; //CH2 PWM2模式
TIM3->CCMR1|=7<<4; //CH1 PWM2模式
//TIM3->CCMR2|=1<<11; //CH4预装载使能
//TIM3->CCMR2|=1<<3; //CH3预装载使能
//TIM3->CCMR1|=1<<11; //CH2预装载使能
//TIM3->CCMR1|=1<<3; //CH1预装载使能
TIM3->CCER|=1<<12; //OC4 输出使能
TIM3->CCER|=1<<8; //OC3 输出使能
TIM3->CCER|=1<<4; //OC2 输出使能
TIM3->CCER|=1<<0; //OC1 输出使能
TIM3->CCER|=1<<1; //OC1 低电平有效
TIM3->CCER|=1<<5; //OC2 低电平有效
TIM3->CCER|=1<<9; //OC3 低电平有效
TIM3->CCER|=1<<13; //OC4 低电平有效
TIM3->CR1=0x0080; //ARPE使能
TIM3->CR1|=0x01; //使能定时器3
}
void init_adc1(unsigned char ch1)
{
TIM3->CCR1=(TIM3->ARR)/2;
ADCDATA->ADC1Chinal=ch1;
init_adc12_chinal(ch1); //初始化通道5
ADC1->CR1&=~(3<<24); //15 ADCCLK 周期
ADC1->CR1&=~(1<<8); //禁止扫描模式
ADC1->CR2&=~(1<<1); //在定时器触发下用单次触发模式
ADC1->CR2&=~(1<<11); //数据右对齐
ADC1->CR2|=7<<24; //定时器3 CC1事件
ADC1->CR2|=(1<<28); //上升沿和下降沿触发
ADC1->SQR1&=~(0XF<<20); //规则通道序列长度 1次转换
ADC1->SQR1|=0<<20; //1个转换在规则序列中 也就是只转换规则序列1
ADC1->SQR3|=ADCDATA->ADC1Chinal<<0; //规则序列1的通道号
//设置采样时间
ADC1->SMPR2&=~(7<<(3*ADCDATA->ADC1Chinal)); //采样时间清空
ADC1->SMPR2|=(0<<(3*ADCDATA->ADC1Chinal)); //3个周期,提高采样时间可以提高精确度
ADC1->CR2|=1<<8; //使能DMA模式
ADC1->CR2|=1<<9; //一直开启DMA,为1一直采集,为0采集1次
ADC1->CR2|=1<<0; //开启AD转换器
ADC1->CR2|=1<<30; //启动规则转换通道
}
//初始化DMA2 组0 通道0
//ADC1 接收口
void init_DMA2_S0C0(unsigned int ADCDMAWei)
{
DMA2_Stream0 ->CR = 0;//禁止数据流 ,才能写寄存器
//外设地址寄存器
//将所需寄存器的地址放入PAR寄存器
DMA2_Stream0 ->PAR = (unsigned int)(&ADC1->DR);
//数据流地址寄存器
//M1AR仅在双通道模式下有用
//将数据所在地址给M0AR寄存器
DMA2_Stream0 ->M0AR = (unsigned int)(&FMAMData->adcf);
DMA2_Stream0 ->NDTR = ADCDMAWei; // 一次传输数量
DMA2_Stream0 ->FCR = 0x21; //FIFO所有配置失效
DMA2_Stream0 ->CR |= 0<< 6; //外设到储存器模式
//循环模式
//当NDTR寄存器减到0时自动重装
//单次模式(普通模式)
//NDTR减到0后停止DMA
DMA2_Stream0 ->CR |= 0<< 8; //非循环模式
DMA2_Stream0 ->CR |= 0<< 9; //外设非增量模式
DMA2_Stream0 ->CR |= 1<<10; //存储器增量模式,指针增加,可用于传输数组
DMA2_Stream0 ->CR |= 1<<11; //外设数据长度:16位
DMA2_Stream0 ->CR |= 1<<13; //存储器数据长度:16位
DMA2_Stream0 ->CR |= 2<<16; //高等优先级
//突发传输
//DMA占用CPU总线时间,此时CPU无法工作
//一个节拍:传输多少次32位变量
//应用场景:从ram里读出字节
DMA2_Stream0 ->CR |= 0<<21; //外设突发单次传输
DMA2_Stream0 ->CR |= 0<<23; //存储器突发单次传输
DMA2_Stream0 ->CR |= 0<<25; //通道0
DMA2_Stream0 ->CR |= 1<<0; //使能数据流
}
我的开启顺序是先开DMA,后定时器,最后ADC。当然大家也可以随意组合开启顺序试试。
起始,以上程序还有“BUG”,并不能无脑照搬,我也没办法,我的程序也在整个工程里还是有粘性的,并不是完全独立的。不过相信认真读了文章还有认真学习了正点原子的童鞋一定能知道哪里有问题,如果还是不懂的话,可以评论区留言。希望大家学有所成,共同成长!