STM32F407使用内置DAC+DMA+TIM制作DDS信号源
电赛的时候需要制作能够实现分辨率到0.001Hz的信号源,平常使用的信号源模块达不到这个精度,故使用DDS算法实现
一、简单原理讲解(个人理解)
频率:
现在假设我们有2的32次方这么大个正弦波波表(当然后面教程中不会这么大,这里方便理解),然后DDS输出波表直接查表的时候频率控制字取1,本振(定时器6)为2MHz,此时输出频率为2M除以2的32次方乘以1约等于0.0004656Hz;若频率控制字取2,输出频率为2M除以2的32次方乘以2约等于0.0009312Hz。这里就能得到频率控制字的计算公式了为:2的32次方除以本振(2M)乘以输出频率。然而在实际工程中,一般情况下,我们不可能有这么大的存储空间去储存2的32次方这么大个正弦波表,这个时候相位累加器的作用就出现了,可以将正弦波波表取样量化到一个2的次方大小的数组,比如2的8次方,相位累加器为32位(也可以取其他的),取高八位作为DDS输出查表的索引,便解决了。
相位:
实际上相位累加器32位,就对应了2的32次方这么大个正弦波波表(虚拟出来的,一个周期),改变相位,只需要在当前相位累加器的基础上加减一次即可,相位控制字计算公式为:改变相位除以360°乘以2的32次方
幅度:
取决于正弦波波表
二、DDS缺陷
尽管DDS能够实现精准的频率相位控制,但在输出高频信号时,会出现不明显的上下波动的现象,笔者在使用这个算法出三角波的时候吃的亏,原因是因为,在频率越高,频率控制字越大,输出对应的DDS数组中一个周期数量少,导致,在DDS查表的时候,取峰峰值取不到。(可通过提高本振频率,相位累加器位数和数组大小改善)
三、DDS算法实现
在使用DDS算法中,有相位累加器uint32_t place=0
,频率控制字 uint32_t ctrl_word
,相位控制字uint32_t phase_word
,DDS输出波表uin32_t pData[256]
,正弦波波表uint16_t Sine_WAVE[256]
;,输出频率uint32_t Fre=10000
,输出相对相位float phase=0
(0到360°)系统时钟(本振)2MHz。故分辨率为2M/2的32次方约等于0.0004656Hz,在配置好cubemx后生成代码。
这里我的f4主频设置成160MHz
一些变量
#define WAVE_POINT 256
#define OF 2147.483648 //2的32次方除以2M
uint16_t pData[WAVE_POINT];
uint16_t Sine_WAVE[WAVE_POINT];
uint32_t place=0;
uint32_t ctrl_word=0;
uint32_t phase_word=0;
uint32_t Fre=10000;
uint8_t phaseFlag=1;
一些代码
void SineWave_Data(uint16_t num, uint16_t* D, float U)//正弦波生成波表代码
{
num++;
uint16_t i;
U=U/2;
for (i = 0; i < num-1; i++)
{
D[i] = (uint16_t)((U*sin((1.0 * i / (num - 1)) * 2 * 3.1415926) + U) * 4095 / 3.3)+300;
}
}
void phaseChange(float phase)``//相位改变代码
{
phase_word=phase/360*4294967295;
if(phaseFlag==1)
{
phaseFlag=0;
place+=phase_word;
}
}
主函数代码
int main(void)
{
/* USER CODE BEGIN 1 */
/* USER CODE END 1 */
/* MCU Configuration--------------------------------------------------------*/
/* Reset of all peripherals, Initializes the Flash interface and the Systick. */
HAL_Init();
/* USER CODE BEGIN Init */
/* USER CODE END Init */
/* Configure the system clock */
SystemClock_Config();
/* USER CODE BEGIN SysInit */
/* USER CODE END SysInit */
/* Initialize all configured peripherals */
MX_GPIO_Init();
MX_DMA_Init();
MX_DAC_Init();
MX_TIM6_Init();
/* USER CODE BEGIN 2 */
SineWave_Data(WAVE_POINT ,Sine_WAVE,2);//生成正弦波数组**
HAL_DAC_Start_DMA(&hdac, DAC_CHANNEL_1, (uint32_t*)pData, WAVE_POINT, DAC_ALIGN_12B_R);//使能DAC**
HAL_TIM_Base_Start(&htim6);//使能定时器**
/* USER CODE END 2 */
/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1)
{
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
phaseChange(phase);//更改相位
ctrl_word=Fre*OF;//频率控制字
}
/* USER CODE END 3 */
}
核心代码
在DMA传输中断中更改波表,传输完成前半段更改前半段波表,完成后半段更改后半段波表
void HAL_DAC_ConvHalfCpltCallbackCh1(DAC_HandleTypeDef *hdac)
{
for(uint16_t i=0;i<WAVE_POINT/2;i++)
{
pData[i]=Sine_WAVE[place>>24];
place+=ctrl_word;
}
}
void HAL_DAC_ConvCpltCallbackCh1(DAC_HandleTypeDef *hdac)
{
for(uint16_t i=WAVE_POINT/2;i<WAVE_POINT;i++)
{
pData[i]=Sine_WAVE[place>>24];
place+=ctrl_word;
}
}