当前位置: 首页 > news >正文

STM32外设DA实战-DAC + DMA 输出正弦波

STM32外设DA实战-DAC + DMA 输出正弦波模板

  • 一,方法思路
  • 二,CubeMX配置
  • 三,代码实现
    • 1,生成正弦波查找表
    • 2,代码实现

一,方法思路

DAC 的一个常见应用是产生任意波形,比如平滑的正弦波。如果让 CPU 频繁计算正弦值并手动更新 DAC 输出,会非常耗费 CPU 资源且难以保证输出频率的精确和稳定。这时,再次请出我们的老朋友:定时器 和 DMA。

思路与 ADC 的定时器触发采样类似,但方向相反:
1,生成波形查找表 (Lookup Table - LUT): 在内存中预先计算并存储一个完整周期的正弦波对应的离散数字值(例如 100 个点),形成一个数组。
2,定时器作为"节拍器": 配置一个定时器(如 TIM6 或 TIM7,它们通常有连接到 DAC 的触发输出)以固定的频率产生触发信号 (TRGO)。这个频率决定了输出正弦波的频率。
3,DAC 听从"节拍器"指挥: 配置 DAC,使其由定时器的 TRGO 事件触发转换。
4,DMA 自动"喂数据": 配置 DMA 通道,在每次接收到定时器触发信号后,自动从内存中的正弦波查找表里取出下一个样本点,写入 DAC 的数据保持寄存器 (DHR)。DMA 设置为循环模式,当读取完查找表的最后一个点后,自动回到开头继续读取,从而循环输出正弦波。
5,CPU “袖手旁观”: 一旦初始化完成,整个波形输出过程完全由 定时器 + DAC + DMA 硬件自动完成,CPU 基本无需干预。

类比: 你预先把一首歌的乐谱 (正弦波查找表) 交给一个自动翻页机 (DMA)。然后设置一个节拍器 (Timer) 控制一个演奏机器人 (DAC)。节拍器每响一次,自动翻页机就把乐谱的下一个音符喂给机器人,机器人立刻演奏出来。整个过程自动化进行,你只需要在开始时启动它们。

二,CubeMX配置

选择DAC的输出通道OUT1,将DMA配置为循环,16位模式
在这里插入图片描述
在参数设置中,选择定时器6事件溢出
在这里插入图片描述
配置定时器6
在这里插入图片描述

配置完成DAC输出,可以看到PA4引脚是DAC输出引脚,输出模拟信号
在这里插入图片描述
现在DAC可以输出模拟信号了,我们如何采集呢?
前面我们用ADC1的通道10采集滑动变阻器的模拟电压,这里我们可以打开ADC1多通道模式(再打开通道4,采集DAC输出的模拟信号)

下面IN4通道变红,说明ADC1的输入4和DAC的输出4冲突,这个引脚已经给DAC使用了,不能再同时给ADC使用了

在这里插入图片描述
既然打开了ADC的多通道,我们就可以使能多通道扫描,这样ADC就会循环扫描我们选择的通道
在这里插入图片描述
使能的ADC的多通道,就要对每个通道进行配置:首先将循环的通道改为2,然后就会自动跳出需要设置的2个通道
在这里插入图片描述
对于ADC的DMA配置
在这里插入图片描述

用同一个ADC读取两个通道数据到同一个数组buffer,我们只需读取偶数位就能得到第二个通道的数据

配置总结
1,配置定时器 (如 TIM6):
1.1启用定时器,设置时钟源。
1.2计算并设置 Prescaler (PSC) 和 Period (ARR) 以获得所需的采样点输出频率 (注意:这不是最终的正弦波频率)。
重要关系: 正弦波频率 = 定时器触发频率 / 每个周期的采样点数
例如,要输出 1kHz 的正弦波,且查找表有 100 个点 (SINE_SAMPLES = 100),则定时器的触发频率需要是 1kHz * 100 = 100kHz。 你需要根据你的系统时钟计算出能产生 100kHz 触发频率的 PSC 和 ARR 组合。
1.3将 Trigger Output (TRGO) 设置为 “Update Event”。
2,配置 DAC:
2.1启用 DAC 通道 (如 Channel 1)。
2.2设置 Output Buffer 为 Enable (通常推荐)。
2.3设置 Trigger 为触发 DAC 的那个定时器的 TRGO 事件,例如 “Timer 6 Trigger Out event”。
3,配置 DMA (在 DAC 的 DMA Settings 页):
3.1为 DAC 通道添加 DMA 请求 (Add DMA Request),选择一个 DMA 通道。
3.2设置 Direction 为 Memory to Peripheral (数据从内存流向外设)。
3.3设置 Mode 为 Circular (循环读取查找表)。
3.4设置 Peripheral 和 Memory 的 Data Width:
Peripheral 通常是 Half Word (16位),因为 DAC 数据寄存器通常只需要写入 12 位或 8 位。
Memory 通常也设置为 Half Word (16位),以匹配我们 uint16_t SineWave[] 数组的元素大小。
3.5确保 Memory 地址是递增的 (Increment Address: Memory)。
3.6Peripheral 地址不递增 (Increment Address: Peripheral - Disabled)。
4,NVIC 配置: 对于纯 DAC 输出,通常不需要启用 DAC 或 DMA 的中断。

DMA 数据宽度说明: 注意这里与 ADC 的 DMA 配置不同。因为 DAC 数据寄存器通常只需要写入有效数据位(如 12 位),并且我们的查找表是 uint16_t 类型,所以 DMA 的外设和内存宽度都设置为 Half Word (16位) 是最自然、最高效的配置。

三,代码实现

1,生成正弦波查找表

首先,我们需要用代码生成包含正弦波数据的数组。以下代码来自 adc_app.c

// --- 全局变量 --- 
#define SINE_SAMPLES 100    // 一个周期内的采样点数
#define DAC_MAX_VALUE 4095 // 12 位 DAC 的最大数字值 (2^12 - 1)uint16_t SineWave[SINE_SAMPLES]; // 存储正弦波数据的数组// --- 生成正弦波数据的函数 ---
/*** @brief 生成正弦波查找表* @param buffer: 存储波形数据的缓冲区指针* @param samples: 一个周期内的采样点数* @param amplitude: 正弦波的峰值幅度 (相对于中心值)* @param phase_shift: 相位偏移 (弧度)* @retval None*/
void Generate_Sine_Wave(uint16_t* buffer, uint32_t samples, uint16_t amplitude, float phase_shift)
{// 计算每个采样点之间的角度步进 (2*PI / samples)float step = 2.0f * 3.14159f / samples; for(uint32_t i = 0; i < samples; i++){// 计算当前点的正弦值 (-1.0 到 1.0)float sine_value = sinf(i * step + phase_shift); // 使用 sinf 提高效率// 将正弦值映射到 DAC 的输出范围 (0 - 4095)// 1. 将 (-1.0 ~ 1.0) 映射到 (-amplitude ~ +amplitude)// 2. 加上中心值 (DAC_MAX_VALUE / 2),将范围平移到 (Center-amp ~ Center+amp)buffer[i] = (uint16_t)((sine_value * amplitude) + (DAC_MAX_VALUE / 2.0f));// 确保值在有效范围内 (钳位)if (buffer[i] > DAC_MAX_VALUE) buffer[i] = DAC_MAX_VALUE;// 由于浮点计算精度问题,理论上不需要检查下限,但加上更健壮// else if (buffer[i] < 0) buffer[i] = 0; }
}

逻辑分解:

1,参数定义: samples 决定了波形的平滑度(点数越多越平滑),amplitude 控制了波形的峰值(相对于中心值),phase_shift 可以调整波形的起始相位。
2,计算步进: step 计算出每个采样点对应的角度增量。
3,循环计算: 遍历所有采样点。
使用 sinf() 函数 (单精度浮点正弦,通常比 sin() 快) 计算当前点的正弦值 (-1.0 到 1.0)。
映射与平移: 这是关键。将 sine_value 乘以 amplitude 得到幅度缩放后的值。然后加上 DAC_MAX_VALUE / 2.0f (中心值,大约是 2047.5),将波形整体向上平移,使其中心对准 DAC 输出范围的中点。最终结果被转换为 uint16_t
钳位 (Clamping) (可选但推荐): 由于浮点计算可能存在微小误差,最好检查计算结果是否超出 DAC 的有效范围 (0 ~ 4095),如果超出则强制限制在边界值。

2,代码实现

完成配置后,只需要在代码中调用生成函数和启动函数即可:

// --- 初始化函数 (在 main 函数或外设初始化后调用) ---
void dac_sin_init(void)
{// 1. 生成正弦波查找表数据//     amplitude = DAC_MAX_VALUE / 2 产生最大幅度的波形 (0-4095)Generate_Sine_Wave(SineWave, SINE_SAMPLES, DAC_MAX_VALUE / 2, 0.0f);// 2. 启动触发 DAC 的定时器 (例如 TIM6)HAL_TIM_Base_Start(&htim6); // htim6 是 TIM6 的句柄// 3. 启动 DAC 通道并通过 DMA 输出查找表数据//    hdac: DAC 句柄//    DAC_CHANNEL_1: 要使用的 DAC 通道//    (uint32_t *)SineWave: 查找表起始地址 (HAL 库常需 uint32_t*)//    SINE_SAMPLES: 查找表中的点数 (DMA 传输单元数)//    DAC_ALIGN_12B_R: 数据对齐方式 (12 位右对齐)HAL_DAC_Start_DMA(&hdac, DAC_CHANNEL_1, (uint32_t *)SineWave, SINE_SAMPLES, DAC_ALIGN_12B_R);
}// --- 无需后台处理任务 --- 
// 一旦 dac_sin_init 调用完成,硬件会自动循环输出波形
// adc_task() 中可以移除 dac 相关的处理

逻辑分解:

1,Generate_Sine_Wave(…): 调用我们之前定义的函数,填充 SineWave 数组。这里设置 amplitudeDAC_MAX_VALUE / 2,使得生成的波形能覆盖 DAC 的整个输出范围 (近似 0V 到 Vref)。
2,HAL_TIM_Base_Start(&htim6);: 启动作为触发源的定时器。定时器会按照预设频率开始产生 TRGO 信号。
3,HAL_DAC_Start_DMA(…): 这是启动 DAC 输出的关键。它会:
3.1启用指定的 DAC 通道 (DAC_CHANNEL_1)。
3.2配置并启动 DMA 通道,使其源地址指向 SineWave 数组的开头,目标地址指向 DAC 的数据寄存器。
3.3DMA 会在每次接收到定时器触发信号时,从 SineWave 数组读取一个 uint16_t 值(因为配置为 Half Word),根据指定的对齐方式 (DAC_ALIGN_12B_R - 12 位右对齐) 写入 DAC 数据寄存器。
3.4由于 DMA 设置为循环模式,读取完 SINE_SAMPLES 个点后会自动回到数组开头,无限循环。

之后,无需 CPU 干预(不用循环遍历,只放在while循环之前初始化一次),DAC 就会在定时器的精确控制下,通过 DMA 持续输出流畅的正弦波信号了!
在这里插入图片描述

相关文章:

  • 打卡Day28
  • 1.2 C++第一个程序
  • JavaScript splice() 方法
  • C语言斐波那契数列
  • 计算机视觉与深度学习 | Python实现EMD-CNN-LSTM时间序列预测(完整源码、数据、公式)
  • 【前端基础】11、CSS的属性特性(继承、层叠、元素类型、隐藏元素的四种方式)
  • 松下SMT贴片机选型与高效应用指南
  • webman用nginx代理静态json文件的异步跨域
  • 2025年数字孪生技术最新应用案例:跨领域实践与技术趋势
  • c++成员函数返回类对象引用和直接返回类对象的区别
  • java中的方法详解
  • Seata源码—5.全局事务的创建与返回处理二
  • Linux中的进程
  • Java-Collections类高效应用的全面指南
  • 如何安装双系统?即windows已经安装,如何安装ubuntu 22.04LTS
  • ​哈夫曼树(Huffman Tree)
  • 【Java ee初阶】HTTP(4)
  • 咖啡叶子病害检测数据集VOC+YOLO格式1468张4类别均为单叶子
  • 二进制与十进制互转的方法
  • Linux的静态库 共享库 进程 主函数的参数
  • 上海一保租房社区亮相,首批546套房源可拎包入住
  • 俄乌官员即将在土耳其会谈,外交部:支持俄乌开启直接对话
  • 一船明月过沧州:为何这座城敢称“文武双全”?
  • 标普500指数连涨四日,大型科技股多数下跌
  • 吉利汽车一季度净利润大增264%,称整合极氪后实现整体效益超5%
  • 泽连斯基:乌代表团已启程,谈判可能于今晚或明天举行