/********************************************************************** * Copyright: (C)2021 LingYun IoT System Studio * Author: GuoWenxue QQ: 281143292 * Description: ISKBoard OLED(N091-2832TSWFG02-H14, 128x32) SSD1306 driver * * ChangeLog: * Version Date Author Description * V1.0.0 2021.08.10 GuoWenxue Release initial version * V2.0.0 2026.04.29 Optimized GRAM buffered rendering ***********************************************************************/ #include #include "oled_drv.h" #include "oled_font.h" #include "miscdev.h" /* I2C 设备 OLED */ static i2c_dev_t oled; /* ----------------------------------------------------------------------- * 屏幕本地显存镜像缓冲区(Shadow GRAM): 128列 x 4页 * 所有绘制操作先写入此缓冲区,调用 OLED_Refresh() 后才真正发送到屏幕, * ----------------------------------------------------------------------- */ static uint8_t s_gram[OLED_PAGES][X_WIDTH]; /* ----------------------------------------------------------------------- * I2C 突发传输数据打包缓冲区,大小为 128列 + 1字节控制头 = 129 字节。 * 用于在冲刷(Refresh)每一页显存时,将控制字节 (OLED_DATA) 与一页的 * 128 字节 GRAM 数据在连续的内存中拼接,从而实现单次 I2C 事务的流式高效写入。 * ----------------------------------------------------------------------- */ static uint8_t s_tx_packet[X_WIDTH + 1]; /* * ==================================== * 1. OLED 底层内部私有通信函数 * ==================================== */ static int oled_write_cmd(uint8_t cmd) { i2c_msg_t msg; int ret = -2; uint8_t buf[2]; /* 准备数据包:控制字节(OLED_CMD) + 命令字节 */ buf[0] = OLED_CMD; buf[1] = cmd; /* 配置 I2C 消息结构体 */ msg.addr = oled.dev_addr; /* 7位从机地址 */ msg.flags = I2C_M_WR; /* 写标志 */ msg.len = 2; /* 数据长度 = 控制字节 + 命令字节 */ msg.buf = buf; /* 数据指针 */ /* 将I2C总线加锁让设备独占总线 */ i2c_lock_dev(&oled); /* 执行 I2C 传输 */ if (i2c_transfer(oled.bus, &msg, 1) != I2C_OK) { goto cleanup; } ret = 0; cleanup: /* 释放设备的 I2C 总线锁 */ i2c_free_dev(&oled); return ret; } int OLED_Init(i2c_bus_t *bus) { if (!bus) return -1; if( i2c_bind_dev(&oled, bus, OLED_I2C_ADDR) < 0) return -2; mdelay(100); oled_write_cmd(0xAE); /* Display OFF */ oled_write_cmd(0x40); /* Display Start Line = 0 */ oled_write_cmd(0xB0); /* Page Start Address = 0 */ oled_write_cmd(0xC8); /* COM Scan: remapped (垂直翻转修正) */ oled_write_cmd(0x81); /* Contrast Control */ oled_write_cmd(0xFF); /* 最大对比度 */ oled_write_cmd(0xA1); /* Segment Remap: SEG127→SEG0 */ oled_write_cmd(0xA6); /* Normal Display (非反色) */ oled_write_cmd(0xA8); /* Multiplex Ratio */ oled_write_cmd(0x1F); /* MUX=31 → 32行 (128×32屏) */ oled_write_cmd(0xD3); /* Display Offset */ oled_write_cmd(0x00); /* 垂直偏移=0 */ oled_write_cmd(0xD5); /* Display Clock Divide Ratio / OSC */ oled_write_cmd(0xF0); /* 高频率 + 默认分频,提高刷新率 */ oled_write_cmd(0xD9); /* Pre-charge Period */ oled_write_cmd(0x22); /* Phase1=2, Phase2=2 DCLK */ oled_write_cmd(0xDA); /* COM Pins Hardware Config */ oled_write_cmd(0x02); /* 适用于 128×32(128×64 用 0x12) */ oled_write_cmd(0xDB); /* VCOMH */ oled_write_cmd(0x49); /* ≈ 0.83×Vcc */ /* 开启 OLED 显示面板 */ OLED_Display_On(); /* 将显示屏黑屏 */ OLED_Clear(); OLED_Refresh(); return 0; } /** * @brief 开启 OLED 显示面板 */ void OLED_Display_On(void) { oled_write_cmd(0x8D); /* Charge Pump Setting */ oled_write_cmd(0x14); /* 电荷泵使能 */ oled_write_cmd(0xAF); /* Display ON */ } /** * @brief 关闭 OLED 显示面板 */ void OLED_Display_Off(void) { oled_write_cmd(0x8D); /* Charge Pump Setting */ oled_write_cmd(0x10); /* 电荷泵停用 */ oled_write_cmd(0xAE); /* Display OFF */ } /* 设置页地址(Page Address), SSD1306 显示屏按页寻址,每页 8 行像素 * page 表示页号(0~3,对应 32px 高度的 4 页) */ void oled_set_pos(uint8_t x, uint8_t page) { oled_write_cmd( 0xB0 | (page & 0x07) ); /* 页地址 */ oled_write_cmd( 0x10 | ((x & 0xF0) >> 4) ); /* 列高4位 */ oled_write_cmd( x & 0x0F ); /* 列低4位 */ } /** * @brief 将本地 GRAM 缓冲区突发(Burst)全量更新至屏幕物理显存 */ void OLED_Refresh(void) { uint8_t page; i2c_msg_t msg; /* 锁住整个整幅图像的刷新周期,避免多页之间总线控制权被切走造成画面闪烁 */ i2c_lock_dev(&oled); msg.addr = oled.dev_addr; msg.flags = I2C_M_WR; msg.len = X_WIDTH + 1; msg.buf = s_tx_packet; s_tx_packet[0] = (uint8_t)OLED_DATA; for (page = 0; page < OLED_PAGES; page++) { oled_set_pos(0, page); memcpy(&s_tx_packet[1], s_gram[page], X_WIDTH); if (i2c_transfer(oled.bus, &msg, 1) != I2C_OK) { goto cleanup; } udelay(5); } cleanup: i2c_free_dev(&oled); } /* * ==================================== * 2. 内存渲染 API (无需涉及I2C总线操作) * ==================================== */ /** * @brief 清空软件 GRAM 缓冲区(全黑), 需配合 OLED_Refresh() 使用。 */ void OLED_Clear(void) { memset(s_gram, 0x00, sizeof(s_gram)); } /** * @brief 点亮软件 GRAM 缓冲区中所有像素(全白), 需配合 OLED_Refresh() 使用。 */ void OLED_On(void) { memset(s_gram, 0xFF, sizeof(s_gram)); } /** * @brief 在本地显存区绘制一个 ASCII 字符 */ void OLED_ShowChar(uint8_t x, uint8_t page, char chr, uint8_t char_size) { uint8_t c; uint8_t i; uint8_t col; if (chr < ' ' || chr > '~') { chr = '?'; } c = chr - ' '; if (char_size == OLED_FONT16) { for (i = 0; i < 8; i++) { col = x + i; if (col >= X_WIDTH) { break; } if (page < OLED_PAGES) { s_gram[page][col] = F8X16[c * 16 + i]; } if ((page + 1) < OLED_PAGES) { s_gram[page + 1][col] = F8X16[c * 16 + i + 8]; } } } else { for (i = 0; i < 6; i++) { col = x + i; if (col >= X_WIDTH) { break; } if (page < OLED_PAGES) { s_gram[page][col] = F6x8[c][i]; } } } } /** * @brief 在本地显存区绘制字符串 */ void OLED_ShowString(uint8_t x, uint8_t page, const char *str, uint8_t font_size) { uint8_t char_width; uint8_t char_pages; char_width = (font_size == OLED_FONT16) ? 8 : 6; char_pages = (font_size == OLED_FONT16) ? 2 : 1; if (!str) { return; } while (*str != '\0') { if (page + char_pages > OLED_PAGES) { break; } OLED_ShowChar(x, page, *str, font_size); x += char_width; if (x + char_width > X_WIDTH) { x = 0; page += char_pages; } str++; } } /** * @brief 在本地显存区绘制 16x16 汉字 */ void OLED_ShowChinese(const uint8_t (*Hzk)[32], uint8_t x, uint8_t page, uint8_t index) { uint8_t i; uint8_t col; if (!Hzk || (page + 1 >= OLED_PAGES)) { return; } for (i = 0; i < 16; i++) { col = x + i; if (col >= X_WIDTH) { break; } s_gram[page][col] = Hzk[2 * index][i]; s_gram[page + 1][col] = Hzk[2 * index + 1][i]; } } /** * @brief 在本地显存区绘制单色 BMP 位图 */ void OLED_DrawBMP(uint8_t x0, uint8_t page0, uint8_t x_width, uint8_t y_width, const uint8_t *bmp) { uint8_t page; uint8_t x; uint8_t pages; uint32_t j; pages = (y_width + 7) / 8; j = 0; if (!bmp) { return; } for (page = page0; page < page0 + pages && page < OLED_PAGES; page++) { for (x = x0; x < x0 + x_width && x < X_WIDTH; x++) { s_gram[page][x] = bmp[j++]; } } } /* * ==================================== * 3. OLED 驱动测试及示范代码 * ==================================== */ void oled_test_example(void) { uint8_t i; const char *title = "ISKBoard"; /* 1. 硬件总线与外设初始化 */ OLED_Init(&i2c1_bus); mdelay(500); /* 2. 第一阶段:全屏点阵 Logo 图像显示测试 */ OLED_Clear(); /* 参数说明: 起始X=0, 起始页=PAGE0, 宽度=128, 高度=32, 数据源 */ OLED_DrawBMP(0, OLED_PAGE0, BMP_LOGO_WIDTH, BMP_LOGO_HEIGHT, bmp_logo); OLED_Refresh(); /* 冲刷到物理屏幕 */ mdelay(3000); /* 保持显示 3 秒 */ /* 3. 第二阶段:16x16 宋体汉字 "凌云实验室" 显示测试 */ OLED_Clear(); /* * 基于 index 寻址: * 传入 index=0,函数内部会自动抓取 Hzk_LingYun[0] 和 Hzk_LingYun[1] 组合成“凌” * 传入 index=1,函数内部会自动抓取 Hzk_LingYun[2] 和 Hzk_LingYun[3] 组合成“云” * 每个字横向占 16 像素,起始 X 从 24 开始,使 5 个字在 128 像素宽的屏幕上完美居中 */ OLED_ShowChinese(Hzk_LingYun, 24, OLED_PAGE1, 0); /* “凌” (index: 0) */ OLED_ShowChinese(Hzk_LingYun, 40, OLED_PAGE1, 1); /* “云” (index: 1) */ OLED_ShowChinese(Hzk_LingYun, 56, OLED_PAGE1, 2); /* “实” (index: 2) */ OLED_ShowChinese(Hzk_LingYun, 72, OLED_PAGE1, 3); /* “验” (index: 3) */ OLED_ShowChinese(Hzk_LingYun, 88, OLED_PAGE1, 4); /* “室” (index: 4) */ OLED_Refresh(); /* 将渲染好的 5 个宋体汉字一次性刷入屏幕 */ mdelay(2500); /* 保持显示 2.5 秒 */ /* 4. 第三阶段:标准 ASCII 字符串动效打字机显示测试 */ OLED_Clear(); OLED_Refresh(); /* 动态计算位置,从第 32 列、第 1 页开始以 16 号大字体逐字敲出 "ISKBoard" */ for (i = 0; title[i] != '\0'; i++) { OLED_ShowChar(32 + (i * 8), OLED_PAGE1, title[i], OLED_FONT16); OLED_Refresh(); /* 每写一个字符,立即同步刷新到屏幕,形成打字机动效 */ mdelay(150); } mdelay(2000); /* 5. 第四阶段:测试结束,清屏复位 */ OLED_Clear(); OLED_ShowString(24, OLED_PAGE1, "- Test Over -", OLED_FONT16); OLED_Refresh(); mdelay(1000); /* 6. 将显示屏黑屏并关闭显示 */ OLED_Clear(); OLED_Refresh(); OLED_Display_Off(); }