1.输出方波、三角波、正弦波三种波形
2.各种波形幅值、频率在一定范围内可调
3.串口控制波形数据
4.基于stm32f103芯片
/*采样点及精度调整*/
#define pi 3.1415926
#define POINT_NUM 128
#define angle 2*pi/POINT_NUM
初学到这里一开始也很懵逼,明明是输出模拟信号凭啥配置成模拟输入模式?
后来看了CSDN几篇大神的文章终于明白了:
因为一但使能 DACx 通道之后,相应的 GPIO 引脚(PA4 或者 PA5)会自动与 DAC 的模拟输出相连。其实,模拟输入通道与模拟输出通道是一起的(本来就是一条通路嘛),其实也可以理解为这里的”输入”是相对于片上外设DAC而言,IO是作为输入,但本质上是一条通路。
那么如此配置的好处嘞
void DAC1_GPIO_config(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
/* DAC的GPIO配置,模拟输入 */
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_4;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN;
GPIO_Init(GPIOA, &GPIO_InitStructure);
}
使用定时器6作为DMA搬运的触发源。
至此,采样点和定时器配置确定了,输出波形的频率也确定了。
可以由以下公式计算:
N是采样点个数,这里设为N=128
systick有系统时钟频率决定,F1这里为72M。
为了提高输出信号的带宽,一般把定时器预分频系数设为0。
void DAC1_TIM_Config(void)
{
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
/* 使能TIM6时钟,TIM2CLK 为72M */
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM6, ENABLE);
/*定时器初始周期*/
TIM_TimeBaseStructure.TIM_Period = 0XF;
/* 预分频,不分频 72M / (0+1) = 72M */
TIM_TimeBaseStructure.TIM_Prescaler = 0x0;
/*时钟分频系数*/
TIM_TimeBaseStructure.TIM_ClockDivision = 0x0;
TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;
TIM_TimeBaseInit(TIM6, &TIM_TimeBaseStructure);
/* 配置TIM6触发源 */
TIM_SelectOutputTrigger(TIM6, TIM_TRGOSource_Update);
/* 使能TIM6 */
TIM_Cmd(TIM6, ENABLE);
}
首先是DAC配置,这里采用DAC1(32上一共有俩)
值得注意的是DAC输出缓冲的结构体成员,使能输出缓冲可以提高输出信号的驱动能力,这里不使能。
其次是DMA配置,查询手册,发现DAC1对应的DMA通道为DMA2-CH3
这里的外设数据地址要查询手册下面给出地址映射:
#define DAC_DHR12RD_ADDRESS (DAC_BASE+0x20) //双通道输出地址
#define DAC_DHR8R1_Address 0x40007410//DAC通道1 8位输出地址
#define DAC_DHR12R1 0x40007408 //DAC通道1 12位输出地址
#define DAC_DHR12R2 0x40007414 //DAC通道2 12位输出地址
注意:DMA_BufferSize要和内存数组的大小相对应
DMA_MemoryDataSize和DMA_PeripheralDataSize要和定义的数据类型相对应
其他应该没啥好注意的了,下面给出配置代码:
void DAC1_DMA_Config(void)
{
DMA_InitTypeDef DMA_InitStructure;
DAC_InitTypeDef DAC_InitStructure;
/* 使能DMA2时钟 */
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA2, ENABLE);
/* 使能DAC时钟 */
RCC_APB1PeriphClockCmd(RCC_APB1Periph_DAC, ENABLE);
/* 配置DAC 通道1 */
DAC_InitStructure.DAC_Trigger = DAC_Trigger_T6_TRGO; //使用TIM6作为触发源
DAC_InitStructure.DAC_WaveGeneration = DAC_WaveGeneration_None; //不使用波形发生器
DAC_InitStructure.DAC_OutputBuffer = DAC_OutputBuffer_Disable; //不使用DAC输出缓冲
DAC_Init(DAC_Channel_1, &DAC_InitStructure);
/* 配置DMA2 */
DMA_InitStructure.DMA_PeripheralBaseAddr = DAC_DHR12R1; //外设数据地址
DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)&Sine12bit; //内存数据地址 Sine12bit
DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralDST; //数据传输方向内存至外设
DMA_InitStructure.DMA_BufferSize = 128; //缓存大小为POINT_NUM字节
DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable; //外设数据地址固定
DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable; //内存数据地址自增
DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord; //外设数据以半字为单位
DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord; //内存数据以半字为单位
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_Channel3, &DMA_InitStructure);
/* 使能DMA2-14通道 */
DMA_Cmd(DMA2_Channel3, ENABLE);
/* 使能通道1 由PA4输出 */
DAC_Cmd(DAC_Channel_1, ENABLE);
/* 使能DAC的DMA请求 */
DAC_DMACmd(DAC_Channel_1, ENABLE);
}
每次设置一次vpp就重新更新内存数组
最大采样值,2的12次=4096
void set_sine_vpp(float vpp)
{
int i;
for(i=0;i<POINT_NUM;i++)
{
Sine12bit[i]= (uint16_t)((sin(i*angle)+1)/2*vpp*4096/3.3);
}
}
void set_Triangle_vpp(float vpp)
{
int i;
for(i=0;i<POINT_NUM;i++)
{
if(i<=POINT_NUM/2 - 1)
TriangleWave12bit[i]= (uint16_t)vpp/3.3*4096/(POINT_NUM/2)*i;
else
TriangleWave12bit[i]= (uint16_t)(vpp/3.3*4096-vpp/3.3*4096/(POINT_NUM/2)*(i-(POINT_NUM/2)));
}
}
void set_Pulse_vpp(float vpp)
{
int i;
for(i=0;i<POINT_NUM;i++)
{
if(i<=POINT_NUM/2 - 1)
PulseWave12bit[i]= 0;
else
PulseWave12bit[i]= (uint16_t)(vpp/3.3*4096);
}
}
主要就是改变DMA搬运的数据地址,思路简单,下面以切换到正弦波为例:
但是需要注意的是,一旦申请了结构体,其他的成员也必须赋值,否则配置缺省!
void change_to_Triangle(void)
{
DMA_InitTypeDef DMA_InitStructure;
DMA_InitStructure.DMA_PeripheralBaseAddr = DAC_DHR12R1; //外设数据地址
DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)&TriangleWave12bit; //内存数据地址 Sine12bit
DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralDST; //数据传输方向内存至外设
DMA_InitStructure.DMA_BufferSize = 128; //缓存大小为POINT_NUM字节
DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable; //外设数据地址固定
DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable; //内存数据地址自增
DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord; //外设数据以字为单位
DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord; //内存数据以字为单位
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_Channel3, &DMA_InitStructure);
}
这里需要注意数据类型的转换
上位机发送1,2000,3.0
即可输出2khz,vpp=3.0的正弦波
void DEBUG_USART_IRQHandler(void)
{
if(USART_GetITStatus(DEBUG_USARTx,USART_IT_RXNE)!=RESET)
{
ucTemp =USART_ReceiveData(USART1);
UART1_RX_BUF[rx_cnt++]=ucTemp;
if(ucTemp==0x0a)
{
sscanf((char *)UART1_RX_BUF,"%d,%d,%f",&wave_flag,&freq,&vpp);
switch(wave_flag)
{
case 1:
{
printf("SET sine_f = %d,sine_VPP = %f\r\n",freq,vpp);
Sine.vpp = vpp;
Sine.freq = freq;
Sine.period = (int)(562500/Sine.freq + 1);
set_sine_vpp(Sine.vpp);
change_to_sine();
set_peroid(Sine.period);
break;
}
case 2:
{
printf("SET Triangle_f = %d,Triangle_VPP = %f\r\n",freq,vpp);
Triangle.vpp = vpp;
Triangle.freq = freq;
Triangle.period = (int)(562500/Triangle.freq + 1);
set_Triangle_vpp(Triangle.vpp);
change_to_Triangle();
set_peroid(Triangle.period);
break;
}
case 3:
{
printf("SET Pulse_f = %d,Pulse_VPP = %f\r\n",freq,vpp);
Pulse.vpp = vpp;
Pulse.freq = freq;
Pulse.period = (int)(562500/Pulse.freq + 1);
set_Pulse_vpp(Pulse.vpp);
change_to_Pulse();
set_peroid(Pulse.period);
break;
}
default:break;
}
rx_cnt = 0;
}
}
}
(以下是F4的程序的效果,F1无法达到这么高的频率)
github源码下载