玩转APM32的DMA-用DAC和DMA生成正弦波
本帖最后由 shanyuxiang 于 2025-9-20 13:19 编辑#申请原创# @21小跑堂
玩转APM32的DMA-用DAC和DMA生成正弦波
一、前言
DAC可以产生0到3.3V的模拟电压,如果动态改变DAC输出的电压,那么就可以生成各种各样的模拟波形。
这里我们就用DAC+DMA+TMR配合来生成正弦波。
首先看看APM32E103上的相关外设资源:
2个12位的DAC;
2个16位基本定时器TMR6/7;
两个DMA,DMA1有7个通道,DMA2有5个通道。
DAC有两个,一个是DAC_OUT1(PA4) ,还有一个是DAC_OUT2(PA5),两个可以同时输出,这里以DAC_OUT1为例。
DAC支持定时器触发,也就是说定时器溢出时更新一次DAC的数据寄存器,这里用基础定时器就够了。
每触发一次DAC转换需要更新DAC数据寄存器的值,用DMA把数组中的数传输到DAC数据寄存器中
二、DAC、TMR、DMA的初始化配置
2.1、DAC的配置
这里以DAC1为例,DAC的配置和单独使用DAC类似,先初始化GPIO为模拟复用。
触发配成TMR6触发,不用内部的波形发生功能,使能输出缓存。
最后一定记得开启DAC的DMA功能。
#define DAC_PIN GPIO_PIN_4
#define DAC_GPIO GPIOA
#define DAC_CHANNEL DAC_CHANNEL_1
#define DAC_TIM TMR6
#define DAC_TRIG DAC_TRIGGER_TMR6_TRGO
//gpio config
RCM_EnableAPB2PeriphClock(RCM_APB2_PERIPH_GPIOA);
GPIO_Config_T GPIO_ConfigStruct;
GPIO_ConfigStruct.pin= DAC_PIN;
GPIO_ConfigStruct.mode = GPIO_MODE_ANALOG;
GPIO_Config(DAC_GPIO, &GPIO_ConfigStruct);
//dac config
RCM_EnableAPB1PeriphClock(RCM_APB1_PERIPH_DAC);
DAC_Config_T DAC_ConfigStruct;
DAC_ConfigStruct.trigger = DAC_TRIG;
DAC_ConfigStruct.waveGeneration = DAC_WAVE_GENERATION_NONE;
DAC_ConfigStruct.maskAmplitudeSelect = DAC_TRIANGLE_AMPLITUDE_1;
DAC_ConfigStruct.outputBuffer = DAC_OUTPUT_BUFFER_ENBALE;
DAC_Config((uint32_t)DAC_CHANNEL, &DAC_ConfigStruct);
DAC_DMA_Enable(DAC_CHANNEL);
DAC_Enable(DAC_CHANNEL);
2.2、TMR的配置
通过用户手册可知,DAC支持多种触发源,这里我们用基础定时器TMR6去触发DAC转换。
定时器配置成最简单的向上计数即可,不需要开中断,更新事件用于触发输出。
定时器的溢出频率也就决定了DAC输出电压的变化快慢,也就是division 和 period,
这两个值越小,生成的正弦波频率越高;或者减小正弦波的采用点数,正弦波频率也能提高。
//timer config
RCM_EnableAPB1PeriphClock(RCM_APB1_PERIPH_TMR6);
TMR_BaseConfig_T TMR_BaseConfigStruct;
TMR_BaseConfigStruct.clockDivision = TMR_CLOCK_DIV_1;
TMR_BaseConfigStruct.countMode = TMR_COUNTER_MODE_UP;
TMR_BaseConfigStruct.division= 60-1;
TMR_BaseConfigStruct.period = 4;
TMR_ConfigTimeBase(DAC_TIM, &TMR_BaseConfigStruct);
TMR_EnableAutoReload(DAC_TIM);
TMR_SelectOutputTrigger(DAC_TIM, TMR_TRGO_SOURCE_UPDATE);
TMR_Enable(DAC_TIM);
2.3、DMA的配置
DMA配成从内存到DAC,内存地址要自增,而DAC数据寄存器地址不变。
因为这里用的DAC是12位,所以内存和外设的数据宽度都设为半字,也就是16位。
如果想开启一次后一直产生波形,则使用循环模式;如果想每个波形周期都要手动启动,则使用正常模式。
通过手册上的请求映射表可看出对应的DMA通道是DMA2-channel3。
特别要注意DAC的数据寄存器地址,这里需要仔细看手册。
//DMA config
RCM_EnableAHBPeriphClock(RCM_AHB_PERIPH_DMA2);
DMA_Config_T dmaConfig;
DMA_Reset(DAC_DMA_CHANNEL);
dmaConfig.peripheralBaseAddr = (uint32_t)DAC_DATA_ADDRESS;
dmaConfig.dir = DMA_DIR_PERIPHERAL_DST;
dmaConfig.memoryBaseAddr = (uint32_t)NULL;
dmaConfig.bufferSize = 0;
dmaConfig.peripheralInc= DMA_PERIPHERAL_INC_DISABLE;
dmaConfig.memoryInc = DMA_MEMORY_INC_ENABLE;
dmaConfig.peripheralDataSize = DMA_PERIPHERAL_DATA_SIZE_HALFWORD;
dmaConfig.memoryDataSize = DMA_MEMORY_DATA_SIZE_HALFWORD;
dmaConfig.loopMode = DMA_MODE_CIRCULAR;
dmaConfig.priority = DMA_PRIORITY_HIGH;
dmaConfig.M2M = DMA_M2MEN_DISABLE;
DMA_Config(DAC_DMA_CHANNEL, &dmaConfig);
2.4 启动DMA的传输
上面的初始化完成后,需要启动使能相关外设才会产生波形。
先设置定时器的自动加载值,这个值决定了波形的频率,
然后波形采样点数组的地址和长度赋值给DMA的地址、长度寄存器,
最后使能DAM传输。
//启动传输
void dac_dma_transmit(unsigned short *addr, unsigned int len, unsigned short period)
{
if (len > 65535)len = 65535;
DMA_Disable(DAC_DMA_CHANNEL);
TMR_ConfigAutoreload(DAC_TIM,period-1);
DAC_DMA_CHANNEL->CHNDATA = len;
DAC_DMA_CHANNEL->CHMADDR = (uint32_t)addr;
DMA_Enable(DAC_DMA_CHANNEL);
}
三、使用与测试
为了展示最终效果,还需要制作一个正弦波的采样点数组。
方法很多,这里就通过正弦函数sin来计算一个360点的波形数据。
正弦函数计算结果有正有负,为了好看,统统平移到0V以上。
#define WaveLength 360
unsigned short WaveBuffer;
#include <math.h>
#define PI 3.14159
int generate_sine_wave(unsigned short buf[])
{
double temp;
unsigned short i;
for (i = 0; i < WaveLength; i++)
{
temp = sin(i * PI / 180.0) * 2048.0;
temp = temp + 2047.0;
buf = (unsigned short)temp;
}
}
最后一步,调用刚才写好的的函数即可。
int main(void)
{
usart_printf_init();
printf("This a dac and dma demo(sine wave)\r\n");
generate_sine_wave(WaveBuffer);
dac_dma_init();
dac_dma_transmit(WaveBuffer, WaveLength, 4);
while (1)
{
}
}
用示波器观察PA4脚上的波形。
顺便又做了个锯齿波。
以上方法除了用来产生各种自定义的波形,也可以用来播放wav音频。
#申请原创# @21小跑堂 使用Timer6来控制生成波形的频率。
学习了,谢谢楼主 能不能生成任意频率的正弦波? 把局部变量地址传给DMA寄存器用不靠谱。仅这个表演程序可用 xch 发表于 2025-9-7 20:45
能不能生成任意频率的正弦波?
最高频率会受DAC的转换速度限制,低于这个频率都可以 xch 发表于 2025-9-7 20:49
把局部变量地址传给DMA寄存器用不靠谱。仅这个表演程序可用
局部变量在函数执行后会被释放,所以这里用的是全局变量 阳光爆裂 发表于 2025-9-7 18:59
使用Timer6来控制生成波形的频率。
学习了,谢谢楼主
{:handshake:}{:handshake:} shanyuxiang 发表于 2025-9-7 23:43
最高频率会受DAC的转换速度限制,低于这个频率都可以
不超过 DAC 转换速率。 任意频率低频。 学习了 TMR_BaseConfigStruct.period = 4;这句啥意义? xch 发表于 2025-9-8 11:10
TMR_BaseConfigStruct.period = 4;这句啥意义?
定时器的计数周期,决定了DAC多久转换一次 使用DMA来输出波形,这样还不影响MCU的通讯。否则,通讯就会导致输出波形变型了。
楼主,厉害!
有机会我也复刻一个 原来这个是这样实现的啊!
以前看人家的作品 也复刻不出来 还真是的,如果把Timer6的频率设置为48000的话,输出的波形就是声音了 xch 发表于 2025-9-7 20:45
能不能生成任意频率的正弦波?
应该是可以的吧,该表周期就行了吧,不过也得试一试。 cooldog123pp 发表于 2025-9-27 10:30
应该是可以的吧,该表周期就行了吧,不过也得试一试。
非整除分频系数的。不好随便做 这个DAC和DMA的结合使用确实挺巧妙的,对于学习APM32的外设操作很有帮助。
DAC和DMA的结合使用确实能提高效率,减少CPU的负担。楼主的代码示例很清晰,学习了。
这个和PWM的优势在哪里啊
页:
[1]
2