第二部分:编写 SSD1306 驱动代码
MCC 生成了硬件底层的代码,现在我们还需要编写 SSD1306 本身的驱动逻辑。
在你的项目中,创建一个新的头文件和一个新的源文件,例如 ssd1306.h 和 ssd1306.c。
ssd1306.h
- #ifndef SSD1306_H
- #define SSD1306_H
- #include <xc.h>
- #include "mcc_generated_files/i2c2.h" // 根据你实际使用的 I2C 模块修改
- // OLED 屏幕尺寸定义
- #define SSD1306_WIDTH 128
- #define SSD1306_HEIGHT 64
- // I2C 地址 (通常为 0x3C 或 0x3D,根据你的模块)
- #define SSD1306_I2C_ADDRESS 0x3C
- // 控制字节
- #define SSD1306_CONTROL_BYTE_CMD_SINGLE 0x80
- #define SSD1306_CONTROL_BYTE_CMD_STREAM 0x00
- #define SSD1306_CONTROL_BYTE_DATA_STREAM 0x40
- // 常用命令 (来自数据手册)
- #define SSD1306_CMD_DISPLAY_OFF 0xAE
- #define SSD1306_CMD_DISPLAY_ON 0xAF
- #define SSD1306_CMD_SET_DISPLAY_CLOCK_DIV 0xD5
- #define SSD1306_CMD_SET_MULTIPLEX 0xA8
- #define SSD1306_CMD_SET_DISPLAY_OFFSET 0xD3
- #define SSD1306_CMD_SET_START_LINE 0x40
- #define SSD1306_CMD_CHARGE_PUMP 0x8D
- #define SSD1306_CMD_MEMORY_MODE 0x20
- #define SSD1306_CMD_SEGREMAP 0xA1
- #define SSD1306_CMD_COMSCAN_DEC 0xC8
- #define SSD1306_CMD_COMSCAN_INC 0xC0
- #define SSD1306_CMD_SET_COMPINS 0xDA
- #define SSD1306_CMD_SET_CONTRAST 0x81
- #define SSD1306_CMD_SET_PRECHARGE 0xD9
- #define SSD1306_CMD_SET_VCOMDETECT 0xDB
- #define SSD1306_CMD_DISPLAYALLON_RESUME 0xA4
- #define SSD1306_CMD_DISPLAYALLON 0xA5
- #define SSD1306_CMD_NORMAL_DISPLAY 0xA6
- #define SSD1306_CMD_INVERT_DISPLAY 0xA7
- #define SSD1306_CMD_DEACTIVATE_SCROLL 0x2E
- #define SSD1306_CMD_ACTIVATE_SCROLL 0x2F
- #define SSD1306_CMD_COLUMN_ADDR 0x21
- #define SSD1306_CMD_PAGE_ADDR 0x22
- // 函数原型
- void SSD1306_WriteCommand(uint8_t command);
- void SSD1306_WriteData(uint8_t data);
- void SSD1306_Init(void);
- void SSD1306_SetCursor(uint8_t x, uint8_t y);
- void SSD1306_Clear(void);
- void SSD1306_Update(void); // 用于在图形模式下更新整个屏幕
- void SSD1306_DrawPixel(uint8_t x, uint8_t y, uint8_t color);
- void SSD1306_WriteChar(char ch);
- void SSD1306_WriteString(char* str);
- #endif /* SSD1306_H */
ssd1306.c
- #include "ssd1306.h"
- // 显存,用于存储屏幕上每个像素的状态 (128x64 pixels / 8 = 1024 bytes)
- static uint8_t SSD1306_Buffer[SSD1306_WIDTH * SSD1306_HEIGHT / 8];
- // 向 SSD1306 发送一个命令
- void SSD1306_WriteCommand(uint8_t command) {
- uint8_t data[2];
- data[0] = SSD1306_CONTROL_BYTE_CMD_SINGLE;
- data[1] = command;
- I2C2_Write(SSD1306_I2C_ADDRESS, data, 2);
- }
- // 向 SSD1306 发送一个数据字节
- void SSD1306_WriteData(uint8_t data) {
- uint8_t packet[2];
- packet[0] = SSD1306_CONTROL_BYTE_DATA_STREAM;
- packet[1] = data;
- I2C2_Write(SSD1306_I2C_ADDRESS, packet, 2);
- }
- // 初始化 SSD1306
- void SSD1306_Init(void) {
- // 给OLED足够的启动时间
- __delay_ms(100);
- // 一系列初始化命令序列
- SSD1306_WriteCommand(SSD1306_CMD_DISPLAY_OFF); // 关闭显示
- SSD1306_WriteCommand(SSD1306_CMD_SET_DISPLAY_CLOCK_DIV); // 设置时钟分频
- SSD1306_WriteCommand(0x80); // 建议值
- SSD1306_WriteCommand(SSD1306_CMD_SET_MULTIPLEX); // 设置多路复用比例
- SSD1306_WriteCommand(SSD1306_HEIGHT - 1); // 对于 64 像素高的屏幕是 63
- SSD1306_WriteCommand(SSD1306_CMD_SET_DISPLAY_OFFSET); // 设置显示偏移
- SSD1306_WriteCommand(0x00); // 无偏移
- SSD1306_WriteCommand(SSD1306_CMD_SET_START_LINE | 0x00);// 设置起始行号为 0
- SSD1306_WriteCommand(SSD1306_CMD_CHARGE_PUMP); // 启用电荷泵
- SSD1306_WriteCommand(0x14); // 0x14 启用,0x10 禁用
- SSD1306_WriteCommand(SSD1306_CMD_MEMORY_MODE); // 设置内存模式
- SSD1306_WriteCommand(0x00); // 水平寻址模式
- SSD1306_WriteCommand(SSD1306_CMD_SEGREMAP | 0x01); // 段重映射,0xA1 翻转
- SSD1306_WriteCommand(SSD1306_CMD_COMSCAN_DEC); // 扫描方向重映射,0xC8 翻转
- SSD1306_WriteCommand(SSD1306_CMD_SET_COMPINS); // 设置硬件引脚配置
- SSD1306_WriteCommand(0x12); // 对于 64 像素高的屏幕是 0x12
- SSD1306_WriteCommand(SSD1306_CMD_SET_CONTRAST); // 设置对比度
- SSD1306_WriteCommand(0xCF); // 对比度值
- SSD1306_WriteCommand(SSD1306_CMD_SET_PRECHARGE); // 设置预充电周期
- SSD1306_WriteCommand(0xF1); // 建议值
- SSD1306_WriteCommand(SSD1306_CMD_SET_VCOMDETECT); // 设置 VCOMH 反压等级
- SSD1306_WriteCommand(0x40); // 建议值
- SSD1306_WriteCommand(SSD1306_CMD_DISPLAYALLON_RESUME); // 不使用整个显示点亮
- SSD1306_WriteCommand(SSD1306_CMD_NORMAL_DISPLAY); // 非反色显示
- SSD1306_WriteCommand(SSD1306_CMD_DEACTIVATE_SCROLL); // 关闭滚动
- SSD1306_WriteCommand(SSD1306_CMD_DISPLAY_ON); // 开启显示!
- // 清空显存并更新显示
- SSD1306_Clear();
- SSD1306_Update();
- }
- // 清空显存 (清屏)
- void SSD1306_Clear(void) {
- for (uint16_t i = 0; i < sizeof(SSD1306_Buffer); i++) {
- SSD1306_Buffer[i] = 0x00;
- }
- }
- // 设置光标位置 (用于文本模式,简单实现)
- void SSD1306_SetCursor(uint8_t x, uint8_t y) {
- // 这个函数在基础图形驱动中不是必须的,但可以用于定位文本
- // 更完善的实现需要管理光标坐标
- }
- // 将整个显存发送到 SSD1306
- void SSD1306_Update(void) {
- // 设置列地址范围 (0 到 127)
- SSD1306_WriteCommand(SSD1306_CMD_COLUMN_ADDR);
- SSD1306_WriteCommand(0);
- SSD1306_WriteCommand(SSD1306_WIDTH - 1);
- // 设置页地址范围 (0 到 7,因为 64/8=8)
- SSD1306_WriteCommand(SSD1306_CMD_PAGE_ADDR);
- SSD1306_WriteCommand(0);
- SSD1306_WriteCommand(7);
- // 准备发送数据流
- uint8_t control_byte = SSD1306_CONTROL_BYTE_DATA_STREAM;
- I2C2_Write(SSD1306_I2C_ADDRESS, &control_byte, 1); // 发送控制字节
- // 发送整个显存
- // 注意:I2C2_Write 可能需要分块发送,如果一次发送太多,请循环发送
- I2C2_Write(SSD1306_I2C_ADDRESS, SSD1306_Buffer, sizeof(SSD1306_Buffer));
- }
- // 画一个像素点 (这是图形驱动的基础)
- void SSD1306_DrawPixel(uint8_t x, uint8_t y, uint8_t color) {
- if (x >= SSD1306_WIDTH || y >= SSD1306_HEIGHT) {
- return; // 超出范围
- }
- uint16_t index = x + (y / 8) * SSD1306_WIDTH;
- if (color) {
- SSD1306_Buffer[index] |= (1 << (y % 8)); // 点亮像素
- } else {
- SSD1306_Buffer[index] &= ~(1 << (y % 8)); // 熄灭像素
- }
- }
- // 注意:WriteChar 和 WriteString 需要一个字体库,这里只是一个框架。
- // 你需要自己实现一个字体数组 (例如 5x7 字体)。
- /*
- typedef struct {
- uint8_t width;
- uint8_t height;
- const uint8_t *data;
- } FontDef;
- extern FontDef Font_5x7;
- */
- void SSD1306_WriteChar(char ch) {
- // 实现需要字体库
- // 1. 根据字符查找字体数据
- // 2. 将字体数据的每一位绘制到显存中
- // 3. 更新光标位置
- }
- void SSD1306_WriteString(char* str) {
- // 循环调用 SSD1306_WriteChar
- while (*str) {
- SSD1306_WriteChar(*str++);
- }
- }
|