STM32 DAC+TIMER+DMA输出正弦波

1 篇文章 0 订阅

前段时间学习了STM32使用DAC模块输出正弦波的功能,在学习过程中遇到了一些问题,在此和各位分享。
DAC是数字/模拟转换模块的简称,STM32中的DAC是12位数字输入,这个就决定了其精度。STM32的DAC模块具有两个通道,可单独进行转换,也就是说可以同时输出两个正弦波或其他波形。输出正弦波的原理简单讲就是每隔一定时间向DAC的数据寄存器写入数据,然后进行数据转换,输出不同电压,然后在时间轴上显示出波形。
这里比较重要的一个公式就是数字量和模拟量的转换公式:

在这里插入图片描述
STM32芯片内部有个参考电压:VREF,这个电压约为3.3V,由于DAC是12位的,所以最大可以表示为4095,将这3.3V电压均分为4095份,如果你向寄存器内写入2048,那么转换后的电压就是3.3V的一半,如果写入4095,那么转换后的电压就约为3.3V,实际输出可能会略有偏差。
简单介绍了下DAC的原理,下面直接来说我所遇到的问题:
下图就是我当时写好代码输出的正弦波,只有一半的正弦波是对的,另外一半有问题。
在这里插入图片描述
我的设计方式采用的是网上分享比较多的DAC+TIMER+DMA的方式,DMA简单理解就是一个通道,从通道一端写入数据,另外一端直达DAC的数据寄存器。这么做有什么好处呢?不占用CPU资源,速度快速,由于我们输出波形的时候,需要很快频率的DAC转换,如果我们写入过程太慢,产生的波形就会出现锯齿。
下面是我写的代码:
1.首先是GPIO的配置,STM32103的芯片DAC输出管脚对应的是PA4,PA5,配置成模拟输入方式就可以了,这比较简单。

void aw_gpio_init(void)
{
	GPIO_InitTypeDef aw_led_gpio_struct;
	GPIO_InitTypeDef aw_dac_gpio_struct;

	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_GPIOC, ENABLE);
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_DAC | RCC_APB1Periph_TIM2, ENABLE);
	RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA2, ENABLE);

	aw_led_gpio_struct.GPIO_Pin = GPIO_Pin_10 | GPIO_Pin_11 | GPIO_Pin_12;
	aw_led_gpio_struct.GPIO_Mode = GPIO_Mode_Out_PP;
	aw_led_gpio_struct.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOC, &aw_led_gpio_struct);

	GPIO_SetBits(GPIOC, GPIO_Pin_10 | GPIO_Pin_12);
	GPIO_ResetBits(GPIOC, GPIO_Pin_11); // green led on

	aw_dac_gpio_struct.GPIO_Pin = GPIO_Pin_4 | GPIO_Pin_5;
	aw_dac_gpio_struct.GPIO_Mode = GPIO_Mode_AIN;
	GPIO_Init(GPIOA, &aw_dac_gpio_struct); // init PA4 and PA5 for DAC1
}

2.TIMER的配置,在这里我使用的是TIM2,时钟不分频,自动重装载计数值用的是19,这和ST官方的例程中分享的也是一致的。定时器在这里的作用就是定时,每隔一段时间向DMA接口写入数据。

#ifndef __AW_TIME_H__
#define __AW_TIME_H__

#include "stm32f10x_tim.h"

#define AW_DAC_PSC			(0x0)
#define AW_DAC_PERIOD		(0x19)

void aw_time_init(void);

#endif  /* __AW_TIME_H__ */

void aw_time_init(void)
{
	TIM_TimeBaseInitTypeDef aw_time_struct;

	aw_time_struct.TIM_Prescaler = AW_DAC_PSC;
	aw_time_struct.TIM_CounterMode = TIM_CounterMode_Up;
	aw_time_struct.TIM_Period = AW_DAC_PERIOD; // timer delay 0.278us once
	aw_time_struct.TIM_ClockDivision = TIM_CKD_DIV1;
	TIM_TimeBaseInit(TIM2, &aw_time_struct);

	TIM_Cmd(TIM2, ENABLE);
	TIM_SelectOutputTrigger(TIM2, TIM_TRGOSource_Update);
}

3.DAC的配置,在这里我使用了双通道,也就是PA4,PA5管脚都可以输出正弦波。

void aw_dac_init(void)
{
	DAC_InitTypeDef aw_dac_struct;

	aw_dac_struct.DAC_Trigger = DAC_Trigger_T2_TRGO; // use TIM2 to trigger DAC
	aw_dac_struct.DAC_WaveGeneration = DAC_WaveGeneration_None;
	aw_dac_struct.DAC_OutputBuffer = DAC_OutputBuffer_Disable;
	DAC_Init(DAC_Channel_1, &aw_dac_struct);
	DAC_Init(DAC_Channel_2, &aw_dac_struct);

	DAC_Cmd(DAC_Channel_1, ENABLE); // enable DAC_Channel_1
	DAC_Cmd(DAC_Channel_2, ENABLE); // enable DAC_Channel_2
	DAC_DMACmd(DAC_Channel_2, ENABLE);
}

以上部分和各位网友的配置都一样,都没有问题,关键的地方在DMA的配置上。
4.DMA配置
我先给出DMA的配置代码,后续我再给大家阐述:

void aw_dma_init(void)
{
	DMA_InitTypeDef aw_dma_struct;

	aw_dma_struct.DMA_PeripheralBaseAddr = DAC_DHR12RD_ADDRESS;
	aw_dma_struct.DMA_MemoryBaseAddr = (AW_U32)&dual_sine_wave_data;
	aw_dma_struct.DMA_DIR = DMA_DIR_PeripheralDST;
	aw_dma_struct.DMA_BufferSize = SINE_WAVE_DATA_MAX;
	aw_dma_struct.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
	aw_dma_struct.DMA_MemoryInc = DMA_MemoryInc_Enable;
	aw_dma_struct.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord;
	aw_dma_struct.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord;
	aw_dma_struct.DMA_Mode = DMA_Mode_Circular;
	aw_dma_struct.DMA_Priority = DMA_Priority_High;
	aw_dma_struct.DMA_M2M = DMA_M2M_Disable;

	DMA_Init(DMA2_Channel4, &aw_dma_struct);
	DMA_Cmd(DMA2_Channel4, ENABLE);
}

(1)DAC_DHR12RD_ADDRESS对应的是DAC的数据寄存器的地址,我在.h文件中是这样定义的:#define DAC_DHR12RD_ADDRESS (DAC_BASE + 0x20),其实也可以定义成:#define DAC_DHR12RD_ADDRESS 0x40007420,其实都是一样的,前者是野火中使用的方法,后者是ST官方例程的用法。这里各位和我一样的新手要注意理解。
(2)然后就是DMA的DMA_MemoryBaseAddr这一项的配置,这是数据的输入接口,我使用的是一个数组buff,给这项传递指针。数字量的计算过程如下所示:

#ifndef __AW_DMA_H__
#define __AW_DMA_H__

#include "aw_type.h"

#define DAC_DHR12RD_ADDRESS		(DAC_BASE + 0x20)
#define SINE_WAVE_DATA_MAX		(256)
#define PI						(3.14159)

void aw_dma_init(void);
void aw_sine_wave_data(AW_U16 cycle);

#endif  /* __AW_DMA_H__ */
static AW_U16 dual_sine_wave_data[SINE_WAVE_DATA_MAX] = {0};
static AW_U16 sine_wave_data[SINE_WAVE_DATA_MAX] = {0};

/**
  * @brief This function is used to get sine value.
  */
void aw_sine_wave_data(AW_U16 cycle)
{
	AW_U16 i = 0;

	for (i = 0; i < cycle; i++) {
		sine_wave_data[i] = 2048 * sin(1.0 * i / (cycle - 1) * 2 * PI) + 2048;
	}
	for (i = 0; i < SINE_WAVE_DATA_MAX; i++) {
		dual_sine_wave_data[i] = (sine_wave_data[i] << 16) + (sine_wave_data[i]);
	}
}

我所配置的正弦波幅度是2048,最小值为0,最大值为4096。数据的计算过程简单来讲就是把正弦波的一个周期均分成256份,那么每份的x轴长度就是1*(2*PI)/255,1.0的作用就是使得后面不要整除。得到正弦波的数子量数据sine_wave_data,然后拆分成双通道的数据dual_sine_wave_data。
关键的配置在这两项,如下所示:

aw_dma_struct.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord;
	aw_dma_struct.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord;

各位和我一样的新手一定要注意,由于我们之前数字量数据dual_sine_wave_data定义的是16位的,所以在这里传输也是16位的传输,一定不要配置成整字32位,不然就出现我之前的问题,正弦波只有一半是好的,另外一半输出不确定的波形,而且这个问题在之前的各位博友的文章中都没有讲述。
最后来看我的主函数:

int main(void)
{
	aw_sine_wave_data(SINE_WAVE_DATA_MAX);
	aw_gpio_init();
	aw_dac_init();
	aw_time_init();
	aw_dma_init();

	while (1) {
		;
	}
}

主函数很简单,就是配置好各个模块,然后while循环一下。
最后我试验测试的正确波形:
在这里插入图片描述
如有疑问,欢迎各位博友一起讨论!

评论 10
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值