Dưới đây là quy trình tích hợp màn hình hiển thị OLED kích thước 0.96 inch lên board vi điều khiển STM32G0B1RET6 thông qua giao tiếp I2C phần cứng (Hardware I2C), hỗ trợ thư viện STM32 HAL. Môi trường phát triển bao gồm STM32CubeMX cho cấu hình hệ thống và Keil/VS Code làm nơi biên dịch mã nguồn.
Cài Đặt Cấu Hình Hệ Thống
Bước đầu tiên trong quá trình phát triển là thiết lập môi trường nhúng cơ bản:
- Kiểm tra xung nhịp hệ thống: Cấu hình bộ điều chỉnh RCC để đảm bảo tần số hoạt động của vi xử lý đạt yêu cầu cho thời gian trễ chính xác.
- Thiết lập chân GPIO: Xác định các chân SCL và SDA tương ứng với ngoại trừ I2C được chọn (ví dụ I2C1 trên port B).
- Tải dự án: Xuất mã nguồn từ công cụ cấu hình sang thư mục dự án IDE (Keil MDK hoặc GCC dựa trên Visual Studio).
Sau khi hoàn thành cấu hình phần cứng, tiến hành tạo các module phần mềm để quản lý lớp truyền thông dữ liệu xuống màn hình.
Triển Khai Tầng Treo Và Điều Khiển
Để giảm độ phức tạp, mã nguồn được chia thành nhiều lớp độc lập. Lớp dưới cùng đảm nhiệm việc giao tiếp I2C, trong khi lớp trên xử lý logic hiển thị điểm ảnh, chữ và hình ảnh.
Mô-đun Giao Diện Cơ Bản
File header này định nghĩa các hằng số địa chỉ, lệnh mặc định và khai báo hàm cần thiết cho việc truy cập màn hình.
#ifndef OLED_INTERFACE_H
#define OLED_INTERFACE_H
#include "main.h"
#include "stm32g0xx_hal.h"
// Địa chỉ thiết bị trên bus I2C
#define OLED_DEV_ADDR 0x78
// Các lệnh điều khiển hệ thống
#define CMD_SCROLL_LEFT 0x27
#define CMD_SCROLL_RIGHT 0x26
#define CMD_DISPLAY_ON 0xA7
#define CMD_DISPLAY_OFF 0xA6
// Đặc điểm màn hình 0.96 inch (128x64)
#define DISP_HEIGHT 64
#define DISP_WIDTH 128
#define DISP_PAGES 8
// Khai báo chức năng điều khiển
void Send_Command(uint8_t cmd);
void Send_Data(uint8_t data);
void Init_OLED(void);
void Clear_Display(void);
void Set_Pixel(int x, int y, uint8_t state);
void Refresh_Buffer(void);
#endif // OLED_INTERFACE_H
Trong file thực thi `.c`, chúng ta sử dụng API của HAL để gửi byte đơn lẻ qua bộ nhớ nhớ của thiết bị.
#include "OLED_Interface.h"
#include "i2c_driver.h"
#include "delay_lib.h"
uint8_t Display_Memory[DISP_PAGES][DISP_WIDTH];
/**
* @brief Gửi một byte dữ liệu tới register của OLED
*/
static void I2C_TransferByte(uint8_t reg_addr, uint8_t val) {
extern I2C_HandleTypeDef hi2c_instance;
HAL_I2C_Mem_Write(&hi2c_instance, OLED_DEV_ADDR,
reg_addr, I2C_MEMADD_SIZE_8BIT, &val, 1, HAL_MAX_DELAY);
}
void Send_Command(uint8_t cmd) {
// Byte lệnh thường bắt đầu ở addr 0x00
I2C_TransferByte(0x00, cmd);
}
void Send_Data(uint8_t data) {
// Byte dữ liệu thường bắt đầu ở addr 0x40
I2C_TransferByte(0x40, data);
}
/**
* @brief Khởi tạo chuỗi lệnh cấu hình màn hình
*/
void Init_OLED(void) {
// tắt hiển thị tạm thời
Send_Command(0xAE);
// đặt chế độ địa chỉ bộ nhớ theo trang
Send_Command(0x20);
Send_Command(0x10);
// định vị COM scan direction
Send_Command(0xC8);
// cài đặt vị trí xuất hàng cột
Send_Command(0x00);
Send_Command(0x10);
// đặt dòng khởi đầu
Send_Command(0x40);
// điều chỉnh độ tương phản
Send_Command(0x81);
Send_Command(0xFF);
// phân đoạn mapping ngược
Send_Command(0xA1);
// hiển thị bình thường
Send_Command(0xA6);
// đặt tỷ lệ đa dạng hóa COM
Send_Command(0xA8);
Send_Command(0x3F);
// hiển thị RAM nội dung
Send_Command(0xA4);
// set hiển thị offset
Send_Command(0xD3);
Send_Command(0x00);
// clock divide ratio
Send_Command(0xD5);
Send_Command(0xF0);
// pre-charge period
Send_Command(0xD9);
Send_Command(0x22);
// com pin configuration
Send_Command(0xDA);
Send_Command(0x12);
// vcomh setting
Send_Command(0xDB);
Send_Command(0x20);
// bật charge pump
Send_Command(0x8D);
Send_Command(0x14);
// mở panel
Send_Command(0xAF);
Clear_Display();
}
/**
* @brief Xóa toàn bộ nội dung trên màn hình
*/
void Clear_Display(void) {
for (int p = 0; p < DISP_PAGES; p++) {
Send_Command(0xB0 + p);
Send_Command(0x00);
Send_Command(0x10);
for (int c = 0; c < DISP_WIDTH; c++) {
Send_Data(0x00);
}
}
}
/**
* @brief Cập nhật bộ đệm Ram ra màn hình vật lý
*/
void Refresh_Buffer(void) {
for (uint8_t p = 0; p < DISP_PAGES; p++) {
Send_Command(0xB0 + p);
Send_Command(0x00);
Send_Command(0x10);
for (uint8_t c = 0; c < DISP_WIDTH; c++) {
Send_Data(Display_Memory[p][c]);
}
}
}
/**
* @brief Đổi trạng thái điểm ảnh tại tọa độ (x,y)
*/
void Set_Pixel(int x, int y, uint8_t set_state) {
if (x >= 0 && x < DISP_WIDTH && y >= 0 && y < DISP_HEIGHT) {
uint8_t page_idx = y / 8;
uint8_t bit_pos = y % 8;
uint8_t mask = (1 << bit_pos);
if (set_state) {
Display_Memory[page_idx][x] |= mask;
} else {
Display_Memory[page_idx][x] &= ~mask;
}
Refresh_Buffer();
}
}
/**
* @brief Hiệu chỉnh độ sáng màn hình
*/
void Adjust_Brightness(uint8_t level) {
Send_Command(0x81);
Send_Command(level);
}
Mô-đun Hiển Thị Cao Cấp
Lớp này xây dựng trên nền tảng I2C bên dưới để cung cấp các hàm vẽ ký tự, văn bản tiếng Trung và hình ảnh bitmap.
#include "OLED_Functions.h"
#include "OLED_Interface.h"
// Hàm vẽ chuỗi ký tự ASCII
void Draw_String(uint8_t x, uint8_t y, const char* str, uint8_t size) {
// Logic lặp qua từng ký tự và vẽ điểm ảnh dựa trên ma trận font đã đăng ký
// Kích thước 1: 6x8 pixel | Kích thước 2: 8x16 pixel
for (const char* p = str; *p != '\0'; p++) {
int char_idx = (*p) - 32;
if (char_idx < 0) continue; // bỏ qua ký tự không nằm trong bảng
// Xử lý giới hạn vùng hiển thị và auto-line wrap
// ... (Logic vẽ chi tiết dựa trên ma trận F6X8 hoặc F8X16)
x += (size == 1 ? 6 : 8);
y += (size == 2 ? 16 : 8);
}
}
// Hàm vẽ ký tự Unicode/CN16x16
void Draw_CN_Char(uint8_t x, uint8_t y, const uint8_t* chinese_code) {
// Duyệt qua mảng từ điển chữ cái Hán, tìm kiếm mã chỉ số phù hợp
// Sau đó gọi Set_Pixel dựa trên ma trận encoder
// ... (Chi tiết thuật toán tìm kiếm và giải mã)
}
// Hàm hiển thị ảnh bitmap
void Show_Image(uint8_t x0, uint8_t y0, uint16_t width, uint16_t height, const uint8_t img_data[]) {
// Duyệt tuần tự dữ liệu ảnh theo bit và viết vào bộ đệm ScreenBuffer
const uint8_t* ptr = img_data;
for (uint16_t row = y0; row < y0 + height; row += 8) {
for (uint16_t col = x0; col < x0 + width; col++) {
for (uint8_t bit = 0; bit < 8; bit++) {
if ((*ptr >> bit) & 0x01) {
Set_Pixel(col, row + bit, 1);
}
}
ptr++;
}
}
}
Ma Trận Font Chữ
Dữ liệu ma trận hiển thị (Font Table) chứa các giá trị hex đại diện cho hình dáng ký tự. Dưới đây là cấu trúc định nghĩa mẫu cho bộ ký tự nhỏ 6x8.
typedef struct {
const uint8_t data[6];
} FontChar6x8;
const FontChar6x8 Font_Ascii[] = {
// Ký hiệu khoảng trắng (Space)
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00},
// Dấu chấm than (!)
{0x00, 0x00, 0x00, 0x2F, 0x00, 0x00},
// Dấu ngoặc kép (")
{0x00, 0x00, 0x07, 0x00, 0x07, 0x00},
// ... (Tiếp tục liệt kê các ký tự còn lại A-Z, 0-9...)
};
Tương tự, đối với ký tự tiếng Trung 16x16, ta sử dụng cấu trúc liên kết chứa cả mã chỉ dẫn và dữ liệu điểm ảnh 32-byte mỗi ký tự.
typedef struct {
uint8_t index_id[2];
uint8_t glyph_data[32];
} CharDB_GB2312;
// Ví dụ mã dữ liệu chữ "嵌入"
const CharDB_GB2312 CN_Font_Table[] = {
{"嵌", {0x80, 0x80, 0xEE, 0x88, /* ... */}},
{"入", {0x00, 0x00, 0x00, 0x00, /* ... */}},
// Danh sách đầy đủ các ký tự GB2312 cần thiết cho ứng dụng
};
Ví Dụ Tích Hợp Vào Ứng Dụng Chính
Cuối cùng, đưa các module đã tạo vào luồng chạy chính của chương trình `main.c` để thực thi các tác vụ hiển thị.
Xử Lý Vẽ Chữ Tiếng Anh
int main(void) {
HAL_Init();
SystemClock_Config();
MX_GPIO_Init();
MX_I2C1_Init();
Init_OLED();
Draw_String(0, 0, "System Ready", 1);
Draw_String(0, 10, "Voltage OK", 2);
while(1) {
// Vòng lặp kiểm soát chính
Task_Loop();
}
}
Xử Lý Vẽ Văn Bản Tiếng Trung
// ...
Init_OLED();
Draw_CN_Char(0, 16, "嵌入式");
Draw_CN_Char(0, 32, "系统开发");
// Lưu ý: Cần đảm bảo mã nguồn project được lưu bằng encoding GB2312/GBK
// để tránh lỗi ký tự khi biên dịch trên compiler Windows.
Hiệu Ứng Chuyển Động Ngang
Màn hình hỗ trợ cuộn nội dung trực tiếp từ driver controller.
void Activate_Scroll_Right(void) {
Send_Command(0x2E); // Dừng cuộn
Send_Command(0x26); // Chọn hướng sang phải
Send_Command(0x00); // Virtual Bytes
Send_Command(0x02); // Start Page
Send_Command(0x05); // Frame interval
Send_Command(0x04); // End Page
Send_Command(0x00);
Send_Command(0xFF);
Send_Command(0x2F); // Bắt đầu cuộn
}
Hiển Thị Hình Ảnh Anim
Sử dụng mảng dữ liệu ảnh bitmap đã lấy từ tool convert để hiển thị chuyển động tuần tự.
// Biến toàn cục chứa danh sách khung hình
extern const unsigned char bmp_sequence[];
while (1) {
uint16_t current_frame_index = Get_Current_Frame();
Show_Image(0, 0, 128, 64, (bmp_sequence + current_frame_index));
Delay_MS(200);
}