From 53de825c4c4d8325c02341177f62ab84897eb5ee Mon Sep 17 00:00:00 2001
From: guowenxue <guowenxue@gmail.com>
Date: Sat, 25 Jul 2026 22:09:43 +0800
Subject: [PATCH] update vscode task.json and add stm32flash tools
---
ex2.i2c-bitbang/Board/oled_drv.c | 402 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++
1 files changed, 402 insertions(+), 0 deletions(-)
diff --git a/ex2.i2c-bitbang/Board/oled_drv.c b/ex2.i2c-bitbang/Board/oled_drv.c
new file mode 100644
index 0000000..5c7c89c
--- /dev/null
+++ b/ex2.i2c-bitbang/Board/oled_drv.c
@@ -0,0 +1,402 @@
+/**********************************************************************
+ * Copyright: (C)2021 LingYun IoT System Studio <www.weike-iot.com>
+ * Author: GuoWenxue<guowenxue@gmail.com> 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 <string.h>
+#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();
+}
--
Gitblit v1.10.0