整个工作流程为:
- 先建立一个含N个点的数据表(正弦波的一个周期由N个点构成)
- 计算所需的定时器定时周期(根据输出信号的周期和周期内的点数计算)
- 配置DAC
- 配置定时器
- 配置DMA用于自动转运数据表中数据到DAC中
1. 建立数据表(存放要输入到DAC的数据点)
实验可知:32个数据点就可以得到一个完整的波形,这里选择50个;
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#define TABLE_SIZE 50
#define PI 3.14159265358979323846
int sineWaveTable[TABLE_SIZE];
void PointGenerate(void)
{
double step = 2 * PI / TABLE_SIZE;
double radian = 0;
for (int i = 0; i < TABLE_SIZE; i++)
{
double out_voltage = 2047 * (sin(radian) + 1);
sineWaveTable[i] = (int)out_voltage;
radian += step;
}
}
int main(void)
{
PointGenerate();
//打印表格中数据,10个数据一行
for (int i = 0; i < TABLE_SIZE; i++)
{
printf("%4d ", sineWaveTable[i]);
if ((i + 1) % 10 == 0)
printf("\n");
}
printf("\nPress Enter to continue...");
getchar();
return 0;
}
2. 配置DAC
函数DAC_Config():
1. 开启GPIO时钟,开启DAC时钟
2. GPIO初始化,DAC初始化
3. 使能DAC
DAC_Cmd(DAC_Channel_1, ENABLE); //打开DAC的通道1
4. 使能DAC的DMA(直接内存访问)请求
这样无需CPU干预,DMA就会自动把数据搬运到DAC
DAC_DMACmd(DAC_Channel_1, ENABLE);
代码:
static void DAC_Config(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
DAC_InitTypeDef DAC_InitStructure;
/* 使能GPIOA时钟 */
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
/* 使能DAC时钟 */
RCC_APB1PeriphClockCmd(RCC_APB1Periph_DAC, ENABLE);
/* DAC的GPIO配置,模拟输入 */
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_4;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN;//没有模拟输出,选这个可以实现模拟输出
GPIO_Init(GPIOA, &GPIO_InitStructure);
/* 配置DAC 通道1 */
DAC_InitStructure.DAC_Trigger = DAC_Trigger_T2_TRGO; //使用TIM2作为触发源
DAC_InitStructure.DAC_WaveGeneration = DAC_WaveGeneration_None; //不使用波形发生器
DAC_InitStructure.DAC_OutputBuffer = DAC_OutputBuffer_Disable; //不使用DAC输出缓冲
DAC_Init(DAC_Channel_1, &DAC_InitStructure);
//使能通道1
DAC_Cmd(DAC_Channel_1, ENABLE);
//使能DAC的DMA请求
DAC_DMACmd(DAC_Channel_1, ENABLE);
}
3. 配置定时器,得到所需定时周期
要点:设置定时器定时周期,时间到了定时器就溢出(称为定时器更新事件),就会立马把一个数据传输到DAC,因此传输一个点的时间为Tupdate,而一个正弦波是由50个点组成,输出50个点需要的时间为Tupdate*50,这个时间就等于输出信号的周期;
正弦信号输出频率fout=10kHz, 也就说输出信号的周期T = 1/fout;
因此:Tupdate*50 = 1/fout (1)
接下来求出所需的Tupdate即可
假设使用单片机的时钟频率F = 280MHz
那么分频后周期:T1 = (PSC+1)/ F
定时周期:Tupdate= T1*(ARR+1) //计数计到ARR+1后溢出
因此:
代码部分:
static void DAC_TIM_Config(void)
{
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
/* 使能TIM2时钟,TIM2CLK 为72M */
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);
/* TIM2基本定时器配置 */
// TIM_TimeBaseStructInit(&TIM_TimeBaseStructure);
TIM_TimeBaseStructure.TIM_Period = 20-1; //定时周期 20
TIM_TimeBaseStructure.TIM_Prescaler = 28-1; //预分频, 280M / (28-1+1) = 10M
TIM_TimeBaseStructure.TIM_ClockDivision = 0x0; //时钟分频系数
TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up; //向上计数模式
TIM_TimeBaseInit(TIM2, &TIM_TimeBaseStructure);
TIM_SelectOutputTrigger(TIM2, TIM_TRGOSource_Update);
//选择发送触发信号的触发源为定时器更新,即计数器溢出时产生触发信号
//(在DAC中设置这个触发信号作为触发DAC转换的信号源)
/* 使能TIM2 */
TIM_Cmd(TIM2, ENABLE);
}
选择触发源为更新时间触发(定时器溢出),定时时间到了以后就会输出触发信号。
在DAC_Config中设置这个触发信号作为DAC转换的触发信号源:
DAC_InitStructure.DAC_Trigger = DAC_Trigger_T2_TRGO;
4. 配置DMA
static void DAC_DMA_Config(void)
{
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA2, ENABLE);//开启DMA2时钟
DMA_InitTypeDef DMA_InitStructure;
DMA_InitStructure.DMA_PeripheralBaseAddr = DAC_DHR12RD_ADDRESS;
//外设数据地址
DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)&DualSine12bit ;
//内存数据地址 DualSine12bit
DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralDST;
//数据传输方向内存至外设
DMA_InitStructure.DMA_BufferSize = POINT_NUM;
//缓存大小为POINT_NUM字节
DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
//外设数据地址固定
DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;
//内存数据地址自增
DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Word;
//外设数据以字为单位
DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Word;
//内存数据以字为单位
DMA_InitStructure.DMA_Mode = DMA_Mode_Circular;
//循环模式
DMA_InitStructure.DMA_Priority = DMA_Priority_High;
//高DMA通道优先级
DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;
//非内存至内存模式
DMA_Init(DMA2_Channel4, &DMA_InitStructure);
//使能DMA2-14通道
DMA_Cmd(DMA2_Channel4, ENABLE);
}
DAC初始化函数:
运行上面的三个函数完成DAC配置、TIM配置、DMA配置;
void DAC_Mode_Init(void)
{
DAC_Config();
DAC_TIM_Config();
DAC_DMA_Config();
}
主函数:
int main(void)
{
DAC_Mode_Init();
while (1);
}