shuichan_new/STM32F103_App1/HardWare/OLED/oled.c

367 lines
11 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/* oled.c - 128x64 OLED(SSD1306, I2C) + 晶联讯 GB2312 字库 IC(SPI) 驱动
* 屏幕: I2C 接口, 与 EEPROM 共用软件 I2C 总线(SCL=PB5 SDA=PB4), 无 RST 脚;
* 与 24C02 的跨任务互斥用 storage_lock(每个完整显示事务内)
* 字库: SPI, 与 W25Q64 共用三线(CLK=PB6 DI=PB7 DO=PB9), CS2=PC12 独立片选;
* DO(PB9) 固定为输入(W25Q64_Init 已配置), 字库读取需持 storage_lock 与 Flash 互斥 */
#include "oled.h"
#include "qrcodegen.h"
#include "iic.h"
#include "app_loop.h" /* storage_lock/unlock */
#include <string.h>
#define OLED_CLK_PORT GPIOB
#define OLED_CLK_PIN GPIO_PIN_6 /* 字库 CLK, 与 W25Q64 CLK 共线 */
#define OLED_MOSI_PORT GPIOB
#define OLED_MOSI_PIN GPIO_PIN_7 /* 字库 DI, 与 W25Q64 DI 共线 */
#define OLED_FSO_PORT GPIOB
#define OLED_FSO_PIN GPIO_PIN_9 /* 字库 DO(FSO), 与 W25Q64 DO 共线, 恒为输入 */
#define OLED_CS2_PORT GPIOC
#define OLED_CS2_PIN GPIO_PIN_12
#define lcd_mosi(a) HAL_GPIO_WritePin(OLED_MOSI_PORT, OLED_MOSI_PIN, (GPIO_PinState)(a))
#define lcd_clk(a) HAL_GPIO_WritePin(OLED_CLK_PORT, OLED_CLK_PIN, (GPIO_PinState)(a))
#define rom_cs2(a) HAL_GPIO_WritePin(OLED_CS2_PORT, OLED_CS2_PIN, (GPIO_PinState)(a))
#define ROM_OUT HAL_GPIO_ReadPin(OLED_FSO_PORT, OLED_FSO_PIN)
static uint8_t s_row = 0; /* 当前页地址16px 行 × 2 */
static uint8_t s_col = 0; /* 当前列(像素) */
/* 注: 字库 DO(FSO) 与 W25Q64 DO 共线(PB9), 由 W25Q64_Init 固定为输入, 不再做方向切换 */
/*==== GPIO 初始化 ====*/
static void oled_gpio_init(void)
{
GPIO_InitTypeDef g = {0};
__HAL_RCC_GPIOA_CLK_ENABLE();
__HAL_RCC_GPIOB_CLK_ENABLE();
__HAL_RCC_GPIOC_CLK_ENABLE();
/* 屏幕 I2C 不占 GPIO; 字库 CLK(PB6)/DI(PB7) 与 W25Q64 共线(其 Init 已配为输出),
* DO(PB9) 恒为输入(其 Init 已配), 这里只配字库独立片选 CS2(PC12), 默认拉高不选中 */
HAL_GPIO_WritePin(OLED_CS2_PORT, OLED_CS2_PIN, GPIO_PIN_SET);
g.Mode = GPIO_MODE_OUTPUT_PP;
g.Pull = GPIO_PULLUP;
g.Speed = GPIO_SPEED_FREQ_LOW;
g.Pin = OLED_CS2_PIN; HAL_GPIO_Init(OLED_CS2_PORT, &g);
}
/*==== SSD1306 I2C 传输(与 EEPROM 共用软件 I2C 总线) ====*/
#define OLED_I2C_ADDR 0x78 /* 8 位写地址(7 位 0x3C); 模块 SA0 拉高时为 0x7A */
/* 写字节到屏幕dc=0 指令 / dc=1 数据): START+地址+控制字节+数据+STOP */
static void oled_write(uint8_t dc, uint8_t dat)
{
iicStart();
iicSendByteAck(OLED_I2C_ADDR);
iicSendByteAck(dc ? 0x40 : 0x00);
iicSendByteAck(dat);
iicStop();
}
/* 数据流: 一次 START 连续写多字节(清屏/画图用, 比逐字节事务快约 3 倍) */
static void oled_data_begin(void)
{
iicStart();
iicSendByteAck(OLED_I2C_ADDR);
iicSendByteAck(0x40);
}
static void oled_data_byte(uint8_t d) { iicSendByteAck(d); }
static void oled_data_end(void) { iicStop(); }
/*==== 设置页/列地址 ====*/
static void lcd_address(uint8_t page, uint8_t column)
{
oled_write(0, 0xb0 + column); /* 页地址 */
oled_write(0, ((page & 0xf0) >> 4) | 0x10); /* 列地址高 4 位 */
oled_write(0, (page & 0x0f)); /* 列地址低 4 位 */
}
/*==== 开机二维码页: 内容=text(设备 ClientID), 左侧 QR 白底暗码, 右侧 ID 文本 ====
* QR 用 Nayuki qrcodegen 生成(V1~V3 自动, ECC-L, 2px/模块放大), 白底亮块+2 模块静区;
* 右侧 x=64 起分行打印 ID(每行 8 字符, 最多 3 行) */
void oled_show_qrcode(const char *text)
{
static uint8_t qr[qrcodegen_BUFFER_LEN_FOR_VERSION(3)];
static uint8_t tmp[qrcodegen_BUFFER_LEN_FOR_VERSION(3)];
static uint8_t pagebuf[8][64];
char id[21];
strncpy(id, text, 20);
id[20] = 0;
if (!qrcodegen_encodeText(id, tmp, qr, qrcodegen_Ecc_LOW, 1, 3,
qrcodegen_Mask_AUTO, true)) {
return; /* 编码失败(ID 为空/超长)则不显示 */
}
int qs = qrcodegen_getSize(qr); /* 21/25/29 */
int px = qs * 2; /* 2px/模块 -> 42/50/58 */
int x0 = 2;
int y0 = (64 - px) / 2; /* 垂直居中 */
memset(pagebuf, 0, sizeof(pagebuf));
/* 白底(含 2 模块=4px 静区): 点亮矩形区域 */
int lx = x0 - 4, rx = x0 + px + 4;
int ty = y0 - 4, by = y0 + px + 4;
if (lx < 0) lx = 0; if (rx > 64) rx = 64;
if (ty < 0) ty = 0; if (by > 64) by = 64;
for (int y = ty; y < by; y++)
for (int x = lx; x < rx; x++)
pagebuf[y >> 3][x] |= (uint8_t)(1u << (y & 7));
/* QR 暗模块: 对应像素清位 */
for (int r = 0; r < qs; r++) {
for (int c = 0; c < qs; c++) {
if (qrcodegen_getModule(qr, c, r)) {
for (int dy = 0; dy < 2; dy++) {
for (int dx = 0; dx < 2; dx++) {
int y = y0 + r * 2 + dy;
int x = x0 + c * 2 + dx;
pagebuf[y >> 3][x] &= (uint8_t)~(1u << (y & 7));
}
}
}
}
}
/* 写左半屏 64 列(整页覆写, 不影响右侧文本区) */
storage_lock();
for (uint8_t pg = 0; pg < 8; pg++) {
oled_write(0, 0xb0 + pg);
oled_write(0, 0x00);
oled_write(0, 0x10);
oled_data_begin();
for (uint8_t x = 0; x < 64; x++) oled_data_byte(pagebuf[pg][x]);
oled_data_end();
}
storage_unlock();
/* 右侧 ID 文本: x=64 起每行 8 个 ASCII, 最多 3 行 */
int len = (int)strlen(id);
oled_locate(0, 8);
oled_print("ID:");
for (int r = 0; r < 3 && r * 8 < len; r++) {
char seg[9];
memcpy(seg, id + r * 8, 8); /* id 已 NUL 填充, 尾部短串会被 NUL 截断 */
seg[8] = 0;
oled_locate(1 + r, 8);
oled_print(seg);
}
}
/*==== 关显示(黑屏,断电预警时调用)====*/
void oled_off(void)
{
storage_lock();
oled_write(0, 0xAE); /* display off */
storage_unlock();
}
/*==== 全屏清屏 ====*/
void oled_clear(void)
{
storage_lock();
for (uint8_t i = 0; i < 8; i++) {
oled_write(0, 0xb0 + i);
oled_write(0, 0x00);
oled_write(0, 0x10);
oled_data_begin();
for (uint8_t j = 0; j < 128; j++) oled_data_byte(0x00);
oled_data_end();
}
storage_unlock();
}
/*==== 屏幕初始化SSD1306 兼容序列)====*/
void oled_init(void)
{
HAL_Delay(400); /* 等屏幕/字库上电稳定(I2C 屏无 RST 脚, 靠上电延时) */
oled_gpio_init();
rom_cs2(1);
oled_write(0, 0xAE); /* display off */
oled_write(0, 0x20); /* 寻址模式 */
oled_write(0, 0x10); /* 水平寻址 */
oled_write(0, 0xb0);
oled_write(0, 0xc8); /* COM 扫描方向 */
oled_write(0, 0x00);
oled_write(0, 0x10);
oled_write(0, 0x40); /* 起始行 */
oled_write(0, 0x81); /* 对比度 */
oled_write(0, 0xFF);
oled_write(0, 0xa1); /* 段重映射 */
oled_write(0, 0xa6); /* 正常显示 */
oled_write(0, 0xa8); /* 复用率 */
oled_write(0, 0x3F);
oled_write(0, 0xa4);
oled_write(0, 0xd3); /* 显示偏移 */
oled_write(0, 0x00);
oled_write(0, 0xd5); /* 时钟分频 */
oled_write(0, 0xf0);
oled_write(0, 0xd9); /* 预充电 */
oled_write(0, 0x22);
oled_write(0, 0xda); /* COM 硬件配置 */
oled_write(0, 0x12);
oled_write(0, 0xdb); /* VCOMH */
oled_write(0, 0x20);
oled_write(0, 0x8d); /* DC-DC */
oled_write(0, 0x14);
oled_write(0, 0xaf); /* display on */
oled_clear();
}
/*==== 显示 16x16 点阵(汉字)====*/
static void display_graphic_16x16(uint8_t page, uint8_t column, const uint8_t *dp)
{
storage_lock();
for (uint8_t j = 2; j > 0; j--) {
lcd_address(column, page);
oled_data_begin();
for (uint8_t i = 0; i < 16; i++) oled_data_byte(*dp++);
oled_data_end();
page++;
}
storage_unlock();
}
/*==== 显示 8x16 点阵ASCII====*/
static void display_graphic_8x16(uint8_t page, uint8_t column, const uint8_t *dp)
{
storage_lock();
for (uint8_t j = 2; j > 0; j--) {
lcd_address(column, page);
oled_data_begin();
for (uint8_t i = 0; i < 8; i++) oled_data_byte(*dp++);
oled_data_end();
page++;
}
storage_unlock();
}
/*==== 字库 IC 写字节(指令/地址)====*/
static void rom_write_byte(uint8_t dat)
{
for (int i = 0; i < 8; i++) {
lcd_mosi(dat & 0x80);
dat <<= 1;
lcd_clk(0);
lcd_clk(1);
}
}
/*==== 字库 IC 读一字节 ====*/
static uint8_t rom_read_byte(void)
{
uint8_t ret = 0;
lcd_clk(1);
for (int i = 0; i < 8; i++) {
lcd_clk(0);
ret <<= 1;
if (ROM_OUT) ret |= 1;
lcd_clk(1);
}
return ret;
}
/*==== 从字库 IC 连续读 DataLen 字节 ====*/
static void rom_read_n(uint8_t ah, uint8_t am, uint8_t al, uint8_t *buf, uint8_t len)
{
storage_lock(); /* 与 W25Q64 共用 SPI 三线, 与 Flash 读写互斥 */
rom_cs2(0);
lcd_clk(0);
rom_write_byte(0x03); /* 读指令 */
rom_write_byte(ah);
rom_write_byte(am);
rom_write_byte(al);
for (uint8_t i = 0; i < len; i++) buf[i] = rom_read_byte();
rom_cs2(1);
storage_unlock();
}
/*==== 定位row 0~316px 行col8 为 8 像素单位 ====*/
void oled_locate(uint8_t row, uint8_t col8)
{
s_row = row * 2;
s_col = col8 * 8;
}
/*==== 显示 GBK 字符串16x16 汉字 / 8x16 ASCII编码换算见晶联讯字库手册====*/
void oled_print(const char *s)
{
uint8_t fontbuf[32];
uint8_t y = s_row;
uint8_t x = s_col;
const uint8_t *text = (const uint8_t *)s;
uint32_t fontaddr;
s_col += strlen(s) * 8; /* 位置自加(按 8px/字符计) */
uint8_t i = 0;
while (text[i] > 0x00) {
if ((text[i] >= 0xb0 && text[i] <= 0xf7) && text[i+1] >= 0xa1) {
/* GB2312 汉字Address = ((MSB-0xB0)*94 + (LSB-0xA1) + 846) * 32 */
fontaddr = (text[i] - 0xb0) * 94;
fontaddr += (text[i+1] - 0xa1) + 846;
fontaddr *= 32;
rom_read_n((uint8_t)(fontaddr >> 16), (uint8_t)(fontaddr >> 8),
(uint8_t)fontaddr, fontbuf, 32);
display_graphic_16x16(y, x, fontbuf);
i += 2;
x += 16;
}
else if ((text[i] >= 0xa1 && text[i] <= 0xa3) && text[i+1] >= 0xa1) {
/* GB2312 全角符号Address = ((MSB-0xA1)*94 + (LSB-0xA1)) * 32 */
fontaddr = (text[i] - 0xa1) * 94;
fontaddr += (text[i+1] - 0xa1);
fontaddr *= 32;
rom_read_n((uint8_t)(fontaddr >> 16), (uint8_t)(fontaddr >> 8),
(uint8_t)fontaddr, fontbuf, 32);
display_graphic_16x16(y, x, fontbuf);
i += 2;
x += 16;
}
else if (text[i] >= 0x20 && text[i] <= 0x7e) {
/* ASCII 8x16Address = (char-0x20)*16 + 0x3CF80 */
fontaddr = (text[i] - 0x20) * 16 + 0x3cf80;
rom_read_n((uint8_t)(fontaddr >> 16), (uint8_t)(fontaddr >> 8),
(uint8_t)fontaddr, fontbuf, 16);
display_graphic_8x16(y, x, fontbuf);
i += 1;
x += 8;
}
else {
i++;
}
}
}
/*==== 当前列右移 px 像素 ====*/
void oled_xoff(uint8_t px)
{
s_col += px;
}
/*==== 文本像素宽度:汉字/全角 16pxASCII 8px ====*/
uint8_t oled_text_w(const char *s)
{
const uint8_t *p = (const uint8_t *)s;
uint32_t w = 0;
while (*p) {
if (p[0] >= 0xa1 && p[1] >= 0xa1) { w += 16; p += 2; }
else { w += 8; p += 1; }
}
return (uint8_t)(w > 128 ? 128 : w);
}
/*==== 整行显示:计算像素宽度,不足 128px 用空格补齐(覆盖上次内容)====*/
void oled_print_line(const char *s)
{
const uint8_t *p = (const uint8_t *)s;
uint32_t w = 0;
while (*p) {
if (p[0] >= 0xa1 && p[1] >= 0xa1) { w += 16; p += 2; } /* 汉字/全角 */
else { w += 8; p += 1; } /* ASCII */
}
oled_print(s);
while (w < 128) { oled_print(" "); w += 8; }
}