10. DAC 输出
📝本节您将了解 DAC(数模转换)的工作原理,学习如何配置 MSPM0G3519 的 DAC 外设,实现输出指定大小的模拟电压。
🏆本章目标
1️⃣ 理解 DAC 的工作原理和输出电压计算
2️⃣ 了解 MSPM0G3519 的 DAC 外设资源
3️⃣ 通过 SysConfig 配置 DAC 输出
4️⃣ 实现输出固定电压和简易正弦波形
10.1 DAC 基本概念
DAC(Digital-to-Analog Converter,数模转换器)是 ADC 的逆操作——将数字值转换为对应的模拟电压输出。
MCU 内部的运算结果是数字量,某些应用需要将其转换为模拟信号输出到外部电路(如音频信号、控制电压、波形发生等),这就需要 DAC。
输出电压计算
输出电压 = DAC数字值 × Vref / (2^分辨率 - 1)(注:MSPM0 DAC 采用直二进制模式,满量程值 0xFFF 对应 Vref)
对于 12-bit DAC,Vref = 3.3V:
输出电压 = DAC值 × 3.3 / 4095| DAC 数字值 | 输出电压 |
|---|---|
| 0 | 0V |
| 1024 | ≈ 0.825V |
| 2048 | ≈ 1.65V |
| 4095 | 3.3V |
反向计算(目标电压 → DAC 值)
DAC值 = 目标电压 × 4095 / 3.3例如需要输出 1.5V:DAC 值 = 1.5 × 4095 / 3.3 ≈ 1861
10.2 MSPM0G3519 DAC 资源
MSPM0G3519 内置 12-bit DAC 模块:
| 参数 | 规格 |
|---|---|
| 分辨率 | 12-bit(0~4095) |
| 输出通道 | 1 路 |
| 参考电压 | VDDA(取决于板级供电,天巧星为 3.3V)/ 内部参考(1.4V / 2.5V) |
| 输出引脚 | PA15 |
| 建立时间 | 数微秒级 |
| 输出驱动 | 内置缓冲运放(可选) |
DAC vs PWM 模拟输出
DAC 和 PWM+滤波都可以实现模拟电压输出,但有本质区别:
- DAC:真正的模拟输出,无纹波,响应快,适合波形生成
- PWM+RC 滤波:数字方波经低通滤波后近似模拟,有纹波残留,适合简单的直流电平控制
10.3 SysConfig 配置 DAC
10.3.1 添加 DAC 外设
- 打开
.syscfg文件 - 左侧面板找到 DAC12 选项卡
- 点击 ADD 添加 DAC 实例
10.3.2 配置参数
| 参数 | 设置值 | 说明 |
|---|---|---|
| Name | DAC0 | 实例名称 |
| Output Resolution | 12-bit resolution | 12 位分辨率 |
| Positive Voltage Reference | VDDA | 正参考电压 3.3V |
| Negative Voltage Reference | VSSA | 负参考电压接模拟地 |
| Enable DAC Output | 勾选 | 使能 DAC 输出 |
| Input Data Format | Binary representation | 直二进制格式 |
| DAC And Output Amplifier | Output and Input amplifiers on | 使能输入/输出缓冲运放 |
| DAC12 Output Pin | PA15/8 | 输出引脚为 PA15 |
| DAC12 Peripheral | DAC0 | 外设实例号 |
保存并编译。
输出缓冲
DAC And Output Amplifier 选项使能内置运放缓冲,可以增强输出驱动能力,使其能直接驱动较大负载。如果 DAC 输出仅接到高阻抗输入(如 ADC 输入、运放输入),可以不使能以降低功耗。

10.4 DAC 相关函数
常用 DAC DriverLib 函数
设置 DAC 输出值:
void DL_DAC12_output12(DAC12_Regs *dac, uint32_t value);设置 12-bit DAC 输出值(0~4095)。
使能 DAC 输出:
void DL_DAC12_enable(DAC12_Regs *dac);禁止 DAC 输出:
void DL_DAC12_disable(DAC12_Regs *dac);10.5 实验一:输出固定电压
输出 1.65V(VDD 的一半):
#include "ti_msp_dl_config.h"
int main(void)
{
SYSCFG_DL_init();
// 使能 DAC
DL_DAC12_enable(DAC0);
// 输出 1.65V:4095 × 1.65 / 3.3 ≈ 2047.5,四舍五入取 2048
DL_DAC12_output12(DAC0, 2048);
while (1) {
// DAC 输出后无需反复更新,让 CPU 进入低功耗等待
__WFI(); // Wait For Interrupt,未使能中断时仅短暂唤醒
}
}2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
使用万用表测量 PA15 引脚电压,应接近 1.65V。
可变电压输出
通过串口接收指令,动态调整 DAC 输出电压:
串口配置
本实验依赖 UART0 串口通信,SysConfig 中需同时配置 DAC12 和 UART(UART 配置参考第 5 章串口通信,中断使能宏为 UART_0_INST_INT_IRQN)。
#include "ti_msp_dl_config.h"
#include <stdio.h>
#include <stdlib.h>
void uart_send_string(const char *str)
{
while (*str) {
while (DL_UART_Main_isBusy(UART_0_INST));
DL_UART_Main_transmitData(UART_0_INST, *str++);
}
}
volatile uint8_t rx_buf[8];
volatile uint8_t rx_idx = 0;
volatile uint8_t rx_done = 0;
// rx_done 和 rx_idx 为单字节变量,在 M0+ 内核中读写不会被中断打断
int main(void)
{
SYSCFG_DL_init();
NVIC_EnableIRQ(UART_0_INST_INT_IRQN);
DL_DAC12_enable(DAC0);
char buf[64];
uart_send_string("DAC Ready. Send voltage in mV (0-3300):\r\n");
while (1) {
if (rx_done) {
rx_done = 0;
rx_buf[rx_idx] = '\0';
// 解析输入的毫伏值
uint32_t mv = atoi((char *)rx_buf);
if (mv > 3300) mv = 3300; // 上限钳位
// 换算为 DAC 值
uint16_t dac_val = (uint16_t)(mv * 4095 / 3300);
DL_DAC12_output12(DAC0, dac_val);
sprintf(buf, "Set: %u mV (DAC: %u)\r\n", (unsigned int)mv, dac_val);
uart_send_string(buf);
rx_idx = 0;
}
}
}
void UART0_IRQHandler(void)
{
switch (DL_UART_Main_getPendingInterrupt(UART_0_INST)) {
case DL_UART_MAIN_IIDX_RX: {
uint8_t ch = DL_UART_Main_receiveData(UART_0_INST);
if (ch == '\r' || ch == '\n') {
rx_done = 1;
} else if (rx_idx < 7) {
rx_buf[rx_idx++] = ch;
}
break;
}
default:
break;
}
}2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
10.6 实验二:输出正弦波
利用 DAC 快速更新输出值,可以生成简单的波形。以下示例生成正弦波(使用查找表避免实时计算三角函数):
#include "ti_msp_dl_config.h"
// 64 点正弦波查找表(中心值 2048,幅值 2047)
// 公式:table[i] = 2048 + 2047 × sin(2π × i / 64)
// 预计算避免运行时调用三角函数,节省 CPU 时间
const uint16_t sin_table[64] = {
2048, 2249, 2447, 2642, 2831, 3013, 3185, 3347,
3495, 3630, 3750, 3853, 3939, 4007, 4056, 4085,
4095, 4085, 4056, 4007, 3939, 3853, 3750, 3630,
3495, 3347, 3185, 3013, 2831, 2642, 2447, 2249,
2048, 1847, 1649, 1454, 1265, 1083, 911, 749,
601, 466, 346, 243, 157, 89, 40, 11,
1, 11, 40, 89, 157, 243, 346, 466,
601, 749, 911, 1083, 1265, 1454, 1649, 1847
};
int main(void)
{
SYSCFG_DL_init();
DL_DAC12_enable(DAC0);
uint8_t idx = 0;
while (1) {
DL_DAC12_output12(DAC0, sin_table[idx]);
idx = (idx + 1) & 0x3F; // 0~63 循环
// 每点耗时 = delay_cycles(500) 约 15.6μs + 循环开销约 1μs ≈ 16.5μs
// 完整周期 = 64 点 × 16.5μs ≈ 1.06ms → 频率约 940Hz
delay_cycles(500); // 500 / 32MHz = 15.6μs
}
}2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
波形频率计算
波形频率 = 1 / (采样点数 × 每点间隔时间)64 个采样点,每点间隔约 16.5μs:
- 周期 = 64 × 16.5μs ≈ 1.06ms
- 频率 ≈ 940Hz(接近 1kHz)
调整 delay_cycles 的参数可改变输出频率。更快的更新速率需配合定时器中断或 DMA。
验证正弦波
如果有示波器,可以观察 PA15 引脚的波形,应看到近似正弦波。没有示波器也可通过以下方法验证:
- 用万用表交流档测量 PA15,读数应在 0~3.3V 范围跳动
- 或继续学习下一节的 DAC + ADC 联动实验,用板载 ADC 验证
10.7 DAC + ADC 联动验证
用杜邦线将 PA15(DAC 输出)连接到 PA14(ADC 输入),即可让板子自己验证自己的 DAC 输出:
(注意:MSPM0G3519 的 ADC1 Channel 0 内部直连 DAC_OUT,但本示例使用 ADC0 Channel 12(PA14)便于接线和 SysConfig 配置。)
#include "ti_msp_dl_config.h"
#include <stdio.h>
void uart_send_string(const char *str)
{
while (*str) {
while (DL_UART_Main_isBusy(UART_0_INST));
DL_UART_Main_transmitData(UART_0_INST, *str++);
}
}
int main(void)
{
SYSCFG_DL_init();
// 使能 ADC 外设中断(仅外设级,不开 NVIC,用 polling 轮询)
DL_ADC12_enableInterrupt(ADC12_0_INST, DL_ADC12_INTERRUPT_MEM0_RESULT_LOADED);
char buf[64];
uint16_t dac_values[] = {0, 1024, 2048, 3072, 4095};
while (1) {
for (int i = 0; i < 5; i++) {
// DAC 输出
DL_DAC12_output12(DAC0, dac_values[i]);
delay_cycles(3200000); // 等待 100ms 稳定
// ADC 四步轮询读取(参见第 9 章 ADC)
// ① startConversion:启动一次转换
// ② 轮询 IIDX:等待 MEM0_RESULT_LOADED 标志
// ③ getMemResult:读取转换结果
// ④ enableConversions:重新使能,准备下次转换
DL_ADC12_startConversion(ADC12_0_INST);
while (DL_ADC12_getPendingInterrupt(ADC12_0_INST) != DL_ADC12_IIDX_MEM0_RESULT_LOADED) {
// 等待转换完成
}
uint16_t adc_val = DL_ADC12_getMemResult(ADC12_0_INST, DL_ADC12_MEM_IDX_0);
DL_ADC12_enableConversions(ADC12_0_INST);
uint32_t dac_mv = (uint32_t)dac_values[i] * 3300 / 4095;
uint32_t adc_mv = (uint32_t)adc_val * 3300 / 4095;
sprintf(buf, "DAC: %u (%u mV) -> ADC: %u (%u mV)\r\n",
dac_values[i], (unsigned int)dac_mv, adc_val, (unsigned int)adc_mv);
uart_send_string(buf);
}
uart_send_string("---\r\n");
delay_cycles(32000000); // 1 秒后重复
}
}2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
DAC 输出值和 ADC 读数应非常接近(误差通常在几个 LSB 以内)。
10.8 知识总结
| 内容 | 说明 |
|---|---|
| DAC 功能 | 数字值 → 模拟电压 |
| 分辨率 | 12-bit(0~4095) |
| 输出范围 | 0V ~ Vref(3.3V) |
| 电压计算 | V = DAC值 × 3.3 / 4095 |
| 反向计算 | DAC值 = V × 4095 / 3.3 |
| 波形生成 | 查找表 + 定时更新 |
| 输出缓冲 | 驱动低阻负载时需使能 |
DAC 使你能够输出精确可控的模拟电压。下一章将学习 DMA(直接内存访问),实现数据在外设和内存之间的自动搬运,进一步释放 CPU 资源。