转载:
原作者
https://www.cnblogs.com/cariohu/p/16152996.html
通过DAC输出正弦波可以直接在while循环中设置DAC的输出值,函数是HAL_DAC_SetValue(&hdac1,DAC1_CHANNEL_1,DAC_ALIGN_12B_R,2000)。但这种方式会导致CPU的负载率太高,刷新频率也不够快。所以,我需要用DMA来帮助CPU把数据快速的写入DAC的值寄存器中。CubeMX中的具体配置步骤如下
第一步,配置总线时钟,按下图配置即可,我们用的一般都是陶瓷晶振。注意是HSE作为时钟输入源,系统时钟选择PLLCLK。
第二步,配置JTAG口,下载和调试程序使用
第三步,配置TIM6,这个是作为DAC输出更新的触发源,DAC就是按这个TIM6的时间间隔更新输出。其他定时器也可以作为触发源的,具体哪些看对应芯片手册。分频系数为0就是不分配,总线时钟168M,计数值1680-1就是10us更新一次。触发事件要选择update,这个一定要选,不然DAC没有触发源,不会工作的。
第四步,配置DAC,DAC的配置要选择连接到外部的pin,这样信号就是在pin脚输出了。Trigger触发源要选择TIM6 trigger,如果配置的是其他TIM就对应更改,DAC配置这两个就可以了,其他默认即可。
第五步,配置DMA,DMA是英文Direct memory access的缩写,就是内存的直接访问,这样的话就不需要CPU来写入了,并且它的速率远高于通过CPU操作的方式。模式选择circular,这样只需启动一次DMA传输即可。数据宽度需要特别注意,外设是选择Word而不是halfword,被这个坑了很久。
问题解决后看到官方手册的如下描述,访问AHB总线的外设不支持byte/half-word传输,这个一定要注意,被坑了很久。
配置好之后用CubeMX工具生产代码,主要要选择对应的编译器,我用的是KEIL v5。只需要在主函数中加两条指令和一个sin波形数组就可以了。如下是sin波形生产数据,这里只是举例,大家可以有自己的数组。
//正弦波单个周期的点数
#define POINT_NUM 32
uint8_t Idx;
/* 波形数据 ---------------------------------------------------------*/
const uint16_t Sine12bit[POINT_NUM] = {
2048 , 2460 , 2856 , 3218 , 3532 , 3786 , 3969 , 4072 ,
4093 , 4031 , 3887 , 3668 , 3382 , 3042 , 2661 , 2255 ,
1841 , 1435 , 1054 , 714 , 428 , 209 , 65 , 3 ,
24 , 127 , 310 , 564 , 878 , 1240 , 1636 , 2048
};
HAL_TIM_Base_Start(&htim7);
HAL_DAC_Start_DMA(&hdac,DAC_CHANNEL_1,(uint32_t *)Sine12bit, 32,DAC_ALIGN_12B_R);
HAL_DAC_Start_DMA(&hdac,DAC_CHANNEL_2,(uint32_t *)DualSine12bit, 32,DAC_ALIGN_12B_R);
在初始化这里添加如下函数,第一个函数是启动TIM,这样TIM就会按设定好的时间去触发DAC。当DAC被触发后就会发送DMA请求,DMA收到请求后就会把一个数据传入DAC寄存器,然后等待下一次的请求。流程就是TIM—>DAC—>DMA,DMA把数据传入DAC后,DAC就会释放DMA请求,等带下一次TIM的触发信号。当整个数组传输完成后,又会开始下一轮的传输,因为DMA的模式是circular。
第二个函数就是启动DAC和DMA,最后一个参数是12bit的右对齐。这个就是这么简单,改变定时器的定时时间也就是改变正弦波的周期,可以根据需要自己调节。正弦波的周期就是 定时时间间隔*数组长度。我试了下TIM6的频率为12Mhz的时候信号输出正常,频率再高的时候DAC输出不变化了,这个12Mhz的更新频率比直接在while循环中赋值的方式至少快了一个数量级。
接示波器效果如下,唯一遇到是就是DMA配置的坑,其他一次成功。