Ở bài học trước, chúng ta đã nắm vững nguyên lý của các chuẩn giao tiếp UART, SPI và I2C. Tuy nhiên, “học đi đôi với hành”, để thực sự hiểu cách các bit dữ liệu chạy trên đường dây, chúng ta cần thực hành kết nối vi điều khiển với các IC ngoại vi thực tế. Trong bài viết này, AIoT Blog sẽ hướng dẫn bạn cách dùng SPI để điều khiển bộ chuyển đổi DAC và dùng I2C để lưu trữ dữ liệu vào EEPROM.
🎯 Mục tiêu học tập
- Làm chủ kỹ thuật cấu hình SPI Master để điều khiển IC DAC MCP4922.
- Hiểu cách đóng gói gói tin (Packet) 16-bit bao gồm bit cấu hình và dữ liệu 12-bit.
- Thành thạo giao thức I2C thông qua việc đọc/ghi dữ liệu vào bộ nhớ EEPROM AT24C32.
- Xây dựng các hàm API cơ bản cho ngoại vi (Write/Read) dựa trên thư viện HAL.
- Ứng dụng UART để Debug và giám sát trạng thái hệ thống.
1. Thực hành SPI: Điều khiển IC DAC 12-bit MCP4922
a) Giới thiệu IC DAC MCP4922
MCP4922 là một bộ chuyển đổi số sang tương tự (DAC) 12-bit mạnh mẽ với 2 kênh đầu ra độc lập. Nó sử dụng giao thức SPI 3 dây tốc độ cao. Dưới đây là các thông số quan trọng:
| Thông số |
Giá trị |
| Độ phân giải |
12 bit (0 – 4095) |
| Số kênh |
2 Kênh (A và B) |
| Điện áp hoạt động |
2.7V – 5.5V |
| Giao diện |
SPI (hỗ trợ lên đến 20MHz) |
b) Phân tích gói tin SPI cho MCP4922
MCP4922 yêu cầu một khung dữ liệu 16-bit được gửi từ Master. Cấu trúc 16 bit này bao gồm:
- Bit 15 (A/B): Chọn kênh (0 = Kênh A, 1 = Kênh B).
- Bit 14 (BUF): Cấu hình bộ đệm Vref (0 = Không đệm, 1 = Có đệm).
- Bit 13 (GA): Cấu hình hệ số khuếch đại (0 = 2x, 1 = 1x).
- Bit 12 (SHDN): Chế độ hoạt động (0 = Shutdown, 1 = Hoạt động bình thường).
- Bit 11 – 0: 12 bit dữ liệu DAC (giá trị từ 0 đến 4095).
c) Cấu hình trên STM32CubeMX và Lập trình
- Cấu hình SPI1 ở chế độ Full-Duplex Master.
- Cấu hình chân PA4 làm Output (GPIO_Output) để điều khiển tín hiệu CS (Chip Select).
- Trong Keil MDK, xây dựng hàm gửi dữ liệu:
void mcp4922_write(uint16_t val) {
uint8_t data[2];
// Đảm bảo giá trị không vượt quá 12-bit
if(val > 4095) val = 4095;
// Đóng gói 16 bit: Kênh A, Gain 1x, Active mode + 12 bit dữ liệu
// Byte cao: [0][0][1][1][bit11][bit10][bit9][bit8]
data[0] = 0x30 | ((val >> 8) & 0x0F);
// Byte thấp: [bit7][bit6][bit5][bit4][bit3][bit2][bit1][bit0]
data[1] = val & 0xFF;
HAL_GPIO_WritePin(GPIOA, GPIO_PIN_4, GPIO_PIN_RESET); // Kéo CS xuống thấp
HAL_SPI_Transmit(&hspi1, data, 2, 100); // Truyền 2 byte
HAL_GPIO_WritePin(GPIOA, GPIO_PIN_4, GPIO_PIN_SET); // Kéo CS lên cao
}
2. Thực hành I2C: Đọc/Ghi bộ nhớ EEPROM AT24C32
a) Giới thiệu AT24C32
AT24C32 là dòng EEPROM dung lượng 32Kbit (4096 byte). Vì các chân địa chỉ A0, A1, A2 thường được nối Mass, nên địa chỉ I2C mặc định của nó là 0xA0 cho tác vụ ghi và 0xA1 cho tác vụ đọc.
b) Quy trình giao tiếp I2C với EEPROM
Khác với các thiết bị đơn giản, EEPROM yêu cầu gửi địa chỉ thanh ghi (Memory Address) 16-bit trước khi truyền nhận dữ liệu thực tế.
- Ghi dữ liệu: [Start] -> [Địa chỉ thiết bị 0xA0] -> [Địa chỉ nhớ High Byte] -> [Địa chỉ nhớ Low Byte] -> [Dữ liệu] -> [Stop].
- Đọc dữ liệu: [Start] -> [Ghi địa chỉ thiết bị 0xA0] -> [Địa chỉ nhớ 16-bit] -> [Restart] -> [Đọc từ thiết bị 0xA1] -> [Nhận dữ liệu] -> [Stop].
c) Lập trình I2C trên STM32
#define EEPROM_ADDR 0xA0
// Hàm ghi 1 byte vào địa chỉ cụ thể
void at24c32_write(uint16_t mem_addr, uint8_t data) {
uint8_t buf[3];
buf[0] = (mem_addr >> 8) & 0xFF; // Address High
buf[1] = mem_addr & 0xFF; // Address Low
buf[2] = data; // Data
HAL_I2C_Master_Transmit(&hi2c1, EEPROM_ADDR, buf, 3, 100);
HAL_Delay(5); // Chờ chu kỳ ghi nội bộ của EEPROM hoàn tất
}
// Hàm đọc 1 byte từ địa chỉ cụ thể
uint8_t at24c32_read(uint16_t mem_addr) {
uint8_t addr_buf[2];
uint8_t data;
addr_buf[0] = (mem_addr >> 8) & 0xFF;
addr_buf[1] = mem_addr & 0xFF;
// Ghi địa chỉ cần đọc
HAL_I2C_Master_Transmit(&hi2c1, EEPROM_ADDR, addr_buf, 2, 100);
// Đọc giá trị trả về
HAL_I2C_Master_Receive(&hi2c1, EEPROM_ADDR, &data, 1, 100);
return data;
}
3. Kết hợp UART để Debug hệ thống
Để biết chắc chắn dữ liệu ghi vào EEPROM có đúng hay không, chúng ta sử dụng UART để in kết quả lên máy tính thông qua phần mềm Hercules. Việc kết hợp Timer Interrupt để gửi dữ liệu định kỳ là cách tốt nhất để theo dõi trạng thái hệ thống mà không làm treo vòng lặp chính.
🚀 Bài tập về nhà
Bài 1 (SPI): Sử dụng 2 Kit STM32 kết nối SPI. Master có nút nhấn, Slave có LED. Khi nhấn nút ở Master, Slave phải nhận được lệnh qua SPI và đảo trạng thái LED.
Bài 2 (I2C): Sử dụng STM32 đọc thời gian (Giờ, Phút, Giây) từ module RTC DS3231 qua I2C và in kết quả lên màn hình máy tính qua UART mỗi giây.
📝 Tóm tắt: Qua bài học này, bạn đã thực sự “chạm” vào các ngoại vi chuyên dụng. SPI mang lại tốc độ vượt trội cho các tác vụ xuất tín hiệu DAC, trong khi I2C thể hiện sự tinh gọn khi quản lý bộ nhớ EEPROM. Hãy luôn chú ý đến sơ đồ Timing trong Datasheet để cấu hình chính xác các bit dữ liệu.
“Thực hành là cách duy nhất để biến kiến thức lý thuyết thành kỹ năng thực thụ.”
Gợi ý bài tiếp theo: Hệ Điều Hành Thời Gian Thực (RTOS): Giải Pháp Đa Tác Vụ Chuyên Nghiệp