guowenxue
4 days ago d42fb887237e30d5026e006a3774d6b0a215b829
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
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();
}