ESP32 DAC – Lập trình tạo âm thanh với Arduino IDE

Trong bài viết hôm nay, chúng ta sẽ cùng tìm hiểu về ESP32 DAC (viết tắt của Digital to Analog Converter). Đây là một công cụ đắc lực nếu bạn muốn làm các dự án liên quan đến sóng hình sin hoặc các dự án liên quan đến âm thanh.

ESP32 DAC là gì?

Chắc hẳn bạn khá quen thuộc với analog-to-digital converters (ADC), đây là bộ phận rất thường thấy trong các bộ vi điều khiển lập trình hiện nay. Với ADC, bạn có thể nhập thông tin điện áp analog và nhận về kết quả digital tương ứng.

Ngược với ADC,DAC cho phép chuyển đổi tín hiệu Digital thành tín hiệu Analog. Trên ESP32 có hai kênh DAC, mỗi kênh có độ phân giải 8 bit và tốc độ lấy mẫu lên đến 16 MH, chúng ta có thể gọi chúng là kênh 1 và kênh 2:

Tìm hiểu về ESP32 DAC là gì?

Các chân ESP32 ADC có thể dùng trong nhiều chức năng khác nhau, nổi bật như:

  • Tạo ra âm thanh: Tạo ra âm thanh cho các ứng dụng như loa, tai nghe, hoặc các thiết bị âm thanh khác.
  • Điều khiển thiết bị analog: Điều khiển các thiết bị analog như động cơ, đèn
  • Tạo ra các tín hiệu khác: Tạo ra các tín hiệu điều khiển, tín hiệu đo lường

Tuy nhiên, ESP32 DAC chỉ là thiết bị 8bit, phù hợp với các dự án phát âm thanh cơ bản, chẳng hạn như âm thanh trong cuộc gọi điện thoại. Còn trong hầu hết các trường hợp cần chất lượng âm thanh cao hơn, bạn nên sử dụng thêm I2S kèm với ESP32.

Chân ESP32 DAC sử dụng nguồn điện áp 3,3V. Tuy nhiên, cần lưu ý rằng khi đầu vào đặt thành 0 thì điện áp của chân DAC sẽ không giảm xuống 0, tương tự như khi đầu vào là 255 thì điện áp DAC không tăng lên 3,3V. Bạn cần để ý vấn đề này khi cần dùng chân ESP32 để điểu khiển các module yêu cầu nguồn điện áp 0V hoặc 3,3V.

Cách hoạt động của ESP32 DAC

ESP32 DAC có 3 chế độ hoạt động khác nhau, cùng tìm hiểu từng chế độ nhé:

Điện áp đầu ra trực tiếp

Đây là chế độ đơn giản nhất, chúng ta chỉ cần ghi đầu ra Digital (8bit) và DAC. Khi đó, chân DAC sẽ đổi chúng thành điện áp Analog tương ứng mà bạn đã ghi. Các mức điện áp này sẽ không thay đổi, trừ khi bạn ghi một tín hiệu Digital mới. Bạn có thể gọi chúng là chế độ “One-Shot” hoặc là “Direct”.

Điện áp đầu ra liên tục

Các kênh ESP32 DAC cũng có thể truy cập vào bộ nhớ trực tiếp DMA để liên tục tạo ra các dạng sóng Analog ở đầu ra, mà không cần quá nhiều khả năng xử lý của CPU. Quá trình này có thể diễn ra theo 3 cách khác nhau:

  1. Ghi đồng bộ: Dữ liệu trong DMA được truyền liên tục và đồng bộ đến các kênh ESP32 DAC. Khi đó, bạn không thể gửi thêm một dữ liệu nào khác cho đến khi quá trình gửi được hoàn tất. Đây là cách nhanh nhất để phát lại một đoạn âm thanh.
  2. Ghi không đồng bộ: Dữ liệu trong DMA được gửi một cách lần lượt đến DAC, không đồng bọ. Khi đó, hệ thống không có chặn các hoạt động khác.
  3. Ghi theo chu kỳ: Dữ liệu được nạp vào bộ nhớ DMA một lần duy nhất, và chúng sẽ được lặp đi lặp lại liên tục và gửi từng dữ liệu đến DAC cho đến khi kết thúc quá trình này. Sau đó, chúng sẽ lại lặp lại bộ đệm dữ liệu từ đầu mà không cần tải lại bộ đệm. Ứng dụng này khá phù hợp để tạo ra các dạng sóng hình sin.

Tạo sóng Cosine

Trên ESP32 DAC có một bộ tạo sóng Cosine tích hợp, cho phép bạn gửi tín hiệu đầu ra đến một hoặc thậm chí là 2 kênh DAC. Do đó, chúng ta có thể kiểm soát tần số, pha và biên độ của sóng Cosin được tạo ra.

Trình điều khiển ESP32 DAC

Hiện nay, Arduino IDE đã có sẵn trình điều khiển ESP32, chúng đang được cải tiến liên tục. Trong đó, trình điều khiển ESP32 DAC đã được tái cấu trúc và có thể sẽ xuất hiện các phiên bản mới tối ưu hơn. Tuy nhiên, hiện tại thì IoTZone chỉ sẽ hướng dẫn bạn với phiên bản ổn định nhất của trình điều khiển này, tính đến thời điểm hiện tại nhé!

Tạo âm thanh với DAC trên ESP32

Có rất nhiều cách để tạo âm thanh bằng ESP32. Trong phạm vi bài này, mình sẽ hướng dẫn bạn cách sử dụng chân DAC và tính năng hẹn giờ nhé! Mình sẽ sử dụng Touch Pads để đọc tín hiệu input, từ đó tạo ra các âm thanh với mức cao độ khác nhau.

ESP32 DAC không thể dùng để điều khiển loa trực tiếp, do đó, chúng ta cần thêm một thiết bị khuếch đại âm thanh hỗ trợ. Trong trường hợp này, mình sử dụng LM386.

Tổng quan dự án

Chúng ta sẽ dùng 4 Touch pad khác nhau, mỗi 1 touch pad sẽ tạo ra 1 âm thanh với mức cao độ nhất định. Khi chúng ta chạm tay vào Touch pad, âm thanh được phát ra, và khi thả tay thì âm thanh cũng dừng.

Kết nối phần cứng

Bạn kết nối ESP32 với bộ khuếch đại âm thanh LM386 và loa như hình dưới. Các touch pad ở đây là bất kỳ miếng kim loại nhôm hoặc đồng nào mà bạn có:

Kết nối thiết bị để tạo âm thanh với ESP32 DAC
Kết nối thiết bị để tạo âm thanh với ESP32 DAC

Chương trình lập trình

#include <driver/dac.h>
 
// Define The TouchPAD Pins (You Can Add More Or Less)
#define TOUCHPAD_1 12
#define TOUCHPAD_2 13
#define TOUCHPAD_3 14
#define TOUCHPAD_4 15
 
// Define The Touch Threshold Value For Your TouchPADs
#define TOUCH_THR 20
 
// Array To Hold TouchPADs Readings (4 TouchPADs)
int TOUCHPAD_Values[4] = {0};
 
// Timer0 Configuration Pointer (Handle)
hw_timer_t *Timer0_Cfg = NULL;
 
// Variable To Control The TMR0 Auto-Reload Register Value In Order To Change The TimerInterrupt Interval
// Which Enable Us To Control The Output WaveForm's Frequency
uint64_t TMR0_ARR_Val = 10;
 
// Sine LookUpTable & Index Variable
uint8_t SampleIdx = 0;
const uint8_t sineLookupTable[100] = {
128, 136, 143, 151, 159, 167, 174, 182,
189, 196, 202, 209, 215, 220, 226, 231,
235, 239, 243, 246, 249, 251, 253, 254,
255, 255, 255, 254, 253, 251, 249, 246,
243, 239, 235, 231, 226, 220, 215, 209,
202, 196, 189, 182, 174, 167, 159, 151,
143, 136, 128, 119, 112, 104, 96, 88,
81, 73, 66, 59, 53, 46, 40, 35,
29, 24, 20, 16, 12, 9, 6, 4,
2, 1, 0, 0, 0, 1, 2, 4,
6, 9, 12, 16, 20, 24, 29, 35,
40, 46, 53, 59, 66, 73, 81, 88,
96, 104, 112, 119};
 
// The Timer0 ISR Function (Executes Every Timer0 Interrupt Interval)
void IRAM_ATTR Timer0_ISR()
{
  // Send SineTable Values To DAC One By One
  dac_output_voltage(DAC_CHANNEL_1, sineLookupTable[SampleIdx++]);
  if(SampleIdx == 100)
  {
    SampleIdx = 0;
  }
}
 
void setup()
{
  // Configure Timer0 Interrupt
  Timer0_Cfg = timerBegin(0, 80, true);
  timerAttachInterrupt(Timer0_Cfg, &Timer0_ISR, true);
  timerAlarmWrite(Timer0_Cfg, TMR0_ARR_Val, true);
  timerAlarmEnable(Timer0_Cfg);
  // Disable DAC1 Channel's Output
  dac_output_disable(DAC_CHANNEL_1);
}
 
void loop()
{
  // Read The Touch PADs
  TOUCHPAD_Values[0] = touchRead(TOUCHPAD_1);
  TOUCHPAD_Values[1] = touchRead(TOUCHPAD_2);
  TOUCHPAD_Values[2] = touchRead(TOUCHPAD_3);
  TOUCHPAD_Values[3] = touchRead(TOUCHPAD_4);
 
  if(TOUCHPAD_Values[0] < TOUCH_THR)  // Play 200Hz Tone
  {
    TMR0_ARR_Val = 50;
    dac_output_enable(DAC_CHANNEL_1);
  }
  else if(TOUCHPAD_Values[1] < TOUCH_THR) // Play 500Hz Tone
  {
    TMR0_ARR_Val = 20;
    dac_output_enable(DAC_CHANNEL_1);
  }
  else if(TOUCHPAD_Values[2] < TOUCH_THR) // Play 1000Hz Tone
  {
    TMR0_ARR_Val = 10;
    dac_output_enable(DAC_CHANNEL_1);
  }
  else if(TOUCHPAD_Values[3] < TOUCH_THR)  // Play 1428Hz Tone
  {
    TMR0_ARR_Val = 7;
    dac_output_enable(DAC_CHANNEL_1);
  }
  else
  {
    dac_output_disable(DAC_CHANNEL_1);
  }
  // Update The Timer Interrupt Interval According To The Desired Tone
  timerAlarmWrite(Timer0_Cfg, TMR0_ARR_Val, true);
}

Trong chương trình trên, đầu tiên thì hàm loop() đọc tín hiệu nhận được từ 4 touch pad, xem có touch pad nào đang được chạm không. Nếu có thì chương trình sẽ chạy, giá trị ngưỡng sẽ thay đổi, thời gian bộ hẹn giờ cũng thay đổi. Điều này làm tốc độ lẫy mẫu của chân ESP32 DAC được tăng hoặc giảm, tạo ra một dạng sóng hình sin.

Bạn hãy nạp code trên vào ESP32 và thử nhé! Chúc các bạn thành công!

Lời kết

Qua bài viết trên, hy vọng bạn đã biết cách vận dụng ESP32 DAC vào các dự án của mình. Trên thực tế, bạn có thể tạo ra các dạng sóng khác nhau trên mạch ESP32. Hãy thử sáng tạo nhé!

IoTZone – Chuyên cung cấp thiết bị điện tử & tài liệu cho Makers

Trả lời

Email của bạn sẽ không được hiển thị công khai. Các trường bắt buộc được đánh dấu *