返回列表 发新帖我要提问本帖赏金: 60.00元(功能说明)

[APM32E1] 玩转APM32的DMA-用DAC和DMA生成正弦波

[复制链接]
1165|18
shanyuxiang 发表于 2025-9-7 16:52 | 显示全部楼层 |阅读模式
, , , ,
本帖最后由 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为例。
1 dac简介.png


DAC支持定时器触发,也就是说定时器溢出时更新一次DAC的数据寄存器,这里用基础定时器就够了。
2 TMR 简介.png



1-1 dac触发源.png



每触发一次DAC转换需要更新DAC数据寄存器的值,用DMA把数组中的数传输到DAC数据寄存器中


二、DAC、TMR、DMA的初始化配置
2.1、DAC的配置
这里以DAC1为例,DAC的配置和单独使用DAC类似,先初始化GPIO为模拟复用。
触发配成TMR6触发,不用内部的波形发生功能,使能输出缓存。
最后一定记得开启DAC的DMA功能。

  1. #define DAC_PIN          GPIO_PIN_4
  2. #define DAC_GPIO         GPIOA
  3. #define DAC_CHANNEL      DAC_CHANNEL_1

  4. #define DAC_TIM          TMR6
  5. #define DAC_TRIG         DAC_TRIGGER_TMR6_TRGO

  1. //gpio config
  2. RCM_EnableAPB2PeriphClock(RCM_APB2_PERIPH_GPIOA);

  3. GPIO_Config_T GPIO_ConfigStruct;
  4. GPIO_ConfigStruct.pin  = DAC_PIN;
  5. GPIO_ConfigStruct.mode = GPIO_MODE_ANALOG;
  6. GPIO_Config(DAC_GPIO, &GPIO_ConfigStruct);

  7. //dac config
  8. RCM_EnableAPB1PeriphClock(RCM_APB1_PERIPH_DAC);

  9. DAC_Config_T DAC_ConfigStruct;
  10. DAC_ConfigStruct.trigger             = DAC_TRIG;
  11. DAC_ConfigStruct.waveGeneration      = DAC_WAVE_GENERATION_NONE;
  12. DAC_ConfigStruct.maskAmplitudeSelect = DAC_TRIANGLE_AMPLITUDE_1;
  13. DAC_ConfigStruct.outputBuffer        = DAC_OUTPUT_BUFFER_ENBALE;
  14. DAC_Config((uint32_t)DAC_CHANNEL, &DAC_ConfigStruct);
  15. DAC_DMA_Enable(DAC_CHANNEL);
  16. DAC_Enable(DAC_CHANNEL);




2.2、TMR的配置
通过用户手册可知,DAC支持多种触发源,这里我们用基础定时器TMR6去触发DAC转换。
定时器配置成最简单的向上计数即可,不需要开中断,更新事件用于触发输出。
定时器的溢出频率也就决定了DAC输出电压的变化快慢,也就是division 和 period,
这两个值越小,生成的正弦波频率越高;或者减小正弦波的采用点数,正弦波频率也能提高。
1-3 dac触发源.png

  1.     //timer config
  2.     RCM_EnableAPB1PeriphClock(RCM_APB1_PERIPH_TMR6);
  3.         
  4.     TMR_BaseConfig_T TMR_BaseConfigStruct;
  5.     TMR_BaseConfigStruct.clockDivision = TMR_CLOCK_DIV_1;
  6.     TMR_BaseConfigStruct.countMode = TMR_COUNTER_MODE_UP;
  7.     TMR_BaseConfigStruct.division  = 60-1;
  8.     TMR_BaseConfigStruct.period    = 4;
  9.     TMR_ConfigTimeBase(DAC_TIM, &TMR_BaseConfigStruct);
  10.     TMR_EnableAutoReload(DAC_TIM);
  11.     TMR_SelectOutputTrigger(DAC_TIM, TMR_TRGO_SOURCE_UPDATE);
  12.     TMR_Enable(DAC_TIM);


2.3、DMA的配置
DMA配成从内存到DAC,内存地址要自增,而DAC数据寄存器地址不变。
因为这里用的DAC是12位,所以内存和外设的数据宽度都设为半字,也就是16位。
如果想开启一次后一直产生波形,则使用循环模式;如果想每个波形周期都要手动启动,则使用正常模式。
通过手册上的请求映射表可看出对应的DMA通道是DMA2-channel3。
3 DMA的DAC请求.png

特别要注意DAC的数据寄存器地址,这里需要仔细看手册。
3-2 dac数据寄存器.png

3-3 dac地址映射.png


  1.     //DMA config
  2.     RCM_EnableAHBPeriphClock(RCM_AHB_PERIPH_DMA2);
  3.    
  4.     DMA_Config_T dmaConfig;
  5.     DMA_Reset(DAC_DMA_CHANNEL);
  6.     dmaConfig.peripheralBaseAddr = (uint32_t)DAC_DATA_ADDRESS;
  7.     dmaConfig.dir                = DMA_DIR_PERIPHERAL_DST;
  8.     dmaConfig.memoryBaseAddr     = (uint32_t)NULL;
  9.     dmaConfig.bufferSize         = 0;
  10.     dmaConfig.peripheralInc  = DMA_PERIPHERAL_INC_DISABLE;
  11.     dmaConfig.memoryInc      = DMA_MEMORY_INC_ENABLE;
  12.     dmaConfig.peripheralDataSize = DMA_PERIPHERAL_DATA_SIZE_HALFWORD;
  13.     dmaConfig.memoryDataSize = DMA_MEMORY_DATA_SIZE_HALFWORD;
  14.     dmaConfig.loopMode       = DMA_MODE_CIRCULAR;
  15.     dmaConfig.priority       = DMA_PRIORITY_HIGH;
  16.     dmaConfig.M2M            = DMA_M2MEN_DISABLE;
  17.     DMA_Config(DAC_DMA_CHANNEL, &dmaConfig);


2.4 启动DMA的传输
上面的初始化完成后,需要启动使能相关外设才会产生波形。
先设置定时器的自动加载值,这个值决定了波形的频率,
然后波形采样点数组的地址和长度赋值给DMA的地址、长度寄存器,
最后使能DAM传输。

  1. //启动传输
  2. void dac_dma_transmit(unsigned short *addr, unsigned int len, unsigned short period)
  3. {
  4.     if (len > 65535)  len = 65535;
  5.     DMA_Disable(DAC_DMA_CHANNEL);

  6.     TMR_ConfigAutoreload(DAC_TIM,period-1);

  7.     DAC_DMA_CHANNEL->CHNDATA = len;
  8.     DAC_DMA_CHANNEL->CHMADDR = (uint32_t)addr;

  9.     DMA_Enable(DAC_DMA_CHANNEL);
  10. }


三、使用与测试
为了展示最终效果,还需要制作一个正弦波的采样点数组。
方法很多,这里就通过正弦函数sin来计算一个360点的波形数据。
正弦函数计算结果有正有负,为了好看,统统平移到0V以上。
  1. #define WaveLength 360
  2. unsigned short WaveBuffer[WaveLength + 1];

  3. #include <math.h>
  4. #define PI 3.14159
  5. int generate_sine_wave(unsigned short buf[])
  6. {
  7.     double temp;
  8.     unsigned short i;

  9.     for (i = 0; i < WaveLength; i++)
  10.     {
  11.         temp = sin(i * PI / 180.0) * 2048.0;
  12.         temp = temp + 2047.0;

  13.         buf[i] = (unsigned short)temp;
  14.     }
  15. }


最后一步,调用刚才写好的的函数即可。
  1. int main(void)
  2. {
  3.     usart_printf_init();

  4.     printf("This a dac and dma demo(sine wave)\r\n");

  5.     generate_sine_wave(WaveBuffer);

  6.     dac_dma_init();
  7.     dac_dma_transmit(WaveBuffer, WaveLength, 4);


  8.     while (1)
  9.     {
  10.     }
  11. }


用示波器观察PA4脚上的波形。
4.jpg


顺便又做了个锯齿波。
5.jpg

以上方法除了用来产生各种自定义的波形,也可以用来播放wav音频。

APM32E10x_SDK_V1.3.1_dac_dma.rar (777.77 KB, 下载次数: 1)


打赏榜单

21小跑堂 打赏了 60.00 元 2025-09-25
理由:恭喜通过原创审核!期待您更多的原创作品~~

评论

基于APM32的DAC+DMA实现正弦波输出示例,通过合理的配置,完成需求电压波形的输出,代码完整,实现效果较好。  发表于 2025-9-25 16:40
 楼主| shanyuxiang 发表于 2025-9-7 16:57 | 显示全部楼层
#申请原创#  @21小跑堂
阳光爆裂 发表于 2025-9-7 18:59 | 显示全部楼层
使用Timer6来控制生成波形的频率。
学习了,谢谢楼主
xch 发表于 2025-9-7 20:45 | 显示全部楼层
能不能生成任意频率的正弦波?
xch 发表于 2025-9-7 20:49 | 显示全部楼层
把局部变量地址传给DMA寄存器用不靠谱。仅这个表演程序可用
 楼主| shanyuxiang 发表于 2025-9-7 23:43 | 显示全部楼层
xch 发表于 2025-9-7 20:45
能不能生成任意频率的正弦波?

最高频率会受DAC的转换速度限制,低于这个频率都可以
 楼主| shanyuxiang 发表于 2025-9-7 23:44 | 显示全部楼层
xch 发表于 2025-9-7 20:49
把局部变量地址传给DMA寄存器用不靠谱。仅这个表演程序可用

局部变量在函数执行后会被释放,所以这里用的是全局变量
 楼主| shanyuxiang 发表于 2025-9-7 23:47 | 显示全部楼层
阳光爆裂 发表于 2025-9-7 18:59
使用Timer6来控制生成波形的频率。
学习了,谢谢楼主

xch 发表于 2025-9-8 01:10 | 显示全部楼层
shanyuxiang 发表于 2025-9-7 23:43
最高频率会受DAC的转换速度限制,低于这个频率都可以

不超过 DAC 转换速率。 任意频率低频。
wangwu1976@ 发表于 2025-9-8 08:04 | 显示全部楼层
学习了
xch 发表于 2025-9-8 11:10 | 显示全部楼层
TMR_BaseConfigStruct.period    = 4;  这句啥意义?
 楼主| shanyuxiang 发表于 2025-9-8 13:31 | 显示全部楼层
xch 发表于 2025-9-8 11:10
TMR_BaseConfigStruct.period    = 4;  这句啥意义?

定时器的计数周期,决定了DAC多久转换一次
天鹅绒星星 发表于 2025-9-9 17:06 | 显示全部楼层
使用DMA来输出波形,这样还不影响MCU的通讯。否则,通讯就会导致输出波形变型了。
楼主,厉害!
有机会我也复刻一个
梦之一瞥 发表于 2025-9-13 10:09 | 显示全部楼层
原来这个是这样实现的啊!
以前看人家的作品 也复刻不出来
CloudKiss 发表于 2025-9-14 21:54 | 显示全部楼层
还真是的,如果把Timer6的频率设置为48000的话,输出的波形就是声音了
cooldog123pp 发表于 2025-9-27 10:30 | 显示全部楼层
xch 发表于 2025-9-7 20:45
能不能生成任意频率的正弦波?

应该是可以的吧,该表周期就行了吧,不过也得试一试。
xch 发表于 2025-9-28 09:31 | 显示全部楼层
cooldog123pp 发表于 2025-9-27 10:30
应该是可以的吧,该表周期就行了吧,不过也得试一试。

非整除分频系数的。不好随便做
魔法森林精灵 发表于 2025-9-30 20:38 | 显示全部楼层
这个DAC和DMA的结合使用确实挺巧妙的,对于学习APM32的外设操作很有帮助。
您需要登录后才可以回帖 登录 | 注册

本版积分规则

8

主题

50

帖子

1

粉丝
快速回复 在线客服 返回列表 返回顶部