Skip to content

22 Cảm biến ánh sáng sử dụng ADC với điện trở quang (LDR)

Nguồn mua module

Link shoppe

img

Thông số kỹ thuật

  • Điện áp hoạt động: 3.3V – 5V
  • Dòng tiêu thụ: 1 mA
  • Kích thước module: 31.15 x 14.10 mm
  • Chế độ xuất tín hiệu:
    • Chân DO: xuất tín hiệu số (digital)
    • Chân AO: xuất tín hiệu tương tự (analog)
  • Cách đọc dữ liệu: sử dụng ADC
  • Số lượng chân: 4 chân (theo chuẩn header 2.54mm)

Hướng dẫn kết nối

Sơ đồ kết nối:

img

Cấu hình Clock (xung nhịp)

Trong ví dụ này, ta sử dụng ADC0, vì vậy cần bật clock cho ADC0.

Tuy nhiên, cần lưu ý rằng theo tài liệu hướng dẫn người dùng của STM32F4, tần số tối đa cho ADC là 40 MHz. Vì vậy, khi sử dụng ADC, ta phải chia tần số của bus chứa ADC xuống thấp hơn hoặc bằng 40 MHz để đảm bảo hoạt động ổn định.

img

ADC时钟说明

img

Nguồn xung nhịp ADC

Trong ví dụ này, ADC sử dụng APB2 làm nguồn clock, mà APB2 có tần số tối đa là 84 MHz. Do đó, chúng ta phải chia tần số này xuống dưới 40 MHz để phù hợp với yêu cầu hoạt động của ADC.

Các hệ số chia tần số khả dụng bao gồm: chia 2, chia 4, chia 6, chia 8, v.v. Ví dụ:

  • Nếu chọn chia 2, tần số ADC sẽ là: 84 / 2 = 42 MHzquá cao, không đạt yêu cầu.
  • Nếu chọn chia 4, tần số ADC sẽ là: 84 / 4 = 21 MHzđạt yêu cầu (dưới 40 MHz).

→ Kết luận: chọn chia 4 là phù hợp.

Cấu hình chân (GPIO)

STM32F407VET6 có tổng cộng 16 kênh ADC ngoại vi.

Trong bài này, ta kết nối chân AO của module điện trở quang vào chân PA5 trên vi điều khiển. Tra theo bảng định nghĩa chân trong datasheet, ta thấy:

  • PA5 có chức năng bổ sung là:
    • ADC1 channel 5
    • ADC2 channel 5

img

Vì ở phần cấu hình clock trước đó, ta đã bật clock cho ADC0 (tức là ADC1), nên tại đây ta sẽ sử dụng chức năng bổ sung của chân PA5 là kênh ADC1 Channel 5 để thực hiện việc đọc tín hiệu.

Để sử dụng được chân GPIO, ta cần thực hiện đầy đủ các bước cấu hình sau:

  • Bật clock cho GPIO
  • Cấu hình chế độ chân (input/analog/v.v.)
  • Cấu hình đầu ra (nếu cần)
  • Gán chức năng đặc biệt (nếu có)

Cấu hình GPIO:

c
GPIO_InitTypeDef  GPIO_InitStructure; // Cấu trúc cấu hình

RCC_AHB1PeriphClockCmd (RCC_AHB1Periph_GPIOA, ENABLE); // Bật clock cho GPIOA

GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AN;    // Chế độ đầu vào analog
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL;

GPIO_Init(GPIOA, &GPIO_InitStructure); // Khởi tạo GPIO

Giải thích từng bước:

  • Bật clock cho cổng GPIOA (RCC_AHB1Periph_GPIOA).
  • Đặt chế độ chân PA5 thành analog input (GPIO_Mode_AN).
  • Cấu hình tốc độ chân là 100 MHz (GPIO_Speed_100MHz).
  • Không sử dụng điện trở kéo lên/kéo xuống (GPIO_PuPd_NOPULL).

Lưu ý**: Khi sử dụng chức năng ADC, chân phải được cấu hình ở chế độ analog input. Nếu để ở chế độ pull-up hoặc pull-down, điện áp thu được sẽ bị sai lệch, dẫn đến kết quả ADC không chính xác.

Cấu hình ADC

ADC trên STM32 có 4 chế độ hoạt động chính:

  • Chế độ chuyển đổi đơn次 (Single conversion)
  • Chế độ chuyển đổi liên tục (Continuous conversion)
  • Chế độ quét (Scan conversion)
  • Chế độ chuyển đổi gián đoạn (Discontinuous conversion)

Trong bài này, ta cấu hình ADC ở chế độ quét (Scan mode), cho phép quét qua các kênh ADC đã chọn và lần lượt đọc các giá trị tín hiệu analog từ từng chân tương ứng.

Tùy theo mục đích sử dụng, bạn cũng có thể chọn các chế độ khác như liên tục hoặc gián đoạn.

Cấu hình chung cho ADC:

c
ADC_CommonInitTypeDef ADC_CommonInitStruct;

ADC_DeInit(); // Reset lại ADC

ADC_CommonInitStruct.ADC_DMAAccessMode = ADC_DMAAccessMode_Disabled; // Tắt chế độ truy cập DMA
ADC_CommonInitStruct.ADC_Mode = ADC_Mode_Independent;                // Chế độ hoạt động độc lập
ADC_CommonInitStruct.ADC_Prescaler = ADC_Prescaler_Div4;             // Chia tần số clock ADC theo hệ số 4
ADC_CommonInitStruct.ADC_TwoSamplingDelay = ADC_TwoSamplingDelay_5Cycles; // Độ trễ giữa 2 lần lấy mẫu là 5 chu kỳ

ADC_CommonInit(&ADC_CommonInitStruct); // Khởi tạo ADC với cấu hình trên

Giải thích:

  • ADC_DeInit() – Reset toàn bộ cấu hình của ADC trước khi cài đặt lại.
  • ADC_DMAAccessMode_DisabledKhông sử dụng DMA, phù hợp với các bài test đơn giản.
  • ADC_Mode_Independent – Cấu hình ADC hoạt động ở chế độ độc lập, không liên kết với các ADC khác.
  • ADC_Prescaler_Div4 – Chia tần số clock đầu vào của ADC xuống còn 84 / 4 = 21 MHz, phù hợp với yêu cầu nhỏ hơn 40 MHz.
  • ADC_TwoSamplingDelay_5Cycles – Đặt thời gian trễ giữa hai lần lấy mẫu là 5 chu kỳ, đảm bảo ổn định tín hiệu.

Cấu hình riêng cho ADC:

c
ADC_InitTypeDef ADC_InitStruct;

RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1, ENABLE); // Bật clock cho ADC1

ADC_InitStruct.ADC_ContinuousConvMode = DISABLE;                  // Tắt chế độ chuyển đổi liên tục
ADC_InitStruct.ADC_DataAlign = ADC_DataAlign_Right;               // Dữ liệu căn phải
ADC_InitStruct.ADC_ExternalTrigConvEdge = ADC_ExternalTrigConvEdge_None; // Không dùng trigger ngoài, dùng phần mềm
ADC_InitStruct.ADC_NbrOfConversion = 1;                            // Chỉ thực hiện 1 lần chuyển đổi
ADC_InitStruct.ADC_Resolution = ADC_Resolution_12b;               // Độ phân giải 12 bit
ADC_InitStruct.ADC_ScanConvMode = DISABLE;                        // Tắt chế độ quét

ADC_Init(ADC1, &ADC_InitStruct); // Khởi tạo ADC1 với cấu hình trên

Giải thích các cấu hình:

  • Tắt chế độ chuyển đổi liên tục (ADC_ContinuousConvMode = DISABLE) → Sau khi hoàn tất một lần chuyển đổi ADC, quá trình sẽ dừng lại.
  • Căn phải dữ liệu đầu ra (ADC_DataAlign_Right) → Giá trị kết quả ADC được đặt bên phải trong thanh ghi, thuận tiện cho xử lý số nguyên.
  • Tắt kích hoạt bằng ngắt ngoài (ADC_ExternalTrigConvEdge = None) → Việc bắt đầu chuyển đổi sẽ do phần mềm điều khiển, không dựa vào tín hiệu ngoài.
  • Chỉ thực hiện 1 lần chuyển đổi (ADC_NbrOfConversion = 1) → Vì không sử dụng chế độ quét nên chỉ cần cấu hình 1 kênh ADC.
  • Độ phân giải 12 bit (ADC_Resolution = 12b) → Kết quả chuyển đổi nằm trong khoảng từ 0 đến 4095.
  • Tắt chế độ quét kênh (ADC_ScanConvMode = DISABLE) → Mỗi lần chỉ chuyển đổi giá trị từ một kênh ADC duy nhất.

Kích hoạt ADC (Enable)

Sau khi cấu hình xong, ADC vẫn chưa bắt đầu hoạt động ngay. Ta cần kích hoạt (enable) ADC, giống như việc bật công tắc nguồn cho nó.

Lưu ý: Phải bật ADC trước khi thực hiện tự hiệu chuẩn (calibration). Nếu không, quá trình hiệu chuẩn sẽ không thực hiện được.

//Kích hoạt ADC (Enable)
ADC_Cmd(ADC1, ENABLE);

Chuyển đổi và thu thập dữ liệu ADC

Hàm đọc dữ liệu ADC:

c
/**********************************************************
 * Tên hàm      : Get_ADC_Value
 * Chức năng    : Đọc giá trị từ ADC
 * Tham số vào  : ADC_CHANNEL_x – kênh ADC cần đọc
 * Trả về       : Giá trị đo được (kiểu unsigned int)
 * Tác giả      : LC
 * Ghi chú      : Mặc định thời gian lấy mẫu là 15 chu kỳ ADC
 **********************************************************/
unsigned int Get_ADC_Value(uint8_t ADC_CHANNEL_x)
{
    unsigned int adc_value = 0;

    // Cấu hình kênh chuyển đổi ADC
    ADC_RegularChannelConfig(PORT_ADC, ADC_CHANNEL_x, 1, ADC_SampleTime_480Cycles);

    // Bắt đầu chuyển đổi bằng phần mềm
    ADC_SoftwareStartConv(PORT_ADC);

    // Chờ đến khi quá trình chuyển đổi hoàn tất
    while (!ADC_GetFlagStatus(PORT_ADC, ADC_FLAG_EOC));

    // Đọc giá trị sau khi chuyển đổi xong
    adc_value = ADC_GetConversionValue(BSP_ADC);

    // Trả về giá trị ADC
    return adc_value;
}

Giải thích chi tiết:

  • ADC_RegularChannelConfig(...) – Cấu hình kênh cần đọc, thứ tự chuyển đổi là 1, thời gian lấy mẫu là 480 chu kỳ (tối ưu cho độ chính xác).
  • ADC_SoftwareStartConv(...) – Khởi động chuyển đổi bằng phần mềm.
  • ADC_GetFlagStatus(..., ADC_FLAG_EOC) – Kiểm tra cờ EOC (End Of Conversion) để biết khi nào ADC hoàn tất việc chuyển đổi.
  • ADC_GetConversionValue(...) – Lấy giá trị vừa được chuyển đổi từ thanh ghi ADC.

Hàm chuyển đổi dữ liệu ADC:

c
/**********************************************************
 * Tên hàm      : Get_Adc_Average
 * Chức năng    : Tính trung bình giá trị ADC sau nhiều lần lấy mẫu
 * Tham số vào  : CHx     – Số thứ tự kênh ADC cần đọc
 *                times   – Số lần lấy mẫu
 * Trả về       : Giá trị trung bình sau khi lấy mẫu (uint16_t)
 * Tác giả      : LC
 * Ghi chú      : LP
 **********************************************************/
uint16_t Get_Adc_Average(uint8_t CHx, uint8_t times)
{
    uint32_t value = 0;
    uint8_t t;

    for (t = 0; t < times; t++)
    {
        value += Get_Adc(CHx); // Gọi hàm đọc ADC
        delay_ms(5);           // Đợi 5ms giữa các lần lấy mẫu
    }

    return value / times; // Trả về giá trị trung bình
}

Giải thích:

  • Mục đích: Giảm nhiễu bằng cách lấy nhiều mẫu ADC rồi tính giá trị trung bình.
  • Get_Adc(CHx) – Giả định là hàm đọc giá trị ADC từ kênh CHx (chính là Get_ADC_Value() đã viết ở trên).
  • delay_ms(5) – Đợi 5ms giữa các lần đọc để đảm bảo tín hiệu ổn định.
  • Kết quả trung bình được trả về dưới dạng số nguyên 16 bit (uint16_t).

Đối tượng thí nghiệm

Trong file main.c, ta gọi hàm:

c
uint16_t Get_Adc_Average(uint8_t CHx, uint8_t times)

→ để đọc điện áp từ chân PA5 (kênh ADC tương ứng), sau đó gửi kết quả ra qua UART để quan sát trên máy tính.

int main(void)
{
    board_init();
    uart1_init(115200U);

    Adc_Init();

    while(1)
    {
        uint16_t value = Get_Adc_Average(ADC_Channel_5,20);

        printf("value = %d\r\n", value);
        delay_ms(1000);
    }
}

Khi ánh sáng đầy đủ, giá trị ADC đọc được vào khoảng 4000, tương ứng với điện áp gần 3.3 V.

Khi dùng tay che cảm biến ánh sáng, giá trị ADC giảm xuống khoảng 60, tương ứng với điện áp gần 0 V.img

Nếu không có điện trở quang, bạn có thể nối trực tiếp chân tín hiệu đến 3.3V hoặc GND để kiểm tra hoạt động của ADC.

  • Khi nối với 3.3V, giá trị ADC đọc được gần 4095 (giới hạn trên của độ phân giải 12-bit).
  • Khi nối với GND, giá trị ADC gần 0.

Mã nguồn của chương này:

Nằm trong liên kết Baidu Netdisk giới thiệu về bo mạch phát triển: 立创·梁山派·天空星STM32F407VET6开发板资料/第03章软件资料/代码例程/011 ADC采集