什麼是 DAC?
DAC(Digital-to-Analog Converter,數位類比轉換器)是 ADC 的反向操作——將數位數值轉換為連續的類比電壓訊號。在嵌入式系統中,DAC 廣泛應用於:
- 波形產生:正弦波、鋸齒波、三角波(測試儀器、訊號激勵)
- 音頻輸出:播放 WAV/MP3 音訊(搭配放大器驅動喇叭)
- 類比控制:可程式電源、VCO 控制、馬達速度
- 感測器模擬:產生特定的類比訊號供 ADC 測試
與 PWM 相比,DAC 輸出平滑無漣波,不需要外部濾波電路。
DAC 關鍵規格
| 規格 | 說明 | 典型值 |
|---|---|---|
| 解析度 (Resolution) | 數位輸入的位元數,決定電壓階數 | 8-bit (256階), 12-bit (4096階) |
| LSB (Least Significant Bit) | 最小可變化的電壓步進 = Vref / 2ⁿ | 3.3V/256=12.9mV (8-bit) 3.3V/4096=0.8mV (12-bit) |
| 建立時間 (Settling Time) | 數值更新到輸出穩定的時間 | ~10 μs(一般) |
| 更新率 (Update Rate) | 每秒可更新多少次輸出 | ~1 Msps(STM32 DAC) |
| 輸出範圍 | DAC 可輸出的最低/最高電壓 | 0V ~ Vref (通常=VDD) |
| DNL/INL | 非線性誤差(差分/積分) | ±1 LSB 以內 |

以 Vref=3.3V 為例:8-bit DAC 的 LSB 是 12.9mV,12-bit 則是 0.8mV——解析度越高波形越平滑。
DAC 轉換時序
典型的 DAC 寫入流程:
- CPU 寫入資料到 DOR(Data Output Register)
- 若硬體觸發已啟用,等待觸發事件(或立即更新)
- DAC 核心開始轉換,輸出電壓逐步建立
- 經過 Settling Time 後,輸出穩定
- 一些 MCU 有 LDAC 腳位,可同時更新多通道 DAC
常見輸出波形
透過改變 DAC 輸出值,可以產生各種波形。最常見的四種:
- 正弦波 (Sine):用查表法(LUT)預先計算 256 點 sin 值
- 鋸齒波 (Sawtooth):計數器從 0 遞增到最大值後歸零
- 三角波 (Triangle):計數器先增後減
- 方波 (Square):直接切換高低電平(但用 GPIO 更方便)

ESP32 DAC 實作
ESP32 內建2 個 8-bit DAC 通道(GPIO25=Channel1, GPIO26=Channel2),輸出電壓範圍 0V~3.3V(Vref)。
Arduino:基本使用
// ESP32 DAC - 基本電壓輸出
#define DAC_PIN 25 // DAC1
void setup() {
Serial.begin(115200);
// DAC 初始化(Arduino 中不需額外設定)
}
void loop() {
// 輸出 1.65V(中點電壓)
// dacWrite(value): value=0→0V, value=255→3.3V
dacWrite(DAC_PIN, 128); // 128/255 * 3.3V ≈ 1.65V
delay(2000);
// 輸出 0V
dacWrite(DAC_PIN, 0);
delay(2000);
// 輸出 3.3V
dacWrite(DAC_PIN, 255);
delay(2000);
}
ESP32:正弦波產生器
// ESP32 DAC - 正弦波產生器(查表法)
#define DAC_PIN 25
// 256 點正弦波查找表(8-bit 值)
const uint8_t sine_table[256] = {
#include "sine_table.h" // 或直接內建陣列
};
// 手動產生正弦表
void generate_sine_table() {
Serial.println("const uint8_t sine_table[256] = {");
for (int i = 0; i < 256; i++) {
float v = sin(2.0 * PI * i / 256.0);
int dac_val = (int)((v + 1.0) * 127.5); // 映射到 0~255
if (i % 16 == 0) Serial.println();
Serial.printf("%3d,", dac_val);
}
Serial.println("}");
}
void setup() {
Serial.begin(115200);
// generate_sine_table(); // 第一次執行後複製結果
}
void loop() {
static int idx = 0;
// 直接輸出正弦值
dacWrite(DAC_PIN, sine_table[idx]);
idx = (idx + 1) % 256;
delayMicroseconds(40); // ~100 Hz 正弦波 (256*40μs≈10ms)
}
ESP-IDF:DAC + DMA 輸出
// ESP-IDF - DAC + DMA 連續波形輸出
#include "driver/dac.h"
#include "driver/dac_common.h"
#define DAC_CHANNEL DAC_CHANNEL_1 // GPIO25
// 8-bit 正弦波資料 (256 點)
uint8_t waveform[256];
void app_main() {
// 初始化正弦波資料
for (int i = 0; i < 256; i++) {
float v = sin(2.0 * M_PI * i / 256.0);
waveform[i] = (uint8_t)((v + 1.0) * 127.5);
}
// 啟用 DAC
dac_output_enable(DAC_CHANNEL);
// 連續輸出(DMA 硬體驅動,CPU 幾乎不用管)
while (1) {
for (int i = 0; i < 256; i++) {
dac_output_voltage(DAC_CHANNEL, waveform[i]);
esp_rom_delay_us(40);
}
}
}
STM32 DAC 實作
STM32 的 DAC 解析度高(12-bit),且支援 DMA + TIM 觸發,可以做到真正的「自動輸出」。
STM32 HAL:基本 DAC 輸出
// STM32 HAL - DAC 基本輸出(STM32F4)
// PA4 = DAC_OUT1, PA5 = DAC_OUT2
void MX_DAC_Init(void)
{
DAC_ChannelConfTypeDef sConfig = {0};
hdac.Instance = DAC;
// 初始化 DAC
HAL_DAC_Init(&hdac);
// 設定 Channel 1
sConfig.DAC_Trigger = DAC_TRIGGER_NONE; // 軟體觸發
sConfig.DAC_OutputBuffer = DAC_OUTPUTBUFFER_ENABLE;
HAL_DAC_ConfigChannel(&hdac, &sConfig, DAC_CHANNEL_1);
}
// 輸出指定電壓
void dac_set_voltage(uint16_t mv)
{
// 計算 DAC 碼值: Data = (mv * 4095) / 3300
// 3.3V Vref, 12-bit
uint32_t dac_val = (uint32_t)mv * 4095 / 3300;
if (dac_val > 4095) dac_val = 4095;
// 設定 DAC 輸出值
HAL_DAC_SetValue(&hdac, DAC_CHANNEL_1, DAC_ALIGN_12B_R, dac_val);
HAL_DAC_Start(&hdac, DAC_CHANNEL_1);
}
// 使用範例
int main()
{
HAL_Init();
SystemClock_Config();
MX_DAC_Init();
HAL_DAC_Start(&hdac, DAC_CHANNEL_1);
while (1)
{
// 輸出 1.65V
HAL_DAC_SetValue(&hdac, DAC_CHANNEL_1,
DAC_ALIGN_12B_R, 2048);
HAL_Delay(1000);
// 輸出 0.5V
HAL_DAC_SetValue(&hdac, DAC_CHANNEL_1,
DAC_ALIGN_12B_R, 621);
HAL_Delay(1000);
// 輸出 3.0V
HAL_DAC_SetValue(&hdac, DAC_CHANNEL_1,
DAC_ALIGN_12B_R, 3723);
HAL_Delay(1000);
}
}
STM32:DAC + TIM + DMA 自動正弦波
// STM32 - DAC + TIM + DMA 自動連續輸出(無 CPU 干預)
// 這是最有效率的方式
#define WAVE_POINTS 256
uint16_t sine_wave_12bit[WAVE_POINTS];
// 產生 12-bit 正弦波資料
void generate_sine_12bit()
{
for (int i = 0; i < WAVE_POINTS; i++) {
float v = sin(2.0 * M_PI * i / WAVE_POINTS);
sine_wave_12bit[i] = (uint16_t)((v + 1.0) * 2047.5);
}
}
// 設定 TIM6 觸發 DAC(自動更新)
void MX_DAC_DMA_Init()
{
// ... DAC 初始化 ...
// 設定 DAC 觸發源為 TIM6
sConfig.DAC_Trigger = DAC_TRIGGER_T6_TRGO;
HAL_DAC_ConfigChannel(&hdac, &sConfig, DAC_CHANNEL_1);
// 啟動 DAC + DMA(循環模式)
HAL_DAC_Start_DMA(&hdac, DAC_CHANNEL_1,
(uint32_t*)sine_wave_12bit,
WAVE_POINTS, DAC_ALIGN_12B_R);
// 啟動 TIM6(觸發頻率決定波形頻率)
// 若 TIM6 頻率 = 25600 Hz, 則正弦頻率 = 100 Hz
HAL_TIM_Base_Start(&htim6);
}
// 在 HAL_DAC_ConvCpltCallbackCh1 中可監控 DMA 完成
void HAL_DAC_ConvCpltCallbackCh1(DAC_HandleTypeDef* hdac)
{
// DMA 完成一個循環(可選)
}
實戰:數位電源供應器
結合 DAC + ADC 就可以做出可程式電源供應器:
// ESP32 - 可程式電源(DAC 控制 + ADC 回讀)
#include <driver/dac.h>
#include <driver/adc.h>
#define DAC_OUT 25 // 控制輸出電壓
#define ADC_FB 34 // 回讀實際電壓
void setup() {
Serial.begin(115200);
dac_output_enable(DAC_CHANNEL_1);
adc1_config_width(ADC_WIDTH_BIT_12);
adc1_config_channel_atten(ADC1_CHANNEL_6, ADC_ATTEN_DB_11);
}
void set_voltage(float mv) {
// 換算 DAC 值(假設 OP Amp 電路增益 = 2)
uint8_t dac_val = (uint8_t)(mv * 255 / 6600);
dac_output_voltage(DAC_CHANNEL_1, dac_val);
}
float read_output() {
int raw = adc1_get_raw(ADC1_CHANNEL_6);
return raw * 3.3 * 2 / 4095.0 * 1000; // mV
}
void loop() {
// 設定 2.5V 輸出
set_voltage(2500);
delay(100);
float actual = read_output();
Serial.printf("設定: 2500mV, 實際: %.0fmV\n", actual);
delay(2000);
}
實戰:簡單音頻播放器

// ESP32 - 簡易 WAV 播放器(DAC + SD + I2S 或 DAC)
// 實際使用建議用 I2S + MAX98357,此處示範 DAC 直出
#define DAC_PIN 25
// 8-bit PCM 資料直接餵給 DAC
void play_pcm(const uint8_t* data, uint32_t len, uint32_t sample_rate)
{
uint32_t delay_us = 1000000 / sample_rate;
for (uint32_t i = 0; i < len; i++) {
dacWrite(DAC_PIN, data[i]);
delayMicroseconds(delay_us);
}
}
// WAV 檔案格式
typedef struct __attribute__((packed)) {
char riff[4]; // "RIFF"
uint32_t fileSize;
char wave[4]; // "WAVE"
char fmt[4]; // "fmt "
uint32_t fmtSize; // 16
uint16_t audioFormat; // 1 = PCM
uint16_t numChannels; // 1 = Mono
uint32_t sampleRate; // 8000, 22050, 44100
uint32_t byteRate;
uint16_t blockAlign;
uint16_t bitsPerSample; // 8 or 16
char data[4]; // "data"
uint32_t dataSize;
} wav_header_t;
// 從 SD 讀取 WAV 並播放(簡化版)
void play_wav(const char* filename) {
// 1. 開啟 WAV 檔案
// 2. 解析 Header
// 3. 取得 PCM 資料位址和長度
// 4. 用 play_pcm() 播放
// 5. 關閉檔案
Serial.println("播放 WAV (需實作 SD 讀取)");
}
ESP32 vs STM32 DAC 對照表
| 功能 | ESP32 | STM32 (F4/H7) |
|---|---|---|
| 解析度 | 8-bit | 12-bit |
| 通道數 | 2 個 | 2 個(獨立/雙通道模式) |
| DMA 支援 | 有限(需連續 CPU 寫入) | 完整(TIM+DMA 自動輸出) |
| 內建 Buffer | 無 | 有(Output Buffer) |
| 雙通道同步 | 各自獨立 | 支援同時更新(LDAC) |
| 波形產生 | 軟體計算 | 硬體三角波產生器(部分型號) |
| 更新率 | ~200 kHz (軟體) | ~1 Msps (DMA) |
總結:ESP32 的 DAC 適合簡單電壓輸出和低頻波形,STM32 的 DAC 適合高精度、高頻率、自動連續輸出場景。
應用場景
| 應用 | 所需解析度 | 更新率 | 適合 MCU |
|---|---|---|---|
| LED 亮度控制 | 8-bit 即可 | ~100 Hz | ESP32 |
| 音頻輸出 | 8~16-bit | 8~48 kHz | ESP32/I2S 或 STM32 |
| 可程式電源 | 10~12-bit | ~10 kHz | STM32 |
| 訊號產生器 | 12-bit | ~1 MHz | STM32 + DMA |
| 馬達控制 | 8~10-bit | ~20 kHz | 兩者皆可 |
總結
DAC 是嵌入式系統中重要的類比輸出介面。雖然 PWM + LPF 可以替代部分 DAC 功能,但 DAC 的平滑輸出、無漣波、高速更新等特性在音頻、訊號產生、精密控制等場景中不可取代。
ESP32 的 8-bit DAC 輕巧易用,適合簡單應用;STM32 的 12-bit DAC + DMA 自動輸出則適合高性能波形產生。選擇哪個 MCU,取決於你需要的解析度、更新率和自動化程度。
文章評論