From d42fb887237e30d5026e006a3774d6b0a215b829 Mon Sep 17 00:00:00 2001
From: guowenxue <guowenxue@gmail.com>
Date: Sat, 25 Jul 2026 21:37:12 +0800
Subject: [PATCH] Add ex2.i2c-bitbang example code

---
 ex2.i2c-bitbang/Board/sht20.c            |  226 ++
 ex2.i2c-bitbang/do_clean.bat             |   39 
 ex2.i2c-bitbang/Board/buzzer.h           |   37 
 ex2.i2c-bitbang/Board/ds18b20.h          |   18 
 ex2.i2c-bitbang/Board/CMakeLists.txt     |   28 
 ex2.i2c-bitbang/Board/buzzer.c           |  191 +
 ex2.i2c-bitbang/Board/ds18b20.c          |  311 ++
 ex2.i2c-bitbang/Board/miscdev.c          |  409 +++
 ex2.i2c-bitbang/Core/Src/main.c          |  323 ++
 ex2.i2c-bitbang/Board/isl1208.c          |  367 +++
 ex2.i2c-bitbang/Board/miscdev.h          |  170 +
 ex2.i2c-bitbang/Board/oled_drv.c         |  402 +++
 ex2.i2c-bitbang/Board/i2c_bitbang.h      |  269 ++
 ex2.i2c-bitbang/Board/eeprom.c           |  284 ++
 ex2.i2c-bitbang/ISKBoard.ioc             |  210 +
 ex2.i2c-bitbang/.vscode/tasks.json       |   48 
 ex2.i2c-bitbang/Board/i2c_bitbang.c      |  734 ++++++
 ex2.i2c-bitbang/MDK-ARM/ISKBoard.uvoptx  |  753 ++++++
 ex2.i2c-bitbang/Board/eeprom.h           |   79 
 ex2.i2c-bitbang/Board/sht20.h            |   33 
 ex2.i2c-bitbang/Board/oled_font.h        |   64 
 ex2.i2c-bitbang/CMakeLists.txt           |   75 
 ex2.i2c-bitbang/MDK-ARM/ISKBoard.uvprojx |  929 ++++++++
 ex2.i2c-bitbang/Board/oled_font.c        |  323 ++
 ex2.i2c-bitbang/.gitignore               |   47 
 ex2.i2c-bitbang/Board/isl1208.h          |   73 
 ex2.i2c-bitbang/Board/oled_drv.h         |  163 +
 27 files changed, 6,605 insertions(+), 0 deletions(-)

diff --git a/ex2.i2c-bitbang/.gitignore b/ex2.i2c-bitbang/.gitignore
new file mode 100644
index 0000000..42d5d2e
--- /dev/null
+++ b/ex2.i2c-bitbang/.gitignore
@@ -0,0 +1,47 @@
+# ===== .vscode: 只保留下面三个文件 =====
+.vscode/*
+!.vscode/stm32flash.bat
+!.vscode/stm32flash.exe
+!.vscode/tasks.json
+
+# ===== Keil MD: 只保留下面两个文件 =====
+MDK-ARM/*
+!MDK-ARM/ISKBoard.uvoptx
+!MDK-ARM/ISKBoard.uvprojx
+
+# ===== STM32CubeIDE: 忽略下面文件 =====
+.project
+
+# ===== 只保留这两个文件不忽略 =====
+# main.c
+Core/*
+!Core/Src/
+Core/Src/*
+!Core/Src/main.c
+
+# ===== 忽略 CMake 相关生成文件 =====
+.clangd
+.mxproject
+CMakePresets.json
+cmake/
+.settings/
+
+# ===== 忽略启动文件、链接脚本 =====
+*.ld
+startup_stm32*.s
+
+# ===== 忽略厂商 SDK(如果之后 Drivers/ 出现的话)=====
+Drivers/
+Middlewares/
+
+# ===== 忽略编译产物 =====
+build/
+Debug/
+Release/
+*.o
+*.d
+*.a
+*.elf
+*.bin
+*.hex
+*.map
diff --git a/ex2.i2c-bitbang/.vscode/tasks.json b/ex2.i2c-bitbang/.vscode/tasks.json
new file mode 100644
index 0000000..3079dd6
--- /dev/null
+++ b/ex2.i2c-bitbang/.vscode/tasks.json
@@ -0,0 +1,48 @@
+{
+    "version": "2.0.0",
+    "tasks": [
+        {
+            "label": "STM32: Flash via ST-Link (SWD)",
+            "type": "shell",
+            "command": "cube",
+            "args": [
+                "programmer",
+                "-c", "port=SWD", "freq=4000", "reset=HWrst",
+                "-w", "${workspaceFolder}\\build\\Release\\ISKBoard.elf",
+                "-rst"
+            ],
+            "group": "build",
+            "problemMatcher": [],
+            "presentation": {
+                "reveal": "always",
+                "panel": "shared",
+                "clear": true
+            }
+        },
+        {
+            "label": "STM32: Flash via Serial (UART)",
+            "type": "shell",
+            "command": "cube",
+            "args": [
+                "programmer",
+                "--connect", "port=${input:comPort}",
+                "-w", "${workspaceFolder}\\build\\Release\\ISKBoard.elf"
+            ],
+            "group": "build",
+            "problemMatcher": [],
+            "presentation": {
+                "reveal": "always",
+                "panel": "shared",
+                "clear": true
+            }
+        }
+    ],
+    "inputs": [
+        {
+            "id": "comPort",
+            "type": "promptString",
+            "description": "!!!请确保开发板进入烧录模式,并输入串口号!!!",
+            "default": "COM22"
+        }
+    ]
+}
diff --git a/ex2.i2c-bitbang/Board/CMakeLists.txt b/ex2.i2c-bitbang/Board/CMakeLists.txt
new file mode 100644
index 0000000..c35aeef
--- /dev/null
+++ b/ex2.i2c-bitbang/Board/CMakeLists.txt
@@ -0,0 +1,28 @@
+cmake_minimum_required(VERSION 3.16)
+
+# 取当前文件夹名,并转成小写作为库名
+get_filename_component(MODULE_NAME ${CMAKE_CURRENT_SOURCE_DIR} NAME)
+string(TOLOWER ${MODULE_NAME} MODULE_NAME_LOWER)
+
+project(${MODULE_NAME_LOWER} C)
+
+# 收集当前目录源码
+file(GLOB MODULE_SRC
+    "${CMAKE_CURRENT_SOURCE_DIR}/*.c"
+)
+
+# 创建静态库
+add_library(${MODULE_NAME_LOWER} STATIC
+    ${MODULE_SRC}
+)
+
+# 头文件搜索路径
+target_include_directories(${MODULE_NAME_LOWER}
+    PUBLIC
+        ${CMAKE_CURRENT_SOURCE_DIR}
+)
+
+target_link_libraries(${MODULE_NAME_LOWER}
+    PUBLIC
+        stm32cubemx
+)
\ No newline at end of file
diff --git a/ex2.i2c-bitbang/Board/buzzer.c b/ex2.i2c-bitbang/Board/buzzer.c
new file mode 100644
index 0000000..f39e80c
--- /dev/null
+++ b/ex2.i2c-bitbang/Board/buzzer.c
@@ -0,0 +1,191 @@
+/**********************************************************************
+*   Copyright: (C)2024 LingYun IoT System Studio
+*      Author: GuoWenxue<guowenxue@gmail.com>
+* Description: ISKBoard buzzer/beeper driver and music play
+*
+*   ChangeLog:
+*        Version    Date       Author            Description
+*        V1.0.0  2024.08.29    GuoWenxue      Release initial version
+***********************************************************************/
+
+#include "tim.h"
+#include "buzzer.h"
+
+#define mdelay(ms)            HAL_Delay(ms)
+#define ARRAY_SIZE(song)      (sizeof(song)/sizeof(song[0]))
+
+/* 音符频率定义(Hz) */
+#define NOTE_C4   524
+#define NOTE_D4   588
+#define NOTE_E4   660
+#define NOTE_F4   698
+#define NOTE_G4   784
+#define NOTE_A4   880
+#define NOTE_B4   988
+#define NOTE_C5   1046
+#define NOTE_D5   1174
+#define NOTE_E5   1318
+#define NOTE_F5   1396
+#define NOTE_G5   1568
+#define NOTE_A5   1760
+#define NOTE_B5   1976
+#define NOTE_C6   2093
+#define REST      0
+
+#define BEAT      400   /* 一拍时长(ms),可自行调整节奏快慢 */
+
+/* 《小星星》简谱:
+ * 1 1 5 5 6 6 5   -   4 4 3 3 2 2 1
+ * 5 5 4 4 3 3 2   -   5 5 4 4 3 3 2
+ * 1 1 5 5 6 6 5   -   4 4 3 3 2 2 1
+ * (C=1, D=2, E=3, F=4, G=5, A=6, B=7)
+ */
+const Note_t twinkle[] =
+{
+    /* Twinkle twinkle little star */
+    {NOTE_C4, BEAT}, {NOTE_C4, BEAT}, {NOTE_G4, BEAT}, {NOTE_G4, BEAT},
+    {NOTE_A4, BEAT}, {NOTE_A4, BEAT}, {NOTE_G4, BEAT * 2},
+
+    /* How I wonder what you are */
+    {NOTE_F4, BEAT}, {NOTE_F4, BEAT}, {NOTE_E4, BEAT}, {NOTE_E4, BEAT},
+    {NOTE_D4, BEAT}, {NOTE_D4, BEAT}, {NOTE_C4, BEAT * 2},
+
+    /* Up above the world so high */
+    {NOTE_G4, BEAT}, {NOTE_G4, BEAT}, {NOTE_F4, BEAT}, {NOTE_F4, BEAT},
+    {NOTE_E4, BEAT}, {NOTE_E4, BEAT}, {NOTE_D4, BEAT * 2},
+
+    /* Like a diamond in the sky */
+    {NOTE_G4, BEAT}, {NOTE_G4, BEAT}, {NOTE_F4, BEAT}, {NOTE_F4, BEAT},
+    {NOTE_E4, BEAT}, {NOTE_E4, BEAT}, {NOTE_D4, BEAT * 2},
+
+    /* Twinkle twinkle little star (repeat) */
+    {NOTE_C4, BEAT}, {NOTE_C4, BEAT}, {NOTE_G4, BEAT}, {NOTE_G4, BEAT},
+    {NOTE_A4, BEAT}, {NOTE_A4, BEAT}, {NOTE_G4, BEAT * 2},
+
+    /* How I wonder what you are (repeat) */
+    {NOTE_F4, BEAT}, {NOTE_F4, BEAT}, {NOTE_E4, BEAT}, {NOTE_E4, BEAT},
+    {NOTE_D4, BEAT}, {NOTE_D4, BEAT}, {NOTE_C4, BEAT * 2},
+};
+const uint32_t twinkle_count = ARRAY_SIZE(twinkle);
+
+/* 《祝你生日快乐》(Happy Birthday to You)
+ * 该曲旋律/歌词经美国法院2015-2016年裁定确认为公有领域作品,可放心使用。
+ * 简谱: 5 5 6 5 1' 7 | 5 5 6 5 2' 1' | 5 5 5' 3' 1' 7 6 | 4' 4' 3' 1' 2' 1'
+ */
+const Note_t happy_birthday[] =
+{
+    /* Happy Birthday to you */
+    {NOTE_G4, BEAT / 2}, {NOTE_G4, BEAT / 2}, {NOTE_A4, BEAT}, {NOTE_G4, BEAT},
+    {NOTE_C5, BEAT}, {NOTE_B4, BEAT * 2},
+
+    /* Happy Birthday to you */
+    {NOTE_G4, BEAT / 2}, {NOTE_G4, BEAT / 2}, {NOTE_A4, BEAT}, {NOTE_G4, BEAT},
+    {NOTE_D5, BEAT}, {NOTE_C5, BEAT * 2},
+
+    /* Happy Birthday dear friend */
+    {NOTE_G4, BEAT / 2}, {NOTE_G4, BEAT / 2}, {NOTE_G5, BEAT}, {NOTE_E5, BEAT},
+    {NOTE_C5, BEAT}, {NOTE_B4, BEAT}, {NOTE_A4, BEAT * 2},
+
+    /* Happy Birthday to you */
+    {NOTE_F5, BEAT / 2}, {NOTE_F5, BEAT / 2}, {NOTE_E5, BEAT}, {NOTE_C5, BEAT},
+    {NOTE_D5, BEAT}, {NOTE_C5, BEAT * 2},
+
+    /* 结尾留白 */
+    {REST, BEAT * 2},
+};
+const uint32_t happy_birthday_count = ARRAY_SIZE(happy_birthday);
+
+/* 《Jingle Bells》(铃儿响叮当)
+ * 1857年James Lord Pierpont创作,早已进入公有领域。
+ * (下面是常见的简化版本,节奏为大致还原,供播放测试使用)
+ */
+const Note_t jingle_bells[] =
+{
+    /* Jingle bells, jingle bells, jingle all the way */
+    {NOTE_E4, BEAT}, {NOTE_E4, BEAT}, {NOTE_E4, BEAT * 2},
+    {NOTE_E4, BEAT}, {NOTE_E4, BEAT}, {NOTE_E4, BEAT * 2},
+    {NOTE_E4, BEAT}, {NOTE_G4, BEAT}, {NOTE_C4, BEAT}, {NOTE_D4, BEAT},
+    {NOTE_E4, BEAT * 4},
+
+    /* Oh what fun it is to ride in a one-horse open sleigh */
+    {NOTE_F4, BEAT}, {NOTE_F4, BEAT}, {NOTE_F4, BEAT}, {NOTE_F4, BEAT},
+    {NOTE_F4, BEAT}, {NOTE_E4, BEAT}, {NOTE_E4, BEAT}, {NOTE_E4, BEAT / 2}, {NOTE_E4, BEAT / 2},
+    {NOTE_E4, BEAT}, {NOTE_D4, BEAT}, {NOTE_D4, BEAT}, {NOTE_E4, BEAT},
+    {NOTE_D4, BEAT * 2}, {NOTE_G4, BEAT * 2},
+
+    /* 结尾留白 */
+    {REST, BEAT * 2},
+};
+const uint32_t jingle_bells_count = ARRAY_SIZE(jingle_bells);
+
+/*
+ *+----------------------------+
+ *|       PWM buzzer API       |
+ *+----------------------------+
+ */
+
+/**
+ * @brief  设置蜂鸣器发声频率(占空比固定50%)
+ */
+static inline void Buzzer_SetFreq(uint32_t freq)
+{
+    /* TIM1 初始化 PSC = 79,计数时钟 = 80MHz/80 = 1MHz */
+    uint32_t arr = 1000000U / freq - 1U;
+    __HAL_TIM_SET_AUTORELOAD(&htim1, arr);
+    __HAL_TIM_SET_COMPARE(&htim1, TIM_CHANNEL_4, arr / 2U);
+    __HAL_TIM_SET_COUNTER(&htim1, 0);
+}
+
+/**
+ * @brief  让蜂鸣器响 $times 次,每次间隔 $interval 毫秒
+ */
+void beep_start(uint16_t times, uint16_t interval)
+{
+    Buzzer_SetFreq(2700);
+
+    while( times-- )
+    {
+        /* Start buzzer */
+        HAL_TIM_PWM_Start(&htim1, TIM_CHANNEL_4);
+
+        mdelay(interval);
+
+        /* Stop buzzer */
+        HAL_TIM_PWM_Stop(&htim1, TIM_CHANNEL_4);
+
+        mdelay(interval);
+    }
+}
+
+/**
+ * @brief  播放单个音符
+ */
+static void Buzzer_PlayNote(uint32_t freq, uint32_t duration_ms)
+{
+    if (freq == 0)
+    {
+        HAL_TIM_PWM_Stop(&htim1, TIM_CHANNEL_4);
+        mdelay(duration_ms);
+        return;
+    }
+
+    Buzzer_SetFreq(freq);
+
+    HAL_TIM_PWM_Start(&htim1, TIM_CHANNEL_4);
+    mdelay(duration_ms - 30);
+
+    HAL_TIM_PWM_Stop(&htim1, TIM_CHANNEL_4);
+
+    mdelay(30);
+}
+
+/**
+ * @brief  播放歌曲
+ */
+void Play_Song(const Note_t *song, uint32_t note_count)
+{
+    for (uint32_t i = 0; i < note_count; i++)
+    {
+        Buzzer_PlayNote(song[i].freq, song[i].duration);
+    }
+}
diff --git a/ex2.i2c-bitbang/Board/buzzer.h b/ex2.i2c-bitbang/Board/buzzer.h
new file mode 100644
index 0000000..549c1a0
--- /dev/null
+++ b/ex2.i2c-bitbang/Board/buzzer.h
@@ -0,0 +1,37 @@
+/**********************************************************************
+*   Copyright: (C)2024 LingYun IoT System Studio
+*      Author: GuoWenxue<guowenxue@gmail.com>
+* Description: ISKBoard buzzer/beeper driver and music play
+*
+*   ChangeLog:
+*        Version    Date       Author            Description
+*        V1.0.0  2024.08.29    GuoWenxue      Release initial version
+***********************************************************************/
+
+#ifndef __BUZZER_H
+#define __BUZZER_H
+
+typedef struct
+{
+    uint32_t freq;      /* 音符频率,0表示休止 */
+    uint32_t duration;  /* 持续时间(ms) */
+} Note_t;
+
+/**
+ * @brief  让蜂鸣器响 $times 次,每次间隔 $interval 毫秒
+ */
+extern void beep_start(uint16_t times, uint16_t interval);
+
+/**
+ * @brief  播放歌曲
+ */
+extern void Play_Song(const Note_t *song, uint32_t note_count);
+
+extern const Note_t twinkle[];
+extern const uint32_t twinkle_count;
+extern const Note_t happy_birthday[];
+extern const uint32_t happy_birthday_count;
+extern const Note_t jingle_bells[];
+extern const uint32_t jingle_bells_count;
+
+#endif
diff --git a/ex2.i2c-bitbang/Board/ds18b20.c b/ex2.i2c-bitbang/Board/ds18b20.c
new file mode 100644
index 0000000..55b295f
--- /dev/null
+++ b/ex2.i2c-bitbang/Board/ds18b20.c
@@ -0,0 +1,311 @@
+/**********************************************************************
+ *   Copyright: (C)2024 LingYun IoT System Studio
+ *      Author: GuoWenxue<guowenxue@gmail.com>
+ *
+ * Description: Temperature sensor DS18B20 driver on ISKBoard
+ *
+ *   ChangeLog:
+ *        Version    Date       Author            Description
+ *        V1.0.0  2024.08.29    GuoWenxue      Release initial version
+ *
+ ***********************************************************************/
+
+ #include "miscdev.h"
+
+/* DS18B20 上的数据通信口 DQ 连到了 CPU 的 PA12 引脚上 */
+static gpio_t   W1Dat =
+{
+    .name = "1-wire",
+    .group = GPIOA,
+    .pin   = GPIO_PIN_12,
+};
+
+/* 下面定义了一些DS18B20通信DQ引脚的操作宏,这里之所以用宏而不是函数是
+ * 减少函数调用的CPU开销。在下面宏中最后面出现的 \ 为断行符,如果不清楚
+ * 的话,可以自行 Deepseek 搜索学习:宏定义 断行符
+ */
+
+/* 这里定义了一个宏,用来初始化DQ引脚:设置为输入模式,并使能内部上拉 */
+#define W1DQ_Input()        \
+{   \
+    GPIO_InitTypeDef GPIO_InitStruct = {0}; \
+    GPIO_InitStruct.Pin = W1Dat.pin; \
+    GPIO_InitStruct.Mode = GPIO_MODE_INPUT; \
+    GPIO_InitStruct.Pull = GPIO_PULLUP; \
+    GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH; \
+    HAL_GPIO_Init(W1Dat.group, &GPIO_InitStruct); \
+}
+
+/* 这里定义了一个宏,用来把 DQ 引脚设置为推挽输出模式 */
+#define W1DQ_Output()       \
+{   \
+    GPIO_InitTypeDef GPIO_InitStruct = {0}; \
+    GPIO_InitStruct.Pin = W1Dat.pin; \
+    GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP; \
+    GPIO_InitStruct.Pull = GPIO_NOPULL; \
+    GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH; \
+    HAL_GPIO_Init(W1Dat.group, &GPIO_InitStruct); \
+}
+
+/* 这里定义了一个宏,用来写控制 DQ 引脚时输出高电平还是低电平 */
+#define W1DQ_Write(x)   HAL_GPIO_WritePin(W1Dat.group, W1Dat.pin, \
+                        (x==1)?GPIO_PIN_SET:GPIO_PIN_RESET)
+
+/* 这里定义了一个宏,用来读 DQ 引脚,当前时高电平还是低电平 */
+#define W1DQ_Read()     HAL_GPIO_ReadPin(W1Dat.group,  W1Dat.pin)
+
+/* 这里定义了一个枚举,用来定义低电平和高电平 */
+enum {
+    LOW,  /* 低电平:LOW=0 */
+    HIGH, /* 高电平: LOW=1 */
+};
+
+/* 单总线上的所有通信都是以初始化序列开始,Master发出初始化信号后等待从设备的应答信号,以确定从设备是否存在并能正常工作。 */
+static int ds18b20_reset(void)
+{
+    int time = 0;
+    int present = 0; /* 设置默认值没有探测到 DS18B20 */
+    static int firstin = 1;
+
+    /* 使能所用 GPIO 口的时钟 */
+    if(firstin)
+    {
+        gpio_clk_enable(W1Dat.group);
+        firstin = 0;
+    }
+
+    /* 设置 DQ 引脚为输出模式 */
+    W1DQ_Output();
+
+    /* 主机(CPU)输出低电平,并保持低电平时间至少 480us,以产生复位脉冲。 */
+    W1DQ_Write(LOW);
+    udelay(480);
+
+    /* 接着主机(CPU)释放总线, 4.7K 的上拉电阻将单总线拉高,延时 15~60 us,并进入接收模式(Rx) */
+    W1DQ_Write(HIGH); // 这里先拉高是确保在模式切换瞬间,总线已经是高电平,避免模式切换瞬间可能出现的不确定状态。
+    udelay(60);
+    W1DQ_Input();
+
+    /* DS18B20芯片在收到主机发送过来的这个复位脉冲后,将会拉低总线 60~240 us,以产生低电平应答脉冲 */
+    while( W1DQ_Read() && time<240) {
+        time++;
+        udelay(1);
+    }
+
+    /* 如果 time<240 则说明上面的while()循环是因为W1DQ_Read()读到了低电平(0) 跳出的,而不是超时
+     * 跳出。这也就意味着CPU在这段期间读到了低电平,探测到DS18B20芯片了。
+    */
+    if( time < 240 )
+        present = 1;
+
+    /* 释放总线并维持至少480us */
+    W1DQ_Output();
+    W1DQ_Write(HIGH);
+    udelay(480-time);
+
+    return present;
+}
+
+/*
+* 写 0 时序:主机输出低电平,延时 60us,然后释放总线,延时 2us;
+* 写 1 时序:主机输出低电平,延时 2us,然后释放总线,延时 60us;
+*/
+void ds18b20_write_byte(uint8_t byte)
+{
+    uint8_t            i = 0;
+
+    /* 设置 DS18B20的DQ引脚为输出模式 */
+    W1DQ_Output();
+
+    /* 1个字节包含8个位,DS18B20数据发送是LSB(低位先发送) */
+    for(i=0; i<8; i++) {
+
+        /* DS18B20的写0逻辑是将 DQ 拉低>=60us 后拉高,写1逻辑是拉低<=15us后拉高 */
+
+        /* 这里先将 DS18B20 的 DQ 线线拉低 10us(<15us) */
+        W1DQ_Write(LOW);
+        udelay(10);
+
+        /* 判断当前位是1还是0 */
+        if( byte & 0x1 )
+            W1DQ_Write(HIGH); /* 如果当前位是1,则现在就拉成高电平,这样低电平维持时间 10us */
+        else
+            W1DQ_Write(LOW); /* 如果当前位是0,则继续维持低电平,这样低电平维持时间位 10+下面的60us */
+
+        /* 无论是写0还是写1,DQ的写时序都要大于15+15+30=60us */
+        udelay(60);
+
+        /* 发送0/1数据位完成后,拉高总线并延时 2us */
+        W1DQ_Write(HIGH);
+        udelay(2);
+
+        /* 该字节数据右移1位,即再发下一位数据 */
+        byte >>= 1;
+    }
+}
+
+/* 当总线控制器把数据线从高电平拉到低电平时,读时序开始,数据线必须至少保持1us,然后总线被释放。
+ * DS18B20 通过拉高或拉低总线上来传输”1”或”0”。
+ */
+uint8_t ds18b20_read_byte(void)
+{
+    uint8_t            i = 0;
+    uint8_t            byte = 0;
+
+    /* 1个字节包含8个位,DS18B20数据发送是LSB(低位先发送),所以读也是LSB */
+    for(i=0; i<8; i++) {
+        /* 当CPU把数据线DQ拉低至少1us后,读时序开始。*/
+        W1DQ_Output();
+        W1DQ_Write(LOW);
+        udelay(2);
+
+        /* CPU拉高DQ释放总线 */
+        W1DQ_Write(HIGH);
+        udelay(2);
+
+        /* CPU设置DQ为输入模式,等待DS18B20发数据过来,注意此时需要靠上拉电阻将总线拉成高电平 */
+        W1DQ_Input();
+
+        /* DS18B20开始以 LSB (lower bit first) 模式发送数据 */
+        if( W1DQ_Read() ) {
+            byte |= 1<<i; /* 如果接收到该位是高电平,则将相应位设置为1,否则为0 */
+        }
+
+        /* 读时序至少维持15+45=60us */
+        udelay(60);
+
+        /* CPU拉高DQ并释放总线 */
+        W1DQ_Output();
+        W1DQ_Write(HIGH);
+        udelay(2);
+    }
+
+    return byte;
+}
+
+/* 该函数用来给 DS18B20 传感器发送开始采样温度命令 */
+static inline int ds18b20_convert(void)
+{
+    /* 发送复位信号,看DS18B20模块是否在位,如果模块不在或坏了就返回错误值(-1)。 */
+    if( !ds18b20_reset() )
+        return -1;
+
+    /* 这里我们不关心ROM中的16位产品序列号,就发送 Skip ROM 命令跳过SN的读取 */
+    ds18b20_write_byte(0xCC);
+
+    /* 发送 Convert T 命令,通知DS18B20开始温度采样 */
+    ds18b20_write_byte(0x44);
+
+    return 0;
+}
+
+/* 根据DS18B20的 CRC 校验公式(CRC = X^8 + X^5 + X^4 + 1) 来检测收到的数据是否出错 */
+static int ds18b20_checkcrc(uint8_t *data, uint8_t length, uint8_t checksum)
+{
+    uint8_t     i, j, byte;
+    uint8_t     mix = 0;
+    uint8_t     crc = 0;
+
+    for ( i=0; i<length; i++ ) {
+        byte = data[i];
+
+        for( j=0; j<8; j++ ) {
+            mix = ( crc ^ byte ) & 0x01;
+            crc >>= 1;
+            if ( mix )
+                crc ^= 0x8C; //POLYNOMIAL;
+            byte >>= 1;
+        }
+    }
+
+    return crc==checksum ? 1 : 0;
+}
+
+/* 读取 DS18B20 的采样温度值 */
+static inline int ds18b20_read(uint8_t *out, int bytes)
+{
+    uint8_t     buf[9];
+    uint8_t     i = 0;
+
+    /* CPU给DS18B02发送复位信号并探测芯片是否存在 */
+    if( !ds18b20_reset() )
+        return -1;
+
+    /* 这里我们不关心ROM中的16位产品序列号,就发送 Skip ROM 命令跳过SN的读取 */
+    ds18b20_write_byte(0xCC);
+
+    /* 发送 Read Scratchpad 命令开始读取DS18B20的采样温度值 */
+    ds18b20_write_byte(0xBE);
+
+    buf[i++] = ds18b20_read_byte(); /* 第1个字节是温度值的低字节(LSB) */
+    buf[i++] = ds18b20_read_byte(); /* 第2个字节是温度值的高字节(MSB) */
+    buf[i++] = ds18b20_read_byte(); /* 第3个字节不关心 */
+    buf[i++] = ds18b20_read_byte(); /* 第4个字节不关心 */
+    buf[i++] = ds18b20_read_byte(); /* 第5个字节是用户配置寄存器,也不关系 */
+    buf[i++] = ds18b20_read_byte(); /* 第6个字节保留(0xFF) */
+    buf[i++] = ds18b20_read_byte(); /* 第7个字节保留 */
+    buf[i++] = ds18b20_read_byte(); /* 第8个字节保留(0x10) */
+    buf[i++] = ds18b20_read_byte(); /* 第9个字节是CRC校验和 */
+
+    /* 将读取到的前8个字节数据按照CRC公司计算校验和,与第9个字节做对比看是否匹配 */
+    if( !ds18b20_checkcrc(buf, 8, buf[8]) ) {
+        return -2; /* CRC校验失败就返回错误值 */
+    }
+
+    /* CRC校验通过就输出读到的2字节温度采样原始数据 */
+    out[0]=buf[0];
+    out[1]=buf[1];
+
+    return 0;
+}
+
+int ds18b20_sample(float *temperature)
+{
+    uint8_t               byte[2];
+    uint8_t               sign;
+    uint16_t              temp;
+    static uint8_t        firstin = 1;
+
+    if( !temperature )
+        return -1;
+
+    /* 发送开始采样的命令 */
+    if( ds18b20_convert()<0 )
+        return -2;
+
+    /* 芯片datasheet说明第一次启动采样最大需要750ms完成,否则将会在100ms内完成采样 */
+    if( firstin ) {
+        mdelay(750);
+        firstin = 0;
+    }
+    else {
+        mdelay(100);
+    }
+
+    /* 开始从DS18B20传感器读取2字节的原始数据 */
+    if( ds18b20_read(byte, 2)<0 )
+        return -3;
+
+    /* 采样温度值两个字节,共16位。其中byte[0]为低字节、byte[1]为高字节:
+     * LSB: Bits[0:3]为小数位 2^(-4) ~ 2^(-1) Bits[4:7]为整数位:2^0~2^3
+     * MSB: Bits[0:2]为为整数位:2^4~2^6   Bits[3:7]为符号位: 全1表示温度为负值
+     */
+
+    /* 高字节的低3位为温度整数值,高8位为符号位。*/
+    if( byte[1]> 0x7 ) { /* 如果>7(低三位最大为111=7),说明为负值 */
+        temp = ~(byte[1]<<8|byte[0]) + 1;
+        sign=0;
+    }
+    else {
+        temp = byte[1]<<8 | byte[0];
+        sign=1;
+    }
+
+   /* DS18b20默认工作精度为12位,高8位是整数部分,低4位为小数部分。小数部分刻度为0.0625(1/16) */
+    *temperature = (temp>>4) + (temp&0xF)*0.0625 ;
+    if( !sign ) {
+        *temperature = -*temperature;
+    }
+
+    return 0;
+}
diff --git a/ex2.i2c-bitbang/Board/ds18b20.h b/ex2.i2c-bitbang/Board/ds18b20.h
new file mode 100644
index 0000000..a5d4647
--- /dev/null
+++ b/ex2.i2c-bitbang/Board/ds18b20.h
@@ -0,0 +1,18 @@
+/**********************************************************************
+ *   Copyright: (C)2024 LingYun IoT System Studio
+ *      Author: GuoWenxue<guowenxue@gmail.com>
+ *
+ * Description: Temperature sensor DS18B20 driver on ISKBoard
+ *
+ *   ChangeLog:
+ *        Version    Date       Author            Description
+ *        V1.0.0  2024.08.29    GuoWenxue      Release initial version
+ *
+ ***********************************************************************/
+
+#ifndef __DS18B20_H_
+#define __DS18B20_H_
+
+extern int ds18b20_sample(float *temperature);
+
+#endif /* __DS18B20_H_ */
diff --git a/ex2.i2c-bitbang/Board/eeprom.c b/ex2.i2c-bitbang/Board/eeprom.c
new file mode 100644
index 0000000..fd489e3
--- /dev/null
+++ b/ex2.i2c-bitbang/Board/eeprom.c
@@ -0,0 +1,284 @@
+/**********************************************************************
+* Copyright: (C)2024 LingYun IoT System Studio
+* Author: GuoWenxue<guowenxue@gmail.com>
+* Description: I2C EEPROM device driver for ISKBoard (Internal Lock Version)
+*
+* ChangeLog:
+* Version    Date       Author            Description
+* V1.5.0  2026.07.08    GuoWenxue         Use goto cleanup for unified lock releasing
+***********************************************************************/
+
+#include "eeprom.h"
+#include "miscdev.h" /* 提供 mdelay() / udelay() */
+#include <stdio.h>
+#include <string.h>
+
+
+/* EEPROM 型号表:未来若有新芯片,只需在此处追加一行,其余代码不用更改 */
+static const eeprom_info_t eeprom_ids[] = {
+    { EEPROM_INFO("AT24C02",  256,   8,  1) },
+    { EEPROM_INFO("AT24C04",  512,   16, 1) },
+    { EEPROM_INFO("AT24C08",  1024,  16, 1) },
+    { EEPROM_INFO("AT24C16",  2048,  16, 1) },
+    { EEPROM_INFO("AT24C32",  4096,  32, 2) },
+    { EEPROM_INFO("AT24C64",  8192,  32, 2) },
+    { EEPROM_INFO("AT24C128", 16384, 64, 2) },
+    { EEPROM_INFO("AT24C256", 32768, 64, 2) },
+};
+#define EEPROM_IDS_NUM  (sizeof(eeprom_ids) / sizeof(eeprom_ids[0]))
+
+/**
+ * @brief  根据芯片型号名称内部查找匹配的型号
+ */
+static const eeprom_info_t *eeprom_find_info_by_name(const char *name)
+{
+    uint32_t i;
+    for (i = 0; i < EEPROM_IDS_NUM; i++)
+    {
+        if (strcmp(eeprom_ids[i].name, name) == 0)
+        {
+            return &eeprom_ids[i];
+        }
+    }
+    return NULL;
+}
+
+/**
+ * @brief  初始化 EEPROM 设备参数(通过型号名称自动查表匹配)
+ */
+int eeprom_init(eeprom_dev_t *eeprom, i2c_bus_t *bus, uint8_t dev_addr, const char *chip_name)
+{
+    const eeprom_info_t *info = eeprom_find_info_by_name(chip_name);
+
+    if (!info || !eeprom || !bus)
+    {
+        printf("EEPROM: Named device [%s] not supported!\r\n", chip_name ? chip_name : "NULL");
+        return -1;
+    }
+
+    eeprom->bus = bus;
+    eeprom->dev_addr = dev_addr;
+    eeprom->info = info;
+
+    printf("Initialized EEPROM(%s), Capacity: %ld Bytes, Page Size: %ld\r\n",
+            info->name, info->capacity, info->page_size);
+    return 0;
+}
+
+/**
+ * @brief  EEPROM 测试函数
+ */
+int eeprom_test(i2c_bus_t *bus)
+{
+    eeprom_dev_t my_at24c256;
+    uint8_t tx_buf[100];
+    uint8_t rx_buf[100];
+    int i;
+    int rv = -1;
+
+    /* 1. 初始化 EEPROM 参数 */
+    if (eeprom_init(&my_at24c256, bus, 0x50, "AT24C256") != 0)
+    {
+        return -1;
+    }
+
+    /* 2. 填充测试数据 */
+    for (i = 0; i < sizeof(tx_buf); i++)
+    {
+        tx_buf[i] = i;
+    }
+
+    /* 3. 测试跨页连续写 */
+    if (eeprom_write(&my_at24c256, 0x00A0, tx_buf, sizeof(tx_buf)) != I2C_OK)
+    {
+        goto cleanup;
+    }
+
+    /* 4. 测试任意地址连续读 */
+    if (eeprom_read(&my_at24c256, 0x00A0, rx_buf, sizeof(rx_buf)) != I2C_OK)
+    {
+        goto cleanup;
+    }
+
+    /* 5. 数据一致性校验 */
+    if (memcmp(tx_buf, rx_buf, sizeof(tx_buf)) == 0)
+    {
+        rv = 0;
+    }
+
+cleanup:
+    printf("AT24C256 Read/Write Test %s!\r\n", rv == 0 ? "Passed" : "Failed");
+    return rv;
+}
+
+/**
+ * @brief  24C16仅支持单字节地址(可寻址256字节),需借用I2C设备地址的低3位充当片内地址的高3位。
+ */
+static inline uint8_t eeprom_get_actual_addr(eeprom_dev_t *eeprom, uint16_t reg_addr)
+{
+    uint8_t actual_dev_addr = eeprom->dev_addr;
+    if (eeprom->info->addr_bytes == 1 && eeprom->info->capacity > 256)
+    {
+        actual_dev_addr |= (uint8_t)((reg_addr >> 8) & 0x07);
+    }
+    return actual_dev_addr;
+}
+
+/**
+ * @brief  从 EEPROM 任意地址读取指定长度数据
+ */
+i2c_status_t eeprom_read(eeprom_dev_t *eeprom, uint16_t reg_addr, uint8_t *buf, uint16_t len)
+{
+    uint8_t addr_buf[2];
+    uint8_t addr_len = 0;
+    i2c_msg_t msgs[2];
+    i2c_status_t status;
+
+    if ( len == 0 || !buf || !eeprom || !eeprom->bus || !eeprom->info )
+    {
+        return I2C_OK;
+    }
+
+    /* 1. 准备发送数据 */
+    if (eeprom->info->addr_bytes == 2)
+    {
+        addr_buf[addr_len++] = (uint8_t)(reg_addr >> 8);
+    }
+    addr_buf[addr_len++] = (uint8_t)(reg_addr & 0xFF);
+
+    /* 2. 封装 I2C 消息 */
+    msgs[0].addr  = eeprom_get_actual_addr(eeprom, reg_addr);
+    msgs[0].flags = I2C_M_WR;
+    msgs[0].len   = addr_len;
+    msgs[0].buf   = addr_buf;
+
+    msgs[1].addr  = msgs[0].addr;
+    msgs[1].flags = I2C_M_RD;
+    msgs[1].len   = len;
+    msgs[1].buf   = buf;
+
+    /* 3. 锁住 I2C 总线  */
+    if (i2c_lock(eeprom->bus) != I2C_OK)
+    {
+        return I2C_BUSY;
+    }
+
+    /* 4. 调用 i2c_transfer 发送消息 */
+    status = i2c_transfer(eeprom->bus, msgs, 2);
+
+    /* 5. 释放 I2C 总线 */
+    i2c_unlock(eeprom->bus);
+
+    return status;
+}
+
+/**
+ * @brief  轮询 EEPROM 硬件内部擦写就绪状态
+ */
+static void eeprom_wait_standby(eeprom_dev_t *eeprom, uint16_t reg_addr)
+{
+    i2c_msg_t msg;
+    uint32_t timeout = 2000;
+
+    msg.addr  = eeprom_get_actual_addr(eeprom, reg_addr);
+    msg.flags = I2C_M_WR;
+    msg.len   = 0;
+    msg.buf   = NULL;
+
+    while (timeout--)
+    {
+        if (i2c_transfer(eeprom->bus, &msg, 1) == I2C_OK)
+        {
+            break; /* 芯片返回 ACK,擦写完成,退出轮询 */
+        }
+        udelay(50);
+    }
+}
+
+/**
+ * @brief  向 EEPROM 写入单页数据
+ */
+static i2c_status_t eeprom_write_page(eeprom_dev_t *eeprom, uint16_t reg_addr, uint8_t *buf, uint16_t len)
+{
+    uint8_t tx_payload[128];
+    uint8_t addr_len = 0;
+    i2c_msg_t msg;
+    i2c_status_t status = I2C_OK;
+
+    /* 1. 准备发送数据 */
+    if (eeprom->info->addr_bytes == 2)
+    {
+        tx_payload[addr_len++] = (uint8_t)(reg_addr >> 8);
+    }
+    tx_payload[addr_len++] = (uint8_t)(reg_addr & 0xFF);
+    memcpy(&tx_payload[addr_len], buf, len);
+
+    msg.addr  = eeprom_get_actual_addr(eeprom, reg_addr);
+    msg.flags = I2C_M_WR;
+    msg.len   = addr_len + len;
+    msg.buf   = tx_payload;
+
+    /* 2. 锁住 I2C 总线 */
+    if (i2c_lock(eeprom->bus) != I2C_OK)
+    {
+        return I2C_BUSY;
+    }
+
+    /* 3. 调用 i2c_transfer 发送消息 */
+    status = i2c_transfer(eeprom->bus, &msg, 1);
+    if (status != I2C_OK)
+    {
+        status = I2C_ERROR;
+        goto cleanup;
+    }
+
+    /* 4. 轮询等待 EEPROM 内部擦写完成 */
+    eeprom_wait_standby(eeprom, reg_addr);
+
+cleanup:
+    /* 5. 释放 I2C 总线 */
+    i2c_unlock(eeprom->bus);
+
+    return status;
+}
+
+/**
+ * @brief  大批量数据动态跨页分包连续写入
+ */
+i2c_status_t eeprom_write(eeprom_dev_t *eeprom, uint16_t reg_addr, uint8_t *buf, uint16_t len)
+{
+    i2c_status_t status = I2C_OK;
+    uint16_t count = 0;
+    uint16_t page_offset;
+    uint16_t bytes_to_write;
+
+    if ( len == 0 || !buf || !eeprom || !eeprom->bus || !eeprom->info )
+    {
+        return I2C_OK;
+    }
+
+    while (len > 0)
+    {
+        page_offset = reg_addr % eeprom->info->page_size;
+
+        bytes_to_write = eeprom->info->page_size - page_offset;
+        if (bytes_to_write > len)
+        {
+            bytes_to_write = len;
+        }
+
+        /* 内部单页级连写带等就绪的一体化锁定 */
+        status = eeprom_write_page(eeprom, reg_addr, &buf[count], bytes_to_write);
+        if (status != I2C_OK)
+        {
+            goto cleanup;
+        }
+
+        reg_addr += bytes_to_write;
+        count    += bytes_to_write;
+        len      -= bytes_to_write;
+    }
+
+cleanup:
+    return status;
+}
diff --git a/ex2.i2c-bitbang/Board/eeprom.h b/ex2.i2c-bitbang/Board/eeprom.h
new file mode 100644
index 0000000..ca823a8
--- /dev/null
+++ b/ex2.i2c-bitbang/Board/eeprom.h
@@ -0,0 +1,79 @@
+/**********************************************************************
+*   Copyright: (C)2024 LingYun IoT System Studio
+*      Author: GuoWenxue<guowenxue@gmail.com>
+* Description: I2C EEPROM device driver for ISKBoard
+*
+*   ChangeLog:
+*        Version    Date       Author            Description
+*        V1.0.0  2024.08.29    GuoWenxue      Release initial version
+***********************************************************************/
+#ifndef __EEPROM_H__
+#define __EEPROM_H__
+
+#include "i2c_bitbang.h"
+
+#define EEPROM_INFO(_name, _cap, _page, _addr_bytes) \
+        .name        = _name,       \
+        .capacity    = _cap,        \
+        .page_size   = _page,       \
+        .addr_bytes  = _addr_bytes, \
+        .n_pages     = (_cap)/(_page)
+
+typedef struct eeprom_info
+{
+    const char     *name;         /* 芯片型号名称,如 "AT24C256" */
+    uint32_t        capacity;     /* 总容量 (Bytes) */
+    uint32_t        page_size;    /* 单页大小 (Bytes) */
+    uint8_t         addr_bytes;   /* 片内寻址字节数:1 或 2 */
+    uint32_t        n_pages;      /* 总页数 */
+} eeprom_info_t;
+
+/* 设备运行时上下文结构体 */
+typedef struct eeprom_dev
+{
+    i2c_bus_t            *bus;       /* 绑定的 I2C 总线 */
+    uint8_t               dev_addr;  /* 基础 I2C 从机地址 (通常为 0x50) */
+    const eeprom_info_t  *info;      /* 指向匹配成功的规格信息,直接替代原 switch-case */
+} eeprom_dev_t;
+
+/* 导出 API */
+
+/**
+ * @brief  初始化 EEPROM 设备结构体对象
+ * @note   该函数仅做内部参数映射与规格配置(如页大小、芯片容量)。
+ * @param  eeprom:   指向 EEPROM 设备句柄的指针
+ * @param  bus:      绑定的物理 I2C 总线对象指针
+ * @param  dev_addr: EEPROM 的 7 位 I2C 从机基地址 (例如 AT24C256 常规为 0x50)
+ * @param  chip_name: EEPROM 的芯片型号名称 (例如 "AT24C256")
+ * @retval None
+ */
+int eeprom_init(eeprom_dev_t *eeprom, i2c_bus_t *bus, uint8_t dev_addr, const char *chip_name);
+
+/**
+ * @brief  从 EEPROM 的指定内部地址连续读取数据
+ * @param  eeprom:   指向已初始化的 EEPROM 设备句柄的指针
+ * @param  reg_addr: 芯片内部物理存储的 16 位起始地址 (0x0000 ~ 0x7FFF)
+ * @param  buf:      存放接收数据的用户缓冲区指针
+ * @param  len:      期望读取的数据字节数
+ * @retval I2C_OK: 读取成功; I2C_ERROR: 总线或从机无应答; I2C_BUSY: 总线被占用
+ */
+i2c_status_t eeprom_read(eeprom_dev_t *eeprom, uint16_t reg_addr, uint8_t *buf, uint16_t len);
+
+/**
+ * @brief  向 EEPROM 的指定内部地址连续写入数据(含跨页切片处理)
+ * @param  eeprom:   指向已初始化的 EEPROM 设备句柄的指针
+ * @param  reg_addr: 芯片内部物理存储的 16 位起始地址 (0x0000 ~ 0x7FFF)
+ * @param  buf:      存放待发送数据的源缓冲区指针
+ * @param  len:      期望写入的数据字节数
+ * @retval I2C_OK: 全部数据写入成功; I2C_ERROR: 期间发生物理通信故障
+ */
+i2c_status_t eeprom_write(eeprom_dev_t *eeprom, uint16_t reg_addr, uint8_t *buf, uint16_t len);
+
+/**
+ * @brief  EEPROM 测试函数
+ * @param  bus: 绑定的物理 I2C 总线对象指针
+ * @retval None
+ */
+int eeprom_test(i2c_bus_t *bus);
+
+#endif /* __EEPROM_H__ */
diff --git a/ex2.i2c-bitbang/Board/i2c_bitbang.c b/ex2.i2c-bitbang/Board/i2c_bitbang.c
new file mode 100644
index 0000000..18adc9c
--- /dev/null
+++ b/ex2.i2c-bitbang/Board/i2c_bitbang.c
@@ -0,0 +1,734 @@
+/**********************************************************************
+ *   Copyright: (C)2024 LingYun IoT System Studio
+ *      Author: GuoWenxue<guowenxue@gmail.com>
+ *
+ * Description: The purpose of this code is to provide a simple C library,
+ *              which providing software bit-bang of the I2C protocol on
+ *              any GPIO pins for ISKBoard.
+ *
+ *   ChangeLog:
+ *        Version    Date       Author            Description
+ *        V1.0.0  2024.08.29    GuoWenxue      Release initial version
+ *
+ ***********************************************************************/
+
+#include <stdbool.h>
+#include <stdio.h>
+#include "miscdev.h"  /* udelay() */
+#include "i2c_bitbang.h"
+
+/* I2C1 bus instance for SHT20, RTC, OLED on board */
+i2c_bus_t i2c1_bus =
+{
+    .lock = ATOMIC_VAR_INIT(false),
+    .addr = 0x00,
+    .scl  = {GPIOB, GPIO_PIN_6},
+    .sda  = {GPIOB, GPIO_PIN_7},
+};
+
+/* I2C2 bus instance on MikroBUS */
+i2c_bus_t i2c2_bus =
+{
+    .lock = ATOMIC_VAR_INIT(false),
+    .addr = 0x00,
+    .scl  = {GPIOB, GPIO_PIN_10},
+    .sda  = {GPIOB, GPIO_PIN_11},
+};
+
+/*
+ *+---------------------------------+
+ *|  I2C GPIO port Level functions  |
+ *+---------------------------------+
+ */
+
+/**
+ * @brief  Initialize I2C GPIO pins
+ * @param  bus: pointer to I2C bus structure
+ * @retval None
+ */
+void i2c_gpio_init(i2c_bus_t *bus)
+{
+    GPIO_InitTypeDef GPIO_InitStruct = {0};
+
+    /* Enable GPIO clocks */
+    gpio_clk_enable(bus->scl.group);
+    gpio_clk_enable(bus->sda.group);
+
+    /* Configure SCL pin */
+    GPIO_InitStruct.Pin = bus->scl.pin;
+    GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_OD;  /* Open-drain output */
+    GPIO_InitStruct.Pull = GPIO_PULLUP;
+    GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
+    HAL_GPIO_Init(bus->scl.group, &GPIO_InitStruct);
+
+    /* Configure SDA pin */
+    GPIO_InitStruct.Pin = bus->sda.pin;
+    GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_OD;  /* Open-drain output */
+    GPIO_InitStruct.Pull = GPIO_PULLUP;
+    GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
+    HAL_GPIO_Init(bus->sda.group, &GPIO_InitStruct);
+
+    /* Set both pins to HIGH (idle state) */
+    HAL_GPIO_WritePin(bus->scl.group, bus->scl.pin, GPIO_PIN_SET);
+    HAL_GPIO_WritePin(bus->sda.group, bus->sda.pin, GPIO_PIN_SET);
+}
+
+/**
+ * @brief  Set SCL pin level
+ * @param  bus: pointer to I2C bus structure
+ * @param  level: LOW or HIGH
+ * @retval None
+ */
+static inline void i2c_scl_set(i2c_bus_t *bus, uint8_t level)
+{
+    HAL_GPIO_WritePin(bus->scl.group, bus->scl.pin, level?GPIO_PIN_SET:GPIO_PIN_RESET);
+}
+
+/**
+ * @brief  Set SDA pin level
+ * @param  bus: pointer to I2C bus structure
+ * @param  level: LOW or HIGH
+ * @retval None
+ */
+static inline void i2c_sda_set(i2c_bus_t *bus, uint8_t level)
+{
+    HAL_GPIO_WritePin(bus->sda.group, bus->sda.pin, level?GPIO_PIN_SET:GPIO_PIN_RESET);
+}
+
+/**
+ * @brief  Read SDA pin level
+ * @param  bus: pointer to I2C bus structure
+ * @retval pin level (0 or 1)
+ */
+static uint8_t i2c_sda_read(i2c_bus_t *bus)
+{
+    return (HAL_GPIO_ReadPin(bus->sda.group, bus->sda.pin) == GPIO_PIN_SET) ? 1 : 0;
+}
+
+
+/*
+ *+---------------------------------+
+ *|    I2C bus control function     |
+ *+---------------------------------+
+ */
+
+/**
+ * @brief  工业级模拟 I2C 总线初始化 (带硬件死锁九脉冲自愈)
+ * @param  bus: pointer to I2C bus structure
+ * @retval None
+ */
+void i2c_init(i2c_bus_t *bus)
+{
+    if (!bus)
+    {
+        return;
+    }
+
+    /* 1. 初始化 I2C GPIO 口 */
+    i2c_gpio_init(bus);
+
+    /* 2. 连续发送 9 个 SCL 时钟脉冲 */
+    i2c_sda_set(bus, HIGH);
+    for (int i = 0; i < 9; i++)
+    {
+        i2c_scl_set(bus, LOW);
+        udelay(I2C_DELAY_US);
+        i2c_scl_set(bus, HIGH);
+        udelay(I2C_DELAY_US);
+    }
+
+    /* 3. 补发一个结束信号,强迫所有从机回到空闲态 */
+    i2c_scl_set(bus, LOW);
+    udelay(I2C_DELAY_US);
+    i2c_sda_set(bus, LOW);
+    udelay(I2C_DELAY_US);
+    i2c_scl_set(bus, HIGH);
+    udelay(I2C_DELAY_US);
+    i2c_sda_set(bus, HIGH);
+    udelay(I2C_DELAY_US);
+}
+
+/**
+ * @brief  Deinitialize I2C GPIO pins
+ * @param  bus: pointer to I2C bus structure
+ * @retval None
+ */
+void i2c_deinit(i2c_bus_t *bus)
+{
+    HAL_GPIO_DeInit(bus->scl.group, bus->scl.pin);
+    HAL_GPIO_DeInit(bus->sda.group, bus->sda.pin);
+    atomic_store(&bus->lock, false);
+}
+
+/**
+ * @brief  绑定设备及地址到 I2C 总线上
+ */
+int i2c_bind_dev(i2c_dev_t *dev, i2c_bus_t *bus, uint8_t dev_addr)
+{
+    if (!dev || !bus)
+    {
+        return -1;
+    }
+
+    dev->bus = bus;
+    dev->dev_addr = dev_addr;
+
+    return 0;
+}
+
+/**
+ * @brief  将I2C总线加锁让设备独占总线
+ */
+int i2c_lock_dev(i2c_dev_t *dev)
+{
+    if( !dev || !dev->bus )
+    {
+        return -1;
+    }
+
+    /* 安全上锁,若总线被占用则在此阻塞等待 */
+    if( I2C_OK != i2c_lock(dev->bus) )
+    {
+        return -2;
+    }
+
+    return 0;
+}
+
+/**
+ * @brief  释放设备的 I2C 总线锁
+ */
+void i2c_free_dev(i2c_dev_t *dev)
+{
+    if (!dev || !dev->bus)
+    {
+        return;
+    }
+
+    /* 1. 释放总线锁,让其他外设可以竞争总线 */
+    i2c_unlock(dev->bus);
+}
+
+/**
+ * @brief  Lock I2C bus for exclusive access (bare metal mutex)
+ * @param  bus: pointer to I2C bus structure
+ * @retval I2C_OK if success, I2C_BUSY if bus is locked
+ */
+i2c_status_t i2c_lock(i2c_bus_t *bus)
+{
+    uint32_t timeout = 10000;
+
+    /* atomic_compare_exchange_weak 会原子性地检查 bus->lock 是否为 false。
+     * 如果是 false,就把它设置为 true,并返回 true;
+     * 如果已经是 true,则返回 false,进入循环等待。
+     */
+    while (timeout > 0)
+    {
+        _Bool expected = false;
+        if (atomic_compare_exchange_weak(&bus->lock, &expected, true))
+        {
+            return I2C_OK;
+        }
+        udelay(10);
+        timeout--;
+    }
+
+    return I2C_BUSY;
+}
+
+/**
+ * @brief  Unlock I2C bus
+ * @param  bus: pointer to I2C bus structure
+ * @retval None
+ */
+void i2c_unlock(i2c_bus_t *bus)
+{
+    atomic_store(&bus->lock, false);
+}
+
+/**
+ * @brief  Scan I2C bus for connected devices
+ * @param  bus: pointer to I2C bus structure
+ * @retval none
+ */
+void i2c_scan(i2c_bus_t *bus)
+{
+    uint8_t addr;
+    int found = 0;
+
+    if (i2c_lock(bus) != I2C_OK) {
+        printf("I2C bus lock failed!\r\n");
+        return;
+    }
+
+    printf("Scanning I2C bus...\r\n");
+
+    /*10-bit 地址前缀保留区(0x70 ~ 0x77)*/
+    for (addr = 0x03; addr < 0x70; addr++) {
+        i2c_start(bus);
+        i2c_write_byte(bus, (addr << 1) | 0x01);  // read mode
+
+        if (i2c_wait_ack(bus) == I2C_ACK) {
+            printf("Found device at 0x%02X\r\n", addr);
+            found = 1;
+        }
+
+        i2c_stop(bus);
+        udelay(100);
+    }
+
+    i2c_unlock(bus);
+
+    if (!found) {
+        printf("No I2C devices found.\r\n");
+    }
+}
+
+
+/*
+ *+---------------------------------+
+ *|  I2C timing sequence function   |
+ *+---------------------------------+
+ */
+
+/**
+ * @brief  Generate I2C start condition
+ * @param  bus: pointer to I2C bus structure
+ * @retval None
+ */
+void i2c_start(i2c_bus_t *bus)
+{
+    /* Start Condition Timing Sequence:
+              ______
+     SCL:           |___
+            _____
+     SDA:        |_____
+    */
+
+    /* 无论前序是空闲还是刚结束 ACK,先确保 SCL 为低时 SDA 释放为高 */
+    i2c_sda_set(bus, HIGH);
+    i2c_scl_set(bus, LOW);
+    udelay(I2C_DELAY_US);
+
+    /* 此时拉高 SCL,SDA 必然处于稳定的高电平 */
+    i2c_scl_set(bus, HIGH);
+    i2c_sda_set(bus, HIGH);
+    udelay(I2C_DELAY_US);
+
+    /* 在 SCL 为高期间拉低 SDA,制造标准的 START / RESTART 沿 */
+    i2c_sda_set(bus, LOW);
+    udelay(I2C_DELAY_US);
+
+    i2c_scl_set(bus, LOW);
+    udelay(I2C_DELAY_US);
+}
+
+/**
+ * @brief  Generate I2C stop condition
+ * @param  bus: pointer to I2C bus structure
+ * @retval None
+ */
+void i2c_stop(i2c_bus_t *bus)
+{
+    /* SCL Stop Condition Timing Sequence:
+                _______
+      SCL:   ___|
+                   _____
+      SDA:   _____|
+    */
+
+    i2c_scl_set(bus, LOW);
+    i2c_sda_set(bus, LOW);
+    udelay(I2C_DELAY_US);
+
+    i2c_scl_set(bus, HIGH);
+    udelay(I2C_DELAY_US);
+
+    i2c_sda_set(bus, HIGH);
+    udelay(I2C_DELAY_US);
+}
+
+/**
+ * @brief  Wait for ACK from slave
+ * @param  bus: pointer to I2C bus structure
+ * @retval I2C_ACK or I2C_NACK
+ */
+i2c_ack_t i2c_wait_ack(i2c_bus_t *bus)
+{
+    uint16_t timeout = 0;
+
+    i2c_sda_set(bus, HIGH); /* 释放SDA线允许从机控制 */
+    udelay(I2C_DELAY_US);
+
+    i2c_scl_set(bus, HIGH);
+    udelay(I2C_DELAY_US); /* 确保SCL高电平波形稳定 */
+
+    while (i2c_sda_read(bus))
+    {
+        timeout++;
+        if (timeout > I2C_TIMEOUT_COUNT)
+        {
+            break; /* 超时退出,返回NACK */
+        }
+    }
+
+    i2c_scl_set(bus, LOW);
+    udelay(I2C_DELAY_US);
+
+    return (timeout > I2C_TIMEOUT_COUNT) ? I2C_NACK : I2C_ACK;
+}
+
+/**
+ * @brief  Send ACK to slave
+ * @param  bus: pointer to I2C bus structure
+ * @retval None
+ */
+void i2c_send_ack(i2c_bus_t *bus)
+{
+    i2c_scl_set(bus, LOW);
+    i2c_sda_set(bus, LOW);
+    udelay(I2C_DELAY_US);
+
+    i2c_scl_set(bus, HIGH);
+    udelay(I2C_DELAY_US);
+
+    i2c_scl_set(bus, LOW);
+    udelay(I2C_DELAY_US);
+}
+
+/**
+ * @brief  Send NACK to slave
+ * @param  bus: pointer to I2C bus structure
+ * @retval None
+ */
+void i2c_send_nack(i2c_bus_t *bus)
+{
+    i2c_scl_set(bus, LOW);
+    i2c_sda_set(bus, HIGH);
+    udelay(I2C_DELAY_US);
+
+    i2c_scl_set(bus, HIGH);
+    udelay(I2C_DELAY_US);
+
+    i2c_scl_set(bus, LOW);
+    udelay(I2C_DELAY_US);
+}
+
+/**
+ * @brief  Write one byte to I2C bus
+ * @param  bus: pointer to I2C bus structure
+ * @param  byte: byte to write
+ * @retval None
+ */
+void i2c_write_byte(i2c_bus_t *bus, uint8_t byte)
+{
+    uint8_t i;
+
+    i2c_scl_set(bus, LOW);
+
+    for (i = 0; i < 8; i++)
+    {
+        i2c_sda_set(bus, (byte & 0x80) ? HIGH : LOW);
+        udelay(I2C_DELAY_US); /* 数据建立时间 Setup Time */
+
+        i2c_scl_set(bus, HIGH);
+        udelay(I2C_DELAY_US); /* 保持高电平采样 */
+
+        i2c_scl_set(bus, LOW);
+        udelay(I2C_DELAY_US); /* 确保时钟低电平持续时间 */
+
+        byte <<= 1;
+    }
+}
+
+/**
+ * @brief  Read one byte from I2C bus
+ * @param  bus: pointer to I2C bus structure
+ * @retval received byte
+ */
+uint8_t i2c_read_byte(i2c_bus_t *bus)
+{
+    uint8_t i;
+    uint8_t byte = 0;
+
+    i2c_sda_set(bus, HIGH);
+    udelay(I2C_DELAY_US);
+
+    for (i = 0; i < 8; i++)
+    {
+        byte <<= 1;
+
+        i2c_scl_set(bus, HIGH);
+        udelay(I2C_DELAY_US); /* 延时确保 SCL 电平平稳后采样 */
+
+        if (i2c_sda_read(bus))
+        {
+            byte |= 0x01;
+        }
+
+        i2c_scl_set(bus, LOW);
+        udelay(I2C_DELAY_US); /* 确保时钟低电平持续时间 */
+    }
+
+    return byte;
+}
+
+
+/*
+ *+-----------------------------------+
+ *|      I2C read/write function      |
+ *+-----------------------------------+
+ */
+
+/**
+ * @brief  Write data to I2C slave device
+ * @param  bus: pointer to I2C bus structure
+ * @param  addr: 7-bit slave address
+ * @param  reg: register address
+ * @param  data: pointer to data buffer
+ * @param  len: data length
+ * @retval I2C status
+ */
+i2c_status_t i2c_write(i2c_bus_t *bus, uint8_t addr, uint8_t reg, uint8_t *data, uint16_t len)
+{
+    uint16_t i;
+    int rv = I2C_OK;
+
+    i2c_start(bus);
+
+    /* Send slave address + write bit */
+    i2c_write_byte(bus, (addr << 1) | 0x00);
+    if (i2c_wait_ack(bus) != I2C_ACK)
+    {
+        rv = I2C_ERROR;
+        goto cleanup;
+    }
+
+    /* Send register address */
+    i2c_write_byte(bus, reg);
+    if (i2c_wait_ack(bus) != I2C_ACK)
+    {
+        rv = I2C_ERROR;
+        goto cleanup;
+    }
+
+    /* Send data */
+    for (i = 0; i < len; i++)
+    {
+        i2c_write_byte(bus, data[i]);
+        if (i2c_wait_ack(bus) != I2C_ACK)
+        {
+            rv = I2C_ERROR;
+            goto cleanup;
+        }
+    }
+
+cleanup:
+    i2c_stop(bus);
+    return rv;
+}
+
+/**
+ * @brief  Read data from I2C slave device
+ * @param  bus: pointer to I2C bus structure
+ * @param  addr: 7-bit slave address
+ * @param  reg: register address
+ * @param  data: pointer to data buffer
+ * @param  len: data length
+ * @retval I2C status
+ */
+i2c_status_t i2c_read(i2c_bus_t *bus, uint8_t addr, uint8_t reg, uint8_t *data, uint16_t len)
+{
+    uint16_t i;
+    int rv = I2C_OK;
+
+    /* Write register address */
+    i2c_start(bus);
+    i2c_write_byte(bus, (addr << 1) | 0x00);
+    if (i2c_wait_ack(bus) != I2C_ACK)
+    {
+        rv = I2C_ERROR;
+        goto cleanup;
+    }
+
+    i2c_write_byte(bus, reg);
+    if (i2c_wait_ack(bus) != I2C_ACK)
+    {
+        rv = I2C_ERROR;
+        goto cleanup;
+    }
+
+    /* Restart and read data */
+    i2c_start(bus);
+    i2c_write_byte(bus, (addr << 1) | 0x01);
+    if (i2c_wait_ack(bus) != I2C_ACK)
+    {
+        rv = I2C_ERROR;
+        goto cleanup;
+    }
+
+    for (i = 0; i < len; i++)
+    {
+        data[i] = i2c_read_byte(bus);
+
+        if (i < len - 1)
+        {
+            i2c_send_ack(bus);
+        }
+        else
+        {
+            i2c_send_nack(bus);
+        }
+    }
+
+cleanup:
+    i2c_stop(bus);
+    return rv;
+}
+
+/**
+ * @brief  Write data to I2C slave device without register address
+ * @param  bus: pointer to I2C bus structure
+ * @param  addr: 7-bit slave address
+ * @param  data: pointer to data buffer
+ * @param  len: data length
+ * @retval I2C status
+ */
+i2c_status_t i2c_write_no_reg(i2c_bus_t *bus, uint8_t addr, uint8_t *data, uint16_t len)
+{
+    uint16_t i;
+    int rv = I2C_OK;
+
+    i2c_start(bus);
+
+    /* Send slave address + write bit */
+    i2c_write_byte(bus, (addr << 1) | 0x00);
+    if (i2c_wait_ack(bus) != I2C_ACK)
+    {
+        rv = I2C_ERROR;
+        goto cleanup;
+    }
+
+    /* Send data */
+    for (i = 0; i < len; i++)
+    {
+        i2c_write_byte(bus, data[i]);
+        if (i2c_wait_ack(bus) != I2C_ACK)
+        {
+            rv = I2C_ERROR;
+            goto cleanup;
+        }
+    }
+
+cleanup:
+    i2c_stop(bus);
+    return rv;
+}
+
+/**
+ * @brief  Read data from I2C slave device without register address
+ * @param  bus: pointer to I2C bus structure
+ * @param  addr: 7-bit slave address
+ * @param  data: pointer to data buffer
+ * @param  len: data length
+ * @retval I2C status
+ */
+i2c_status_t i2c_read_no_reg(i2c_bus_t *bus, uint8_t addr, uint8_t *data, uint16_t len)
+{
+    uint16_t i;
+    int rv = I2C_OK;
+
+    i2c_start(bus);
+
+    /* Send slave address + read bit */
+    i2c_write_byte(bus, (addr << 1) | 0x01);
+    if (i2c_wait_ack(bus) != I2C_ACK)
+    {
+        rv = I2C_ERROR;
+        goto cleanup;
+    }
+
+    /* Read data */
+    for (i = 0; i < len; i++)
+    {
+        data[i] = i2c_read_byte(bus);
+
+        if (i < len - 1)
+        {
+            i2c_send_ack(bus);
+        }
+        else
+        {
+            i2c_send_nack(bus);
+        }
+    }
+
+cleanup:
+    i2c_stop(bus);
+    return rv;
+}
+
+
+/*
+ *+-----------------------------------+
+ *| Linux I2C API–compatible function |
+ *+-----------------------------------+
+ */
+
+ /* Functions compatible with the Linux I2C adapter API */
+i2c_status_t i2c_transfer(i2c_bus_t *bus, i2c_msg_t *msgs, int num)
+{
+    i2c_status_t status = I2C_OK;
+    int i, j;
+
+    for (i = 0; i < num; i++)
+    {
+        i2c_msg_t *msg = &msgs[i];
+
+        /* 1. 每条消息都以 Start (或 Repeated Start) 开始 */
+        i2c_start(bus);
+
+        /* 2. 发送从机地址 + 读写位 */
+        uint8_t dir = (msg->flags & I2C_M_RD) ? 0x01 : 0x00;
+        i2c_write_byte(bus, (msg->addr << 1) | dir);
+        if (i2c_wait_ack(bus) != I2C_ACK)
+        {
+            status = I2C_ERROR;
+            goto cleanup;
+        }
+
+        /* 3. 根据读写标志传输主体数据 */
+        if (msg->flags & I2C_M_RD)
+        {
+            /* 读模式 */
+            for (j = 0; j < msg->len; j++)
+            {
+                msg->buf[j] = i2c_read_byte(bus);
+                /* 只要不是当前消息的最后一个字节,就回复 ACK */
+                if (j < msg->len - 1)
+                    i2c_send_ack(bus);
+                else
+                    i2c_send_nack(bus);
+            }
+        }
+        else
+        {
+            /* 写模式 */
+            for (j = 0; j < msg->len; j++)
+            {
+                i2c_write_byte(bus, msg->buf[j]);
+                if (i2c_wait_ack(bus) != I2C_ACK)
+                {
+                    status = I2C_ERROR;
+                    goto cleanup;
+                }
+            }
+        }
+    }
+
+cleanup:
+    /* 4. 只有当所有消息都传输完毕,或者中途出错时,才发送 Stop 释放总线 */
+    i2c_stop(bus);
+
+    return status;
+}
diff --git a/ex2.i2c-bitbang/Board/i2c_bitbang.h b/ex2.i2c-bitbang/Board/i2c_bitbang.h
new file mode 100644
index 0000000..9a3dc0b
--- /dev/null
+++ b/ex2.i2c-bitbang/Board/i2c_bitbang.h
@@ -0,0 +1,269 @@
+/**********************************************************************
+ *   Copyright: (C)2024 LingYun IoT System Studio
+ *      Author: GuoWenxue<guowenxue@gmail.com>
+ *
+ * Description: The purpose of this code is to provide a simple C library,
+ *              which providing software bit-bang of the I2C protocol on
+ *              any GPIO pins for ISKBoard.
+ *
+ *   ChangeLog:
+ *        Version    Date       Author            Description
+ *        V1.0.0  2024.08.29    GuoWenxue      Release initial version
+ *
+ ***********************************************************************/
+
+#ifndef __I2C_BITBANG_H
+#define __I2C_BITBANG_H
+
+#include "stm32l4xx_hal.h"
+#include <stdint.h>
+#include <stdatomic.h>
+
+#define I2C_DELAY_US        5       /* I2C timing delay in microseconds */
+#define I2C_TIMEOUT_COUNT   100     /* Timeout counter for waiting ACK */
+
+/* GPIO port for I2C typedef */
+typedef struct i2c_gpio_s
+{
+    GPIO_TypeDef           *group; /* GPIO group */
+    uint16_t                pin;   /* GPIO pin   */
+} i2c_gpio_t;
+
+/* I2C bus typedef */
+typedef struct i2c_bus_s
+{
+    volatile atomic_bool    lock;   /* Bus lock flag */
+    uint8_t                 addr;   /* 7-bits slave address */
+    i2c_gpio_t              scl;    /* SCL GPIO pin */
+    i2c_gpio_t              sda;    /* SDA GPIO pin */
+} i2c_bus_t;
+
+/* GPIO level enum */
+enum
+{
+    LOW  = 0,
+    HIGH = 1,
+};
+
+/* I2C ACK/NACK enum */
+typedef enum
+{
+    I2C_ACK  = 0,
+    I2C_NACK = 1
+} i2c_ack_t;
+
+/* I2C status enum */
+typedef enum
+{
+    I2C_OK       = 0,
+    I2C_ERROR    = 1,
+    I2C_TIMEOUT  = 2,
+    I2C_BUSY     = 3
+} i2c_status_t;
+
+/*
+ *+---------------------------------+
+ *|    I2C bus control function     |
+ *+---------------------------------+
+ */
+
+/* I2C1 bus instance for SHT20, RTC, OLED on board */
+extern i2c_bus_t i2c1_bus;
+
+/* I2C2 bus instance on MikroBUS */
+extern i2c_bus_t i2c2_bus;
+
+/**
+ * @brief  Initialize I2C GPIO pins
+ * @param  bus: pointer to I2C bus structure
+ * @retval None
+ */
+extern void i2c_init(i2c_bus_t *bus);
+
+/**
+ * @brief  Deinitialize I2C GPIO pins
+ * @param  bus: pointer to I2C bus structure
+ * @retval None
+ */
+extern void i2c_deinit(i2c_bus_t *bus);
+
+/**
+ * @brief  通用 I2C 设备属性结构体
+ */
+typedef struct {
+    i2c_bus_t *bus;         /* 绑定的 I2C 物理总线实例 */
+    uint8_t dev_addr;       /* 7位设备物理从机地址 */
+} i2c_dev_t;
+
+/**
+ * @brief  绑定设备及地址到 I2C 总线上
+ * @param  dev:      I2C 设备结构体指针
+ * @param  bus:      物理总线指针
+ * @param  dev_addr: 7位从机地址
+ * @retval 0: 成功; -1: 失败
+ */
+extern int i2c_bind_dev(i2c_dev_t *dev, i2c_bus_t *bus, uint8_t dev_addr);
+
+
+/**
+ * @brief  将I2C总线加锁让设备独占总线
+ * @param  dev:      I2C 设备结构体指针
+ * @retval 0: 成功; -1: 失败
+ */
+extern int i2c_lock_dev(i2c_dev_t *dev);
+
+/**
+ * @brief  释放设备的 I2C 总线锁
+ * @param  dev:      I2C 设备结构体指针
+ * @retval 无
+ */
+extern void i2c_free_dev(i2c_dev_t *dev);
+
+/**
+ * @brief  Lock I2C bus for exclusive access (bare metal mutex)
+ * @param  bus: pointer to I2C bus structure
+ * @retval I2C_OK if success, I2C_BUSY if bus is locked
+ */
+extern i2c_status_t i2c_lock(i2c_bus_t *bus);
+
+/**
+ * @brief  Unlock I2C bus
+ * @param  bus: pointer to I2C bus structure
+ * @retval None
+ */
+extern void i2c_unlock(i2c_bus_t *bus);
+
+/**
+ * @brief  Scan I2C bus for connected devices
+ * @param  bus: pointer to I2C bus structure
+ * @retval none
+ */
+extern void i2c_scan(i2c_bus_t *bus);
+
+
+/*
+ *+---------------------------------+
+ *|  I2C timing sequence function   |
+ *+---------------------------------+
+ */
+
+ /**
+ * @brief  Generate I2C start condition
+ * @param  bus: pointer to I2C bus structure
+ * @retval None
+ */
+ extern void i2c_start(i2c_bus_t *bus);
+
+/**
+ * @brief  Generate I2C stop condition
+ * @param  bus: pointer to I2C bus structure
+ * @retval None
+ */
+ extern void i2c_stop(i2c_bus_t *bus);
+
+ /**
+ * @brief  Wait for ACK from slave
+ * @param  bus: pointer to I2C bus structure
+ * @retval I2C_ACK or I2C_NACK
+ */
+extern i2c_ack_t i2c_wait_ack(i2c_bus_t *bus);
+
+/**
+ * @brief  Send ACK to slave
+ * @param  bus: pointer to I2C bus structure
+ * @retval None
+ */
+extern void i2c_send_ack(i2c_bus_t *bus);
+
+/**
+ * @brief  Send NACK to slave
+ * @param  bus: pointer to I2C bus structure
+ * @retval None
+ */
+extern void i2c_send_nack(i2c_bus_t *bus);
+
+/**
+ * @brief  Write one byte to I2C bus
+ * @param  bus: pointer to I2C bus structure
+ * @param  byte: byte to write
+ * @retval None
+ */
+extern void i2c_write_byte(i2c_bus_t *bus, uint8_t byte);
+
+/**
+ * @brief  Read one byte from I2C bus
+ * @param  bus: pointer to I2C bus structure
+ * @retval received byte
+ */
+extern uint8_t i2c_read_byte(i2c_bus_t *bus);
+
+
+/*
+ *+-----------------------------------+
+ *|      I2C read/write function      |
+ *+-----------------------------------+
+ */
+
+/**
+ * @brief  Write data to I2C slave device
+ * @param  bus: pointer to I2C bus structure
+ * @param  addr: 7-bit slave address
+ * @param  reg: register address
+ * @param  data: pointer to data buffer
+ * @param  len: data length
+ * @retval I2C status
+ */
+extern i2c_status_t i2c_write(i2c_bus_t *bus, uint8_t addr, uint8_t reg, uint8_t *data, uint16_t len);
+
+/**
+ * @brief  Read data from I2C slave device
+ * @param  bus: pointer to I2C bus structure
+ * @param  addr: 7-bit slave address
+ * @param  reg: register address
+ * @param  data: pointer to data buffer
+ * @param  len: data length
+ * @retval I2C status
+ */
+extern i2c_status_t i2c_read(i2c_bus_t *bus, uint8_t addr, uint8_t reg, uint8_t *data, uint16_t len);
+
+/**
+ * @brief  Write data to I2C slave device without register address
+ * @param  bus: pointer to I2C bus structure
+ * @param  addr: 7-bit slave address
+ * @param  data: pointer to data buffer
+ * @param  len: data length
+ * @retval I2C status
+ */
+extern i2c_status_t i2c_write_no_reg(i2c_bus_t *bus, uint8_t addr, uint8_t *data, uint16_t len);
+
+/**
+ * @brief  Read data from I2C slave device without register address
+ * @param  bus: pointer to I2C bus structure
+ * @param  addr: 7-bit slave address
+ * @param  data: pointer to data buffer
+ * @param  len: data length
+ * @retval I2C status
+ */
+extern i2c_status_t i2c_read_no_reg(i2c_bus_t *bus, uint8_t addr, uint8_t *data, uint16_t len);
+
+
+/*
+ *+-----------------------------------+
+ *| Linux I2C API–compatible function |
+ *+-----------------------------------+
+ */
+
+#define I2C_M_WR           0x0000  /* 写控制标志 */
+#define I2C_M_RD           0x0001  /* 读控制标志 */
+
+typedef struct {
+    uint8_t  addr;    /* 7位从机地址 */
+    uint16_t flags;   /* I2C_M_WR 或 I2C_M_RD */
+    uint16_t len;     /* 数据长度 */
+    uint8_t  *buf;    /* 数据指针 */
+} i2c_msg_t;
+
+/* Functions compatible with the Linux I2C adapter API */
+extern i2c_status_t i2c_transfer(i2c_bus_t *bus, i2c_msg_t *msgs, int num);
+
+#endif /* __I2C_BITBANG_H */
diff --git a/ex2.i2c-bitbang/Board/isl1208.c b/ex2.i2c-bitbang/Board/isl1208.c
new file mode 100644
index 0000000..d9dcd5d
--- /dev/null
+++ b/ex2.i2c-bitbang/Board/isl1208.c
@@ -0,0 +1,367 @@
+/**********************************************************************
+ * Copyright: (C)2026 LingYun IoT System Studio
+ * Author: GuoWenxue<guowenxue@gmail.com>
+ *
+ * Description: ISL1208 RTC driver on ISKBoard (Unlocked Core Pattern)
+ *
+ * ChangeLog:
+ * Version    Date       Author            Description
+ * V1.1.0  2026.07.08    GuoWenxue      Refactor with unlocked core functions
+ *
+ ***********************************************************************/
+
+#include <stdio.h>
+#include "isl1208.h"
+
+/* 隐藏在私有源文件内部的 ISL1208 寄存器映射与参数 */
+#define ISL1208_I2C_ADDR        0x6F
+
+#define ISL1208_REG_SC          0x00    /* 秒 (00-59) */
+#define ISL1208_REG_MN          0x01    /* 分 (00-59) */
+#define ISL1208_REG_HR          0x02    /* 时 (24小时模式) */
+#define ISL1208_REG_DT          0x03    /* 日 (01-31) */
+#define ISL1208_REG_MO          0x04    /* 月 (01-12) */
+#define ISL1208_REG_YR          0x05    /* 年 (00-99) */
+#define ISL1208_REG_DW          0x06    /* 星期 (00-06) */
+
+#define ISL1208_REG_SR          0x07    /* 状态寄存器 */
+#define ISL1208_SR_RTCF         0x80    /* 掉电/晶振失效标志位 */
+#define ISL1208_SR_WRTC         0x10    /* 写使能位 */
+
+/* 前置私有静态辅助函数声明 (无锁底层业务函数) */
+static int __isl1208_set_time_unlocked(const rtc_time_t *time);
+static int __isl1208_get_time_unlocked(rtc_time_t *time);
+
+/*
+ *+---------------------------------+
+ *|     RTC 公共接口函数(带锁)       |
+ *+---------------------------------+
+ */
+
+/* I2C 设备 RTC */
+static i2c_dev_t rtc;
+
+/**
+ * @brief  ISL1208 硬件初始化与掉电自愈函数
+ * @note   只有检测到掉电(RTCF=1)或停振(ST=1)时,才强制清理控制位并激活时钟,平时不干扰原有走时
+ * @param  bus: 绑定的 I2C 物理总线指针 (如 &i2c1_bus)
+ * @retval 0: 初始化成功; -1: 总线通信失败或芯片未响应
+ */
+int isl1208_init(i2c_bus_t *bus)
+{
+    uint8_t sr_val;
+    uint8_t sc_val;
+    int rv = -1;
+
+    if (!bus)
+        return -1;
+
+    /* 绑定设备及其设备号到 I2C 总线上 */
+    i2c_bind_dev(&rtc, bus, ISL1208_I2C_ADDR);
+
+    /* 将I2C总线加锁让设备独占总线 */
+    i2c_lock_dev(&rtc);
+
+    /* 读取当前状态寄存器 (检查 RTCF) */
+    if (i2c_read(rtc.bus, rtc.dev_addr, ISL1208_REG_SR, &sr_val, 1) != I2C_OK)
+    {
+        goto cleanup;
+    }
+
+    /* 读取当前秒寄存器 (检查 ST) */
+    if (i2c_read(rtc.bus, rtc.dev_addr, ISL1208_REG_SC, &sc_val, 1) != I2C_OK)
+    {
+        goto cleanup;
+    }
+
+    /* 条件触发:只要 SBIB 位为 1 证明是断电冷启动,必须强制清除解冻 */
+    if (sr_val & 0x01)
+    {
+        printf("ISL1208 Power-Loss or Stop detected! Activating oscillator...\r\n");
+
+        /* 强开写保护 (WRTC=1),并抹除 SBIB 冻结位 */
+        sr_val = ISL1208_SR_WRTC;
+        if (i2c_write(rtc.bus, rtc.dev_addr, ISL1208_REG_SR, &sr_val, 1) != I2C_OK)
+        {
+            goto cleanup;
+        }
+
+        /* 写入完整的 2026-01-01 00:00:00 (星期四) 基准时间 */
+        uint8_t init_buf[7];
+        init_buf[0] = 0x00; /* 00秒,同时最高位 ST=0 激活晶振起振 */
+        init_buf[1] = 0x00; /* 00分 */
+        init_buf[2] = 0x80; /* 00时,Bit7=1 代表开启 24 小时制 */
+        init_buf[3] = 0x01; /* 01日 (BCD: 0x01) */
+        init_buf[4] = 0x01; /* 01月 (BCD: 0x01) */
+        init_buf[5] = 0x26; /* 26年 (BCD: 0x26 代表 2026年) */
+        init_buf[6] = 0x04; /* 星期四 (BCD: 0x04) */
+
+        /* 写入 7 个时间寄存器 */
+        if (i2c_write(rtc.bus, rtc.dev_addr, ISL1208_REG_SC, init_buf, 7) != I2C_OK)
+        {
+            goto cleanup;
+        }
+
+        /* 强制将秒寄存器的最高位 ST (Stop) 清零以激活晶振 */
+        sc_val &= 0x7F;
+        if (i2c_write(rtc.bus, rtc.dev_addr, ISL1208_REG_SC, &sc_val, 1) != I2C_OK)
+        {
+            goto cleanup;
+        }
+
+        /* 关闭写保护,并将状态寄存器洗干净 (SBIB 清零) */
+        sr_val = 0x00;
+        if (i2c_write(rtc.bus, rtc.dev_addr, ISL1208_REG_SR, &sr_val, 1) != I2C_OK)
+        {
+            goto cleanup;
+        }
+    }
+
+    rv = 0;
+
+cleanup:
+    i2c_free_dev(&rtc);
+    return rv;
+}
+
+/**
+ * @brief  应用层设置 RTC 时间接口函数(rtc_time_t格式)
+ * @param  time  指向待写入时间数据的 rtc_time_t 结构体常指针
+ * @retval 0:成功, -1:失败
+ */
+int isl1208_set_time(const rtc_time_t *time)
+{
+    int rv = -1;
+
+    if (!rtc.bus || !time)
+    {
+        return -1;
+    }
+
+    i2c_lock_dev(&rtc);
+
+    /* 调用无锁底层核心实现写操作 */
+    rv = __isl1208_set_time_unlocked(time);
+
+    i2c_free_dev(&rtc);
+
+    return rv;
+}
+
+/**
+ * @brief  应用层设置 RTC 时间接口函数(字符串格式)
+ * @param  time_str  输入的时间字符串指针,格式如 "2026-01-01 10:00:00"
+ * @retval 0:成功, -1:失败
+ */
+int isl1208_set_time_str(const char *time_str)
+{
+    rtc_time_t tm;
+    int year, month, day, hour, minute, second;
+
+    if (!time_str)
+    {
+        return -1;
+    }
+
+    /* 1. 解析时间字符串 */
+    if (sscanf(time_str, "%d-%d-%d %d:%d:%d",
+               &year, &month, &day, &hour, &minute, &second) != 6)
+    {
+        return -1;
+    }
+
+    /* 2. 填充结构体(年份仅保留低两位) */
+    tm.year    = year % 100;
+    tm.month   = month;
+    tm.day     = day;
+    tm.hour    = hour;
+    tm.minute  = minute;
+    tm.second  = second;
+    tm.weekday = 0;
+
+    /* 3. 直接调用带锁的原本接口 */
+    return isl1208_set_time(&tm);
+}
+
+/**
+ * @brief  应用层获取 RTC 时间接口函数
+ * @param  time  指向用于接收时间数据的 rtc_time_t 结构体指针
+ * @retval 0:成功, -1:失败
+ */
+int isl1208_get_time(rtc_time_t *time)
+{
+    int rv = -1;
+
+    if (!rtc.bus || !time)
+    {
+        return -1;
+    }
+
+    i2c_lock_dev(&rtc);
+
+    /* 调用无锁底层核心实现读操作 */
+    rv = __isl1208_get_time_unlocked(time);
+
+    i2c_free_dev(&rtc);
+    return rv;
+}
+
+/**
+ * @brief  应用层获取 RTC 时间接口函数(字符串格式)
+ * @param  buf      用于存储格式化时间字符串的缓冲区指针
+ * @param  buf_len  接收缓冲区长度(建议至少20字节)
+ * @retval 0:成功, -1:失败
+ */
+int isl1208_get_time_str(char *buf, size_t buf_len)
+{
+    rtc_time_t tm;
+    int rv = -1;
+
+    if (!buf || buf_len < 20)
+    {
+        return -1;
+    }
+
+    /* 直接调用带锁的原本接口获取时间结构体 */
+    rv = isl1208_get_time(&tm);
+    if (rv != 0)
+    {
+        return rv;
+    }
+
+    /* 格式化为字符串输出 */
+    snprintf(buf, buf_len, "20%02d-%02d-%02d %02d:%02d:%02d",
+             tm.year, tm.month, tm.day,
+             tm.hour, tm.minute, tm.second);
+
+    return 0;
+}
+
+/**
+ * @brief  获取并格式化打印当前 RTC 时间接口函数
+ * @param  bus  指向已初始化的 I2C 总线结构体指针
+ * @retval 0:成功, -1:失败
+ */
+int isl1208_print_time(void)
+{
+    char time_str[32];
+
+    /* 直接调用刚刚封装好的获取字符串接口 */
+    if (isl1208_get_time_str(time_str, sizeof(time_str)) != 0)
+    {
+        return -1;
+    }
+
+    /* 格式化输出 */
+    printf("ISL1208 Time: %s\r\n", time_str);
+    return 0;
+}
+
+/*
+ *+---------------------------------+
+ *|     RTC 私有底层函数(无锁)       |
+ *+---------------------------------+
+ */
+
+/**
+ * @brief  十进制转 BCD 码
+ */
+static inline uint8_t rtc_dec_to_bcd(uint8_t dec)
+{
+    uint8_t high;
+    uint8_t low;
+
+    high = dec / 10;
+    low = dec % 10;
+
+    return (uint8_t)((high << 4) | low);
+}
+
+/**
+ * @brief  BCD 码转十进制
+ */
+static inline uint8_t rtc_bcd_to_dec(uint8_t bcd)
+{
+    uint8_t high;
+    uint8_t low;
+
+    high = bcd >> 4;
+    low = bcd & 0x0F;
+
+    return (uint8_t)((high * 10) + low);
+}
+
+/**
+ * @brief  向 RTC 芯片写入时间
+ */
+static int __isl1208_set_time_unlocked(const rtc_time_t *time)
+{
+    uint8_t time_buf[7];
+    uint8_t sr_val;
+
+    if( !rtc.bus )
+        return -1;
+
+    /* 读取当前状态寄存器 */
+    if (i2c_read(rtc.bus, ISL1208_I2C_ADDR, ISL1208_REG_SR, &sr_val, 1) != I2C_OK)
+    {
+        return -1;
+    }
+
+    /* 解除 RTC 写保护 (WRTC 置 1) */
+    sr_val |= ISL1208_SR_WRTC;
+    if (i2c_write(rtc.bus, ISL1208_I2C_ADDR, ISL1208_REG_SR, &sr_val, 1) != I2C_OK)
+    {
+        return -1;
+    }
+
+    /* 将标准十进制转换为 BCD 码并打包 */
+    time_buf[0] = rtc_dec_to_bcd(time->second);
+    time_buf[1] = rtc_dec_to_bcd(time->minute);
+    time_buf[2] = (rtc_dec_to_bcd(time->hour) & 0x3F) | 0x80;
+    time_buf[3] = rtc_dec_to_bcd(time->day);
+    time_buf[4] = rtc_dec_to_bcd(time->month);
+    time_buf[5] = rtc_dec_to_bcd(time->year);
+    time_buf[6] = rtc_dec_to_bcd(time->weekday);
+
+    /* 写入 7 个时间寄存器 */
+    if (i2c_write(rtc.bus, ISL1208_I2C_ADDR, ISL1208_REG_SC, time_buf, 7) != I2C_OK)
+    {
+        return -1;
+    }
+
+    /* 恢复写保护并同时清除掉电失效位 (WRTC = 0, RTCF = 0) */
+    sr_val &= ~ISL1208_SR_WRTC;
+    sr_val &= ~ISL1208_SR_RTCF;
+    if (i2c_write(rtc.bus, ISL1208_I2C_ADDR, ISL1208_REG_SR, &sr_val, 1) != I2C_OK)
+    {
+        return -1;
+    }
+
+    return 0;
+}
+
+/**
+ * @brief  从 RTC 芯片读取时间
+ */
+static int __isl1208_get_time_unlocked(rtc_time_t *time)
+{
+    uint8_t time_buf[7];
+
+    /* 连续读取 7 个寄存器的原始 BCD 数据 */
+    if (i2c_read(rtc.bus, ISL1208_I2C_ADDR, ISL1208_REG_SC, time_buf, 7) != I2C_OK)
+    {
+        return -1;
+    }
+
+    /* 解析 BCD 码并做去噪掩码隔离 */
+    time->second  = rtc_bcd_to_dec(time_buf[0] & 0x7F);
+    time->minute  = rtc_bcd_to_dec(time_buf[1] & 0x7F);
+    time->hour    = rtc_bcd_to_dec(time_buf[2] & 0x3F);
+    time->day     = rtc_bcd_to_dec(time_buf[3] & 0x3F);
+    time->month   = rtc_bcd_to_dec(time_buf[4] & 0x1F);
+    time->year    = rtc_bcd_to_dec(time_buf[5]);
+    time->weekday = rtc_bcd_to_dec(time_buf[6] & 0x07);
+
+    return 0;
+}
diff --git a/ex2.i2c-bitbang/Board/isl1208.h b/ex2.i2c-bitbang/Board/isl1208.h
new file mode 100644
index 0000000..b9f8856
--- /dev/null
+++ b/ex2.i2c-bitbang/Board/isl1208.h
@@ -0,0 +1,73 @@
+/**********************************************************************
+ * Copyright: (C)2026 LingYun IoT System Studio
+ * Author: GuoWenxue<guowenxue@gmail.com>
+ *
+ * Description: ISL1208 RTC driver on ISKBoard
+ *
+ * ChangeLog:
+ * Version    Date       Author            Description
+ * V1.0.0  2026.07.08    GuoWenxue      Release initial version
+ *
+ ************************************************************************/
+
+#ifndef __ISL1208_H
+#define __ISL1208_H
+
+#include "i2c_bitbang.h"
+
+/**
+ * @brief RTC 时间结构体 (标准十进制格式,应用层直接读写)
+ */
+typedef struct {
+    uint8_t second;   /* 秒: 00 - 59 */
+    uint8_t minute;   /* 分: 00 - 59 */
+    uint8_t hour;     /* 时: 00 - 23 (固定为24小时制) */
+    uint8_t day;      /* 日: 01 - 31 */
+    uint8_t month;    /* 月: 01 - 12 */
+    uint8_t year;     /* 年: 00 - 99 (代表 2000 - 2099 年) */
+    uint8_t weekday;  /* 星期: 00 - 06 (00代表星期天) */
+} rtc_time_t;
+
+/**
+ * @brief  ISL1208 硬件初始化与掉电自愈函数
+ * @param  bus: 绑定的 I2C 物理总线指针 (如 &i2c1_bus)
+ * @retval 0: 初始化成功; -1: 总线通信失败或芯片未响应
+ */
+extern int isl1208_init(i2c_bus_t *bus);
+
+/**
+ * @brief  应用层设置 RTC 时间接口函数(rtc_time_t格式)
+ * @param  time  指向待写入时间数据的 rtc_time_t 结构体常指针
+ * @retval 0:成功, -1:失败
+ */
+extern int isl1208_set_time(const rtc_time_t *time);
+
+/**
+ * @brief  应用层设置 RTC 时间接口函数(字符串格式)
+ * @param  time_str  输入的时间字符串指针,格式如 "2026-01-01 10:00:00"
+ * @retval 0:成功, -1:失败
+ */
+extern int isl1208_set_time_str(const char *time_str);
+
+/**
+ * @brief  应用层获取 RTC 时间接口函数
+ * @param  time  指向用于接收时间数据的 rtc_time_t 结构体指针
+ * @retval 0:成功, -1:失败
+ */
+extern int isl1208_get_time(rtc_time_t *time);
+
+/**
+ * @brief  应用层获取 RTC 时间接口函数(字符串格式)
+ * @param  buf      用于存储格式化时间字符串的缓冲区指针
+ * @param  buf_len  接收缓冲区长度(建议至少20字节)
+ * @retval 0:成功, -1:失败
+ */
+extern int isl1208_get_time_str(char *buf, size_t buf_len);
+
+/**
+ * @brief  获取并格式化打印当前 RTC 时间接口函数
+ * @retval 0:成功, -1:失败
+ */
+extern int isl1208_print_time(void);
+
+#endif /* __ISL1208_H */
diff --git a/ex2.i2c-bitbang/Board/miscdev.c b/ex2.i2c-bitbang/Board/miscdev.c
new file mode 100644
index 0000000..98ceda3
--- /dev/null
+++ b/ex2.i2c-bitbang/Board/miscdev.c
@@ -0,0 +1,409 @@
+/**********************************************************************
+*   Copyright: (C)2024 LingYun IoT System Studio
+*      Author: GuoWenxue<guowenxue@gmail.com>
+* Description: ISKBoard Hardware Abstract Layer driver
+*
+*   ChangeLog:
+*        Version    Date       Author            Description
+*        V1.0.0  2024.08.29    GuoWenxue      Release initial version
+***********************************************************************/
+
+#include "miscdev.h"
+
+/*
+ *+----------------------+
+ *|    STM32 GPIO API    |
+ *+----------------------+
+ */
+
+void gpio_clk_enable(GPIO_TypeDef *port)
+{
+    if      (port == GPIOA) __HAL_RCC_GPIOA_CLK_ENABLE();
+    else if (port == GPIOB) __HAL_RCC_GPIOB_CLK_ENABLE();
+    else if (port == GPIOC) __HAL_RCC_GPIOC_CLK_ENABLE();
+    else if (port == GPIOD) __HAL_RCC_GPIOD_CLK_ENABLE();
+#ifdef GPIOE
+    else if (port == GPIOE) __HAL_RCC_GPIOE_CLK_ENABLE();
+#endif
+#ifdef GPIOF
+    else if (port == GPIOF) __HAL_RCC_GPIOF_CLK_ENABLE();
+#endif
+#ifdef GPIOG
+    else if (port == GPIOG) __HAL_RCC_GPIOG_CLK_ENABLE();
+#endif
+#ifdef GPIOH
+    else if (port == GPIOH) __HAL_RCC_GPIOH_CLK_ENABLE();
+#endif
+}
+
+
+/*
+ *+----------------------+
+ *|  GPIO Led/Relay API  |
+ *+----------------------+
+ */
+
+/* ISKBoard 有一个继电器 */
+gpio_t     relay = { "Relay", GPIOD, GPIO_PIN_2, 1 };
+
+/* ISKBoard 有三个Led灯 */
+gpio_t     leds[LedMax] =
+{
+    { "RedLed",   GPIOC, GPIO_PIN_9, 0 },
+    { "GreenLed", GPIOC, GPIO_PIN_6, 0 },
+    { "BlueLed",  GPIOB, GPIO_PIN_2, 0 },
+};
+
+/**
+ * @brief  继电器初始化:配置继电器控制引脚对应的GPIO
+ */
+void init_relay(void)
+{
+    /* set default output level to inactive */
+    HAL_GPIO_WritePin(relay.group, relay.pin, !relay.active);
+
+    return;
+}
+
+/**
+ * @brief  控制继电器的开关状态
+ * @param  status: ON 或 OFF
+ */
+void turn_relay(int status)
+{
+    GPIO_PinState       level;
+
+    level = status==OFF ? !relay.active : relay.active;
+    HAL_GPIO_WritePin(relay.group, relay.pin, level);
+}
+
+/**
+ * @brief  LED初始化:配置各LED对应的GPIO状态。
+ */
+void init_led(void)
+{
+    int                 which;
+
+    /* Initial all leds GPIO */
+    for(which=0; which<LedMax; which++)
+    {
+        /* set default output level to inactive */
+        HAL_GPIO_WritePin(leds[which].group, leds[which].pin, !leds[which].active);
+    }
+
+    return;
+}
+
+/**
+ * @brief  控制指定LED的开关状态
+ * @param  which:  LED编号(参考 lednum_t,如 Led_R/Led_G/Led_B)
+ * @param  status: ON 或 OFF
+ */
+void turn_led(int which, int status)
+{
+    GPIO_PinState       level;
+
+    if( which >= LedMax )
+        return ;
+
+    level = status==ON ? leds[which].active : !leds[which].active;
+    HAL_GPIO_WritePin(leds[which].group, leds[which].pin, level);
+}
+
+/**
+ * @brief  翻转指定LED当前状态(亮变灭,灭变亮)
+ * @param  which: LED编号(参考 lednum_t)
+ */
+void toggle_led(int which)
+{
+    if( which >= LedMax )
+        return ;
+
+    HAL_GPIO_TogglePin(leds[which].group, leds[which].pin);
+}
+
+/**
+ * @brief  阻塞式闪烁指定LED一次(亮interval毫秒,灭interval毫秒)
+ * @param  which:    LED编号(参考 lednum_t)
+ * @param  interval: 亮灭各自持续时间(ms)
+ */
+void blink_led(int which, uint32_t interval)
+{
+    turn_led(which, ON);
+    mdelay(interval);
+
+    turn_led(which, OFF);
+    mdelay(interval);
+}
+
+/**
+ * @brief  绿灯"滴-答"双闪心跳灯:每隔 interval 秒心跳一次。
+ * @param  interval: 心跳总周期,单位:秒 (参数 > 1)。
+ */
+void heartbeat_led(uint32_t interval)
+{
+    /* 总周期 - (亮 + 灭 + 亮) = 停顿时长 */
+    uint32_t pause_time = (interval * 1000) - 300;
+    uint32_t hb_duration[4] = {60, 60, 180, pause_time};
+
+    static const uint8_t  hb_status[4]   = {ON,  OFF, ON,  OFF};
+
+    static uint8_t  state = 0;
+    static uint32_t last_tick = 0;
+    static uint8_t  first_run = 1;
+
+    uint32_t now = HAL_GetTick();
+
+    if (first_run)
+    {
+        turn_led(Led_G, hb_status[state]);
+        last_tick = now;
+        first_run = 0;
+    }
+
+    if (now - last_tick >= hb_duration[state])
+    {
+        state = (state + 1) % 4;
+        turn_led(Led_G, hb_status[state]);
+        last_tick = now;
+    }
+}
+
+/*
+ *+----------------------+
+ *|     GPIO Key API     |
+ *+----------------------+
+ */
+ /* 全局按键状态位图定义,初始为0(所有按键都未按下) */
+int  g_keys_status = 0;
+
+/**
+ * @brief  GPIO外部中断统一回调函数(HAL库弱函数重写),
+ *         按键触发的EXTI中断都会进入这里,根据引脚号区分是哪个按键按下
+ * @param  GPIO_Pin: 触发中断的引脚号(如 GPIO_PIN_12/13/14)
+ */
+void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
+{
+    if( GPIO_PIN_12 == GPIO_Pin ) /* Key1 */
+    {
+        g_keys_status |= KEY1_PRESSED;
+    }
+    else if( GPIO_PIN_13 == GPIO_Pin ) /* Key2 */
+    {
+        g_keys_status |= KEY2_PRESSED;
+    }
+    else if( GPIO_PIN_14 == GPIO_Pin ) /* Key3 */
+    {
+        g_keys_status |= KEY3_PRESSED;
+    }
+}
+
+/*
+ *+----------------------+
+ *|      printf API      |
+ *+----------------------+
+ */
+
+/* gcc 编译器的 printf 将会调用 __io_putchar() 函数,实现最终的字符打印
+ * keil编译器的 printf 将会调用 fputc() 函数,实现最终的字符打印
+ * 这里我们定义一个宏 PUTCHAR_PROTOTYPE 来兼容两个编译器所需要的函数原型
+ *
+ * 注意:
+ *  1. Keil5 工程需要勾选:Options for Target -> Target -> Use MicroLIB
+ *  2. Keil5 的编译器 armclang(AC6) 会定义 __GNUC__ 宏,而旧版 armcc(AC5) 则不会
+ */
+#if defined(__ARMCC_VERSION) /*  */
+#define PUTCHAR_PROTOTYPE int fputc(int ch, FILE *f)
+#elif defined(__GNUC__) /* GCC */
+#define PUTCHAR_PROTOTYPE int __io_putchar(int ch)
+#endif
+
+PUTCHAR_PROTOTYPE
+{
+    /* 调用STM32 HAL库的串口发送函数,将printf要打印的这个字符通过串口发送出去 */
+    HAL_UART_Transmit(&huart1, (uint8_t *)&ch, 1, 0xFFFF);
+    return ch;
+}
+
+/*
+ *+----------------------------+
+ *| ADC noisy & lux sensor API |
+ *+----------------------------+
+ */
+
+#include "adc.h"
+
+/**
+ * @brief  读取指定通道的ADC值
+ * @param  channel: 通道号,例如 ADC_CHANNEL_15 / ADC_CHANNEL_16
+ * @param  value:   读取结果输出指针
+ * @retval 0 成功, -1 失败
+ */
+int adc_read_channel(uint32_t channel, uint32_t *value)
+{
+    ADC_ChannelConfTypeDef sConfig = {0};
+    uint32_t timeout = 0xffffff;
+
+    /* 咪头(麦克风)输出的是交流信号,叠加在某个直流偏置上。 当声音信号处于波形过零点附近时,
+     * 2.5 个采样周期极短,ADC 输入阻抗在如此短的时间内无法对麦克风输出的高阻信号完成充电,
+     * 信号还没稳定就完成了采样,所以这里修改采样时间为 247.5 Cycles。 */
+    sConfig.Channel      = channel;
+    sConfig.Rank         = ADC_REGULAR_RANK_1;
+    sConfig.SamplingTime = ADC_SAMPLETIME_247CYCLES_5;
+    sConfig.SingleDiff   = ADC_SINGLE_ENDED;
+    sConfig.OffsetNumber = ADC_OFFSET_NONE;
+    sConfig.Offset       = 0;
+
+    if (HAL_ADC_ConfigChannel(&hadc1, &sConfig) != HAL_OK)
+    {
+        return -1;
+    }
+
+    HAL_ADC_Start(&hadc1);
+
+    if (HAL_ADC_PollForConversion(&hadc1, timeout) != HAL_OK)
+    {
+        HAL_ADC_Stop(&hadc1);
+        return -1;
+    }
+
+    *value = HAL_ADC_GetValue(&hadc1);
+    HAL_ADC_Stop(&hadc1);
+
+    return 0;
+}
+
+/**
+ * @brief  在指定时间窗口内对麦克风多次采样,返回峰峰值作为响度
+ * @param  noisy:        响度输出(峰峰值,范围 0~4095)
+ * @param  window_ms:    采样时间窗口,建议 50~100ms(覆盖完整音节)
+ * @retval 0表示成功,-1 表示失败
+ */
+int adc_read_noisy_peak(uint32_t *noisy, uint32_t window_ms)
+{
+    uint32_t val;
+    uint32_t max_val = 0;
+    uint32_t min_val = 0xFFFFFFFF;
+    uint32_t deadline = HAL_GetTick() + window_ms;
+
+    while (HAL_GetTick() < deadline)
+    {
+        if (adc_read_channel(ADC_CHANNEL_16, &val) != 0)
+            return -1;
+
+        if (val > max_val) max_val = val;
+        if (val < min_val) min_val = val;
+    }
+
+    /* 峰峰值消除直流偏置影响,更真实反映响度 */
+    *noisy = (max_val > min_val) ? (max_val - min_val) : 0;
+    return 0;
+}
+
+/* ------------------- Lux 滤波相关参数 ------------------- */
+#define LUX_SAMPLE_COUNT   10   /* 每次采集的原始样本数 */
+#define LUX_TRIM_COUNT      2   /* 排序后,头尾各丢弃的样本数 */
+
+/**
+ * @brief  简单冒泡排序(数据量很小,不追求性能)
+ */
+static void sort_u32(uint32_t *arr, int n)
+{
+    for (int i = 0; i < n - 1; i++)
+    {
+        for (int j = 0; j < n - 1 - i; j++)
+        {
+            if (arr[j] > arr[j + 1])
+            {
+                uint32_t tmp = arr[j];
+                arr[j] = arr[j + 1];
+                arr[j + 1] = tmp;
+            }
+        }
+    }
+}
+
+/**
+ * @brief  多次采样光照度ADC值,排序后去掉两端的离群样本,再取平均
+ * @param  lux: 输出参数,滤波后的光照度值
+ * @retval 0 成功, -1 失败
+ */
+static int adc_read_lux_filtered(uint32_t *lux)
+{
+    uint32_t samples[LUX_SAMPLE_COUNT];
+    uint32_t sum = 0;
+    int valid_count;
+
+    /* 多次采样光照度ADC值 */
+    for (int i = 0; i < LUX_SAMPLE_COUNT; i++)
+    {
+        if (adc_read_channel(ADC_CHANNEL_15, &samples[i]) != 0)
+            return -1;
+    }
+
+    /* 将多次采样的数据排序 */
+    sort_u32(samples, LUX_SAMPLE_COUNT);
+
+    /* 丢弃排序后头尾各 LUX_TRIM_COUNT 个可能的离群值 */
+    valid_count = LUX_SAMPLE_COUNT - 2 * LUX_TRIM_COUNT;
+    if (valid_count <= 0)
+    {
+        /* 参数配置不合理时兜底,避免除0 */
+        valid_count = LUX_SAMPLE_COUNT;
+        for (int i = 0; i < LUX_SAMPLE_COUNT; i++)
+            sum += samples[i];
+    }
+    else
+    {
+        for (int i = LUX_TRIM_COUNT; i < LUX_SAMPLE_COUNT - LUX_TRIM_COUNT; i++)
+            sum += samples[i];
+    }
+
+    *lux = sum / valid_count;
+
+    return 0;
+}
+
+ /**
+ * @brief  采集光照度与噪声值
+ * @param  lux:   输出参数,采集到的光照度值
+ * @param  noisy: 输出参数,采集到的噪声值
+ * @return 0表示成功,-1 表示失败
+ */
+int adc_sample_lux_noisy(uint32_t *lux, uint32_t *noisy)
+{
+    if (adc_read_lux_filtered(lux) != 0)
+        return -1;
+
+    if (adc_read_noisy_peak(noisy, 50) != 0)
+        return -2;
+
+    return 0;
+}
+
+/*
+ *+----------------------------+
+ *|      Timer delay API       |
+ *+----------------------------+
+ */
+#include "tim.h"
+
+/**
+ * @brief  微秒级延时,最大支持 60000us,更长延时可以使用 HAL_Delay()
+ * @param  us: 延时时长(微秒)
+ */
+void udelay(uint16_t us)
+{
+    uint16_t differ = 60000-us;
+
+    HAL_TIM_Base_Start(&htim6);
+
+    __HAL_TIM_SET_COUNTER(&htim6, differ);
+
+    while( differ < 60000 )
+    {
+        differ=__HAL_TIM_GET_COUNTER(&htim6);
+    }
+
+    HAL_TIM_Base_Stop(&htim6);
+}
diff --git a/ex2.i2c-bitbang/Board/miscdev.h b/ex2.i2c-bitbang/Board/miscdev.h
new file mode 100644
index 0000000..3c35898
--- /dev/null
+++ b/ex2.i2c-bitbang/Board/miscdev.h
@@ -0,0 +1,170 @@
+/**********************************************************************
+*   Copyright: (C)2024 LingYun IoT System Studio
+*      Author: GuoWenxue<guowenxue@gmail.com>
+* Description: ISKBoard Hardware Abstract Layer driver
+*
+*   ChangeLog:
+*        Version    Date       Author            Description
+*        V1.0.0  2024.08.29    GuoWenxue      Release initial version
+***********************************************************************/
+
+#ifndef __MISCDEV_H
+#define __MISCDEV_H
+
+#include <stdio.h>
+
+#include "stm32l4xx_hal.h"
+#include "usart.h"
+#include "buzzer.h"
+
+/* 定义ON/OFF两个宏,这样在代码中使用比 1/0 更具有可读性 */
+#define OFF    0
+#define ON     1
+
+/*
+ *+----------------------+
+ *|    STM32 GPIO API    |
+ *+----------------------+
+ */
+
+
+/* 定义一个gpio的结构体,用来描述这个引脚的相关信息 */
+typedef struct gpio_s
+{
+    const char         *name;
+    GPIO_TypeDef       *group;
+    uint16_t            pin;
+    int                 active; /* 1 for active high, 0 for active low */
+} gpio_t;
+
+extern void gpio_clk_enable(GPIO_TypeDef *port);
+
+/*
+ *+----------------------+
+ *|    GPIO Relay API    |
+ *+----------------------+
+ */
+
+/**
+ * @brief  继电器初始化:配置继电器控制引脚对应的GPIO
+ */
+extern void init_relay(void);
+
+/**
+ * @brief  控制继电器的开关状态
+ * @param  status: ON 或 OFF
+ */
+extern void turn_relay(int status);
+
+/*
+ *+----------------------+
+ *|     GPIO Led API     |
+ *+----------------------+
+ */
+
+/* 定义了一个枚举,分别表示RGB三个灯的索引:
+ * 枚举的第1个成员,如果没赋值则其值为0,后面的成员值永远是前面成员的值加1.
+ */
+typedef enum
+{
+    Led_R, /* 其值为0 */
+    Led_G, /* 其值为1 */
+    Led_B, /* 其值为2 */
+    LedMax,/* 其值为3,刚好是Led的总数 */
+} lednum_t;
+
+/**
+ * @brief  LED初始化:配置各LED对应的GPIO状态。
+ */
+extern void init_led(void);
+
+/**
+ * @brief  控制指定LED的开关状态
+ * @param  which:  LED编号(参考 lednum_t,如 Led_R/Led_G/Led_B)
+ * @param  status: ON 或 OFF
+ */
+extern void turn_led(int which, int status);
+
+/**
+ * @brief  翻转指定LED当前状态(亮变灭,灭变亮)
+ * @param  which: LED编号(参考 lednum_t)
+ */
+extern void toggle_led(int which);
+
+/**
+ * @brief  阻塞式闪烁指定LED一次(亮interval毫秒,灭interval毫秒)
+ * @param  which:    LED编号(参考 lednum_t)
+ * @param  interval: 亮灭各自持续时间(ms)
+ */
+extern void blink_led(int which, uint32_t interval);
+
+/**
+ * @brief  绿灯"滴-答"双闪心跳灯:每隔 interval 秒心跳一次。
+ * @param  interval: 心跳总周期,单位:秒 (参数 > 1)。
+ */
+extern void heartbeat_led(uint32_t interval);
+
+/*
+ *+----------------------+
+ *|     GPIO Led API     |
+ *+----------------------+
+ */
+
+/* 定义了一个枚举,分别表示RGB三个灯的索引:
+ * 枚举的第1个成员,如果没赋值则其值为0,后面的成员值永远是前面成员的值加1.
+ */
+typedef enum
+{
+    Key1, /* 其值为0 */
+    Key2, /* 其值为1 */
+    Key3, /* 其值为2 */
+    KeyMax,/* 其值为3,刚好是Key的总数 */
+} keynum_t;
+
+/* 按键状态位掩码:对应 g_keys_status 中的第 Key1/Key2/Key3 位被按下 */
+#define KEY1_PRESSED    (1<<Key1)
+#define KEY2_PRESSED    (1<<Key2)
+#define KEY3_PRESSED    (1<<Key3)
+
+/* 全局按键状态位图,每一位代表当前按键是否处于"已按下"状态,由中断逻辑置位 */
+extern int  g_keys_status;
+
+/* 判断指定按键当前是否处于按下状态 */
+#define key_pressed(key)  (g_keys_status & (1<<key) )
+
+/* 清除指定按键的按下标志 */
+#define key_clear(key)    (g_keys_status &= ~(1<<key) )
+
+/*
+ *+----------------------------+
+ *| ADC noisy & lux sensor API |
+ *+----------------------------+
+ */
+
+ /**
+ * @brief  采集光照度与噪声值
+ * @param  lux:   输出参数,采集到的光照度值
+ * @param  noisy: 输出参数,采集到的噪声值
+ * @return 0表示成功,-1 表示失败
+ */
+extern int adc_sample_lux_noisy(uint32_t *lux, uint32_t *noisy);
+
+/*
+ *+----------------------------+
+ *|      Timer delay API       |
+ *+----------------------------+
+ */
+
+/**
+ * @brief  微秒级延时,最大支持 60000us,更长延时可以使用 HAL_Delay()
+ * @param  us: 延时时长(微秒)
+ */
+extern void udelay(uint16_t us);
+
+/**
+ * @brief  毫秒秒级延时,直接使用 HAL_Delay(),方便代码可读性
+ * @param  ms: 延时时长(毫秒)
+ */
+#define mdelay(ms)  HAL_Delay(ms)
+
+#endif /* __MISCDEV_H */
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();
+}
diff --git a/ex2.i2c-bitbang/Board/oled_drv.h b/ex2.i2c-bitbang/Board/oled_drv.h
new file mode 100644
index 0000000..a152b21
--- /dev/null
+++ b/ex2.i2c-bitbang/Board/oled_drv.h
@@ -0,0 +1,163 @@
+/**********************************************************************
+ * 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
+ ***********************************************************************/
+
+/* SSD1306 OLED显示器内部按页(Page)组织数据:
+
+     屏幕布局(128x32):
+     +------------------+
+     | Page 0 (0~7)     | ← 8像素
+     +------------------+
+     | Page 1 (8~15)    | ← 8像素
+     +------------------+
+     | Page 2 (16~23)   | ← 8像素
+     +------------------+
+     | Page 3 (24~31)   | ← 8像素
+     +------------------+
+
+     字体大小说明:
+     +---------------+---------+-------+----------+-------------+
+     |    FONT       |  宽度   |  高度 |  占用页数 |   Y坐标类型 |
+     +---------------+---------+-------+----------+-------------+
+     | FONT8(6x8)    |  6像素  | 8像素 |   1页    |  页码(0~3)  |
+     +---------------+---------+-------+----------+-------------+
+     | FONT16(8x16)  |  8像素  | 16像素|   2页    |  页码(0~2)  |
+     +---------------+---------+-------+----------+-------------+
+     | Chinese(16x16)|  16像素 | 16像素|   2页    |  页码(0~2)  |
+     +---------------+---------+-------+----------+-------------+
+ */
+
+#ifndef __OLED_DRV_H
+#define __OLED_DRV_H
+
+#include "stm32l4xx_hal.h"
+#include "i2c_bitbang.h"
+
+/* OLED I2C 从设备地址 */
+#define OLED_I2C_ADDR   0x3C
+
+/* 屏幕物理尺寸 */
+#define X_WIDTH         128
+#define Y_WIDTH         32
+
+/* 字体大小选项 */
+#define OLED_FONT16     16  /* 8×16 像素字体 */
+#define OLED_FONT8      8   /* 6×8  像素字体 */
+
+/* SSD1306 页数(128×32 屏共 4 页,每页 8 行像素) */
+enum
+{
+    OLED_PAGE0, /* 第0个页,控制纵坐标从 0 开始 */
+    OLED_PAGE1, /* 第1个页,控制纵坐标从 8 开始 */
+    OLED_PAGE2, /* 第2个页,控制纵坐标从 16 开始 */
+    OLED_PAGE3, /* 第3个页,控制纵坐标从 24 开始 */
+    OLED_PAGES, /* 总共 4 个页 */
+};
+
+/* 根据 SSD1306 芯片 datasheet 说明定义控制流标记 */
+typedef enum
+{
+    OLED_CMD  = 0x00,   /* Co=0, D/C#=0 -> 后面跟着命令 */
+    OLED_DATA = 0x40,   /* Co=0, D/C#=1 -> 后面跟着全数据流 */
+} oled_ctrl_t;
+
+/*
+ * =========================================================================
+ * 显示控制 API
+ * =========================================================================
+ */
+
+/**
+ * @brief 初始化 OLED 显示屏(包含寄存器流配置、本地 GRAM 格式化、以及首次总线强刷)
+ * @param bus: 绑定的 i2c-bitbang 物理总线句柄
+ */
+int OLED_Init(i2c_bus_t *bus);
+
+/**
+ * @brief 清空软件 GRAM 缓冲区(全黑), 需配合 OLED_Refresh() 使用。
+ */
+void OLED_Clear(void);
+
+/**
+ * @brief 点亮软件 GRAM 缓冲区中所有像素(全白), 需配合 OLED_Refresh() 使用。
+ */
+void OLED_On(void);
+
+/**
+ * @brief 开启 OLED 显示(打开内建 Charge Pump 倍压器 + 开启显示面板)
+ */
+void OLED_Display_On(void);
+
+/**
+ * @brief 关闭 OLED 显示(进入超低功耗硬件待机模式,显存内容保持不动)
+ */
+void OLED_Display_Off(void);
+
+/**
+ * @brief 将软件 GRAM 缓冲区一次性突发刷新到 OLED 硬件显存中
+ * 每页采用单次加锁机制,极大幅度压缩总线事务开销,给其他设备留足带宽。
+ */
+void OLED_Refresh(void);
+
+/*
+ * =========================================================================
+ * 绘制 API(写缓冲区,需配合 OLED_Refresh() 显示)
+ * =========================================================================
+ */
+
+/**
+ * @brief 在本地 GRAM 缓冲区中写入单个可打印 ASCII 字符
+ *
+ * @param x         列坐标 (0 ~ X_WIDTH-1)
+ * @param page      页坐标 (0 ~ OLED_PAGES-1)
+ * @param chr       ASCII 可打印字符
+ * @param char_size OLED_FONT16 (占 2 页) 或 OLED_FONT8 (占 1 页)
+ */
+void OLED_ShowChar(uint8_t x, uint8_t page, char chr, uint8_t char_size);
+
+/**
+ * @brief 在本地 GRAM 缓冲区中写入 ASCII 字符串(到行尾自动换行)
+ *
+ * @param x         起始列 (0 ~ X_WIDTH-1)
+ * @param page      起始页 (0 ~ OLED_PAGES-1)
+ * @param str       以 '\0' 结尾的字符串指针(支持 const 静态字面量)
+ * @param font_size OLED_FONT16 或 OLED_FONT8
+ */
+void OLED_ShowString(uint8_t x, uint8_t page, const char *str, uint8_t font_size);
+
+/**
+ * @brief 在本地 GRAM 缓冲区中写入一个 16×16 的标准汉字
+ *
+ * @param Hzk       汉字字模二维数组只读指针(每个汉字 32 字节,分立上下两页)
+ * @param x         起始列 (0 ~ X_WIDTH-1)
+ * @param page      起始页 (0 ~ OLED_PAGES-1)
+ * @param index     目标汉字在点阵数组中的索引序号
+ */
+void OLED_ShowChinese(const uint8_t (*Hzk)[32], uint8_t x, uint8_t page, uint8_t index);
+
+/**
+ * @brief 在本地 GRAM 缓冲区中绘制单色二进制 BMP 图像
+ *
+ * @param x0        起始列 (0 ~ X_WIDTH-1)
+ * @param page0     起始页 (0 ~ OLED_PAGES-1)
+ * @param x_width   图像像素宽度
+ * @param y_width   图像像素高度(工程规范:必须为 8 的整数倍)
+ * @param bmp       单色点阵数组指针(列行式,逆向取模)
+ */
+void OLED_DrawBMP(uint8_t x0, uint8_t page0,
+                  uint8_t x_width, uint8_t y_width,
+                  const uint8_t *bmp);
+
+/**
+ * @brief OLED 驱动功能及综合演示函数(包含动态打字机、混排、全图爆破等)
+ */
+void oled_test_example(void);
+
+#endif /* __OLED_DRV_H */
diff --git a/ex2.i2c-bitbang/Board/oled_font.c b/ex2.i2c-bitbang/Board/oled_font.c
new file mode 100644
index 0000000..8b70276
--- /dev/null
+++ b/ex2.i2c-bitbang/Board/oled_font.c
@@ -0,0 +1,323 @@
+/**********************************************************************
+*   Copyright: (C)2021 LingYun IoT System Studio <www.weike-iot.com>
+*      Author: GuoWenxue<guowenxue@gmail.com> QQ: 281143292
+* Description: ISKBoard OLED font data (PCtoLCD.exe generated)
+*
+*   ChangeLog:
+*        Version    Date       Author            Description
+*        V1.0.0  2021.08.10  GuoWenxue      Release initial version
+***********************************************************************/
+
+#include "oled_font.h"
+
+/*+--------------------------------+
+ *|      ASCII font size 6x8       |
+ *+--------------------------------+*/
+
+/* 可打印 ASCII 范围: ' '(0x20) ~ '~'(0x7E), 共 95 个字符 */
+const uint8_t F6x8[][6] =
+{
+    {0x00,0x00,0x00,0x00,0x00,0x00},/* sp  */
+    {0x00,0x00,0x00,0x2F,0x00,0x00},/* !   */
+    {0x00,0x00,0x07,0x00,0x07,0x00},/* "   */
+    {0x00,0x14,0x7F,0x14,0x7F,0x14},/* #   */
+    {0x00,0x24,0x2A,0x7F,0x2A,0x12},/* $   */
+    {0x00,0x62,0x64,0x08,0x13,0x23},/* %   */
+    {0x00,0x36,0x49,0x55,0x22,0x50},/* &   */
+    {0x00,0x00,0x05,0x03,0x00,0x00},/* '   */
+    {0x00,0x00,0x1C,0x22,0x41,0x00},/* (   */
+    {0x00,0x00,0x41,0x22,0x1C,0x00},/* )   */
+    {0x00,0x14,0x08,0x3E,0x08,0x14},/* * */
+    {0x00,0x08,0x08,0x3E,0x08,0x08},/* +   */
+    {0x00,0x00,0x00,0xA0,0x60,0x00},/* ,   */
+    {0x00,0x08,0x08,0x08,0x08,0x08},/* -   */
+    {0x00,0x00,0x60,0x60,0x00,0x00},/* .   */
+    {0x00,0x20,0x10,0x08,0x04,0x02},/* /   */
+    {0x00,0x3E,0x51,0x49,0x45,0x3E},/* 0   */
+    {0x00,0x00,0x42,0x7F,0x40,0x00},/* 1   */
+    {0x00,0x42,0x61,0x51,0x49,0x46},/* 2   */
+    {0x00,0x21,0x41,0x45,0x4B,0x31},/* 3   */
+    {0x00,0x18,0x14,0x12,0x7F,0x10},/* 4   */
+    {0x00,0x27,0x45,0x45,0x45,0x39},/* 5   */
+    {0x00,0x3C,0x4A,0x49,0x49,0x30},/* 6   */
+    {0x00,0x01,0x71,0x09,0x05,0x03},/* 7   */
+    {0x00,0x36,0x49,0x49,0x49,0x36},/* 8   */
+    {0x00,0x06,0x49,0x49,0x29,0x1E},/* 9   */
+    {0x00,0x00,0x36,0x36,0x00,0x00},/* :   */
+    {0x00,0x00,0x56,0x36,0x00,0x00},/* ;   */
+    {0x00,0x08,0x14,0x22,0x41,0x00},/* <   */
+    {0x00,0x14,0x14,0x14,0x14,0x14},/* =   */
+    {0x00,0x00,0x41,0x22,0x14,0x08},/* >   */
+    {0x00,0x02,0x01,0x51,0x09,0x06},/* ?   */
+    {0x00,0x32,0x49,0x59,0x51,0x3E},/* @   */
+    {0x00,0x7C,0x12,0x11,0x12,0x7C},/* A   */
+    {0x00,0x7F,0x49,0x49,0x49,0x36},/* B   */
+    {0x00,0x3E,0x41,0x41,0x41,0x22},/* C   */
+    {0x00,0x7F,0x41,0x41,0x22,0x1C},/* D   */
+    {0x00,0x7F,0x49,0x49,0x49,0x41},/* E   */
+    {0x00,0x7F,0x09,0x09,0x09,0x01},/* F   */
+    {0x00,0x3E,0x41,0x49,0x49,0x7A},/* G   */
+    {0x00,0x7F,0x08,0x08,0x08,0x7F},/* H   */
+    {0x00,0x00,0x41,0x7F,0x41,0x00},/* I   */
+    {0x00,0x20,0x40,0x41,0x3F,0x01},/* J   */
+    {0x00,0x7F,0x08,0x14,0x22,0x41},/* K   */
+    {0x00,0x7F,0x40,0x40,0x40,0x40},/* L   */
+    {0x00,0x7F,0x02,0x0C,0x02,0x7F},/* M   */
+    {0x00,0x7F,0x04,0x08,0x10,0x7F},/* N   */
+    {0x00,0x3E,0x41,0x41,0x41,0x3E},/* O   */
+    {0x00,0x7F,0x09,0x09,0x09,0x06},/* P   */
+    {0x00,0x3E,0x41,0x51,0x21,0x5E},/* Q   */
+    {0x00,0x7F,0x09,0x19,0x29,0x46},/* R   */
+    {0x00,0x46,0x49,0x49,0x49,0x31},/* S   */
+    {0x00,0x01,0x01,0x7F,0x01,0x01},/* T   */
+    {0x00,0x3F,0x40,0x40,0x40,0x3F},/* U   */
+    {0x00,0x1F,0x20,0x40,0x20,0x1F},/* V   */
+    {0x00,0x3F,0x40,0x38,0x40,0x3F},/* W   */
+    {0x00,0x63,0x14,0x08,0x14,0x63},/* X   */
+    {0x00,0x07,0x08,0x70,0x08,0x07},/* Y   */
+    {0x00,0x61,0x51,0x49,0x45,0x43},/* Z   */
+    {0x00,0x00,0x7F,0x41,0x41,0x00},/* [   */
+    {0x00,0x0C,0x30,0xC0,0x00,0x00},/* \   */
+    {0x00,0x00,0x41,0x41,0x7F,0x00},/* ]   */
+    {0x00,0x04,0x02,0x01,0x02,0x04},/* ^   */
+    {0x00,0x40,0x40,0x40,0x40,0x40},/* _   */
+    {0x00,0x00,0x01,0x02,0x04,0x00},/* `   */
+    {0x00,0x20,0x54,0x54,0x54,0x78},/* a   */
+    {0x00,0x7F,0x48,0x44,0x44,0x38},/* b   */
+    {0x00,0x38,0x44,0x44,0x44,0x20},/* c   */
+    {0x00,0x38,0x44,0x44,0x48,0x7F},/* d   */
+    {0x00,0x38,0x54,0x54,0x54,0x18},/* e   */
+    {0x00,0x08,0x7E,0x09,0x01,0x02},/* f   */
+    {0x00,0x18,0xA4,0xA4,0xA4,0x7C},/* g   */
+    {0x00,0x7F,0x08,0x04,0x04,0x78},/* h   */
+    {0x00,0x00,0x44,0x7D,0x40,0x00},/* i   */
+    {0x00,0x40,0x80,0x84,0x7D,0x00},/* j   */
+    {0x00,0x7F,0x10,0x28,0x44,0x00},/* k   */
+    {0x00,0x00,0x41,0x7F,0x40,0x00},/* l   */
+    {0x00,0x7C,0x04,0x18,0x04,0x78},/* m   */
+    {0x00,0x7C,0x08,0x04,0x04,0x78},/* n   */
+    {0x00,0x38,0x44,0x44,0x44,0x38},/* o   */
+    {0x00,0xFC,0x24,0x24,0x24,0x18},/* p   */
+    {0x00,0x18,0x24,0x24,0x18,0xFC},/* q   */
+    {0x00,0x7C,0x08,0x04,0x04,0x08},/* r   */
+    {0x00,0x48,0x54,0x54,0x54,0x20},/* s   */
+    {0x00,0x04,0x3F,0x44,0x40,0x20},/* t   */
+    {0x00,0x3C,0x40,0x40,0x20,0x7C},/* u   */
+    {0x00,0x1C,0x20,0x40,0x20,0x1C},/* v   */
+    {0x00,0x3C,0x40,0x30,0x40,0x3C},/* w   */
+    {0x00,0x44,0x28,0x10,0x28,0x44},/* x   */
+    {0x00,0x1C,0xA0,0xA0,0xA0,0x7C},/* y   */
+    {0x00,0x44,0x64,0x54,0x4C,0x44},/* z   */
+    {0x14,0x14,0x14,0x14,0x14,0x14},/* horiz lines */
+};
+
+/*+--------------------------------+
+ *|      ASCII font size 8x16      |
+ *+--------------------------------+*/
+
+/* 每个字符 16 字节, 共 95 个字符 */
+ const uint8_t F8X16[] =
+{
+    0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,/* sp  0 */
+    0x00,0x00,0x00,0xF8,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x33,0x30,0x00,0x00,0x00,/* !   1 */
+    0x00,0x10,0x0C,0x06,0x10,0x0C,0x06,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,/* "   2 */
+    0x40,0xC0,0x78,0x40,0xC0,0x78,0x40,0x00,0x04,0x3F,0x04,0x04,0x3F,0x04,0x04,0x00,/* #   3 */
+    0x00,0x70,0x88,0xFC,0x08,0x30,0x00,0x00,0x00,0x18,0x20,0xFF,0x21,0x1E,0x00,0x00,/* $   4 */
+    0xF0,0x08,0xF0,0x00,0xE0,0x18,0x00,0x00,0x00,0x21,0x1C,0x03,0x1E,0x21,0x1E,0x00,/* %   5 */
+    0x00,0xF0,0x08,0x88,0x70,0x00,0x00,0x00,0x1E,0x21,0x23,0x24,0x19,0x27,0x21,0x10,/* &   6 */
+    0x10,0x16,0x0E,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,/* '   7 */
+    0x00,0x00,0x00,0xE0,0x18,0x04,0x02,0x00,0x00,0x00,0x00,0x07,0x18,0x20,0x40,0x00,/* (   8 */
+    0x00,0x02,0x04,0x18,0xE0,0x00,0x00,0x00,0x00,0x40,0x20,0x18,0x07,0x00,0x00,0x00,/* )   9 */
+    0x40,0x40,0x80,0xF0,0x80,0x40,0x40,0x00,0x02,0x02,0x01,0x0F,0x01,0x02,0x02,0x00,/* * 10 */
+    0x00,0x00,0x00,0xF0,0x00,0x00,0x00,0x00,0x01,0x01,0x01,0x1F,0x01,0x01,0x01,0x00,/* +  11 */
+    0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x80,0xB0,0x70,0x00,0x00,0x00,0x00,0x00,/* ,  12 */
+    0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x01,0x01,0x01,0x01,0x01,0x01,/* -  13 */
+    0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x30,0x30,0x00,0x00,0x00,0x00,0x00,/* .  14 */
+    0x00,0x00,0x00,0x00,0x80,0x60,0x18,0x04,0x00,0x60,0x18,0x06,0x01,0x00,0x00,0x00,/* /  15 */
+    0x00,0xE0,0x10,0x08,0x08,0x10,0xE0,0x00,0x00,0x0F,0x10,0x20,0x20,0x10,0x0F,0x00,/* 0  16 */
+    0x00,0x10,0x10,0xF8,0x00,0x00,0x00,0x00,0x00,0x20,0x20,0x3F,0x20,0x20,0x00,0x00,/* 1  17 */
+    0x00,0x70,0x08,0x08,0x08,0x88,0x70,0x00,0x00,0x30,0x28,0x24,0x22,0x21,0x30,0x00,/* 2  18 */
+    0x00,0x30,0x08,0x88,0x88,0x48,0x30,0x00,0x00,0x18,0x20,0x20,0x20,0x11,0x0E,0x00,/* 3  19 */
+    0x00,0x00,0xC0,0x20,0x10,0xF8,0x00,0x00,0x00,0x07,0x04,0x24,0x24,0x3F,0x24,0x00,/* 4  20 */
+    0x00,0xF8,0x08,0x88,0x88,0x08,0x08,0x00,0x00,0x19,0x21,0x20,0x20,0x11,0x0E,0x00,/* 5  21 */
+    0x00,0xE0,0x10,0x88,0x88,0x18,0x00,0x00,0x00,0x0F,0x11,0x20,0x20,0x11,0x0E,0x00,/* 6  22 */
+    0x00,0x38,0x08,0x08,0xC8,0x38,0x08,0x00,0x00,0x00,0x00,0x3F,0x00,0x00,0x00,0x00,/* 7  23 */
+    0x00,0x70,0x88,0x08,0x08,0x88,0x70,0x00,0x00,0x1C,0x22,0x21,0x21,0x22,0x1C,0x00,/* 8  24 */
+    0x00,0xE0,0x10,0x08,0x08,0x10,0xE0,0x00,0x00,0x00,0x31,0x22,0x22,0x11,0x0F,0x00,/* 9  25 */
+    0x00,0x00,0x00,0xC0,0xC0,0x00,0x00,0x00,0x00,0x00,0x00,0x30,0x30,0x00,0x00,0x00,/* :  26 */
+    0x00,0x00,0x00,0x80,0x00,0x00,0x00,0x00,0x00,0x00,0x80,0x60,0x00,0x00,0x00,0x00,/* ;  27 */
+    0x00,0x00,0x80,0x40,0x20,0x10,0x08,0x00,0x00,0x01,0x02,0x04,0x08,0x10,0x20,0x00,/* <  28 */
+    0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x00,0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x00,/* =  29 */
+    0x00,0x08,0x10,0x20,0x40,0x80,0x00,0x00,0x00,0x20,0x10,0x08,0x04,0x02,0x01,0x00,/* >  30 */
+    0x00,0x70,0x48,0x08,0x08,0x08,0xF0,0x00,0x00,0x00,0x00,0x30,0x36,0x01,0x00,0x00,/* ?  31 */
+    0xC0,0x30,0xC8,0x28,0xE8,0x10,0xE0,0x00,0x07,0x18,0x27,0x24,0x23,0x14,0x0B,0x00,/* @  32 */
+    0x00,0x00,0xC0,0x38,0xE0,0x00,0x00,0x00,0x20,0x3C,0x23,0x02,0x02,0x27,0x38,0x20,/* A  33 */
+    0x08,0xF8,0x88,0x88,0x88,0x70,0x00,0x00,0x20,0x3F,0x20,0x20,0x20,0x11,0x0E,0x00,/* B  34 */
+    0xC0,0x30,0x08,0x08,0x08,0x08,0x38,0x00,0x07,0x18,0x20,0x20,0x20,0x10,0x08,0x00,/* C  35 */
+    0x08,0xF8,0x08,0x08,0x08,0x10,0xE0,0x00,0x20,0x3F,0x20,0x20,0x20,0x10,0x0F,0x00,/* D  36 */
+    0x08,0xF8,0x88,0x88,0xE8,0x08,0x10,0x00,0x20,0x3F,0x20,0x20,0x23,0x20,0x18,0x00,/* E  37 */
+    0x08,0xF8,0x88,0x88,0xE8,0x08,0x10,0x00,0x20,0x3F,0x20,0x00,0x03,0x00,0x00,0x00,/* F  38 */
+    0xC0,0x30,0x08,0x08,0x08,0x38,0x00,0x00,0x07,0x18,0x20,0x20,0x22,0x1E,0x02,0x00,/* G  39 */
+    0x08,0xF8,0x08,0x00,0x00,0x08,0xF8,0x08,0x20,0x3F,0x21,0x01,0x01,0x21,0x3F,0x20,/* H  40 */
+    0x00,0x08,0x08,0xF8,0x08,0x08,0x00,0x00,0x00,0x20,0x20,0x3F,0x20,0x20,0x00,0x00,/* I  41 */
+    0x00,0x00,0x08,0x08,0xF8,0x08,0x08,0x00,0xC0,0x80,0x80,0x80,0x7F,0x00,0x00,0x00,/* J  42 */
+    0x08,0xF8,0x88,0xC0,0x28,0x18,0x08,0x00,0x20,0x3F,0x20,0x01,0x26,0x38,0x20,0x00,/* K  43 */
+    0x08,0xF8,0x08,0x00,0x00,0x00,0x00,0x00,0x20,0x3F,0x20,0x20,0x20,0x20,0x30,0x00,/* L  44 */
+    0x08,0xF8,0xF8,0x00,0xF8,0xF8,0x08,0x00,0x20,0x3F,0x00,0x3F,0x00,0x3F,0x20,0x00,/* M  45 */
+    0x08,0xF8,0x30,0xC0,0x00,0x08,0xF8,0x08,0x20,0x3F,0x20,0x00,0x07,0x18,0x3F,0x00,/* N  46 */
+    0xE0,0x10,0x08,0x08,0x08,0x10,0xE0,0x00,0x0F,0x10,0x20,0x20,0x20,0x10,0x0F,0x00,/* O  47 */
+    0x08,0xF8,0x08,0x08,0x08,0x08,0xF0,0x00,0x20,0x3F,0x21,0x01,0x01,0x01,0x00,0x00,/* P  48 */
+    0xE0,0x10,0x08,0x08,0x08,0x10,0xE0,0x00,0x0F,0x18,0x24,0x24,0x38,0x50,0x4F,0x00,/* Q  49 */
+    0x08,0xF8,0x88,0x88,0x88,0x88,0x70,0x00,0x20,0x3F,0x20,0x00,0x03,0x0C,0x30,0x20,/* R  50 */
+    0x00,0x70,0x88,0x08,0x08,0x08,0x38,0x00,0x00,0x38,0x20,0x21,0x21,0x22,0x1C,0x00,/* S  51 */
+    0x18,0x08,0x08,0xF8,0x08,0x08,0x18,0x00,0x00,0x00,0x20,0x3F,0x20,0x00,0x00,0x00,/* T  52 */
+    0x08,0xF8,0x08,0x00,0x00,0x08,0xF8,0x08,0x00,0x1F,0x20,0x20,0x20,0x20,0x1F,0x00,/* U  53 */
+    0x08,0x78,0x88,0x00,0x00,0xC8,0x38,0x08,0x00,0x00,0x07,0x38,0x0E,0x01,0x00,0x00,/* V  54 */
+    0xF8,0x08,0x00,0xF8,0x00,0x08,0xF8,0x00,0x03,0x3C,0x07,0x00,0x07,0x3C,0x03,0x00,/* W  55 */
+    0x08,0x18,0x68,0x80,0x80,0x68,0x18,0x08,0x20,0x30,0x2C,0x03,0x03,0x2C,0x30,0x20,/* X  56 */
+    0x08,0x38,0xC8,0x00,0xC8,0x38,0x08,0x00,0x00,0x00,0x20,0x3F,0x20,0x00,0x00,0x00,/* Y  57 */
+    0x10,0x08,0x08,0x08,0xC8,0x38,0x08,0x00,0x20,0x38,0x26,0x21,0x20,0x20,0x18,0x00,/* Z  58 */
+    0x00,0x00,0x00,0xFE,0x02,0x02,0x02,0x00,0x00,0x00,0x00,0x7F,0x40,0x40,0x40,0x00,/* [  59 */
+    0x00,0x0C,0x30,0xC0,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x06,0x38,0xC0,0x00,/* \  60 */
+    0x00,0x02,0x02,0x02,0xFE,0x00,0x00,0x00,0x00,0x40,0x40,0x40,0x7F,0x00,0x00,0x00,/* ]  61 */
+    0x00,0x00,0x04,0x02,0x02,0x02,0x04,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,/* ^  62 */
+    0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,/* _  63 */
+    0x00,0x02,0x02,0x04,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,/* `  64 */
+    0x00,0x00,0x80,0x80,0x80,0x80,0x00,0x00,0x00,0x19,0x24,0x22,0x22,0x22,0x3F,0x20,/* a  65 */
+    0x08,0xF8,0x00,0x80,0x80,0x00,0x00,0x00,0x00,0x3F,0x11,0x20,0x20,0x11,0x0E,0x00,/* b  66 */
+    0x00,0x00,0x00,0x80,0x80,0x80,0x00,0x00,0x00,0x0E,0x11,0x20,0x20,0x20,0x11,0x00,/* c  67 */
+    0x00,0x00,0x00,0x80,0x80,0x88,0xF8,0x00,0x00,0x0E,0x11,0x20,0x20,0x10,0x3F,0x20,/* d  68 */
+    0x00,0x00,0x80,0x80,0x80,0x80,0x00,0x00,0x00,0x1F,0x22,0x22,0x22,0x22,0x13,0x00,/* e  69 */
+    0x00,0x80,0x80,0xF0,0x88,0x88,0x88,0x18,0x00,0x20,0x20,0x3F,0x20,0x20,0x00,0x00,/* f  70 */
+    0x00,0x00,0x80,0x80,0x80,0x80,0x80,0x00,0x00,0x6B,0x94,0x94,0x94,0x93,0x60,0x00,/* g  71 */
+    0x08,0xF8,0x00,0x80,0x80,0x80,0x00,0x00,0x20,0x3F,0x21,0x00,0x00,0x20,0x3F,0x20,/* h  72 */
+    0x00,0x80,0x98,0x98,0x00,0x00,0x00,0x00,0x00,0x20,0x20,0x3F,0x20,0x20,0x00,0x00,/* i  73 */
+    0x00,0x00,0x00,0x80,0x98,0x98,0x00,0x00,0x00,0xC0,0x80,0x80,0x80,0x7F,0x00,0x00,/* j  74 */
+    0x08,0xF8,0x00,0x00,0x80,0x80,0x80,0x00,0x20,0x3F,0x24,0x02,0x2D,0x30,0x20,0x00,/* k  75 */
+    0x00,0x08,0x08,0xF8,0x00,0x00,0x00,0x00,0x00,0x20,0x20,0x3F,0x20,0x20,0x00,0x00,/* l  76 */
+    0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x00,0x20,0x3F,0x20,0x00,0x3F,0x20,0x00,0x3F,/* m  77 */
+    0x80,0x80,0x00,0x80,0x80,0x80,0x00,0x00,0x20,0x3F,0x21,0x00,0x00,0x20,0x3F,0x20,/* n  78 */
+    0x00,0x00,0x80,0x80,0x80,0x80,0x00,0x00,0x00,0x1F,0x20,0x20,0x20,0x20,0x1F,0x00,/* o  79 */
+    0x80,0x80,0x00,0x80,0x80,0x00,0x00,0x00,0x80,0xFF,0xA1,0x20,0x20,0x11,0x0E,0x00,/* p  80 */
+    0x00,0x00,0x00,0x80,0x80,0x80,0x80,0x00,0x00,0x0E,0x11,0x20,0x20,0xA0,0xFF,0x80,/* q  81 */
+    0x80,0x80,0x80,0x00,0x80,0x80,0x80,0x00,0x20,0x20,0x3F,0x21,0x20,0x00,0x01,0x00,/* r  82 */
+    0x00,0x00,0x80,0x80,0x80,0x80,0x80,0x00,0x00,0x33,0x24,0x24,0x24,0x24,0x19,0x00,/* s  83 */
+    0x00,0x80,0x80,0xE0,0x80,0x80,0x00,0x00,0x00,0x00,0x00,0x1F,0x20,0x20,0x00,0x00,/* t  84 */
+    0x80,0x80,0x00,0x00,0x00,0x80,0x80,0x00,0x00,0x1F,0x20,0x20,0x20,0x10,0x3F,0x20,/* u  85 */
+    0x80,0x80,0x80,0x00,0x00,0x80,0x80,0x80,0x00,0x01,0x0E,0x30,0x08,0x06,0x01,0x00,/* v  86 */
+    0x80,0x80,0x00,0x80,0x00,0x80,0x80,0x80,0x0F,0x30,0x0C,0x03,0x0C,0x30,0x0F,0x00,/* w  87 */
+    0x00,0x80,0x80,0x00,0x80,0x80,0x80,0x00,0x00,0x20,0x31,0x2E,0x0E,0x31,0x20,0x00,/* x  88 */
+    0x80,0x80,0x80,0x00,0x00,0x80,0x80,0x80,0x80,0x81,0x8E,0x70,0x18,0x06,0x01,0x00,/* y  89 */
+    0x00,0x80,0x80,0x80,0x80,0x80,0x80,0x00,0x00,0x21,0x30,0x2C,0x22,0x21,0x30,0x00,/* z  90 */
+    0x00,0x00,0x00,0x00,0x80,0x7C,0x02,0x02,0x00,0x00,0x00,0x00,0x00,0x3F,0x40,0x40,/* {  91 */
+    0x00,0x00,0x00,0x00,0xFF,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xFF,0x00,0x00,0x00,/* |  92 */
+    0x00,0x02,0x02,0x7C,0x80,0x00,0x00,0x00,0x00,0x40,0x40,0x3F,0x00,0x00,0x00,0x00,/* }  93 */
+    0x00,0x06,0x01,0x01,0x02,0x02,0x04,0x04,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,/* ~  94 */
+};
+
+/*+--------------------------------+
+ *|      BMP Logo for 128x32       |
+ *+--------------------------------+*/
+
+/* lingyun-128x32.bmp 使用 PCtoLCD.exe 取模软件生成配置(图形模式): 阴码、列行式、逆向、十六进制数、C51格式  */
+const uint8_t bmp_logo[] = {
+    0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+    0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+    0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+    0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+    0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+    0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+    0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x03,0x03,0xFF,0xFF,0x03,0x03,0x00,0x00,
+    0x00,0xFC,0xFE,0x87,0x03,0x03,0x87,0xFE,0xFC,0x00,0x03,0x03,0xFF,0xFF,0x03,0x03,
+    0x00,0x00,0xF8,0xF8,0xF8,0xF8,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+    0x78,0x78,0x78,0x78,0x00,0x00,0x00,0x80,0xC0,0xE0,0xF0,0xF8,0xF8,0x78,0x78,0x78,
+    0xF8,0xF8,0xF8,0xF0,0xE0,0x80,0x00,0x00,0x00,0x80,0x80,0xE0,0xE0,0xF0,0xF0,0x78,
+    0x78,0x78,0x78,0x78,0x78,0xF0,0xF0,0xE0,0xE0,0xC0,0x80,0x00,0x00,0xF0,0xF0,0xF0,
+    0xF0,0x00,0x00,0x00,0x00,0x00,0x00,0xF0,0xF0,0xF0,0xF0,0xF0,0x00,0x00,0xF0,0xF0,
+    0xF0,0xF0,0xF0,0x00,0x00,0x00,0x00,0x00,0x00,0xF0,0xF0,0xF0,0xF0,0x00,0x00,0x00,
+    0xC0,0xE0,0xF0,0xF8,0xF8,0xF8,0xF8,0xF8,0xFB,0xFB,0xF3,0xF3,0xE3,0xC3,0x00,0x03,
+    0x03,0x00,0x01,0x03,0x03,0x03,0x03,0x01,0x00,0x03,0x03,0x00,0x03,0x03,0x00,0x00,
+    0x00,0x00,0xFF,0xFF,0xFF,0xFF,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+    0xFF,0xFF,0xFF,0xFF,0x00,0x00,0x00,0xFF,0xFF,0xFF,0xFF,0x01,0x00,0x00,0x00,0x00,
+    0x00,0x01,0xFF,0xFF,0xFF,0xFF,0x00,0x00,0xFF,0xFF,0xFF,0xFF,0x07,0x03,0x00,0x00,
+    0x00,0x00,0xE0,0xE0,0xE0,0xE0,0xE1,0xE3,0xE3,0xE3,0xE3,0xE0,0x00,0x1F,0x3F,0x7F,
+    0xFF,0xFC,0xF0,0xE0,0xF0,0xF0,0xF8,0xFF,0xFF,0xFF,0x3F,0x1F,0x00,0x00,0xFF,0xFF,
+    0xFF,0xFF,0xFF,0x00,0x00,0x00,0x00,0x00,0x00,0xFF,0xFF,0xFF,0xFF,0x00,0x00,0x00,
+    0xFF,0xFF,0xFF,0xFF,0x00,0x00,0x00,0x00,0x00,0x00,0xFF,0xFF,0xFF,0xFF,0x00,0x00,
+    0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+    0x00,0x00,0x7F,0x7F,0x7F,0x7F,0x70,0x70,0x70,0x70,0x70,0x70,0x70,0x70,0x00,0x00,
+    0x7F,0x7F,0x7F,0x7F,0x00,0x00,0x00,0x7F,0x7F,0x7F,0x7F,0x00,0x00,0x00,0x00,0x00,
+    0x00,0x00,0x7F,0x7F,0x7F,0x7F,0x00,0x00,0x03,0x0F,0x1F,0x3F,0x7E,0x7C,0x78,0x70,
+    0x70,0x60,0x61,0x71,0x71,0x7F,0x7F,0x7F,0x3F,0x1F,0x07,0x03,0x00,0x00,0x00,0x00,
+    0x00,0x01,0x7F,0x7F,0x7F,0x7F,0x7F,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x0F,0x1F,
+    0x3F,0x7F,0x7F,0x78,0x70,0x70,0x70,0x70,0x78,0x7F,0x3F,0x1F,0x0F,0x00,0x00,0x00,
+    0x7F,0x7F,0x7F,0x7F,0x00,0x00,0x00,0x00,0x00,0x00,0x7F,0x7F,0x7F,0x7F,0x00,0x00,
+    0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00
+};
+
+/*+--------------------------------+
+ *|   Chinese Font 16x16 SongTi    |
+ *+--------------------------------+*/
+
+/* 汉字生成使用 PCtoLCD.exe(字符模式):宋体、16x16、阴码、列行式、逆向、十六进制数、C51格式
+ * 每个汉字 32 字节 (16x16 点阵, 上下各 16 字节分属两页)
+ */
+
+/*  宋体 16X16:  凌云实验室 */
+const uint8_t Hzk_LingYun[][HZK_CHAR_BYTES]=
+{
+    {0x00,0x02,0x0C,0xC0,0x00,0x20,0xA4,0x64,0xA4,0x3F,0x24,0x64,0xA4,0x20,0x20,0x00},
+    {0x02,0x02,0x7F,0x00,0x80,0x89,0x44,0x46,0x2B,0x12,0x2A,0x46,0x80,0x81,0x00,0x00},/*"凌",0*/
+
+    {0x40,0x40,0x42,0x42,0x42,0x42,0xC2,0x42,0x42,0x42,0x42,0x42,0x42,0x40,0x40,0x00},
+    {0x00,0x20,0x70,0x28,0x24,0x23,0x20,0x20,0x20,0x24,0x28,0x30,0xE0,0x00,0x00,0x00},/*"云",1*/
+
+    {0x10,0x0C,0x04,0x84,0x14,0x64,0x05,0x06,0xF4,0x04,0x04,0x04,0x04,0x14,0x0C,0x00},
+    {0x04,0x84,0x84,0x44,0x47,0x24,0x14,0x0C,0x07,0x0C,0x14,0x24,0x44,0x84,0x04,0x00},/*"实",2*/
+
+    {0x02,0xFA,0x82,0x82,0xFE,0x80,0x40,0x20,0x50,0x4C,0x43,0x4C,0x50,0x20,0x40,0x00},
+    {0x08,0x18,0x48,0x84,0x44,0x3F,0x40,0x44,0x58,0x41,0x4E,0x60,0x58,0x47,0x40,0x00},/*"验",3*/
+
+    {0x10,0x0C,0x24,0x24,0xA4,0x64,0x25,0x26,0x24,0x24,0xA4,0x24,0x24,0x14,0x0C,0x00},
+    {0x40,0x40,0x48,0x49,0x49,0x49,0x49,0x7F,0x49,0x49,0x49,0x4B,0x48,0x40,0x40,0x00},/*"室",4*/
+};
+
+/* 宋体 16x16: 温度 */
+const uint8_t Hzk_Temp[][HZK_CHAR_BYTES]=
+{
+    {0x10,0x60,0x02,0x8C,0x00,0x00,0xFE,0x92,0x92,0x92,0x92,0x92,0xFE,0x00,0x00,0x00},
+    {0x04,0x04,0x7E,0x01,0x40,0x7E,0x42,0x42,0x7E,0x42,0x7E,0x42,0x42,0x7E,0x40,0x00},/*"温",0*/
+
+    {0x00,0x00,0xFC,0x24,0x24,0x24,0xFC,0x25,0x26,0x24,0xFC,0x24,0x24,0x24,0x04,0x00},
+    {0x40,0x30,0x8F,0x80,0x84,0x4C,0x55,0x25,0x25,0x25,0x55,0x4C,0x80,0x80,0x80,0x00},/*"度",1*/
+};
+
+/* 宋体 16x16: 湿度 */
+const uint8_t Hzk_Humd[][HZK_CHAR_BYTES]=
+{
+    {0x10,0x60,0x02,0x8C,0x00,0xFE,0x92,0x92,0x92,0x92,0x92,0x92,0xFE,0x00,0x00,0x00},
+    {0x04,0x04,0x7E,0x01,0x44,0x48,0x50,0x7F,0x40,0x40,0x7F,0x50,0x48,0x44,0x40,0x00},/*"湿",0*/
+
+    {0x00,0x00,0xFC,0x24,0x24,0x24,0xFC,0x25,0x26,0x24,0xFC,0x24,0x24,0x24,0x04,0x00},
+    {0x40,0x30,0x8F,0x80,0x84,0x4C,0x55,0x25,0x25,0x25,0x55,0x4C,0x80,0x80,0x80,0x00},/*"度",1*/
+};
+
+/* 宋体 16X16: 光强 */
+const uint8_t Hzk_Light[][HZK_CHAR_BYTES]=
+{
+    {0x40,0x40,0x42,0x44,0x58,0xC0,0x40,0x7F,0x40,0xC0,0x50,0x48,0x46,0x40,0x40,0x00},
+    {0x80,0x80,0x40,0x20,0x18,0x07,0x00,0x00,0x00,0x3F,0x40,0x40,0x40,0x40,0x78,0x00},/*"光",0*/
+
+    {0x02,0xE2,0x22,0x22,0x3E,0x00,0x80,0x9E,0x92,0x92,0xF2,0x92,0x92,0x9E,0x80,0x00},
+    {0x00,0x43,0x82,0x42,0x3E,0x40,0x47,0x44,0x44,0x44,0x7F,0x44,0x44,0x54,0xE7,0x00},/*"强",1*/
+};
+
+/* 宋体 16X16:  声音 */
+const uint8_t Hzk_Noisy[][HZK_CHAR_BYTES]=
+{
+    {0x04,0x14,0xD4,0x54,0x54,0x54,0x54,0xDF,0x54,0x54,0x54,0x54,0xD4,0x14,0x04,0x00},
+    {0x80,0x60,0x1F,0x02,0x02,0x02,0x02,0x03,0x02,0x02,0x02,0x02,0x03,0x00,0x00,0x00},/*"声",0*/
+
+    {0x40,0x40,0x44,0x44,0x54,0x64,0x45,0x46,0x44,0x64,0x54,0x44,0x44,0x40,0x40,0x00},
+    {0x00,0x00,0x00,0xFF,0x49,0x49,0x49,0x49,0x49,0x49,0x49,0xFF,0x00,0x00,0x00,0x00},/*"音",1*/
+};
diff --git a/ex2.i2c-bitbang/Board/oled_font.h b/ex2.i2c-bitbang/Board/oled_font.h
new file mode 100644
index 0000000..7439ff2
--- /dev/null
+++ b/ex2.i2c-bitbang/Board/oled_font.h
@@ -0,0 +1,64 @@
+/**********************************************************************
+*   Copyright: (C)2021 LingYun IoT System Studio <www.weike-iot.com>
+*      Author: GuoWenxue<guowenxue@gmail.com> QQ: 281143292
+* Description: ISKBoard OLED font data (PCtoLCD.exe generated)
+*
+*   ChangeLog:
+*        Version    Date       Author            Description
+*        V1.0.0  2021.08.10  GuoWenxue      Release initial version
+***********************************************************************/
+
+#ifndef __OLED_FONT_H
+#define __OLED_FONT_H
+
+#include <stdint.h>
+
+/*+--------------------------------+
+ *|      ASCII font size 6x8       |
+ *+--------------------------------+*/
+#define F6x8_COUNT  95
+extern const uint8_t F6x8[][6];
+
+/*+--------------------------------+
+ *|      ASCII font size 8x16      |
+ *+--------------------------------+*/
+#define F8X16_CHAR_BYTES  16
+#define F8X16_COUNT       95
+extern const uint8_t F8X16[];
+
+/*+--------------------------------+
+ *|      BMP Logo for 128x32       |
+ *+--------------------------------+*/
+#define BMP_LOGO_WIDTH   128
+#define BMP_LOGO_HEIGHT  32
+#define BMP_LOGO_SIZE    (BMP_LOGO_WIDTH * BMP_LOGO_HEIGHT / 8)
+extern const uint8_t bmp_logo[];
+
+/*+--------------------------------+
+ *|   Chinese Font 16x16 SongTi    |
+ *+--------------------------------+*/
+
+/* 每个汉字 32 字节 (16x16 点阵, 上下各 16 字节分属两页) */
+#define HZK_CHAR_BYTES  32
+
+/* 宋体 16X16: 凌云实验室 */
+#define HZK_LEN_LINGYUN         5
+extern const uint8_t Hzk_LingYun[][HZK_CHAR_BYTES];
+
+/* 宋体 16x16: 温度 */
+#define HZK_LEN_TEMP            2
+extern const uint8_t Hzk_Temp[][HZK_CHAR_BYTES];
+
+/* 宋体 16x16: 湿度 */
+#define HZK_LEN_HUMD            2
+extern const uint8_t Hzk_Humd[][HZK_CHAR_BYTES];
+
+/* 宋体 16X16: 光强 */
+#define HZK_LEN_LIGHT            2
+extern const uint8_t Hzk_Light[][HZK_CHAR_BYTES];
+
+/* 宋体 16X16:  声音 */
+#define HZK_LEN_NOISY            2
+extern const uint8_t Hzk_Noisy[][HZK_CHAR_BYTES];
+
+#endif /* __OLED_FONT_H */
diff --git a/ex2.i2c-bitbang/Board/sht20.c b/ex2.i2c-bitbang/Board/sht20.c
new file mode 100644
index 0000000..5352041
--- /dev/null
+++ b/ex2.i2c-bitbang/Board/sht20.c
@@ -0,0 +1,226 @@
+/**********************************************************************
+ *   Copyright: (C)2024 LingYun IoT System Studio
+ *      Author: GuoWenxue<guowenxue@gmail.com>
+ *
+ * Description: SHT20 Temperature & humidity sensor driver on ISKBoard
+ *
+ *   ChangeLog:
+ *        Version    Date       Author            Description
+ *        V1.0.0  2024.08.29    GuoWenxue      Release initial version
+ *
+ ***********************************************************************/
+
+#include <stdio.h>
+#include "sht20.h"
+#include "miscdev.h" /* udelay() / mdelay() */
+
+/* SHT20 7位标准 I2C 从机地址 */
+#define SHT20_I2C_ADDR          0x40
+
+/* SHT20 控制指令 (No Hold Master) */
+#define SHT20_CMD_TRIG_T        0xF3  /* 触发温度测量 (非阻塞) */
+#define SHT20_CMD_TRIG_RH       0xF5  /* 触发湿度测量 (非阻塞) */
+#define SHT20_CMD_SOFT_RESET    0xFE  /* 软件复位 */
+
+/* I2C 设备 SHT20 */
+static     i2c_dev_t sht20;
+
+/* 前置静态辅助函数声明 */
+static uint8_t sht20_check_crc(uint8_t *data, uint8_t nbytes, uint8_t checksum);
+i2c_status_t sht20_read_value(i2c_dev_t *sht20, uint8_t cmd, float *value);
+
+/**
+ * @brief  SHT20 初始化并软复位芯片
+ * @param  bus: 绑定的 I2C 物理总线指针
+ * @retval 0: 成功; -1: 失败
+ */
+int sht20_init(i2c_bus_t *bus)
+{
+    uint8_t cmd = SHT20_CMD_SOFT_RESET;
+    int rv = 0;
+
+    if (!bus)
+    {
+        return -1;
+    }
+
+    mdelay(20);
+
+    /* 绑定设备及其设备号到 I2C 总线上 */
+    i2c_bind_dev(&sht20, bus, SHT20_I2C_ADDR);
+
+    /* 将I2C总线加锁让设备独占总线 */
+    i2c_lock_dev(&sht20);
+
+    /* 向 SHT20 发送软件复位指令,并等待复位完成 */
+    i2c_write_no_reg(sht20.bus, sht20.dev_addr, &cmd, 1);
+    mdelay(20);
+
+    /* 释放设备的 I2C 总线锁 */
+    i2c_free_dev(&sht20);
+    return rv;
+}
+
+/**
+ * @brief  应用层温湿度采样接口函数 (带总线锁)
+ * @param  temperature: 存放输出温度结果的指针
+ * @param  humidity:    存放输出湿度结果的指针
+ * @retval 0: 采样成功且CRC通过; -1: 采样或校验失败
+ */
+int sht20_sample_TrH(float *temperature, float *humidity)
+{
+    int rv = -1;
+
+    if( !temperature || !humidity )
+    {
+        return -1;
+    }
+
+    /* 将I2C总线加锁让设备独占总线 */
+    i2c_lock_dev(&sht20);
+
+    /* 2. 采集温度 */
+    if (sht20_read_value(&sht20, SHT20_CMD_TRIG_T, temperature) != I2C_OK)
+    {
+        goto cleanup;
+    }
+
+    /* 3. 采集湿度 */
+    if (sht20_read_value(&sht20, SHT20_CMD_TRIG_RH, humidity) != I2C_OK)
+    {
+        goto cleanup;
+    }
+
+    rv = 0;
+
+cleanup:
+    /* 4. 释放总线并解锁 */
+    i2c_free_dev(&sht20);
+    return rv;
+}
+
+/**
+ * @brief  SHT20 硬件标准 CRC-8 校验
+ * @note   多项式 P(x) = x^8 + x^5 + x^4 + 1 = 1 0011 0001 = 0x31
+ * @param  data: 待校验的数据缓冲区指针 (含 High Byte 和 Low Byte)
+ * @param  nbytes: 校验的数据长度 (固定为 2 字节)
+ * @param  checksum: SHT20 芯片硬件返回的 CRC 结果字节
+ * @retval 0: 校验通过; 其余值: 校验失败
+ */
+static uint8_t sht20_check_crc(uint8_t *data, uint8_t nbytes, uint8_t checksum)
+{
+    uint8_t crc = 0x00; /* 初始值为 0 */
+    uint8_t byte_ctr;
+    uint8_t bit;
+
+    /* 逐个字节计算 */
+    for (byte_ctr = 0; byte_ctr < nbytes; ++byte_ctr)
+    {
+        crc ^= (data[byte_ctr]);
+
+        /* 按位进行模二除法 */
+        for (bit = 8; bit > 0; --bit)
+        {
+            if (crc & 0x80)
+            {
+                crc = (uint8_t)((crc << 1) ^ 0x31);
+            }
+            else
+            {
+                crc = (uint8_t)(crc << 1);
+            }
+        }
+    }
+
+    if (crc != checksum)
+    {
+        return 1; /* 校验不匹配 */
+    }
+
+    return 0; /* 成功 */
+}
+
+/**
+ * @brief  读取温湿度的底层通用核心函数 (加入完备的 CRC-8 硬件校验)
+ */
+i2c_status_t sht20_read_value(i2c_dev_t *dev, uint8_t cmd, float *value)
+{
+    uint8_t raw_data[2];
+    uint8_t crc_byte;
+    uint8_t rx_buf[3];
+    uint16_t raw_result;
+    uint32_t poll_timeout = 2000;
+    i2c_status_t status;
+
+    if ( !value )
+    {
+        return I2C_ERROR;
+    }
+
+    /* 1. 发送触发测量命令 (无寄存器写模式) */
+    status = i2c_write_no_reg(dev->bus, dev->dev_addr, &cmd, 1);
+    if (status != I2C_OK)
+    {
+        return I2C_ERROR;
+    }
+
+    /* 根据测量指令进行预留的 ADC 硬件转换安全延时 */
+    if (cmd == SHT20_CMD_TRIG_T)
+    {
+        mdelay(65);
+    }
+    else
+    {
+        mdelay(25);
+    }
+
+    /* 2. 利用高级读 API 的内部寻址应答机制等待芯片转换完成 */
+    while (poll_timeout--)
+    {
+        /* SHT20 在进行 ADC 转换时,它会对读模式寻址返回 NACK。
+         * i2c_read_no_reg() 发现地址无应答,会自动发 Stop 并返回错误。
+         * 我们直接利用这一机制作为非阻塞就绪判断!
+         */
+        status = i2c_read_no_reg(dev->bus, dev->dev_addr, rx_buf, 3);
+        if (status == I2C_OK)
+        {
+            /* 读取成功,3 字节(高、低、CRC)*/
+            break;
+        }
+
+        udelay(100);
+    }
+
+    if (poll_timeout == 0)
+    {
+        return I2C_ERROR;
+    }
+
+    /* 3. 分离出读回的 High Byte, Low Byte 以及 CRC 校验码 */
+    raw_data[0] = rx_buf[0];
+    raw_data[1] = rx_buf[1];
+    crc_byte    = rx_buf[2];
+
+    /* 4. 执行 CRC-8 校验 */
+    if (sht20_check_crc(raw_data, 2, crc_byte) != 0)
+    {
+        printf("SHT20 CRC Check Failed!\r\n");
+        return I2C_ERROR;
+    }
+
+    /* 5. 清除低 2 位状态位 */
+    raw_result = (uint16_t)((raw_data[0] << 8) | raw_data[1]);
+    raw_result &= ~0x0003;
+
+    /* 6. 代入物理公式转换 */
+    if (cmd == SHT20_CMD_TRIG_T)
+    {
+        *value = -46.85f + 175.72f * ((float)raw_result / 65536.0f);
+    }
+    else
+    {
+        *value = -6.0f + 125.0f * ((float)raw_result / 65536.0f);
+    }
+
+    return I2C_OK;
+}
diff --git a/ex2.i2c-bitbang/Board/sht20.h b/ex2.i2c-bitbang/Board/sht20.h
new file mode 100644
index 0000000..0968ded
--- /dev/null
+++ b/ex2.i2c-bitbang/Board/sht20.h
@@ -0,0 +1,33 @@
+/**********************************************************************
+ *   Copyright: (C)2024 LingYun IoT System Studio
+ *      Author: GuoWenxue<guowenxue@gmail.com>
+ *
+ * Description: SHT20 Temperature & humidity sensor driver on ISKBoard
+ *
+ *   ChangeLog:
+ *        Version    Date       Author            Description
+ *        V1.0.0  2024.08.29    GuoWenxue      Release initial version
+ *
+ ***********************************************************************/
+
+#ifndef __SHT20_H
+#define __SHT20_H
+
+#include "i2c_bitbang.h"
+
+/**
+ * @brief  SHT20 初始化并软复位芯片
+ * @param  bus: 绑定的 I2C 物理总线指针
+ * @retval 0: 成功; -1: 失败
+ */
+extern int sht20_init(i2c_bus_t *bus);
+
+/**
+ * @brief  应用层温湿度采样接口函数
+ * @param  temperature: 存放输出温度结果的指针
+ * @param  humidity:    存放输出湿度结果的指针
+ * @retval 0: 采样成功且CRC通过; -1: 采样或校验失败
+ */
+extern int sht20_sample_TrH(float *temperature, float *humidity);
+
+#endif
diff --git a/ex2.i2c-bitbang/CMakeLists.txt b/ex2.i2c-bitbang/CMakeLists.txt
new file mode 100644
index 0000000..2c732da
--- /dev/null
+++ b/ex2.i2c-bitbang/CMakeLists.txt
@@ -0,0 +1,75 @@
+cmake_minimum_required(VERSION 3.22)
+
+#
+# This file is generated only once,
+# and is not re-generated if converter is called multiple times.
+#
+# User is free to modify the file as much as necessary
+#
+
+# Setup compiler settings
+set(CMAKE_C_STANDARD 11)
+set(CMAKE_C_STANDARD_REQUIRED ON)
+set(CMAKE_C_EXTENSIONS ON)
+
+
+# Define the build type
+if(NOT CMAKE_BUILD_TYPE)
+    set(CMAKE_BUILD_TYPE "Debug")
+endif()
+
+# Set the project name
+set(CMAKE_PROJECT_NAME ISKBoard)
+
+# Enable compile command to ease indexing with e.g. clangd
+set(CMAKE_EXPORT_COMPILE_COMMANDS TRUE)
+
+# Core project settings
+project(${CMAKE_PROJECT_NAME})
+message("Build type: " ${CMAKE_BUILD_TYPE})
+
+# Enable CMake support for ASM and C languages
+enable_language(C ASM)
+
+# Create an executable object type
+add_executable(${CMAKE_PROJECT_NAME})
+
+# Add STM32CubeMX generated sources
+add_subdirectory(cmake/stm32cubemx)
+
+# Add our custom module subdirectories here
+add_subdirectory(Board)
+
+# Link directories setup
+target_link_directories(${CMAKE_PROJECT_NAME} PRIVATE
+    # Add user defined library search paths
+)
+
+# Add sources to executable
+target_sources(${CMAKE_PROJECT_NAME} PRIVATE
+    # Add user sources here
+)
+
+# Add include paths
+target_include_directories(${CMAKE_PROJECT_NAME} PRIVATE
+    # Add user defined include paths
+)
+
+# Add project symbols (macros)
+target_compile_definitions(${CMAKE_PROJECT_NAME} PRIVATE
+    # Add user defined symbols
+)
+
+# Remove wrong libob.a library dependency when using cpp files
+list(REMOVE_ITEM CMAKE_C_IMPLICIT_LINK_LIBRARIES ob)
+
+# Add linked libraries
+target_link_libraries(${CMAKE_PROJECT_NAME}
+    stm32cubemx
+    board
+
+    # Add user defined libraries
+)
+
+# Setup linker settings
+set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -u _printf_float")
diff --git a/ex2.i2c-bitbang/Core/Src/main.c b/ex2.i2c-bitbang/Core/Src/main.c
new file mode 100644
index 0000000..ec11a87
--- /dev/null
+++ b/ex2.i2c-bitbang/Core/Src/main.c
@@ -0,0 +1,323 @@
+/* USER CODE BEGIN Header */
+/**
+  ******************************************************************************
+  * @file           : main.c
+  * @brief          : Main program body
+  ******************************************************************************
+  * @attention
+  *
+  * Copyright (c) 2026 STMicroelectronics.
+  * All rights reserved.
+  *
+  * This software is licensed under terms that can be found in the LICENSE file
+  * in the root directory of this software component.
+  * If no LICENSE file comes with this software, it is provided AS-IS.
+  *
+  ******************************************************************************
+  */
+/* USER CODE END Header */
+/* Includes ------------------------------------------------------------------*/
+#include "main.h"
+#include "adc.h"
+#include "tim.h"
+#include "usart.h"
+#include "gpio.h"
+
+/* Private includes ----------------------------------------------------------*/
+/* USER CODE BEGIN Includes */
+#include "miscdev.h"
+#include "sht20.h"
+#include "ds18b20.h"
+#include "isl1208.h"
+#include "oled_drv.h"
+#include "oled_font.h"
+/* USER CODE END Includes */
+
+/* Private typedef -----------------------------------------------------------*/
+/* USER CODE BEGIN PTD */
+
+/* USER CODE END PTD */
+
+/* Private define ------------------------------------------------------------*/
+/* USER CODE BEGIN PD */
+
+/* USER CODE END PD */
+
+/* Private macro -------------------------------------------------------------*/
+/* USER CODE BEGIN PM */
+
+/* USER CODE END PM */
+
+/* Private variables ---------------------------------------------------------*/
+
+/* USER CODE BEGIN PV */
+
+/* USER CODE END PV */
+
+/* Private function prototypes -----------------------------------------------*/
+void SystemClock_Config(void);
+/* USER CODE BEGIN PFP */
+
+/* USER CODE END PFP */
+
+/* Private user code ---------------------------------------------------------*/
+/* USER CODE BEGIN 0 */
+
+/**
+ * @brief  OLED显示时间
+ */
+void oled_show_time(void)
+{
+  rtc_time_t  tm;
+  char buf[64];
+
+  /* 获取RTC时间并保存到 buf */
+  isl1208_get_time(&tm);
+  snprintf(buf, sizeof(buf), "20%02d-%02d-%02d %02d:%02d:%02d",
+            tm.year, tm.month, tm.day,
+            tm.hour, tm.minute, tm.second);
+
+  /* 输出显示内容 OLED 屏幕上 */
+  OLED_Clear();
+  OLED_ShowString(10, OLED_PAGE2, buf, OLED_FONT8);
+  OLED_Refresh();
+
+  return;
+}
+
+void oled_show_rh(void)
+{
+  float temperature = 0.0;
+  float humidity = 0.0;
+  char  buf[64];
+
+  /* 采样并输出显示内容到 buf */
+  sht20_sample_TrH(&temperature, &humidity);
+  snprintf(buf, sizeof(buf), "%.2f'C  %.2f%%RH", temperature, humidity);
+
+  /* 输出显示内容 OLED 屏幕上 */
+  OLED_Clear();
+  OLED_ShowString(10, OLED_PAGE2, buf, OLED_FONT8);
+  OLED_Refresh();
+
+  return;
+}
+
+void oled_show_logo(void)
+{
+  /* 输出显示内容 OLED 屏幕上 */
+  OLED_Clear();
+  OLED_DrawBMP(0, OLED_PAGE0, BMP_LOGO_WIDTH, BMP_LOGO_HEIGHT, bmp_logo);
+  OLED_Refresh();
+
+  return;
+}
+
+/* USER CODE END 0 */
+
+/**
+  * @brief  The application entry point.
+  * @retval int
+  */
+int main(void)
+{
+
+  /* USER CODE BEGIN 1 */
+  float temperature;
+  /* 各个任务的计时基准 */
+  uint32_t now = 0;
+  uint32_t last_time_tick = 0;
+  uint32_t last_rh_tick = 0;
+  uint32_t last_logo_tick = 0;
+  uint32_t display_unlock_time = 0;
+
+  /* USER CODE END 1 */
+
+  /* MCU Configuration--------------------------------------------------------*/
+
+  /* Reset of all peripherals, Initializes the Flash interface and the Systick. */
+  HAL_Init();
+
+  /* USER CODE BEGIN Init */
+
+  /* USER CODE END Init */
+
+  /* Configure the system clock */
+  SystemClock_Config();
+
+  /* USER CODE BEGIN SysInit */
+
+  /* USER CODE END SysInit */
+
+  /* Initialize all configured peripherals */
+  MX_GPIO_Init();
+  MX_USART1_UART_Init();
+  MX_ADC1_Init();
+  MX_TIM6_Init();
+  MX_TIM1_Init();
+  /* USER CODE BEGIN 2 */
+  init_led();
+  i2c_init(&i2c1_bus);
+
+  beep_start(2, 300);
+  printf("ISKBoard: I2C Bitbang Drivers Demo.\r\n");
+
+  if( sht20_init(&i2c1_bus) != 0 )
+  {
+      printf("SHT20 Sensor init failed!\r\n");
+  }
+
+  if( isl1208_init(&i2c1_bus) != 0 )
+  {
+      printf("ISL1208 RTC init failed!\r\n");
+  }
+  isl1208_set_time_str("2026-07-09 21:15:30");
+
+  if( OLED_Init(&i2c1_bus) != 0 )
+  {
+      printf("OLED display init failed!\r\n");
+  }
+
+  OLED_Clear();
+  OLED_ShowString(30, OLED_PAGE2, "ISKBoard", OLED_FONT16);
+  OLED_Refresh();
+
+  ds18b20_sample(&temperature);
+  printf("DS18B20 sample temperature: %.2f'C\r\n", temperature);
+
+  /* USER CODE END 2 */
+
+  /* Infinite loop */
+  /* USER CODE BEGIN WHILE */
+  while (1)
+  {
+    /* 获取当前系统时间 */
+    now = HAL_GetTick();
+
+    /* 心跳灯任务:不需要锁屏,始终高频调用 */
+    heartbeat_led(3);
+
+    /* 检查屏幕是否处于“锁定”状态 (正在显示温湿度或Logo) */
+    if (now >= display_unlock_time)
+    {
+        /* 优先检查是否有高优先级任务触发 (Logo -> 温湿度) */
+        uint8_t is_displaying = 0;
+
+        /* 每隔 13s 显示 Logo,持续 1s */
+        if (now - last_logo_tick >= 13000)
+        {
+            oled_show_logo();          // 执行显示
+            last_logo_tick = now;      // 重置Logo计时器
+            display_unlock_time = now + 1000;
+            is_displaying = 1;
+        }
+
+        /* 如果Logo刚显示了,本轮跳过温湿度 */
+        else if (now - last_rh_tick >= 5000)
+        {
+            oled_show_rh();            // 执行显示
+            last_rh_tick = now;        // 重置温湿度计时器
+            display_unlock_time = now + 2000; // 锁屏 2s
+            is_displaying = 1;
+        }
+
+        /* 如果上面都没有显示特殊内容,则显示时间 */
+        if (is_displaying == 0)
+        {
+            if (now - last_time_tick >= 1000)
+            {
+                oled_show_time();      // 执行显示
+                last_time_tick = now;  // 重置时间计时器
+            }
+        }
+    }
+
+    /* USER CODE END WHILE */
+
+    /* USER CODE BEGIN 3 */
+  }
+  /* USER CODE END 3 */
+}
+
+/**
+  * @brief System Clock Configuration
+  * @retval None
+  */
+void SystemClock_Config(void)
+{
+  RCC_OscInitTypeDef RCC_OscInitStruct = {0};
+  RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};
+
+  /** Configure the main internal regulator output voltage
+  */
+  if (HAL_PWREx_ControlVoltageScaling(PWR_REGULATOR_VOLTAGE_SCALE1) != HAL_OK)
+  {
+    Error_Handler();
+  }
+
+  /** Initializes the RCC Oscillators according to the specified parameters
+  * in the RCC_OscInitTypeDef structure.
+  */
+  RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE;
+  RCC_OscInitStruct.HSEState = RCC_HSE_ON;
+  RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
+  RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE;
+  RCC_OscInitStruct.PLL.PLLM = 1;
+  RCC_OscInitStruct.PLL.PLLN = 20;
+  RCC_OscInitStruct.PLL.PLLP = RCC_PLLP_DIV7;
+  RCC_OscInitStruct.PLL.PLLQ = RCC_PLLQ_DIV2;
+  RCC_OscInitStruct.PLL.PLLR = RCC_PLLR_DIV2;
+  if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
+  {
+    Error_Handler();
+  }
+
+  /** Initializes the CPU, AHB and APB buses clocks
+  */
+  RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK
+                              |RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2;
+  RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
+  RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
+  RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV1;
+  RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1;
+
+  if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_4) != HAL_OK)
+  {
+    Error_Handler();
+  }
+}
+
+/* USER CODE BEGIN 4 */
+
+/* USER CODE END 4 */
+
+/**
+  * @brief  This function is executed in case of error occurrence.
+  * @retval None
+  */
+void Error_Handler(void)
+{
+  /* USER CODE BEGIN Error_Handler_Debug */
+  /* User can add his own implementation to report the HAL error return state */
+  __disable_irq();
+  while (1)
+  {
+  }
+  /* USER CODE END Error_Handler_Debug */
+}
+#ifdef USE_FULL_ASSERT
+/**
+  * @brief  Reports the name of the source file and the source line number
+  *         where the assert_param error has occurred.
+  * @param  file: pointer to the source file name
+  * @param  line: assert_param error line source number
+  * @retval None
+  */
+void assert_failed(uint8_t *file, uint32_t line)
+{
+  /* USER CODE BEGIN 6 */
+  /* User can add his own implementation to report the file name and line number,
+     ex: printf("Wrong parameters value: file %s on line %d\r\n", file, line) */
+  /* USER CODE END 6 */
+}
+#endif /* USE_FULL_ASSERT */
diff --git a/ex2.i2c-bitbang/ISKBoard.ioc b/ex2.i2c-bitbang/ISKBoard.ioc
new file mode 100644
index 0000000..8170d31
--- /dev/null
+++ b/ex2.i2c-bitbang/ISKBoard.ioc
@@ -0,0 +1,210 @@
+#MicroXplorer Configuration settings - do not modify
+ADC1.Channel-0\#ChannelRegularConversion=ADC_CHANNEL_15
+ADC1.CommonPathInternal=null|null|null|null
+ADC1.IPParameters=Rank-0\#ChannelRegularConversion,Channel-0\#ChannelRegularConversion,SamplingTime-0\#ChannelRegularConversion,OffsetNumber-0\#ChannelRegularConversion,NbrOfConversionFlag,master,NbrOfConversion,CommonPathInternal
+ADC1.NbrOfConversion=1
+ADC1.NbrOfConversionFlag=1
+ADC1.OffsetNumber-0\#ChannelRegularConversion=ADC_OFFSET_NONE
+ADC1.Rank-0\#ChannelRegularConversion=1
+ADC1.SamplingTime-0\#ChannelRegularConversion=ADC_SAMPLETIME_2CYCLES_5
+ADC1.master=1
+CAD.formats=
+CAD.pinconfig=
+CAD.provider=
+File.Version=6
+GPIO.groupedBy=Group By Peripherals
+KeepUserPlacement=false
+Mcu.CPN=STM32L431RCT6
+Mcu.Family=STM32L4
+Mcu.IP0=ADC1
+Mcu.IP1=NVIC
+Mcu.IP2=RCC
+Mcu.IP3=SYS
+Mcu.IP4=TIM1
+Mcu.IP5=TIM6
+Mcu.IP6=USART1
+Mcu.IPNb=7
+Mcu.Name=STM32L431R(B-C)Tx
+Mcu.Package=LQFP64
+Mcu.Pin0=PC14-OSC32_IN (PC14)
+Mcu.Pin1=PC15-OSC32_OUT (PC15)
+Mcu.Pin10=PC6
+Mcu.Pin11=PC9
+Mcu.Pin12=PA9
+Mcu.Pin13=PA10
+Mcu.Pin14=PA11
+Mcu.Pin15=PD2
+Mcu.Pin16=VP_SYS_VS_Systick
+Mcu.Pin17=VP_TIM6_VS_ClockSourceINT
+Mcu.Pin2=PH0-OSC_IN (PH0)
+Mcu.Pin3=PH1-OSC_OUT (PH1)
+Mcu.Pin4=PB0
+Mcu.Pin5=PB1
+Mcu.Pin6=PB2
+Mcu.Pin7=PB12
+Mcu.Pin8=PB13
+Mcu.Pin9=PB14
+Mcu.PinsNb=18
+Mcu.ThirdPartyNb=0
+Mcu.UserConstants=
+Mcu.UserName=STM32L431RCTx
+MxCube.Version=6.18.0
+MxDb.Version=DB.6.0.180
+NVIC.BusFault_IRQn=true\:0\:0\:false\:false\:true\:false\:false\:false
+NVIC.DebugMonitor_IRQn=true\:0\:0\:false\:false\:true\:false\:false\:false
+NVIC.EXTI15_10_IRQn=true\:2\:0\:true\:false\:true\:true\:true\:true
+NVIC.ForceEnableDMAVector=true
+NVIC.HardFault_IRQn=true\:0\:0\:false\:false\:true\:false\:false\:false
+NVIC.MemoryManagement_IRQn=true\:0\:0\:false\:false\:true\:false\:false\:false
+NVIC.NonMaskableInt_IRQn=true\:0\:0\:false\:false\:true\:false\:false\:false
+NVIC.PendSV_IRQn=true\:0\:0\:false\:false\:true\:false\:false\:false
+NVIC.PriorityGroup=NVIC_PRIORITYGROUP_4
+NVIC.SVCall_IRQn=true\:0\:0\:false\:false\:true\:false\:false\:false
+NVIC.SysTick_IRQn=true\:1\:0\:true\:false\:true\:false\:true\:false
+NVIC.UsageFault_IRQn=true\:0\:0\:false\:false\:true\:false\:false\:false
+PA10.Mode=Asynchronous
+PA10.Signal=USART1_RX
+PA11.GPIOParameters=GPIO_PuPd
+PA11.GPIO_PuPd=GPIO_PULLDOWN
+PA11.Locked=true
+PA11.Signal=S_TIM1_CH4
+PA9.Mode=Asynchronous
+PA9.Signal=USART1_TX
+PB0.Signal=ADCx_IN15
+PB1.Signal=ADCx_IN16
+PB12.GPIOParameters=GPIO_ModeDefaultEXTI
+PB12.GPIO_ModeDefaultEXTI=GPIO_MODE_IT_FALLING
+PB12.Locked=true
+PB12.Signal=GPXTI12
+PB13.GPIOParameters=GPIO_ModeDefaultEXTI
+PB13.GPIO_ModeDefaultEXTI=GPIO_MODE_IT_FALLING
+PB13.Locked=true
+PB13.Signal=GPXTI13
+PB14.GPIOParameters=GPIO_ModeDefaultEXTI
+PB14.GPIO_ModeDefaultEXTI=GPIO_MODE_IT_FALLING
+PB14.Locked=true
+PB14.Signal=GPXTI14
+PB2.Locked=true
+PB2.Signal=GPIO_Output
+PC14-OSC32_IN\ (PC14).Mode=LSE-External-Oscillator
+PC14-OSC32_IN\ (PC14).Signal=RCC_OSC32_IN
+PC15-OSC32_OUT\ (PC15).Mode=LSE-External-Oscillator
+PC15-OSC32_OUT\ (PC15).Signal=RCC_OSC32_OUT
+PC6.Locked=true
+PC6.Signal=GPIO_Output
+PC9.Locked=true
+PC9.Signal=GPIO_Output
+PD2.Locked=true
+PD2.Signal=GPIO_Output
+PH0-OSC_IN\ (PH0).Mode=HSE-External-Oscillator
+PH0-OSC_IN\ (PH0).Signal=RCC_OSC_IN
+PH1-OSC_OUT\ (PH1).Mode=HSE-External-Oscillator
+PH1-OSC_OUT\ (PH1).Signal=RCC_OSC_OUT
+PinOutPanel.RotationAngle=0
+ProjectManager.AskForMigrate=true
+ProjectManager.BackupPrevious=false
+ProjectManager.CompilerLinker=GCC
+ProjectManager.CompilerOptimize=6
+ProjectManager.ComputerToolchain=false
+ProjectManager.CoupleFile=true
+ProjectManager.CustomerFirmwarePackage=
+ProjectManager.DefaultFWLocation=true
+ProjectManager.DeletePrevious=true
+ProjectManager.DeviceId=STM32L431RCTx
+ProjectManager.FirmwarePackage=STM32Cube FW_L4 V1.18.2
+ProjectManager.FreePins=false
+ProjectManager.FreePinsContext=
+ProjectManager.HalAssertFull=false
+ProjectManager.HeapSize=0x200
+ProjectManager.KeepUserCode=true
+ProjectManager.LastFirmware=true
+ProjectManager.LibraryCopy=1
+ProjectManager.MainLocation=Core/Src
+ProjectManager.NoMain=false
+ProjectManager.PreviousToolchain=
+ProjectManager.ProjectBuild=false
+ProjectManager.ProjectFileName=ISKBoard.ioc
+ProjectManager.ProjectName=ISKBoard
+ProjectManager.ProjectStructure=
+ProjectManager.RegisterCallBack=
+ProjectManager.StackSize=0x400
+ProjectManager.TargetToolchain=CMake
+ProjectManager.ToolChainLocation=
+ProjectManager.UAScriptAfterPath=
+ProjectManager.UAScriptBeforePath=
+ProjectManager.UnderRoot=false
+ProjectManager.functionlistsort=1-SystemClock_Config-RCC-false-HAL-false,2-MX_GPIO_Init-GPIO-false-HAL-true,3-MX_USART1_UART_Init-USART1-false-HAL-true,4-MX_ADC1_Init-ADC1-false-HAL-true,5-MX_TIM6_Init-TIM6-false-HAL-true,6-MX_TIM1_Init-TIM1-false-HAL-true
+RCC.ADCFreq_Value=32000000
+RCC.AHBFreq_Value=80000000
+RCC.APB1Freq_Value=80000000
+RCC.APB1TimFreq_Value=80000000
+RCC.APB2Freq_Value=80000000
+RCC.APB2TimFreq_Value=80000000
+RCC.CortexFreq_Value=80000000
+RCC.FCLKCortexFreq_Value=80000000
+RCC.FamilyName=M
+RCC.HCLKFreq_Value=80000000
+RCC.HSE_VALUE=8000000
+RCC.HSI48_VALUE=48000000
+RCC.HSI_VALUE=16000000
+RCC.I2C1Freq_Value=80000000
+RCC.I2C2Freq_Value=80000000
+RCC.I2C3Freq_Value=80000000
+RCC.IPParameters=ADCFreq_Value,AHBFreq_Value,APB1Freq_Value,APB1TimFreq_Value,APB2Freq_Value,APB2TimFreq_Value,CortexFreq_Value,FCLKCortexFreq_Value,FamilyName,HCLKFreq_Value,HSE_VALUE,HSI48_VALUE,HSI_VALUE,I2C1Freq_Value,I2C2Freq_Value,I2C3Freq_Value,LPTIM1Freq_Value,LPTIM2Freq_Value,LPUART1Freq_Value,LSCOPinFreq_Value,LSI_VALUE,MCO1PinFreq_Value,MSI_VALUE,PLLN,PLLPoutputFreq_Value,PLLQoutputFreq_Value,PLLRCLKFreq_Value,PLLSAI1PoutputFreq_Value,PLLSAI1QoutputFreq_Value,PLLSAI1RoutputFreq_Value,PLLSourceVirtual,PWRFreq_Value,RNGFreq_Value,SAI1Freq_Value,SDMMCFreq_Value,SWPMI1Freq_Value,SYSCLKFreq_VALUE,SYSCLKSource,USART1Freq_Value,USART2Freq_Value,USART3Freq_Value,VCOInputFreq_Value,VCOOutputFreq_Value,VCOSAI1OutputFreq_Value
+RCC.LPTIM1Freq_Value=80000000
+RCC.LPTIM2Freq_Value=80000000
+RCC.LPUART1Freq_Value=80000000
+RCC.LSCOPinFreq_Value=32000
+RCC.LSI_VALUE=32000
+RCC.MCO1PinFreq_Value=80000000
+RCC.MSI_VALUE=4000000
+RCC.PLLN=20
+RCC.PLLPoutputFreq_Value=22857142.85714286
+RCC.PLLQoutputFreq_Value=80000000
+RCC.PLLRCLKFreq_Value=80000000
+RCC.PLLSAI1PoutputFreq_Value=9142857.142857144
+RCC.PLLSAI1QoutputFreq_Value=32000000
+RCC.PLLSAI1RoutputFreq_Value=32000000
+RCC.PLLSourceVirtual=RCC_PLLSOURCE_HSE
+RCC.PWRFreq_Value=80000000
+RCC.RNGFreq_Value=32000000
+RCC.SAI1Freq_Value=9142857.142857144
+RCC.SDMMCFreq_Value=32000000
+RCC.SWPMI1Freq_Value=80000000
+RCC.SYSCLKFreq_VALUE=80000000
+RCC.SYSCLKSource=RCC_SYSCLKSOURCE_PLLCLK
+RCC.USART1Freq_Value=80000000
+RCC.USART2Freq_Value=80000000
+RCC.USART3Freq_Value=80000000
+RCC.VCOInputFreq_Value=8000000
+RCC.VCOOutputFreq_Value=160000000
+RCC.VCOSAI1OutputFreq_Value=64000000
+SH.ADCx_IN15.0=ADC1_IN15,IN15-Single-Ended
+SH.ADCx_IN15.ConfNb=1
+SH.ADCx_IN16.0=ADC1_IN16,IN16-Single-Ended
+SH.ADCx_IN16.ConfNb=1
+SH.GPXTI12.0=GPIO_EXTI12
+SH.GPXTI12.ConfNb=1
+SH.GPXTI13.0=GPIO_EXTI13
+SH.GPXTI13.ConfNb=1
+SH.GPXTI14.0=GPIO_EXTI14
+SH.GPXTI14.ConfNb=1
+SH.S_TIM1_CH4.0=TIM1_CH4,PWM Generation4 CH4
+SH.S_TIM1_CH4.ConfNb=1
+TIM1.Channel-PWM\ Generation4\ CH4=TIM_CHANNEL_4
+TIM1.IPParameters=Channel-PWM Generation4 CH4,Prescaler,RepetitionCounter,Period,Pulse-PWM Generation4 CH4
+TIM1.Period=370-1
+TIM1.Prescaler=80-1
+TIM1.Pulse-PWM\ Generation4\ CH4=185
+TIM1.RepetitionCounter=0
+TIM6.AutoReloadPreload=TIM_AUTORELOAD_PRELOAD_DISABLE
+TIM6.IPParameters=Prescaler,Period,AutoReloadPreload
+TIM6.Period=1
+TIM6.Prescaler=80-1
+USART1.IPParameters=VirtualMode-Asynchronous
+USART1.VirtualMode-Asynchronous=VM_ASYNC
+VP_SYS_VS_Systick.Mode=SysTick
+VP_SYS_VS_Systick.Signal=SYS_VS_Systick
+VP_TIM6_VS_ClockSourceINT.Mode=Enable_Timer
+VP_TIM6_VS_ClockSourceINT.Signal=TIM6_VS_ClockSourceINT
+board=custom
diff --git a/ex2.i2c-bitbang/MDK-ARM/ISKBoard.uvoptx b/ex2.i2c-bitbang/MDK-ARM/ISKBoard.uvoptx
new file mode 100644
index 0000000..7f7eb1e
--- /dev/null
+++ b/ex2.i2c-bitbang/MDK-ARM/ISKBoard.uvoptx
@@ -0,0 +1,753 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no" ?>
+<ProjectOpt xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="project_optx.xsd">
+
+  <SchemaVersion>1.0</SchemaVersion>
+
+  <Header>### uVision Project, (C) Keil Software</Header>
+
+  <Extensions>
+    <cExt>*.c</cExt>
+    <aExt>*.s*; *.src; *.a*</aExt>
+    <oExt>*.obj; *.o</oExt>
+    <lExt>*.lib</lExt>
+    <tExt>*.txt; *.h; *.inc; *.md</tExt>
+    <pExt>*.plm</pExt>
+    <CppX>*.cpp</CppX>
+    <nMigrate>0</nMigrate>
+  </Extensions>
+
+  <DaveTm>
+    <dwLowDateTime>0</dwLowDateTime>
+    <dwHighDateTime>0</dwHighDateTime>
+  </DaveTm>
+
+  <Target>
+    <TargetName>ISKBoard</TargetName>
+    <ToolsetNumber>0x4</ToolsetNumber>
+    <ToolsetName>ARM-ADS</ToolsetName>
+    <TargetOption>
+      <CLKADS>8000000</CLKADS>
+      <OPTTT>
+        <gFlags>1</gFlags>
+        <BeepAtEnd>1</BeepAtEnd>
+        <RunSim>0</RunSim>
+        <RunTarget>1</RunTarget>
+        <RunAbUc>0</RunAbUc>
+      </OPTTT>
+      <OPTHX>
+        <HexSelection>1</HexSelection>
+        <FlashByte>65535</FlashByte>
+        <HexRangeLowAddress>0</HexRangeLowAddress>
+        <HexRangeHighAddress>0</HexRangeHighAddress>
+        <HexOffset>0</HexOffset>
+      </OPTHX>
+      <OPTLEX>
+        <PageWidth>79</PageWidth>
+        <PageLength>66</PageLength>
+        <TabStop>8</TabStop>
+        <ListingPath></ListingPath>
+      </OPTLEX>
+      <ListingPage>
+        <CreateCListing>1</CreateCListing>
+        <CreateAListing>1</CreateAListing>
+        <CreateLListing>1</CreateLListing>
+        <CreateIListing>0</CreateIListing>
+        <AsmCond>1</AsmCond>
+        <AsmSymb>1</AsmSymb>
+        <AsmXref>0</AsmXref>
+        <CCond>1</CCond>
+        <CCode>0</CCode>
+        <CListInc>0</CListInc>
+        <CSymb>0</CSymb>
+        <LinkerCodeListing>0</LinkerCodeListing>
+      </ListingPage>
+      <OPTXL>
+        <LMap>1</LMap>
+        <LComments>1</LComments>
+        <LGenerateSymbols>1</LGenerateSymbols>
+        <LLibSym>1</LLibSym>
+        <LLines>1</LLines>
+        <LLocSym>1</LLocSym>
+        <LPubSym>1</LPubSym>
+        <LXref>0</LXref>
+        <LExpSel>0</LExpSel>
+      </OPTXL>
+      <OPTFL>
+        <tvExp>1</tvExp>
+        <tvExpOptDlg>0</tvExpOptDlg>
+        <IsCurrentTarget>1</IsCurrentTarget>
+      </OPTFL>
+      <CpuCode>18</CpuCode>
+      <DebugOpt>
+        <uSim>0</uSim>
+        <uTrg>1</uTrg>
+        <sLdApp>1</sLdApp>
+        <sGomain>1</sGomain>
+        <sRbreak>1</sRbreak>
+        <sRwatch>1</sRwatch>
+        <sRmem>1</sRmem>
+        <sRfunc>1</sRfunc>
+        <sRbox>1</sRbox>
+        <tLdApp>1</tLdApp>
+        <tGomain>1</tGomain>
+        <tRbreak>1</tRbreak>
+        <tRwatch>1</tRwatch>
+        <tRmem>1</tRmem>
+        <tRfunc>1</tRfunc>
+        <tRbox>1</tRbox>
+        <tRtrace>1</tRtrace>
+        <sRSysVw>1</sRSysVw>
+        <tRSysVw>1</tRSysVw>
+        <sRunDeb>0</sRunDeb>
+        <sLrtime>0</sLrtime>
+        <bEvRecOn>1</bEvRecOn>
+        <bSchkAxf>0</bSchkAxf>
+        <bTchkAxf>0</bTchkAxf>
+        <nTsel>6</nTsel>
+        <sDll></sDll>
+        <sDllPa></sDllPa>
+        <sDlgDll></sDlgDll>
+        <sDlgPa></sDlgPa>
+        <sIfile></sIfile>
+        <tDll></tDll>
+        <tDllPa></tDllPa>
+        <tDlgDll></tDlgDll>
+        <tDlgPa></tDlgPa>
+        <tIfile></tIfile>
+        <pMon>STLink\ST-LINKIII-KEIL_SWO.dll</pMon>
+      </DebugOpt>
+      <TargetDriverDllRegistry>
+        <SetRegEntry>
+          <Number>0</Number>
+          <Key>ARMRTXEVENTFLAGS</Key>
+          <Name>-L70 -Z18 -C0 -M0 -T1</Name>
+        </SetRegEntry>
+        <SetRegEntry>
+          <Number>0</Number>
+          <Key>DLGTARM</Key>
+          <Name>(1010=-1,-1,-1,-1,0)(1007=-1,-1,-1,-1,0)(1008=-1,-1,-1,-1,0)(1009=-1,-1,-1,-1,0)(1012=-1,-1,-1,-1,0)</Name>
+        </SetRegEntry>
+        <SetRegEntry>
+          <Number>0</Number>
+          <Key>ARMDBGFLAGS</Key>
+          <Name></Name>
+        </SetRegEntry>
+        <SetRegEntry>
+          <Number>0</Number>
+          <Key>DLGUARM</Key>
+          <Name>(105=-1,-1,-1,-1,0)</Name>
+        </SetRegEntry>
+        <SetRegEntry>
+          <Number>0</Number>
+          <Key>UL2CM3</Key>
+          <Name>UL2CM3(-S0 -C0 -P0 -FD20000000 -FC8000 -FN1 -FF0STM32L4xx_256 -FS08000000 -FL040000 -FP0($$Device:STM32L431RCTx$CMSIS\Flash\STM32L4xx_256.FLM))</Name>
+        </SetRegEntry>
+        <SetRegEntry>
+          <Number>0</Number>
+          <Key>ST-LINKIII-KEIL_SWO</Key>
+          <Name>-U53FF69064885495006370487 -O2254 -SF10000 -C0 -A0 -I0 -HNlocalhost -HP7184 -P1 -N00("ARM CoreSight SW-DP (ARM Core") -D00(2BA01477) -L00(0) -TO131090 -TC10000000 -TT10000000 -TP21 -TDS8007 -TDT0 -TDC1F -TIEFFFFFFFF -TIP8 -FO7 -FD20000000 -FC1000 -FN1 -FF0STM32L4xx_256.FLM -FS08000000 -FL040000 -FP0($$Device:STM32L431RCTx$CMSIS\Flash\STM32L4xx_256.FLM)</Name>
+        </SetRegEntry>
+      </TargetDriverDllRegistry>
+      <Breakpoint>
+        <Bp>
+          <Number>0</Number>
+          <Type>0</Type>
+          <LineNumber>95</LineNumber>
+          <EnabledFlag>1</EnabledFlag>
+          <Address>0</Address>
+          <ByteObject>0</ByteObject>
+          <HtxType>0</HtxType>
+          <ManyObjects>0</ManyObjects>
+          <SizeOfObject>0</SizeOfObject>
+          <BreakByAccess>0</BreakByAccess>
+          <BreakIfRCount>0</BreakIfRCount>
+          <Filename>../Core/Src/main.c</Filename>
+          <ExecCommand></ExecCommand>
+          <Expression></Expression>
+        </Bp>
+        <Bp>
+          <Number>1</Number>
+          <Type>0</Type>
+          <LineNumber>97</LineNumber>
+          <EnabledFlag>1</EnabledFlag>
+          <Address>0</Address>
+          <ByteObject>0</ByteObject>
+          <HtxType>0</HtxType>
+          <ManyObjects>0</ManyObjects>
+          <SizeOfObject>0</SizeOfObject>
+          <BreakByAccess>0</BreakByAccess>
+          <BreakIfRCount>0</BreakIfRCount>
+          <Filename>../Core/Src/main.c</Filename>
+          <ExecCommand></ExecCommand>
+          <Expression></Expression>
+        </Bp>
+      </Breakpoint>
+      <Tracepoint>
+        <THDelay>0</THDelay>
+      </Tracepoint>
+      <DebugFlag>
+        <trace>0</trace>
+        <periodic>1</periodic>
+        <aLwin>1</aLwin>
+        <aCover>0</aCover>
+        <aSer1>0</aSer1>
+        <aSer2>0</aSer2>
+        <aPa>0</aPa>
+        <viewmode>1</viewmode>
+        <vrSel>0</vrSel>
+        <aSym>0</aSym>
+        <aTbox>0</aTbox>
+        <AscS1>0</AscS1>
+        <AscS2>0</AscS2>
+        <AscS3>0</AscS3>
+        <aSer3>0</aSer3>
+        <eProf>0</eProf>
+        <aLa>0</aLa>
+        <aPa1>0</aPa1>
+        <AscS4>0</AscS4>
+        <aSer4>0</aSer4>
+        <StkLoc>1</StkLoc>
+        <TrcWin>0</TrcWin>
+        <newCpu>0</newCpu>
+        <uProt>0</uProt>
+      </DebugFlag>
+      <LintExecutable></LintExecutable>
+      <LintConfigFile></LintConfigFile>
+      <bLintAuto>0</bLintAuto>
+      <bAutoGenD>0</bAutoGenD>
+      <LntExFlags>0</LntExFlags>
+      <pMisraName></pMisraName>
+      <pszMrule></pszMrule>
+      <pSingCmds></pSingCmds>
+      <pMultCmds></pMultCmds>
+      <pMisraNamep></pMisraNamep>
+      <pszMrulep></pszMrulep>
+      <pSingCmdsp></pSingCmdsp>
+      <pMultCmdsp></pMultCmdsp>
+      <DebugDescription>
+        <Enable>1</Enable>
+        <EnableFlashSeq>0</EnableFlashSeq>
+        <EnableLog>0</EnableLog>
+        <Protocol>2</Protocol>
+        <DbgClock>10000000</DbgClock>
+      </DebugDescription>
+    </TargetOption>
+  </Target>
+
+  <Group>
+    <GroupName>Application/MDK-ARM</GroupName>
+    <tvExp>0</tvExp>
+    <tvExpOptDlg>0</tvExpOptDlg>
+    <cbSel>0</cbSel>
+    <RteFlg>0</RteFlg>
+    <File>
+      <GroupNumber>1</GroupNumber>
+      <FileNumber>1</FileNumber>
+      <FileType>2</FileType>
+      <tvExp>0</tvExp>
+      <tvExpOptDlg>0</tvExpOptDlg>
+      <bDave2>0</bDave2>
+      <PathWithFileName>startup_stm32l431xx.s</PathWithFileName>
+      <FilenameWithoutPath>startup_stm32l431xx.s</FilenameWithoutPath>
+      <RteFlg>0</RteFlg>
+      <bShared>0</bShared>
+    </File>
+  </Group>
+
+  <Group>
+    <GroupName>Application/User/Core</GroupName>
+    <tvExp>1</tvExp>
+    <tvExpOptDlg>0</tvExpOptDlg>
+    <cbSel>0</cbSel>
+    <RteFlg>0</RteFlg>
+    <File>
+      <GroupNumber>2</GroupNumber>
+      <FileNumber>2</FileNumber>
+      <FileType>1</FileType>
+      <tvExp>0</tvExp>
+      <tvExpOptDlg>0</tvExpOptDlg>
+      <bDave2>0</bDave2>
+      <PathWithFileName>../Core/Src/main.c</PathWithFileName>
+      <FilenameWithoutPath>main.c</FilenameWithoutPath>
+      <RteFlg>0</RteFlg>
+      <bShared>0</bShared>
+    </File>
+    <File>
+      <GroupNumber>2</GroupNumber>
+      <FileNumber>3</FileNumber>
+      <FileType>1</FileType>
+      <tvExp>0</tvExp>
+      <tvExpOptDlg>0</tvExpOptDlg>
+      <bDave2>0</bDave2>
+      <PathWithFileName>../Core/Src/gpio.c</PathWithFileName>
+      <FilenameWithoutPath>gpio.c</FilenameWithoutPath>
+      <RteFlg>0</RteFlg>
+      <bShared>0</bShared>
+    </File>
+    <File>
+      <GroupNumber>2</GroupNumber>
+      <FileNumber>4</FileNumber>
+      <FileType>1</FileType>
+      <tvExp>0</tvExp>
+      <tvExpOptDlg>0</tvExpOptDlg>
+      <bDave2>0</bDave2>
+      <PathWithFileName>../Core/Src/adc.c</PathWithFileName>
+      <FilenameWithoutPath>adc.c</FilenameWithoutPath>
+      <RteFlg>0</RteFlg>
+      <bShared>0</bShared>
+    </File>
+    <File>
+      <GroupNumber>2</GroupNumber>
+      <FileNumber>5</FileNumber>
+      <FileType>1</FileType>
+      <tvExp>0</tvExp>
+      <tvExpOptDlg>0</tvExpOptDlg>
+      <bDave2>0</bDave2>
+      <PathWithFileName>../Core/Src/tim.c</PathWithFileName>
+      <FilenameWithoutPath>tim.c</FilenameWithoutPath>
+      <RteFlg>0</RteFlg>
+      <bShared>0</bShared>
+    </File>
+    <File>
+      <GroupNumber>2</GroupNumber>
+      <FileNumber>6</FileNumber>
+      <FileType>1</FileType>
+      <tvExp>0</tvExp>
+      <tvExpOptDlg>0</tvExpOptDlg>
+      <bDave2>0</bDave2>
+      <PathWithFileName>../Core/Src/usart.c</PathWithFileName>
+      <FilenameWithoutPath>usart.c</FilenameWithoutPath>
+      <RteFlg>0</RteFlg>
+      <bShared>0</bShared>
+    </File>
+    <File>
+      <GroupNumber>2</GroupNumber>
+      <FileNumber>7</FileNumber>
+      <FileType>1</FileType>
+      <tvExp>0</tvExp>
+      <tvExpOptDlg>0</tvExpOptDlg>
+      <bDave2>0</bDave2>
+      <PathWithFileName>../Core/Src/stm32l4xx_it.c</PathWithFileName>
+      <FilenameWithoutPath>stm32l4xx_it.c</FilenameWithoutPath>
+      <RteFlg>0</RteFlg>
+      <bShared>0</bShared>
+    </File>
+    <File>
+      <GroupNumber>2</GroupNumber>
+      <FileNumber>8</FileNumber>
+      <FileType>1</FileType>
+      <tvExp>0</tvExp>
+      <tvExpOptDlg>0</tvExpOptDlg>
+      <bDave2>0</bDave2>
+      <PathWithFileName>../Core/Src/stm32l4xx_hal_msp.c</PathWithFileName>
+      <FilenameWithoutPath>stm32l4xx_hal_msp.c</FilenameWithoutPath>
+      <RteFlg>0</RteFlg>
+      <bShared>0</bShared>
+    </File>
+  </Group>
+
+  <Group>
+    <GroupName>Drivers/STM32L4xx_HAL_Driver</GroupName>
+    <tvExp>0</tvExp>
+    <tvExpOptDlg>0</tvExpOptDlg>
+    <cbSel>0</cbSel>
+    <RteFlg>0</RteFlg>
+    <File>
+      <GroupNumber>3</GroupNumber>
+      <FileNumber>9</FileNumber>
+      <FileType>1</FileType>
+      <tvExp>0</tvExp>
+      <tvExpOptDlg>0</tvExpOptDlg>
+      <bDave2>0</bDave2>
+      <PathWithFileName>../Drivers/STM32L4xx_HAL_Driver/Src/stm32l4xx_hal_adc.c</PathWithFileName>
+      <FilenameWithoutPath>stm32l4xx_hal_adc.c</FilenameWithoutPath>
+      <RteFlg>0</RteFlg>
+      <bShared>0</bShared>
+    </File>
+    <File>
+      <GroupNumber>3</GroupNumber>
+      <FileNumber>10</FileNumber>
+      <FileType>1</FileType>
+      <tvExp>0</tvExp>
+      <tvExpOptDlg>0</tvExpOptDlg>
+      <bDave2>0</bDave2>
+      <PathWithFileName>../Drivers/STM32L4xx_HAL_Driver/Src/stm32l4xx_hal_adc_ex.c</PathWithFileName>
+      <FilenameWithoutPath>stm32l4xx_hal_adc_ex.c</FilenameWithoutPath>
+      <RteFlg>0</RteFlg>
+      <bShared>0</bShared>
+    </File>
+    <File>
+      <GroupNumber>3</GroupNumber>
+      <FileNumber>11</FileNumber>
+      <FileType>1</FileType>
+      <tvExp>0</tvExp>
+      <tvExpOptDlg>0</tvExpOptDlg>
+      <bDave2>0</bDave2>
+      <PathWithFileName>../Drivers/STM32L4xx_HAL_Driver/Src/stm32l4xx_hal.c</PathWithFileName>
+      <FilenameWithoutPath>stm32l4xx_hal.c</FilenameWithoutPath>
+      <RteFlg>0</RteFlg>
+      <bShared>0</bShared>
+    </File>
+    <File>
+      <GroupNumber>3</GroupNumber>
+      <FileNumber>12</FileNumber>
+      <FileType>1</FileType>
+      <tvExp>0</tvExp>
+      <tvExpOptDlg>0</tvExpOptDlg>
+      <bDave2>0</bDave2>
+      <PathWithFileName>../Drivers/STM32L4xx_HAL_Driver/Src/stm32l4xx_hal_rcc.c</PathWithFileName>
+      <FilenameWithoutPath>stm32l4xx_hal_rcc.c</FilenameWithoutPath>
+      <RteFlg>0</RteFlg>
+      <bShared>0</bShared>
+    </File>
+    <File>
+      <GroupNumber>3</GroupNumber>
+      <FileNumber>13</FileNumber>
+      <FileType>1</FileType>
+      <tvExp>0</tvExp>
+      <tvExpOptDlg>0</tvExpOptDlg>
+      <bDave2>0</bDave2>
+      <PathWithFileName>../Drivers/STM32L4xx_HAL_Driver/Src/stm32l4xx_hal_rcc_ex.c</PathWithFileName>
+      <FilenameWithoutPath>stm32l4xx_hal_rcc_ex.c</FilenameWithoutPath>
+      <RteFlg>0</RteFlg>
+      <bShared>0</bShared>
+    </File>
+    <File>
+      <GroupNumber>3</GroupNumber>
+      <FileNumber>14</FileNumber>
+      <FileType>1</FileType>
+      <tvExp>0</tvExp>
+      <tvExpOptDlg>0</tvExpOptDlg>
+      <bDave2>0</bDave2>
+      <PathWithFileName>../Drivers/STM32L4xx_HAL_Driver/Src/stm32l4xx_hal_flash.c</PathWithFileName>
+      <FilenameWithoutPath>stm32l4xx_hal_flash.c</FilenameWithoutPath>
+      <RteFlg>0</RteFlg>
+      <bShared>0</bShared>
+    </File>
+    <File>
+      <GroupNumber>3</GroupNumber>
+      <FileNumber>15</FileNumber>
+      <FileType>1</FileType>
+      <tvExp>0</tvExp>
+      <tvExpOptDlg>0</tvExpOptDlg>
+      <bDave2>0</bDave2>
+      <PathWithFileName>../Drivers/STM32L4xx_HAL_Driver/Src/stm32l4xx_hal_flash_ex.c</PathWithFileName>
+      <FilenameWithoutPath>stm32l4xx_hal_flash_ex.c</FilenameWithoutPath>
+      <RteFlg>0</RteFlg>
+      <bShared>0</bShared>
+    </File>
+    <File>
+      <GroupNumber>3</GroupNumber>
+      <FileNumber>16</FileNumber>
+      <FileType>1</FileType>
+      <tvExp>0</tvExp>
+      <tvExpOptDlg>0</tvExpOptDlg>
+      <bDave2>0</bDave2>
+      <PathWithFileName>../Drivers/STM32L4xx_HAL_Driver/Src/stm32l4xx_hal_flash_ramfunc.c</PathWithFileName>
+      <FilenameWithoutPath>stm32l4xx_hal_flash_ramfunc.c</FilenameWithoutPath>
+      <RteFlg>0</RteFlg>
+      <bShared>0</bShared>
+    </File>
+    <File>
+      <GroupNumber>3</GroupNumber>
+      <FileNumber>17</FileNumber>
+      <FileType>1</FileType>
+      <tvExp>0</tvExp>
+      <tvExpOptDlg>0</tvExpOptDlg>
+      <bDave2>0</bDave2>
+      <PathWithFileName>../Drivers/STM32L4xx_HAL_Driver/Src/stm32l4xx_hal_gpio.c</PathWithFileName>
+      <FilenameWithoutPath>stm32l4xx_hal_gpio.c</FilenameWithoutPath>
+      <RteFlg>0</RteFlg>
+      <bShared>0</bShared>
+    </File>
+    <File>
+      <GroupNumber>3</GroupNumber>
+      <FileNumber>18</FileNumber>
+      <FileType>1</FileType>
+      <tvExp>0</tvExp>
+      <tvExpOptDlg>0</tvExpOptDlg>
+      <bDave2>0</bDave2>
+      <PathWithFileName>../Drivers/STM32L4xx_HAL_Driver/Src/stm32l4xx_hal_i2c.c</PathWithFileName>
+      <FilenameWithoutPath>stm32l4xx_hal_i2c.c</FilenameWithoutPath>
+      <RteFlg>0</RteFlg>
+      <bShared>0</bShared>
+    </File>
+    <File>
+      <GroupNumber>3</GroupNumber>
+      <FileNumber>19</FileNumber>
+      <FileType>1</FileType>
+      <tvExp>0</tvExp>
+      <tvExpOptDlg>0</tvExpOptDlg>
+      <bDave2>0</bDave2>
+      <PathWithFileName>../Drivers/STM32L4xx_HAL_Driver/Src/stm32l4xx_hal_i2c_ex.c</PathWithFileName>
+      <FilenameWithoutPath>stm32l4xx_hal_i2c_ex.c</FilenameWithoutPath>
+      <RteFlg>0</RteFlg>
+      <bShared>0</bShared>
+    </File>
+    <File>
+      <GroupNumber>3</GroupNumber>
+      <FileNumber>20</FileNumber>
+      <FileType>1</FileType>
+      <tvExp>0</tvExp>
+      <tvExpOptDlg>0</tvExpOptDlg>
+      <bDave2>0</bDave2>
+      <PathWithFileName>../Drivers/STM32L4xx_HAL_Driver/Src/stm32l4xx_hal_dma.c</PathWithFileName>
+      <FilenameWithoutPath>stm32l4xx_hal_dma.c</FilenameWithoutPath>
+      <RteFlg>0</RteFlg>
+      <bShared>0</bShared>
+    </File>
+    <File>
+      <GroupNumber>3</GroupNumber>
+      <FileNumber>21</FileNumber>
+      <FileType>1</FileType>
+      <tvExp>0</tvExp>
+      <tvExpOptDlg>0</tvExpOptDlg>
+      <bDave2>0</bDave2>
+      <PathWithFileName>../Drivers/STM32L4xx_HAL_Driver/Src/stm32l4xx_hal_dma_ex.c</PathWithFileName>
+      <FilenameWithoutPath>stm32l4xx_hal_dma_ex.c</FilenameWithoutPath>
+      <RteFlg>0</RteFlg>
+      <bShared>0</bShared>
+    </File>
+    <File>
+      <GroupNumber>3</GroupNumber>
+      <FileNumber>22</FileNumber>
+      <FileType>1</FileType>
+      <tvExp>0</tvExp>
+      <tvExpOptDlg>0</tvExpOptDlg>
+      <bDave2>0</bDave2>
+      <PathWithFileName>../Drivers/STM32L4xx_HAL_Driver/Src/stm32l4xx_hal_pwr.c</PathWithFileName>
+      <FilenameWithoutPath>stm32l4xx_hal_pwr.c</FilenameWithoutPath>
+      <RteFlg>0</RteFlg>
+      <bShared>0</bShared>
+    </File>
+    <File>
+      <GroupNumber>3</GroupNumber>
+      <FileNumber>23</FileNumber>
+      <FileType>1</FileType>
+      <tvExp>0</tvExp>
+      <tvExpOptDlg>0</tvExpOptDlg>
+      <bDave2>0</bDave2>
+      <PathWithFileName>../Drivers/STM32L4xx_HAL_Driver/Src/stm32l4xx_hal_pwr_ex.c</PathWithFileName>
+      <FilenameWithoutPath>stm32l4xx_hal_pwr_ex.c</FilenameWithoutPath>
+      <RteFlg>0</RteFlg>
+      <bShared>0</bShared>
+    </File>
+    <File>
+      <GroupNumber>3</GroupNumber>
+      <FileNumber>24</FileNumber>
+      <FileType>1</FileType>
+      <tvExp>0</tvExp>
+      <tvExpOptDlg>0</tvExpOptDlg>
+      <bDave2>0</bDave2>
+      <PathWithFileName>../Drivers/STM32L4xx_HAL_Driver/Src/stm32l4xx_hal_cortex.c</PathWithFileName>
+      <FilenameWithoutPath>stm32l4xx_hal_cortex.c</FilenameWithoutPath>
+      <RteFlg>0</RteFlg>
+      <bShared>0</bShared>
+    </File>
+    <File>
+      <GroupNumber>3</GroupNumber>
+      <FileNumber>25</FileNumber>
+      <FileType>1</FileType>
+      <tvExp>0</tvExp>
+      <tvExpOptDlg>0</tvExpOptDlg>
+      <bDave2>0</bDave2>
+      <PathWithFileName>../Drivers/STM32L4xx_HAL_Driver/Src/stm32l4xx_hal_exti.c</PathWithFileName>
+      <FilenameWithoutPath>stm32l4xx_hal_exti.c</FilenameWithoutPath>
+      <RteFlg>0</RteFlg>
+      <bShared>0</bShared>
+    </File>
+    <File>
+      <GroupNumber>3</GroupNumber>
+      <FileNumber>26</FileNumber>
+      <FileType>1</FileType>
+      <tvExp>0</tvExp>
+      <tvExpOptDlg>0</tvExpOptDlg>
+      <bDave2>0</bDave2>
+      <PathWithFileName>../Drivers/STM32L4xx_HAL_Driver/Src/stm32l4xx_hal_tim.c</PathWithFileName>
+      <FilenameWithoutPath>stm32l4xx_hal_tim.c</FilenameWithoutPath>
+      <RteFlg>0</RteFlg>
+      <bShared>0</bShared>
+    </File>
+    <File>
+      <GroupNumber>3</GroupNumber>
+      <FileNumber>27</FileNumber>
+      <FileType>1</FileType>
+      <tvExp>0</tvExp>
+      <tvExpOptDlg>0</tvExpOptDlg>
+      <bDave2>0</bDave2>
+      <PathWithFileName>../Drivers/STM32L4xx_HAL_Driver/Src/stm32l4xx_hal_tim_ex.c</PathWithFileName>
+      <FilenameWithoutPath>stm32l4xx_hal_tim_ex.c</FilenameWithoutPath>
+      <RteFlg>0</RteFlg>
+      <bShared>0</bShared>
+    </File>
+    <File>
+      <GroupNumber>3</GroupNumber>
+      <FileNumber>28</FileNumber>
+      <FileType>1</FileType>
+      <tvExp>0</tvExp>
+      <tvExpOptDlg>0</tvExpOptDlg>
+      <bDave2>0</bDave2>
+      <PathWithFileName>../Drivers/STM32L4xx_HAL_Driver/Src/stm32l4xx_hal_uart.c</PathWithFileName>
+      <FilenameWithoutPath>stm32l4xx_hal_uart.c</FilenameWithoutPath>
+      <RteFlg>0</RteFlg>
+      <bShared>0</bShared>
+    </File>
+    <File>
+      <GroupNumber>3</GroupNumber>
+      <FileNumber>29</FileNumber>
+      <FileType>1</FileType>
+      <tvExp>0</tvExp>
+      <tvExpOptDlg>0</tvExpOptDlg>
+      <bDave2>0</bDave2>
+      <PathWithFileName>../Drivers/STM32L4xx_HAL_Driver/Src/stm32l4xx_hal_uart_ex.c</PathWithFileName>
+      <FilenameWithoutPath>stm32l4xx_hal_uart_ex.c</FilenameWithoutPath>
+      <RteFlg>0</RteFlg>
+      <bShared>0</bShared>
+    </File>
+  </Group>
+
+  <Group>
+    <GroupName>Drivers/CMSIS</GroupName>
+    <tvExp>0</tvExp>
+    <tvExpOptDlg>0</tvExpOptDlg>
+    <cbSel>0</cbSel>
+    <RteFlg>0</RteFlg>
+    <File>
+      <GroupNumber>4</GroupNumber>
+      <FileNumber>30</FileNumber>
+      <FileType>1</FileType>
+      <tvExp>0</tvExp>
+      <tvExpOptDlg>0</tvExpOptDlg>
+      <bDave2>0</bDave2>
+      <PathWithFileName>../Core/Src/system_stm32l4xx.c</PathWithFileName>
+      <FilenameWithoutPath>system_stm32l4xx.c</FilenameWithoutPath>
+      <RteFlg>0</RteFlg>
+      <bShared>0</bShared>
+    </File>
+  </Group>
+
+  <Group>
+    <GroupName>Board</GroupName>
+    <tvExp>1</tvExp>
+    <tvExpOptDlg>0</tvExpOptDlg>
+    <cbSel>0</cbSel>
+    <RteFlg>0</RteFlg>
+    <File>
+      <GroupNumber>5</GroupNumber>
+      <FileNumber>31</FileNumber>
+      <FileType>1</FileType>
+      <tvExp>0</tvExp>
+      <tvExpOptDlg>0</tvExpOptDlg>
+      <bDave2>0</bDave2>
+      <PathWithFileName>..\Board\miscdev.c</PathWithFileName>
+      <FilenameWithoutPath>miscdev.c</FilenameWithoutPath>
+      <RteFlg>0</RteFlg>
+      <bShared>0</bShared>
+    </File>
+    <File>
+      <GroupNumber>5</GroupNumber>
+      <FileNumber>32</FileNumber>
+      <FileType>1</FileType>
+      <tvExp>0</tvExp>
+      <tvExpOptDlg>0</tvExpOptDlg>
+      <bDave2>0</bDave2>
+      <PathWithFileName>..\Board\buzzer.c</PathWithFileName>
+      <FilenameWithoutPath>buzzer.c</FilenameWithoutPath>
+      <RteFlg>0</RteFlg>
+      <bShared>0</bShared>
+    </File>
+    <File>
+      <GroupNumber>5</GroupNumber>
+      <FileNumber>33</FileNumber>
+      <FileType>1</FileType>
+      <tvExp>0</tvExp>
+      <tvExpOptDlg>0</tvExpOptDlg>
+      <bDave2>0</bDave2>
+      <PathWithFileName>..\Board\ds18b20.c</PathWithFileName>
+      <FilenameWithoutPath>ds18b20.c</FilenameWithoutPath>
+      <RteFlg>0</RteFlg>
+      <bShared>0</bShared>
+    </File>
+    <File>
+      <GroupNumber>5</GroupNumber>
+      <FileNumber>34</FileNumber>
+      <FileType>1</FileType>
+      <tvExp>0</tvExp>
+      <tvExpOptDlg>0</tvExpOptDlg>
+      <bDave2>0</bDave2>
+      <PathWithFileName>..\Board\eeprom.c</PathWithFileName>
+      <FilenameWithoutPath>eeprom.c</FilenameWithoutPath>
+      <RteFlg>0</RteFlg>
+      <bShared>0</bShared>
+    </File>
+    <File>
+      <GroupNumber>5</GroupNumber>
+      <FileNumber>35</FileNumber>
+      <FileType>1</FileType>
+      <tvExp>0</tvExp>
+      <tvExpOptDlg>0</tvExpOptDlg>
+      <bDave2>0</bDave2>
+      <PathWithFileName>..\Board\i2c_bitbang.c</PathWithFileName>
+      <FilenameWithoutPath>i2c_bitbang.c</FilenameWithoutPath>
+      <RteFlg>0</RteFlg>
+      <bShared>0</bShared>
+    </File>
+    <File>
+      <GroupNumber>5</GroupNumber>
+      <FileNumber>36</FileNumber>
+      <FileType>1</FileType>
+      <tvExp>0</tvExp>
+      <tvExpOptDlg>0</tvExpOptDlg>
+      <bDave2>0</bDave2>
+      <PathWithFileName>..\Board\isl1208.c</PathWithFileName>
+      <FilenameWithoutPath>isl1208.c</FilenameWithoutPath>
+      <RteFlg>0</RteFlg>
+      <bShared>0</bShared>
+    </File>
+    <File>
+      <GroupNumber>5</GroupNumber>
+      <FileNumber>37</FileNumber>
+      <FileType>1</FileType>
+      <tvExp>0</tvExp>
+      <tvExpOptDlg>0</tvExpOptDlg>
+      <bDave2>0</bDave2>
+      <PathWithFileName>..\Board\oled_drv.c</PathWithFileName>
+      <FilenameWithoutPath>oled_drv.c</FilenameWithoutPath>
+      <RteFlg>0</RteFlg>
+      <bShared>0</bShared>
+    </File>
+    <File>
+      <GroupNumber>5</GroupNumber>
+      <FileNumber>38</FileNumber>
+      <FileType>1</FileType>
+      <tvExp>0</tvExp>
+      <tvExpOptDlg>0</tvExpOptDlg>
+      <bDave2>0</bDave2>
+      <PathWithFileName>..\Board\oled_font.c</PathWithFileName>
+      <FilenameWithoutPath>oled_font.c</FilenameWithoutPath>
+      <RteFlg>0</RteFlg>
+      <bShared>0</bShared>
+    </File>
+    <File>
+      <GroupNumber>5</GroupNumber>
+      <FileNumber>39</FileNumber>
+      <FileType>1</FileType>
+      <tvExp>0</tvExp>
+      <tvExpOptDlg>0</tvExpOptDlg>
+      <bDave2>0</bDave2>
+      <PathWithFileName>..\Board\sht20.c</PathWithFileName>
+      <FilenameWithoutPath>sht20.c</FilenameWithoutPath>
+      <RteFlg>0</RteFlg>
+      <bShared>0</bShared>
+    </File>
+  </Group>
+
+  <Group>
+    <GroupName>::CMSIS</GroupName>
+    <tvExp>0</tvExp>
+    <tvExpOptDlg>0</tvExpOptDlg>
+    <cbSel>0</cbSel>
+    <RteFlg>1</RteFlg>
+  </Group>
+
+</ProjectOpt>
diff --git a/ex2.i2c-bitbang/MDK-ARM/ISKBoard.uvprojx b/ex2.i2c-bitbang/MDK-ARM/ISKBoard.uvprojx
new file mode 100644
index 0000000..3c2e207
--- /dev/null
+++ b/ex2.i2c-bitbang/MDK-ARM/ISKBoard.uvprojx
@@ -0,0 +1,929 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no" ?>
+<Project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="project_projx.xsd">
+
+  <SchemaVersion>2.1</SchemaVersion>
+
+  <Header>### uVision Project, (C) Keil Software</Header>
+
+  <Targets>
+    <Target>
+      <TargetName>ISKBoard</TargetName>
+      <ToolsetNumber>0x4</ToolsetNumber>
+      <ToolsetName>ARM-ADS</ToolsetName>
+      <pCCUsed>6150000::V6.15::ARMCLANG</pCCUsed>
+      <uAC6>1</uAC6>
+      <TargetOption>
+        <TargetCommonOption>
+          <Device>STM32L431RCTx</Device>
+          <Vendor>STMicroelectronics</Vendor>
+          <PackID>Keil.STM32L4xx_DFP.2.5.0</PackID>
+          <PackURL>http://www.keil.com/pack/</PackURL>
+          <Cpu>IRAM(0x20000000-0x2000BFFF) IRAM2(0x10000000-0x10003FFF) IROM(0x8000000-0x803FFFF)  CLOCK(8000000) FPU2 CPUTYPE("Cortex-M4") TZ</Cpu>
+          <FlashUtilSpec></FlashUtilSpec>
+          <StartupFile></StartupFile>
+          <FlashDriverDll></FlashDriverDll>
+          <DeviceId>0</DeviceId>
+          <RegisterFile></RegisterFile>
+          <MemoryEnv></MemoryEnv>
+          <Cmp></Cmp>
+          <Asm></Asm>
+          <Linker></Linker>
+          <OHString></OHString>
+          <InfinionOptionDll></InfinionOptionDll>
+          <SLE66CMisc></SLE66CMisc>
+          <SLE66AMisc></SLE66AMisc>
+          <SLE66LinkerMisc></SLE66LinkerMisc>
+          <SFDFile>$$Device:STM32L431RCTx$CMSIS\SVD\STM32L4x1.svd</SFDFile>
+          <bCustSvd>0</bCustSvd>
+          <UseEnv>0</UseEnv>
+          <BinPath></BinPath>
+          <IncludePath></IncludePath>
+          <LibPath></LibPath>
+          <RegisterFilePath></RegisterFilePath>
+          <DBRegisterFilePath></DBRegisterFilePath>
+          <TargetStatus>
+            <Error>0</Error>
+            <ExitCodeStop>0</ExitCodeStop>
+            <ButtonStop>0</ButtonStop>
+            <NotGenerated>0</NotGenerated>
+            <InvalidFlash>1</InvalidFlash>
+          </TargetStatus>
+          <OutputDirectory>ISKBoard\</OutputDirectory>
+          <OutputName>ISKBoard</OutputName>
+          <CreateExecutable>1</CreateExecutable>
+          <CreateLib>0</CreateLib>
+          <CreateHexFile>1</CreateHexFile>
+          <DebugInformation>1</DebugInformation>
+          <BrowseInformation>1</BrowseInformation>
+          <ListingPath></ListingPath>
+          <HexFormatSelection>1</HexFormatSelection>
+          <Merge32K>0</Merge32K>
+          <CreateBatchFile>0</CreateBatchFile>
+          <BeforeCompile>
+            <RunUserProg1>0</RunUserProg1>
+            <RunUserProg2>0</RunUserProg2>
+            <UserProg1Name></UserProg1Name>
+            <UserProg2Name></UserProg2Name>
+            <UserProg1Dos16Mode>0</UserProg1Dos16Mode>
+            <UserProg2Dos16Mode>0</UserProg2Dos16Mode>
+            <nStopU1X>0</nStopU1X>
+            <nStopU2X>0</nStopU2X>
+          </BeforeCompile>
+          <BeforeMake>
+            <RunUserProg1>0</RunUserProg1>
+            <RunUserProg2>0</RunUserProg2>
+            <UserProg1Name></UserProg1Name>
+            <UserProg2Name></UserProg2Name>
+            <UserProg1Dos16Mode>0</UserProg1Dos16Mode>
+            <UserProg2Dos16Mode>0</UserProg2Dos16Mode>
+            <nStopB1X>0</nStopB1X>
+            <nStopB2X>0</nStopB2X>
+          </BeforeMake>
+          <AfterMake>
+            <RunUserProg1>0</RunUserProg1>
+            <RunUserProg2>1</RunUserProg2>
+            <UserProg1Name></UserProg1Name>
+            <UserProg2Name></UserProg2Name>
+            <UserProg1Dos16Mode>0</UserProg1Dos16Mode>
+            <UserProg2Dos16Mode>0</UserProg2Dos16Mode>
+            <nStopA1X>0</nStopA1X>
+            <nStopA2X>0</nStopA2X>
+          </AfterMake>
+          <SelectedForBatchBuild>1</SelectedForBatchBuild>
+          <SVCSIdString></SVCSIdString>
+        </TargetCommonOption>
+        <CommonProperty>
+          <UseCPPCompiler>0</UseCPPCompiler>
+          <RVCTCodeConst>0</RVCTCodeConst>
+          <RVCTZI>0</RVCTZI>
+          <RVCTOtherData>0</RVCTOtherData>
+          <ModuleSelection>0</ModuleSelection>
+          <IncludeInBuild>1</IncludeInBuild>
+          <AlwaysBuild>0</AlwaysBuild>
+          <GenerateAssemblyFile>0</GenerateAssemblyFile>
+          <AssembleAssemblyFile>0</AssembleAssemblyFile>
+          <PublicsOnly>0</PublicsOnly>
+          <StopOnExitCode>3</StopOnExitCode>
+          <CustomArgument></CustomArgument>
+          <IncludeLibraryModules></IncludeLibraryModules>
+          <ComprImg>0</ComprImg>
+        </CommonProperty>
+        <DllOption>
+          <SimDllName>SARMCM3.DLL</SimDllName>
+          <SimDllArguments>-REMAP -MPU</SimDllArguments>
+          <SimDlgDll>DCM.DLL</SimDlgDll>
+          <SimDlgDllArguments>-pCM4</SimDlgDllArguments>
+          <TargetDllName>SARMCM3.DLL</TargetDllName>
+          <TargetDllArguments>-MPU</TargetDllArguments>
+          <TargetDlgDll>TCM.DLL</TargetDlgDll>
+          <TargetDlgDllArguments>-pCM4</TargetDlgDllArguments>
+        </DllOption>
+        <DebugOption>
+          <OPTHX>
+            <HexSelection>1</HexSelection>
+            <HexRangeLowAddress>0</HexRangeLowAddress>
+            <HexRangeHighAddress>0</HexRangeHighAddress>
+            <HexOffset>0</HexOffset>
+            <Oh166RecLen>16</Oh166RecLen>
+          </OPTHX>
+        </DebugOption>
+        <Utilities>
+          <Flash1>
+            <UseTargetDll>1</UseTargetDll>
+            <UseExternalTool>0</UseExternalTool>
+            <RunIndependent>0</RunIndependent>
+            <UpdateFlashBeforeDebugging>1</UpdateFlashBeforeDebugging>
+            <Capability>1</Capability>
+            <DriverSelection>4101</DriverSelection>
+          </Flash1>
+          <bUseTDR>1</bUseTDR>
+          <Flash2>BIN\UL2V8M.DLL</Flash2>
+          <Flash3>"" ()</Flash3>
+          <Flash4></Flash4>
+          <pFcarmOut></pFcarmOut>
+          <pFcarmGrp></pFcarmGrp>
+          <pFcArmRoot></pFcArmRoot>
+          <FcArmLst>0</FcArmLst>
+        </Utilities>
+        <TargetArmAds>
+          <ArmAdsMisc>
+            <GenerateListings>0</GenerateListings>
+            <asHll>1</asHll>
+            <asAsm>1</asAsm>
+            <asMacX>1</asMacX>
+            <asSyms>1</asSyms>
+            <asFals>1</asFals>
+            <asDbgD>1</asDbgD>
+            <asForm>1</asForm>
+            <ldLst>0</ldLst>
+            <ldmm>1</ldmm>
+            <ldXref>1</ldXref>
+            <BigEnd>0</BigEnd>
+            <AdsALst>1</AdsALst>
+            <AdsACrf>1</AdsACrf>
+            <AdsANop>0</AdsANop>
+            <AdsANot>0</AdsANot>
+            <AdsLLst>1</AdsLLst>
+            <AdsLmap>1</AdsLmap>
+            <AdsLcgr>1</AdsLcgr>
+            <AdsLsym>1</AdsLsym>
+            <AdsLszi>1</AdsLszi>
+            <AdsLtoi>1</AdsLtoi>
+            <AdsLsun>1</AdsLsun>
+            <AdsLven>1</AdsLven>
+            <AdsLsxf>1</AdsLsxf>
+            <RvctClst>0</RvctClst>
+            <GenPPlst>0</GenPPlst>
+            <AdsCpuType>"Cortex-M4"</AdsCpuType>
+            <RvctDeviceName></RvctDeviceName>
+            <mOS>0</mOS>
+            <uocRom>0</uocRom>
+            <uocRam>0</uocRam>
+            <hadIROM>1</hadIROM>
+            <hadIRAM>1</hadIRAM>
+            <hadXRAM>0</hadXRAM>
+            <uocXRam>0</uocXRam>
+            <RvdsVP>2</RvdsVP>
+            <RvdsMve>0</RvdsMve>
+            <RvdsCdeCp>0</RvdsCdeCp>
+            <hadIRAM2>1</hadIRAM2>
+            <hadIROM2>0</hadIROM2>
+            <StupSel>8</StupSel>
+            <useUlib>1</useUlib>
+            <EndSel>0</EndSel>
+            <uLtcg>0</uLtcg>
+            <nSecure>0</nSecure>
+            <RoSelD>3</RoSelD>
+            <RwSelD>4</RwSelD>
+            <CodeSel>0</CodeSel>
+            <OptFeed>0</OptFeed>
+            <NoZi1>0</NoZi1>
+            <NoZi2>0</NoZi2>
+            <NoZi3>0</NoZi3>
+            <NoZi4>0</NoZi4>
+            <NoZi5>0</NoZi5>
+            <Ro1Chk>0</Ro1Chk>
+            <Ro2Chk>0</Ro2Chk>
+            <Ro3Chk>0</Ro3Chk>
+            <Ir1Chk>1</Ir1Chk>
+            <Ir2Chk>0</Ir2Chk>
+            <Ra1Chk>0</Ra1Chk>
+            <Ra2Chk>0</Ra2Chk>
+            <Ra3Chk>0</Ra3Chk>
+            <Im1Chk>1</Im1Chk>
+            <Im2Chk>1</Im2Chk>
+            <OnChipMemories>
+              <Ocm1>
+                <Type>0</Type>
+                <StartAddress>0x0</StartAddress>
+                <Size>0x0</Size>
+              </Ocm1>
+              <Ocm2>
+                <Type>0</Type>
+                <StartAddress>0x0</StartAddress>
+                <Size>0x0</Size>
+              </Ocm2>
+              <Ocm3>
+                <Type>0</Type>
+                <StartAddress>0x0</StartAddress>
+                <Size>0x0</Size>
+              </Ocm3>
+              <Ocm4>
+                <Type>0</Type>
+                <StartAddress>0x0</StartAddress>
+                <Size>0x0</Size>
+              </Ocm4>
+              <Ocm5>
+                <Type>0</Type>
+                <StartAddress>0x0</StartAddress>
+                <Size>0x0</Size>
+              </Ocm5>
+              <Ocm6>
+                <Type>0</Type>
+                <StartAddress>0x0</StartAddress>
+                <Size>0x0</Size>
+              </Ocm6>
+              <IRAM>
+                <Type>0</Type>
+                <StartAddress>0x20000000</StartAddress>
+                <Size>0xc000</Size>
+              </IRAM>
+              <IROM>
+                <Type>1</Type>
+                <StartAddress>0x8000000</StartAddress>
+                <Size>0x40000</Size>
+              </IROM>
+              <XRAM>
+                <Type>0</Type>
+                <StartAddress>0x0</StartAddress>
+                <Size>0x0</Size>
+              </XRAM>
+              <OCR_RVCT1>
+                <Type>1</Type>
+                <StartAddress>0x0</StartAddress>
+                <Size>0x0</Size>
+              </OCR_RVCT1>
+              <OCR_RVCT2>
+                <Type>1</Type>
+                <StartAddress>0x0</StartAddress>
+                <Size>0x0</Size>
+              </OCR_RVCT2>
+              <OCR_RVCT3>
+                <Type>1</Type>
+                <StartAddress>0x0</StartAddress>
+                <Size>0x0</Size>
+              </OCR_RVCT3>
+              <OCR_RVCT4>
+                <Type>1</Type>
+                <StartAddress>0x8000000</StartAddress>
+                <Size>0x40000</Size>
+              </OCR_RVCT4>
+              <OCR_RVCT5>
+                <Type>1</Type>
+                <StartAddress>0x0</StartAddress>
+                <Size>0x0</Size>
+              </OCR_RVCT5>
+              <OCR_RVCT6>
+                <Type>0</Type>
+                <StartAddress>0x0</StartAddress>
+                <Size>0x0</Size>
+              </OCR_RVCT6>
+              <OCR_RVCT7>
+                <Type>0</Type>
+                <StartAddress>0x0</StartAddress>
+                <Size>0x0</Size>
+              </OCR_RVCT7>
+              <OCR_RVCT8>
+                <Type>0</Type>
+                <StartAddress>0x0</StartAddress>
+                <Size>0x0</Size>
+              </OCR_RVCT8>
+              <OCR_RVCT9>
+                <Type>0</Type>
+                <StartAddress>0x20000000</StartAddress>
+                <Size>0xc000</Size>
+              </OCR_RVCT9>
+              <OCR_RVCT10>
+                <Type>0</Type>
+                <StartAddress>0x10000000</StartAddress>
+                <Size>0x4000</Size>
+              </OCR_RVCT10>
+            </OnChipMemories>
+            <RvctStartVector></RvctStartVector>
+          </ArmAdsMisc>
+          <Cads>
+            <interw>1</interw>
+            <Optim>2</Optim>
+            <oTime>0</oTime>
+            <SplitLS>0</SplitLS>
+            <OneElfS>1</OneElfS>
+            <Strict>0</Strict>
+            <EnumInt>0</EnumInt>
+            <PlainCh>0</PlainCh>
+            <Ropi>0</Ropi>
+            <Rwpi>0</Rwpi>
+            <wLevel>3</wLevel>
+            <uThumb>0</uThumb>
+            <uSurpInc>0</uSurpInc>
+            <uC99>1</uC99>
+            <uGnu>0</uGnu>
+            <useXO>0</useXO>
+            <v6Lang>3</v6Lang>
+            <v6LangP>3</v6LangP>
+            <vShortEn>1</vShortEn>
+            <vShortWch>1</vShortWch>
+            <v6Lto>0</v6Lto>
+            <v6WtE>0</v6WtE>
+            <v6Rtti>0</v6Rtti>
+            <VariousControls>
+              <MiscControls></MiscControls>
+              <Define>USE_HAL_DRIVER,STM32L431xx</Define>
+              <Undefine></Undefine>
+              <IncludePath>../Core/Inc;../Drivers/STM32L4xx_HAL_Driver/Inc;../Drivers/STM32L4xx_HAL_Driver/Inc/Legacy;../Drivers/CMSIS/Device/ST/STM32L4xx/Include;../Drivers/CMSIS/Include;../Board</IncludePath>
+            </VariousControls>
+          </Cads>
+          <Aads>
+            <interw>1</interw>
+            <Ropi>0</Ropi>
+            <Rwpi>0</Rwpi>
+            <thumb>0</thumb>
+            <SplitLS>0</SplitLS>
+            <SwStkChk>0</SwStkChk>
+            <NoWarn>0</NoWarn>
+            <uSurpInc>0</uSurpInc>
+            <useXO>0</useXO>
+            <ClangAsOpt>1</ClangAsOpt>
+            <VariousControls>
+              <MiscControls></MiscControls>
+              <Define></Define>
+              <Undefine></Undefine>
+              <IncludePath></IncludePath>
+            </VariousControls>
+          </Aads>
+          <LDads>
+            <umfTarg>1</umfTarg>
+            <Ropi>0</Ropi>
+            <Rwpi>0</Rwpi>
+            <noStLib>0</noStLib>
+            <RepFail>1</RepFail>
+            <useFile>0</useFile>
+            <TextAddressRange></TextAddressRange>
+            <DataAddressRange></DataAddressRange>
+            <pXoBase></pXoBase>
+            <ScatterFile></ScatterFile>
+            <IncludeLibs></IncludeLibs>
+            <IncludeLibsPath></IncludeLibsPath>
+            <Misc></Misc>
+            <LinkerInputFile></LinkerInputFile>
+            <DisabledWarnings></DisabledWarnings>
+          </LDads>
+        </TargetArmAds>
+      </TargetOption>
+      <Groups>
+        <Group>
+          <GroupName>Application/MDK-ARM</GroupName>
+          <Files>
+            <File>
+              <FileName>startup_stm32l431xx.s</FileName>
+              <FileType>2</FileType>
+              <FilePath>startup_stm32l431xx.s</FilePath>
+            </File>
+          </Files>
+        </Group>
+        <Group>
+          <GroupName>Application/User/Core</GroupName>
+          <Files>
+            <File>
+              <FileName>main.c</FileName>
+              <FileType>1</FileType>
+              <FilePath>../Core/Src/main.c</FilePath>
+            </File>
+            <File>
+              <FileName>gpio.c</FileName>
+              <FileType>1</FileType>
+              <FilePath>../Core/Src/gpio.c</FilePath>
+            </File>
+            <File>
+              <FileName>adc.c</FileName>
+              <FileType>1</FileType>
+              <FilePath>../Core/Src/adc.c</FilePath>
+              <FileOption>
+                <CommonProperty>
+                  <UseCPPCompiler>2</UseCPPCompiler>
+                  <RVCTCodeConst>0</RVCTCodeConst>
+                  <RVCTZI>0</RVCTZI>
+                  <RVCTOtherData>0</RVCTOtherData>
+                  <ModuleSelection>0</ModuleSelection>
+                  <IncludeInBuild>1</IncludeInBuild>
+                  <AlwaysBuild>2</AlwaysBuild>
+                  <GenerateAssemblyFile>2</GenerateAssemblyFile>
+                  <AssembleAssemblyFile>2</AssembleAssemblyFile>
+                  <PublicsOnly>2</PublicsOnly>
+                  <StopOnExitCode>11</StopOnExitCode>
+                  <CustomArgument></CustomArgument>
+                  <IncludeLibraryModules></IncludeLibraryModules>
+                  <ComprImg>1</ComprImg>
+                </CommonProperty>
+                <FileArmAds>
+                  <Cads>
+                    <interw>2</interw>
+                    <Optim>0</Optim>
+                    <oTime>2</oTime>
+                    <SplitLS>2</SplitLS>
+                    <OneElfS>2</OneElfS>
+                    <Strict>2</Strict>
+                    <EnumInt>2</EnumInt>
+                    <PlainCh>2</PlainCh>
+                    <Ropi>2</Ropi>
+                    <Rwpi>2</Rwpi>
+                    <wLevel>0</wLevel>
+                    <uThumb>2</uThumb>
+                    <uSurpInc>2</uSurpInc>
+                    <uC99>2</uC99>
+                    <uGnu>2</uGnu>
+                    <useXO>2</useXO>
+                    <v6Lang>0</v6Lang>
+                    <v6LangP>0</v6LangP>
+                    <vShortEn>2</vShortEn>
+                    <vShortWch>2</vShortWch>
+                    <v6Lto>2</v6Lto>
+                    <v6WtE>2</v6WtE>
+                    <v6Rtti>2</v6Rtti>
+                    <VariousControls>
+                      <MiscControls></MiscControls>
+                      <Define></Define>
+                      <Undefine></Undefine>
+                      <IncludePath></IncludePath>
+                    </VariousControls>
+                  </Cads>
+                </FileArmAds>
+              </FileOption>
+            </File>
+            <File>
+              <FileName>tim.c</FileName>
+              <FileType>1</FileType>
+              <FilePath>../Core/Src/tim.c</FilePath>
+              <FileOption>
+                <CommonProperty>
+                  <UseCPPCompiler>2</UseCPPCompiler>
+                  <RVCTCodeConst>0</RVCTCodeConst>
+                  <RVCTZI>0</RVCTZI>
+                  <RVCTOtherData>0</RVCTOtherData>
+                  <ModuleSelection>0</ModuleSelection>
+                  <IncludeInBuild>1</IncludeInBuild>
+                  <AlwaysBuild>2</AlwaysBuild>
+                  <GenerateAssemblyFile>2</GenerateAssemblyFile>
+                  <AssembleAssemblyFile>2</AssembleAssemblyFile>
+                  <PublicsOnly>2</PublicsOnly>
+                  <StopOnExitCode>11</StopOnExitCode>
+                  <CustomArgument></CustomArgument>
+                  <IncludeLibraryModules></IncludeLibraryModules>
+                  <ComprImg>1</ComprImg>
+                </CommonProperty>
+                <FileArmAds>
+                  <Cads>
+                    <interw>2</interw>
+                    <Optim>0</Optim>
+                    <oTime>2</oTime>
+                    <SplitLS>2</SplitLS>
+                    <OneElfS>2</OneElfS>
+                    <Strict>2</Strict>
+                    <EnumInt>2</EnumInt>
+                    <PlainCh>2</PlainCh>
+                    <Ropi>2</Ropi>
+                    <Rwpi>2</Rwpi>
+                    <wLevel>0</wLevel>
+                    <uThumb>2</uThumb>
+                    <uSurpInc>2</uSurpInc>
+                    <uC99>2</uC99>
+                    <uGnu>2</uGnu>
+                    <useXO>2</useXO>
+                    <v6Lang>0</v6Lang>
+                    <v6LangP>0</v6LangP>
+                    <vShortEn>2</vShortEn>
+                    <vShortWch>2</vShortWch>
+                    <v6Lto>2</v6Lto>
+                    <v6WtE>2</v6WtE>
+                    <v6Rtti>2</v6Rtti>
+                    <VariousControls>
+                      <MiscControls></MiscControls>
+                      <Define></Define>
+                      <Undefine></Undefine>
+                      <IncludePath></IncludePath>
+                    </VariousControls>
+                  </Cads>
+                </FileArmAds>
+              </FileOption>
+            </File>
+            <File>
+              <FileName>usart.c</FileName>
+              <FileType>1</FileType>
+              <FilePath>../Core/Src/usart.c</FilePath>
+            </File>
+            <File>
+              <FileName>stm32l4xx_it.c</FileName>
+              <FileType>1</FileType>
+              <FilePath>../Core/Src/stm32l4xx_it.c</FilePath>
+            </File>
+            <File>
+              <FileName>stm32l4xx_hal_msp.c</FileName>
+              <FileType>1</FileType>
+              <FilePath>../Core/Src/stm32l4xx_hal_msp.c</FilePath>
+            </File>
+          </Files>
+        </Group>
+        <Group>
+          <GroupName>Drivers/STM32L4xx_HAL_Driver</GroupName>
+          <Files>
+            <File>
+              <FileName>stm32l4xx_hal_adc.c</FileName>
+              <FileType>1</FileType>
+              <FilePath>../Drivers/STM32L4xx_HAL_Driver/Src/stm32l4xx_hal_adc.c</FilePath>
+              <FileOption>
+                <CommonProperty>
+                  <UseCPPCompiler>2</UseCPPCompiler>
+                  <RVCTCodeConst>0</RVCTCodeConst>
+                  <RVCTZI>0</RVCTZI>
+                  <RVCTOtherData>0</RVCTOtherData>
+                  <ModuleSelection>0</ModuleSelection>
+                  <IncludeInBuild>1</IncludeInBuild>
+                  <AlwaysBuild>2</AlwaysBuild>
+                  <GenerateAssemblyFile>2</GenerateAssemblyFile>
+                  <AssembleAssemblyFile>2</AssembleAssemblyFile>
+                  <PublicsOnly>2</PublicsOnly>
+                  <StopOnExitCode>11</StopOnExitCode>
+                  <CustomArgument></CustomArgument>
+                  <IncludeLibraryModules></IncludeLibraryModules>
+                  <ComprImg>1</ComprImg>
+                </CommonProperty>
+                <FileArmAds>
+                  <Cads>
+                    <interw>2</interw>
+                    <Optim>0</Optim>
+                    <oTime>2</oTime>
+                    <SplitLS>2</SplitLS>
+                    <OneElfS>2</OneElfS>
+                    <Strict>2</Strict>
+                    <EnumInt>2</EnumInt>
+                    <PlainCh>2</PlainCh>
+                    <Ropi>2</Ropi>
+                    <Rwpi>2</Rwpi>
+                    <wLevel>0</wLevel>
+                    <uThumb>2</uThumb>
+                    <uSurpInc>2</uSurpInc>
+                    <uC99>2</uC99>
+                    <uGnu>2</uGnu>
+                    <useXO>2</useXO>
+                    <v6Lang>0</v6Lang>
+                    <v6LangP>0</v6LangP>
+                    <vShortEn>2</vShortEn>
+                    <vShortWch>2</vShortWch>
+                    <v6Lto>2</v6Lto>
+                    <v6WtE>2</v6WtE>
+                    <v6Rtti>2</v6Rtti>
+                    <VariousControls>
+                      <MiscControls></MiscControls>
+                      <Define></Define>
+                      <Undefine></Undefine>
+                      <IncludePath></IncludePath>
+                    </VariousControls>
+                  </Cads>
+                </FileArmAds>
+              </FileOption>
+            </File>
+            <File>
+              <FileName>stm32l4xx_hal_adc_ex.c</FileName>
+              <FileType>1</FileType>
+              <FilePath>../Drivers/STM32L4xx_HAL_Driver/Src/stm32l4xx_hal_adc_ex.c</FilePath>
+              <FileOption>
+                <CommonProperty>
+                  <UseCPPCompiler>2</UseCPPCompiler>
+                  <RVCTCodeConst>0</RVCTCodeConst>
+                  <RVCTZI>0</RVCTZI>
+                  <RVCTOtherData>0</RVCTOtherData>
+                  <ModuleSelection>0</ModuleSelection>
+                  <IncludeInBuild>1</IncludeInBuild>
+                  <AlwaysBuild>2</AlwaysBuild>
+                  <GenerateAssemblyFile>2</GenerateAssemblyFile>
+                  <AssembleAssemblyFile>2</AssembleAssemblyFile>
+                  <PublicsOnly>2</PublicsOnly>
+                  <StopOnExitCode>11</StopOnExitCode>
+                  <CustomArgument></CustomArgument>
+                  <IncludeLibraryModules></IncludeLibraryModules>
+                  <ComprImg>1</ComprImg>
+                </CommonProperty>
+                <FileArmAds>
+                  <Cads>
+                    <interw>2</interw>
+                    <Optim>0</Optim>
+                    <oTime>2</oTime>
+                    <SplitLS>2</SplitLS>
+                    <OneElfS>2</OneElfS>
+                    <Strict>2</Strict>
+                    <EnumInt>2</EnumInt>
+                    <PlainCh>2</PlainCh>
+                    <Ropi>2</Ropi>
+                    <Rwpi>2</Rwpi>
+                    <wLevel>0</wLevel>
+                    <uThumb>2</uThumb>
+                    <uSurpInc>2</uSurpInc>
+                    <uC99>2</uC99>
+                    <uGnu>2</uGnu>
+                    <useXO>2</useXO>
+                    <v6Lang>0</v6Lang>
+                    <v6LangP>0</v6LangP>
+                    <vShortEn>2</vShortEn>
+                    <vShortWch>2</vShortWch>
+                    <v6Lto>2</v6Lto>
+                    <v6WtE>2</v6WtE>
+                    <v6Rtti>2</v6Rtti>
+                    <VariousControls>
+                      <MiscControls></MiscControls>
+                      <Define></Define>
+                      <Undefine></Undefine>
+                      <IncludePath></IncludePath>
+                    </VariousControls>
+                  </Cads>
+                </FileArmAds>
+              </FileOption>
+            </File>
+            <File>
+              <FileName>stm32l4xx_hal.c</FileName>
+              <FileType>1</FileType>
+              <FilePath>../Drivers/STM32L4xx_HAL_Driver/Src/stm32l4xx_hal.c</FilePath>
+            </File>
+            <File>
+              <FileName>stm32l4xx_hal_rcc.c</FileName>
+              <FileType>1</FileType>
+              <FilePath>../Drivers/STM32L4xx_HAL_Driver/Src/stm32l4xx_hal_rcc.c</FilePath>
+            </File>
+            <File>
+              <FileName>stm32l4xx_hal_rcc_ex.c</FileName>
+              <FileType>1</FileType>
+              <FilePath>../Drivers/STM32L4xx_HAL_Driver/Src/stm32l4xx_hal_rcc_ex.c</FilePath>
+            </File>
+            <File>
+              <FileName>stm32l4xx_hal_flash.c</FileName>
+              <FileType>1</FileType>
+              <FilePath>../Drivers/STM32L4xx_HAL_Driver/Src/stm32l4xx_hal_flash.c</FilePath>
+            </File>
+            <File>
+              <FileName>stm32l4xx_hal_flash_ex.c</FileName>
+              <FileType>1</FileType>
+              <FilePath>../Drivers/STM32L4xx_HAL_Driver/Src/stm32l4xx_hal_flash_ex.c</FilePath>
+            </File>
+            <File>
+              <FileName>stm32l4xx_hal_flash_ramfunc.c</FileName>
+              <FileType>1</FileType>
+              <FilePath>../Drivers/STM32L4xx_HAL_Driver/Src/stm32l4xx_hal_flash_ramfunc.c</FilePath>
+            </File>
+            <File>
+              <FileName>stm32l4xx_hal_gpio.c</FileName>
+              <FileType>1</FileType>
+              <FilePath>../Drivers/STM32L4xx_HAL_Driver/Src/stm32l4xx_hal_gpio.c</FilePath>
+            </File>
+            <File>
+              <FileName>stm32l4xx_hal_i2c.c</FileName>
+              <FileType>1</FileType>
+              <FilePath>../Drivers/STM32L4xx_HAL_Driver/Src/stm32l4xx_hal_i2c.c</FilePath>
+            </File>
+            <File>
+              <FileName>stm32l4xx_hal_i2c_ex.c</FileName>
+              <FileType>1</FileType>
+              <FilePath>../Drivers/STM32L4xx_HAL_Driver/Src/stm32l4xx_hal_i2c_ex.c</FilePath>
+            </File>
+            <File>
+              <FileName>stm32l4xx_hal_dma.c</FileName>
+              <FileType>1</FileType>
+              <FilePath>../Drivers/STM32L4xx_HAL_Driver/Src/stm32l4xx_hal_dma.c</FilePath>
+            </File>
+            <File>
+              <FileName>stm32l4xx_hal_dma_ex.c</FileName>
+              <FileType>1</FileType>
+              <FilePath>../Drivers/STM32L4xx_HAL_Driver/Src/stm32l4xx_hal_dma_ex.c</FilePath>
+            </File>
+            <File>
+              <FileName>stm32l4xx_hal_pwr.c</FileName>
+              <FileType>1</FileType>
+              <FilePath>../Drivers/STM32L4xx_HAL_Driver/Src/stm32l4xx_hal_pwr.c</FilePath>
+            </File>
+            <File>
+              <FileName>stm32l4xx_hal_pwr_ex.c</FileName>
+              <FileType>1</FileType>
+              <FilePath>../Drivers/STM32L4xx_HAL_Driver/Src/stm32l4xx_hal_pwr_ex.c</FilePath>
+            </File>
+            <File>
+              <FileName>stm32l4xx_hal_cortex.c</FileName>
+              <FileType>1</FileType>
+              <FilePath>../Drivers/STM32L4xx_HAL_Driver/Src/stm32l4xx_hal_cortex.c</FilePath>
+            </File>
+            <File>
+              <FileName>stm32l4xx_hal_exti.c</FileName>
+              <FileType>1</FileType>
+              <FilePath>../Drivers/STM32L4xx_HAL_Driver/Src/stm32l4xx_hal_exti.c</FilePath>
+            </File>
+            <File>
+              <FileName>stm32l4xx_hal_tim.c</FileName>
+              <FileType>1</FileType>
+              <FilePath>../Drivers/STM32L4xx_HAL_Driver/Src/stm32l4xx_hal_tim.c</FilePath>
+              <FileOption>
+                <CommonProperty>
+                  <UseCPPCompiler>2</UseCPPCompiler>
+                  <RVCTCodeConst>0</RVCTCodeConst>
+                  <RVCTZI>0</RVCTZI>
+                  <RVCTOtherData>0</RVCTOtherData>
+                  <ModuleSelection>0</ModuleSelection>
+                  <IncludeInBuild>1</IncludeInBuild>
+                  <AlwaysBuild>2</AlwaysBuild>
+                  <GenerateAssemblyFile>2</GenerateAssemblyFile>
+                  <AssembleAssemblyFile>2</AssembleAssemblyFile>
+                  <PublicsOnly>2</PublicsOnly>
+                  <StopOnExitCode>11</StopOnExitCode>
+                  <CustomArgument></CustomArgument>
+                  <IncludeLibraryModules></IncludeLibraryModules>
+                  <ComprImg>1</ComprImg>
+                </CommonProperty>
+                <FileArmAds>
+                  <Cads>
+                    <interw>2</interw>
+                    <Optim>0</Optim>
+                    <oTime>2</oTime>
+                    <SplitLS>2</SplitLS>
+                    <OneElfS>2</OneElfS>
+                    <Strict>2</Strict>
+                    <EnumInt>2</EnumInt>
+                    <PlainCh>2</PlainCh>
+                    <Ropi>2</Ropi>
+                    <Rwpi>2</Rwpi>
+                    <wLevel>0</wLevel>
+                    <uThumb>2</uThumb>
+                    <uSurpInc>2</uSurpInc>
+                    <uC99>2</uC99>
+                    <uGnu>2</uGnu>
+                    <useXO>2</useXO>
+                    <v6Lang>0</v6Lang>
+                    <v6LangP>0</v6LangP>
+                    <vShortEn>2</vShortEn>
+                    <vShortWch>2</vShortWch>
+                    <v6Lto>2</v6Lto>
+                    <v6WtE>2</v6WtE>
+                    <v6Rtti>2</v6Rtti>
+                    <VariousControls>
+                      <MiscControls></MiscControls>
+                      <Define></Define>
+                      <Undefine></Undefine>
+                      <IncludePath></IncludePath>
+                    </VariousControls>
+                  </Cads>
+                </FileArmAds>
+              </FileOption>
+            </File>
+            <File>
+              <FileName>stm32l4xx_hal_tim_ex.c</FileName>
+              <FileType>1</FileType>
+              <FilePath>../Drivers/STM32L4xx_HAL_Driver/Src/stm32l4xx_hal_tim_ex.c</FilePath>
+              <FileOption>
+                <CommonProperty>
+                  <UseCPPCompiler>2</UseCPPCompiler>
+                  <RVCTCodeConst>0</RVCTCodeConst>
+                  <RVCTZI>0</RVCTZI>
+                  <RVCTOtherData>0</RVCTOtherData>
+                  <ModuleSelection>0</ModuleSelection>
+                  <IncludeInBuild>1</IncludeInBuild>
+                  <AlwaysBuild>2</AlwaysBuild>
+                  <GenerateAssemblyFile>2</GenerateAssemblyFile>
+                  <AssembleAssemblyFile>2</AssembleAssemblyFile>
+                  <PublicsOnly>2</PublicsOnly>
+                  <StopOnExitCode>11</StopOnExitCode>
+                  <CustomArgument></CustomArgument>
+                  <IncludeLibraryModules></IncludeLibraryModules>
+                  <ComprImg>1</ComprImg>
+                </CommonProperty>
+                <FileArmAds>
+                  <Cads>
+                    <interw>2</interw>
+                    <Optim>0</Optim>
+                    <oTime>2</oTime>
+                    <SplitLS>2</SplitLS>
+                    <OneElfS>2</OneElfS>
+                    <Strict>2</Strict>
+                    <EnumInt>2</EnumInt>
+                    <PlainCh>2</PlainCh>
+                    <Ropi>2</Ropi>
+                    <Rwpi>2</Rwpi>
+                    <wLevel>0</wLevel>
+                    <uThumb>2</uThumb>
+                    <uSurpInc>2</uSurpInc>
+                    <uC99>2</uC99>
+                    <uGnu>2</uGnu>
+                    <useXO>2</useXO>
+                    <v6Lang>0</v6Lang>
+                    <v6LangP>0</v6LangP>
+                    <vShortEn>2</vShortEn>
+                    <vShortWch>2</vShortWch>
+                    <v6Lto>2</v6Lto>
+                    <v6WtE>2</v6WtE>
+                    <v6Rtti>2</v6Rtti>
+                    <VariousControls>
+                      <MiscControls></MiscControls>
+                      <Define></Define>
+                      <Undefine></Undefine>
+                      <IncludePath></IncludePath>
+                    </VariousControls>
+                  </Cads>
+                </FileArmAds>
+              </FileOption>
+            </File>
+            <File>
+              <FileName>stm32l4xx_hal_uart.c</FileName>
+              <FileType>1</FileType>
+              <FilePath>../Drivers/STM32L4xx_HAL_Driver/Src/stm32l4xx_hal_uart.c</FilePath>
+            </File>
+            <File>
+              <FileName>stm32l4xx_hal_uart_ex.c</FileName>
+              <FileType>1</FileType>
+              <FilePath>../Drivers/STM32L4xx_HAL_Driver/Src/stm32l4xx_hal_uart_ex.c</FilePath>
+            </File>
+          </Files>
+        </Group>
+        <Group>
+          <GroupName>Drivers/CMSIS</GroupName>
+          <Files>
+            <File>
+              <FileName>system_stm32l4xx.c</FileName>
+              <FileType>1</FileType>
+              <FilePath>../Core/Src/system_stm32l4xx.c</FilePath>
+            </File>
+          </Files>
+        </Group>
+        <Group>
+          <GroupName>Board</GroupName>
+          <Files>
+            <File>
+              <FileName>miscdev.c</FileName>
+              <FileType>1</FileType>
+              <FilePath>..\Board\miscdev.c</FilePath>
+            </File>
+            <File>
+              <FileName>buzzer.c</FileName>
+              <FileType>1</FileType>
+              <FilePath>..\Board\buzzer.c</FilePath>
+            </File>
+            <File>
+              <FileName>ds18b20.c</FileName>
+              <FileType>1</FileType>
+              <FilePath>..\Board\ds18b20.c</FilePath>
+            </File>
+            <File>
+              <FileName>eeprom.c</FileName>
+              <FileType>1</FileType>
+              <FilePath>..\Board\eeprom.c</FilePath>
+            </File>
+            <File>
+              <FileName>i2c_bitbang.c</FileName>
+              <FileType>1</FileType>
+              <FilePath>..\Board\i2c_bitbang.c</FilePath>
+            </File>
+            <File>
+              <FileName>isl1208.c</FileName>
+              <FileType>1</FileType>
+              <FilePath>..\Board\isl1208.c</FilePath>
+            </File>
+            <File>
+              <FileName>oled_drv.c</FileName>
+              <FileType>1</FileType>
+              <FilePath>..\Board\oled_drv.c</FilePath>
+            </File>
+            <File>
+              <FileName>oled_font.c</FileName>
+              <FileType>1</FileType>
+              <FilePath>..\Board\oled_font.c</FilePath>
+            </File>
+            <File>
+              <FileName>sht20.c</FileName>
+              <FileType>1</FileType>
+              <FilePath>..\Board\sht20.c</FilePath>
+            </File>
+          </Files>
+        </Group>
+        <Group>
+          <GroupName>::CMSIS</GroupName>
+        </Group>
+      </Groups>
+    </Target>
+  </Targets>
+
+  <RTE>
+    <apis/>
+    <components>
+      <component Cclass="CMSIS" Cgroup="CORE" Cvendor="ARM" Cversion="6.2.0" condition="ARMv6_7_8-M Device" ymlID="CMSIS:CORE">
+        <package name="CMSIS" schemaVersion="1.7.53" url="https://www.keil.com/pack/" vendor="ARM" version="6.3.0"/>
+        <targetInfos>
+          <targetInfo name="ISKBoard"/>
+        </targetInfos>
+      </component>
+    </components>
+    <files/>
+  </RTE>
+
+</Project>
diff --git a/ex2.i2c-bitbang/do_clean.bat b/ex2.i2c-bitbang/do_clean.bat
new file mode 100644
index 0000000..2ab17eb
--- /dev/null
+++ b/ex2.i2c-bitbang/do_clean.bat
@@ -0,0 +1,39 @@
+@echo off
+setlocal
+
+cd /d "%~dp0"
+
+REM ===== Clean Core (keep main.c) =====
+if exist "Core\Src\main.c" (
+    copy /y "Core\Src\main.c" "main.c" >nul
+    rmdir /s /q "Core" 2>nul
+    mkdir "Core\Src" 2>nul
+    copy /y "main.c" "Core\Src\main.c" >nul
+    del /f /q "main.c"
+)
+
+REM ===== Clean artifacts =====
+call :del_file .mxproject
+call :del_file .cproject
+call :del_file .project
+call :del_file .clangd
+call :del_file *.json
+call :del_file *.ld
+call :del_file *.s
+
+call :del_dir .settings
+call :del_dir Drivers
+call :del_dir Debug
+call :del_dir Release
+call :del_dir build
+call :del_dir cmake
+
+exit /b
+
+:del_file
+if exist "%~1" del /f /q "%~1"
+exit /b
+
+:del_dir
+if exist "%~1\" rmdir /s /q "%~1"
+exit /b
\ No newline at end of file

--
Gitblit v1.10.0