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