最近在备战电赛,所以这几天研究了一下用32产生可以控制频率的波形的方法。
32的功能还是很强大的,F4有168MHZ的主频,时钟频率可以达到84MHZ,对于产生波形来说,如果只是产生中低频率的波形来说是完全够用的,借助DMA后产生5MHZ的正弦波是没什么问题的。
我也分享一下我产生三角波和正弦波的方式,因为方波的产生基本上都能做出来,是比较简单的,当然也有不少的大神在研究如何产生更高频率的方波,使32的速度最大化,但我还没有做那方面的深入了解,所以以后学习后再做记录。
一、 首先是三角波
STM32的DAC控制器就支持生成三角波,Datesheet的截图如下
通过阅读以上内容我们可以知道,DAC产生三角波的方式是:设定一个最大振幅(MAMPx[3:0]),使用事件触发,每次触发后DAC值就自增1,当达到最大幅值后,每次事件触发后DAC的值就自减1,直到为0,至此一个周期结束,循环往复,由此可知产生三角波的方式并不需要CPU控制,只需要提供触发事件比如定时器等,然后DAC就能一直产生三角波,如果需要更改三角波的频率只需要更改定时器的分频系数以及自动重装载值即可。
与生成三角波相关的寄存器如下
MAMP1[3:0]是用来设置通道1三角波的幅值的,如果设置为4096,那么产生的三角波就是0-3.3伏的,如果改为2047,那么产生的就是0-1.65伏的三角波,我一开始以为这个寄存器更改的只是三角波产生的精度,后来使用示波器测量后发现并不是这样。
WAVE1[1:0]是用来设置通道1产生的波形的,可以设置为噪声波、三角波以及不生成。
TSEL1[2:0]是用来设置通道1触发器的选择,可以使用定时器TRGO时间触发,也可以使用外部中断以及软件触发的方式,但后俩者我并没有尝试。
TEN1是通道1触发使能,*注意*这个必须首先置1否则波形设置以及触发器设置都是无法进行的。
这个是定时器相关的寄存器的设置,设置分频系数以及自动重装载值的就不说了,最主要的设置就是MMS(主模式选择),在上面的DAC的寄存器你设置了定时器触发以后,一定要更改对应定时器的MMS位,它默认的是复位产生TRGO,显然这不是我们想要的,我们要将它更改为更新触发,也就是每次重装载时触发一次DAC,这样波形的频率也就与定时器的更新频率挂钩了。
我的源代码如下大家可以进行参考:
//DAC通道1输出初始化
void Dac1_Init(void)
{
RCC->APB1ENR|=1<<29; //使能DAC时钟
RCC->AHB1ENR|=1<<0; //使能PORTA时钟
GPIO_Set(GPIOA,PIN4,GPIO_MODE_AIN,0,0,GPIO_PUPD_PU);//PA4,模拟输入,下拉
DAC->CR|=1<<2; //使能触发功能 TEN1=1
DAC->CR|=1<<1; //DAC1输出缓存不使能 BOFF1=1
DAC->CR|=4<<3; //DAC TIM2 TRGO,不过要TEN1=1才行
DAC->CR|=2<<6; //生成三角波
DAC->CR|=12<<8; //设置振幅4095
DAC->CR|=0<<12; //DAC1 DMA不使能
DAC->CR|=1<<0; //使能DAC1
DAC->DHR12R1=0;
}
//TIM2初始化
//arr:自动重装载值 psc:预分频系数
void TIM2_Int_Init(u16 arr,u16 psc)
{
RCC->APB1ENR|=1<<0; //TIM2时钟使能
TIM2->ARR=arr; //设定计数器自动重装值
TIM2->PSC=psc; //预分频器
TIM2->CR2|=2<<4; //选择更新事件作为触发输出(TRGO)
TIM2->CR1|=0x01; //使能定时器2
}
主函数只需要调用这俩个初始化函数,在三个APB1周期后在PA4就会产生三角波。
二、接下来是介绍如何产生正弦波
与三角波不同,32DAC并不支持自动生成正弦波,所以我们采用读表法来生成,事先计算正弦波一个周期500个点对应的DAC的值,将它存在一个数组中,然后每次时钟更新时使用DAC输出电压,当然我们为了提高速率并且解放CPU使正弦波生成独立运行我们可以采用DMA的方式。
寄存器配置
TSEL1[2:0]、TEN1也是必须配置的,此外还需要配置
这一位是用来使能DAC1通道1的DMA模式
代码如下
/*DAC1通道2初始化*/
RCC->APB1ENR|=1<<29; //使能DAC时钟
RCC->AHB1ENR|=1<<0; //使能PORTA时钟
GPIO_Set(GPIOA,PIN5,GPIO_MODE_AIN,0,0,GPIO_PUPD_PU);//PA5,模拟输入,下拉
DAC->CR|=1<<18; //使能触发功能 TEN1=1
DAC->CR|=1<<17; //DAC1输出缓存不使能 BOFF1=1
DAC->CR|=5<<19; //DAC TIM4 TRGO,不过要TEN1=1才行
DAC->CR|=0<<22; //禁止生成波形
DAC->CR|=1<<28; //DAC1 通道2 DMA使能
DAC->CR|=1<<16; //使能DAC1 通道2
DAC->DHR12R2=0;
接下来我们定义一个只读数组来存放点以及一个同样大小的数组一会儿进行数据的暂存
const u16 Sine12bit2[500] = {
2048,2073,2099,2125,2150,2176,2202,2227,2253,2279,2304,2330,2355,2380,
2406,2431,2456,2482,2507,2532,2557,2582,2606,2631,2656,2680,2705,2729,2753,
2777,2801,2825,2849,2872,2896,2919,2942,2966,2988,3011,3034,3056,3079,3101,
3123,3145,3166,3188,3209,3230,3251,3272,3292,3313,3333,3353,3372,3392,3411,
3430,3449,3468,3486,3504,3522,3540,3558,3575,3592,3609,3625,3641,3657,3673,
3689,3704,3719,3734,3748,3762,3776,3790,3803,3816,3829,3842,3854,3866,3878,
3889,3900,3911,3921,3932,3942,3951,3961,3970,3978,3987,3995,4003,4010,4017,
4024,4031,4037,4043,4048,4054,4059,4063,4068,4072,4075,4079,4082,4085,4087,
4089,4091,4092,4094,4094,4095,4095,4095,4094,4094,4092,4091,4089,4087,4085,
4082,4079,4075,4072,4068,4063,4059,4054,4048,4043,4037,4031,4024,4017,4010,
4003,3995,3987,3978,3970,3961,3951,3942,3932,3921,3911,3900,3889,3878,3866,
3854,3842,3829,3816,3803,3790,3776,3762,3748,3734,3719,3704,3689,3673,3657,
3641,3625,3609,3592,3575,3558,3540,3522,3504,3486,3468,3449,3430,3411,3392,
3372,3353,3333,3313,3292,3272,3251,3230,3209,3188,3166,3145,3123,3101,3079,
3056,3034,3011,2988,2966,2943,2919,2896,2872,2849,2825,2801,2777,2753,2729,
2705,2680,2656,2631,2606,2582,2557,2532,2507,2482,2456,2431,2406,2381,2355,
2330,2304,2279,2253,2227,2202,2176,2150,2125,2099,2073,2048,2022,1996,1970,
1945,1919,1893,1868,1842,1816,1791,1765,1740,1715,1689,1664,1639,1613,1588,
1563,1538,1513,1489,1464,1439,1415,1390,1366,1342,1318,1294,1270,1246,1223,
1199,1176,1153,1129,1107,1084,1061,1039,1016,994,972,950,929,907,886,865,
844,823,803,782,762,742,723,703,684,665,646,627,609,591,573,555,537,520,
503,486,470,454,438,422,406,391,376,361,347,333,319,305,292,279,266,253,
241,229,217,206,195,184,174,163,153,144,134,125,117,108,100,92,85,78,71,
64,58,52,47,41,36,32,27,23,20,16,13,10,8,6,4,3,1,1,0,0,0,1,1,3,4,6,8,10,
13,16,20,23,27,32,36,41,47,52,58,64,71,78,85,92,100,108,117,125,134,144,
153,163,174,184,195,206,217,229,241,253,266,279,292,305,319,333,347,361,
376,391,406,422,438,454,470,486,503,520,537,555,573,591,609,627,646,665,
684,703,723,742,762,782,803,823,844,865,886,907,929,950,972,994,1016,1039,
1061,1084,1107,1129,1153,1176,1199,1223,1246,1270,1294,1318,1342,1366,1390,
1415,1439,1464,1489,1513,1538,1563,1588,1613,1639,1664,1689,1715,1740,1765,
1791,1816,1842,1868,1893,1919,1945,1970,1996,2022};
u16 DualSine12bit[500];
还有一个步骤就是将数值读入暂存数组
u32 Idx;
/*读入正弦波数据*/
for (Idx = 0; Idx < 500; Idx++)
{
DualSine12bit[Idx] = (Sine12bit2[Idx] << 16) + (Sine12bit2[Idx]);
}
接下来配置DMA,这里就不详细介绍了,只需要将存储器地址设置为数组DualSine12bit,外设地址设为DAC1的DHR12R2寄存器,并且设置循环传输模式,其他的就和其他外设使用DMA大同小异了。
值得注意的是与各个外设相连的DMA通道是固定的,需要选择DAC1专用的通道,而不是随便选择一个通道
根据映射图可知,与DAC1相连的DMA为DMA1通道7数据流5
配置代码如下:
//初始化DMA通道
void SinWave_DMA_Config()
{
RCC->AHB1ENR|=1<<21;//DMA1时钟使能
DMA1_Stream6->PAR=(u32)&(DAC->DHR12R2); //DMA外设地址
DMA1_Stream6->M0AR=(u32)DualSine12bit; //DMA 存储器0地址
DMA1_Stream6->NDTR=500; //传输500个数据
DMA1_Stream6->CR=0; //先全部复位CR寄存器值
DMA1_Stream6->CR|=1<<6; //存储器到外设模式
DMA1_Stream6->CR|=1<<8; //循环模式
DMA1_Stream6->CR|=0<<9; //外设非增量模式
DMA1_Stream6->CR|=1<<10; //存储器增量模式
DMA1_Stream6->CR|=1<<11; //外设数据长度:16位
DMA1_Stream6->CR|=1<<13; //存储器数据长度:16位
DMA1_Stream6->CR|=1<<16; //中等优先级
DMA1_Stream6->CR|=0<<21; //外设突发单次传输
DMA1_Stream6->CR|=0<<23; //存储器突发单次传输
DMA1_Stream6->CR|=(u32)7<<25;//通道选择(5通道)
}
接下来再配置定时器,我上面DAC中选择了TIM4的TRGO时间触发,所以我们要配置TIM4,与生成三角波的代码几乎完全相同,就不做过多的介绍了
//初始化TIM4
void SinWave_TIM_Config(u16 arr,u16 psc)
{
RCC->APB1ENR|=1<<2; //TIM2时钟使能
TIM4->ARR=arr; //设定计数器自动重装值
TIM4->PSC=psc; //预分频器
TIM4->CR2|=2<<4; //选择更新事件作为触发输出(TRGO)
TIM4->CR1|=0x01; //使能定时器4
}
最后使能DMA传输,所有的配置就完成了。
通过以上的配置PA5就能生成正弦波了,通过改变TIM4 的预分频系数和预加载值就可以改变正弦波的频率。
三、总结
以上就是使用STM32生成三角波和正弦波的讲解与代码,还是比较简单的,如果使用32来做一个函数信号发生器,只要改变定时器的配置就能改变波形的频率,但是幅值就不是那么好改变了,三角波明显幅值是不可变的,如果要改变可能必须要外接一个降压电路,正弦波倒是可以通过在将点读到暂存数组的时候乘一个数来实现幅值的改变。