🌱 STM32 - 21. Bộ chuyển đổi tương tự-số ADC

🌱 STM32 - 21. Bộ Chuyển Đổi Tương Tự Số ADC

    Ngoại vi tiếp theo mà mình muốn giới thiệu tới các bạn trong Series STM32 đó là ADC - Analog-to-Digital Converter. Chắc hẳn các bạn lập trình Vi điều khiển không còn xa lạ gì với ngoại vi này. Ở bài viết này mình sẽ tập trung vào đặc điểm của ADC nói chung.

👉 ADC và hoạt động của ADC

    ADC – Analog-to-Digital Converter là hệ thống mạch thực hiện chuyển đổi một tín hiệu tương tự sang tín hiệu số. ADC dùng để đọc giá trị điện áp tương tự từ bên ngoài và biến đổi thành tín hiệu số, mục tiêu là để vi xử lý có thể tính toán và đo lường được giá trị tương tự cần đo.

ADC
Hình 1: Nguyên lý cơ bản của ADC

👉 Hoạt động của ADC:

  • Xung Start khởi động hệ thống.
  • Xung Clock quyết định bộ điều khiển liên tục chỉnh sử số nhị phân lưu trong thanh ghi.
  • Số nhị phân trong thanh ghi được DAC chuyển đổi thành mức điện áp tương tự VAX.
  • Bộ so sánh sẽ so sánh VAX với đầu vào tương tự VA. Nếu VAX < VA, đầu ra của bộ so sánh lên mức cao. Nếu VAX > VA, ít nhất bằng một khoảng VT (điện thế ngưỡng), đầu ra của bộ so sánh sẽ xuống mức thấp và ngừng tiến trình biến đổi số nhị phân ở thanh ghi. Tại thời điểm này VAX ≈ VA, giá trị nhị phân ở thanh ghi là đại lượng số tương đương VA, trong giới hạn độ phân giải và độ chính xác của hệ thống.
  • Logic điều khiển kích hoạt tín hiệu EOC khi chu kỳ chuyển đổi kết thúc.

Tiến trình này có thể có nhiều thay đổi đối với một số loại ADC khác, chủ yếu là sự khác nhau ở cách thức bộ điều khiển sửa đổi số nhị phân trong thanh ghi.

👉 Các chỉ tiêu kỹ thuật chủ yếu của ADC:

  • Độ phân giải: Biểu thị bằng số bit của tín hiệu số đầu ra. Số lượng bit nhiều sai số lượng tử càng nhỏ, độ chính xác càng cao.
  • Độ chính xác tương đối: Là sai số của các điểm chuyển đổi thực tế so với đặc tuyến chuyển đổi lý tưởng.
  • Tốc độ chuyển đổi: Được xác định thời gian bởi thời gian cần thiết hoàn thành một lần chuyển đổi A/D.

👉 Các dạng ADC

💥 Flash ADC

Đây là loại ADC đơn giản nhất và nhanh nhất, nó bao gồm một loạt các bộ so sánh với các đầu vào không đảo ngược nối với đầu vào tín hiệu và các chân đảo ngược nối với một thang chia điện áp.

Flash ADC
Hình 2: Cấu trúc Flash ADC

Rõ ràng với việc thiết kế các bộ OPAMP so sánh, việc chuyển đổi chỉ mất 1 chu kỳ xung Clock 😀 Vì vậy, Flash ADC có tốc độ chuyển đổi là nhanh nhất, nhưng việc thiết kế tích hợp nhiều OPAMP như vậy có thể khiến kích thước IC tăng lên, và khiến Flash ADC khá đắt.

💥 ADC dạng sóng bậc thang

ADC
Hình 3: Cấu trúc ADC dạng sóng bậc thang

ADC dạng sóng bậc thang sử dụng một bộ đếm, và giá trị bộ đếm này sẽ bắt đầu từ 0, giá trị đếm sẽ tăng dần cho đến khi giá trị đầu ra của DAC đạt được lớn hơn hoặc bằng giá trị điện áp đầu vào VA.

Chính vì vậy, nếu điện áp đầu vào nhỏ, ADC bậc thang sẽ đếm rất nhanh, còn khi điện áp càng cao thì ADC bậc thang càng mất nhiều thời gian chuyển đổi, tối đa có thể lên đến 2^N - 1 chu kỳ xung nhịp (Với N là số bit của bộ đếm).

Độ phân giải của ADC chính là kích thước bậc thang, tức là số bit của bộ đếm.

💥 ADC xấp xỉ liên tiếp

ADC
Hình 4: Cấu trúc ADC xấp xỉ liên tiếp

    Khác với ADC bậc thang, ADC xấp xỉ liên tiếp sử dụng một thanh ghi để đếm. Ý tưởng của ADC xấp xỉ liên tiếp là đếm từng bit của thanh ghi này.

    ADC sẽ bắt đầu xem xét từ bit MSB (Giá trị cao nhất), ADC sẽ xét xem bit này có cần thiết bằng 1 hay không? Mình ví dụ thanh ghi 8-bit, khi set bit MSB lên 1 sẽ có giá trị 1000.0000 = 128 (không phải 255 như bài gốc, sửa lỗi). Sau đó đi qua bộ DAC sẽ có giá trị khoảng 2.5V (Với điện áp tham chiếu 5V). Ở đây nếu điện áp VA đầu vào > 2.5V thì bit MSB này cần set lên 1. Còn VA < 2.5V thì MSB = 0. Cứ tiếp tục với các bit sau đó cho đến bit cuối cùng LSB.

    Có thể thấy với quá trình trên, mỗi chu kỳ thực hiện xét được 1 bit. Vậy nếu thanh ghi N bit thì sẽ luôn mất N chu kỳ clock để chuyển đổi.

    Số bit của thanh ghi cũng chính là độ phân giải của ADC.

👉 ADC xấp xỉ liên tiếp chính là loại ADC được sử dụng (tích hợp) thường xuyên nhất trong các dòng vi điều khiển.

👉 ADC trong Vi điều khiển STM32

    Các dòng Vi điều khiển STM32 thường sử dụng bộ ADC xấp xỉ liên tiếp, với độ phân giải tối đa là 12-bit (Có thể cấu hình xuống 8-bit/6-bit).

    Đối với vi điều khiển mình đang xét STM32F401, hỗ trợ đến 19 kênh (16 kênh đo điện áp ngoài, 2 nguồn bên trong và một kênh VBAT). Việc thực hiện ADC có thể thực hiện ở 4 chế độ: chế độ đơn, liên tục, quét hoặc không liên tục. Kết quả chuyển đổi được lưu trong một thanh ghi 16-bit (Chỉ sử dụng số bit bằng độ phân giải).

    ADC còn hỗ trợ bộ Analog Watchdog để phát hiện điện áp vào vượt quá rải cho phép.

    Nguồn cấp ADC: 2.4 V to 3.6 V với full speed và giảm còn 1.8 V ở slower speed.


Hình 5: Sơ đồ khối ADC trong STM32F401

Trên đây là sơ đồ khối của ADC trong STM32F401, các bạn có thể vào Reference Manual để xem full sơ đồ. Dưới đây là một số thông số về điện áp tham chiếu:


Hình 6: Thông số điện áp tham chiếu của ADC

    Nguồn cấp xung Clock của ADC là bus APB2, với độ chia 2/4/6/8.


Hình 7: Cấu hình Clock cho ADC

    Trên đây là những đặc điểm cơ bản của ADC và ngoại vi ADC trong STM32. Còn rất nhiều đặc điểm khác mà các bạn có thể đọc thêm trong RM và chờ các bài viết sau của mình 😁

👉 Ví dụ lập trình ADC trên STM32F401

    Dưới đây là đoạn code bare-metal đơn giản để cấu hình và đọc giá trị ADC từ kênh 0 (PA0) trên STM32F401RE:

#include 

// Định nghĩa địa chỉ cơ bản
#define RCC_BASE    0x40023800
#define GPIOA_BASE  0x40020000
#define ADC1_BASE   0x40012000

// Định nghĩa thanh ghi RCC
#define RCC_AHB1ENR (*(volatile uint32_t *)(RCC_BASE + 0x30))
#define RCC_APB2ENR (*(volatile uint32_t *)(RCC_BASE + 0x44))

// Định nghĩa thanh ghi GPIOA
#define GPIOA_MODER (*(volatile uint32_t *)(GPIOA_BASE + 0x00))
#define GPIOA_PUPDR (*(volatile uint32_t *)(GPIOA_BASE + 0x0C))

// Định nghĩa thanh ghi ADC1
#define ADC1_SR     (*(volatile uint32_t *)(ADC1_BASE + 0x00))
#define ADC1_CR1    (*(volatile uint32_t *)(ADC1_BASE + 0x04))
#define ADC1_CR2    (*(volatile uint32_t *)(ADC1_BASE + 0x08))
#define ADC1_SMPR1  (*(volatile uint32_t *)(ADC1_BASE + 0x0C))
#define ADC1_SMPR2  (*(volatile uint32_t *)(ADC1_BASE + 0x10))
#define ADC1_SQR1   (*(volatile uint32_t *)(ADC1_BASE + 0x2C))
#define ADC1_SQR3   (*(volatile uint32_t *)(ADC1_BASE + 0x30))
#define ADC1_DR     (*(volatile uint32_t *)(ADC1_BASE + 0x4C))

// Định nghĩa bit
#define RCC_AHB1ENR_GPIOAEN (1 << 0)
#define RCC_APB2ENR_ADC1EN  (1 << 8)
#define ADC_CR2_ADON        (1 << 0)
#define ADC_CR2_SWSTART     (1 << 30)
#define ADC_SR_EOC          (1 << 1)

// Hàm cấu hình ADC
void ADC_Init(void) {
    // Bật clock cho GPIOA và ADC1
    RCC_AHB1ENR |= RCC_AHB1ENR_GPIOAEN;
    RCC_APB2ENR |= RCC_APB2ENR_ADC1EN;

    // Cấu hình PA0 thành chế độ analog (MODER = 11)
    GPIOA_MODER |= (3 << 0); // PA0: Analog mode
    GPIOA_PUPDR &= ~(3 << 0); // Không pull-up/pull-down

    // Cấu hình ADC1
    ADC1_CR1 = 0; // Độ phân giải mặc định 12-bit
    ADC1_SMPR2 = (3 << 0); // Kênh 0: 56 chu kỳ lấy mẫu
    ADC1_SQR1 = 0; // 1 kênh chuyển đổi
    ADC1_SQR3 = 0; // Kênh 0
    ADC1_CR2 |= ADC_CR2_ADON; // Bật ADC
}

// Hàm đọc giá trị ADC
uint16_t ADC_Read(void) {
    ADC1_CR2 |= ADC_CR2_SWSTART; // Bắt đầu chuyển đổi
    while (!(ADC1_SR & ADC_SR_EOC)); // Chờ EOC
    return ADC1_DR; // Trả về giá trị ADC
}

int main(void) {
    ADC_Init();
    while (1) {
        uint16_t adc_value = ADC_Read(); // Đọc giá trị ADC từ PA0
        // Giá trị từ 0-4095 tương ứng 0-3.3V (Vref = 3.3V)
        (void)adc_value; // Biến tránh cảnh báo unused
    }
}
    

>>>>>> Follow ngay <<<<<<<

Để nhận được những bài học miễn phí mới nhất nhé 😊
Chúc các bạn học tập tốt 😊

Nguyễn Văn Nghĩa

Mình là một người thích học hỏi và chia sẻ các kiến thức về Nhúng IOT.

Đăng nhận xét

Mới hơn Cũ hơn
//