0x6A Logbook

0x6A Logbook
Shi6a的筆記本
  1. 首頁
  2. 程式開發
  3. 正文

DAC 數位類比轉換器完整教學:從原理到 ESP32/STM32 波形產生實作

2026 年 6 月 9 日 27點熱度 0人點贊 0條評論

什麼是 DAC?

DAC(Digital-to-Analog Converter,數位類比轉換器)是 ADC 的反向操作——將數位數值轉換為連續的類比電壓訊號。在嵌入式系統中,DAC 廣泛應用於:

  • 波形產生:正弦波、鋸齒波、三角波(測試儀器、訊號激勵)
  • 音頻輸出:播放 WAV/MP3 音訊(搭配放大器驅動喇叭)
  • 類比控制:可程式電源、VCO 控制、馬達速度
  • 感測器模擬:產生特定的類比訊號供 ADC 測試

與 PWM 相比,DAC 輸出平滑無漣波,不需要外部濾波電路。

DAC vs PWM 輸出比較

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 以內

DAC 解析度比較

以 Vref=3.3V 為例:8-bit DAC 的 LSB 是 12.9mV,12-bit 則是 0.8mV——解析度越高波形越平滑。

DAC 轉換時序

DAC 轉換時序

典型的 DAC 寫入流程:

  1. CPU 寫入資料到 DOR(Data Output Register)
  2. 若硬體觸發已啟用,等待觸發事件(或立即更新)
  3. DAC 核心開始轉換,輸出電壓逐步建立
  4. 經過 Settling Time 後,輸出穩定
  5. 一些 MCU 有 LDAC 腳位,可同時更新多通道 DAC

常見輸出波形

DAC 常見輸出波形

透過改變 DAC 輸出值,可以產生各種波形。最常見的四種:

  • 正弦波 (Sine):用查表法(LUT)預先計算 256 點 sin 值
  • 鋸齒波 (Sawtooth):計數器從 0 遞增到最大值後歸零
  • 三角波 (Triangle):計數器先增後減
  • 方波 (Square):直接切換高低電平(但用 GPIO 更方便)

8-bit vs 12-bit DAC 波形比較

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);
}

實戰:簡單音頻播放器

DAC 音頻播放管線

// 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,取決於你需要的解析度、更新率和自動化程度。

標籤: 教學
最後更新:2026 年 6 月 9 日

shi6a

這個人很懶,什麼都沒留下

點贊
< 上一篇
下一篇 >

文章評論

razz evil exclaim smile redface biggrin eek confused idea lol mad twisted rolleyes wink cool arrow neutral cry mrgreen drooling persevering
取消回覆

COPYRIGHT © 2026 0x6A Logbook. ALL RIGHTS RESERVED.

Theme Kratos Made By Seaton Jiang