V1.5: 新增128x64 OLED+GB2312字库IC显示(GPIO模拟SPI,1s刷电压/电流/功率/电能/继电器/网络)

This commit is contained in:
helei 2026-08-15 01:00:17 +08:00
parent 79ea75f669
commit 9bdee63b31
6 changed files with 358 additions and 1 deletions

View File

@ -0,0 +1,289 @@
/* oled.c - 128x64 OLED + 晶联讯 GB2312 字库 IC 驱动GPIO 模拟 SPI
* 485CRC oledisp.c
* CLK=PC12 MOSI=PC9 DC=PC8 CS1=PC7 FSO=PC6 CS2=PB15 */
#include "oled.h"
#include <string.h>
#define OLED_CLK_PORT GPIOC
#define OLED_CLK_PIN GPIO_PIN_12
#define OLED_MOSI_PORT GPIOC
#define OLED_MOSI_PIN GPIO_PIN_9
#define OLED_DC_PORT GPIOC
#define OLED_DC_PIN GPIO_PIN_8
#define OLED_CS1_PORT GPIOC
#define OLED_CS1_PIN GPIO_PIN_7
#define OLED_FSO_PORT GPIOC
#define OLED_FSO_PIN GPIO_PIN_6
#define OLED_CS2_PORT GPIOB
#define OLED_CS2_PIN GPIO_PIN_15
#define lcd_cs1(a) HAL_GPIO_WritePin(OLED_CS1_PORT, OLED_CS1_PIN, (GPIO_PinState)(a))
#define lcd_dc(a) HAL_GPIO_WritePin(OLED_DC_PORT, OLED_DC_PIN, (GPIO_PinState)(a))
#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; /* 当前列(像素) */
/*==== FSO 脚输入/输出切换(读字库时为输入)====*/
static void fso_input(void)
{
GPIO_InitTypeDef g = {0};
g.Pin = OLED_FSO_PIN;
g.Mode = GPIO_MODE_INPUT;
g.Pull = GPIO_NOPULL;
HAL_GPIO_Init(OLED_FSO_PORT, &g);
}
static void fso_output(void)
{
GPIO_InitTypeDef g = {0};
g.Pin = OLED_FSO_PIN;
g.Mode = GPIO_MODE_OUTPUT_PP;
g.Pull = GPIO_PULLUP;
g.Speed = GPIO_SPEED_FREQ_LOW;
HAL_GPIO_Init(OLED_FSO_PORT, &g);
}
/*==== GPIO 初始化 ====*/
static void oled_gpio_init(void)
{
GPIO_InitTypeDef g = {0};
__HAL_RCC_GPIOB_CLK_ENABLE();
__HAL_RCC_GPIOC_CLK_ENABLE();
/* 先置好空闲电平再开推挽,避免毛刺 */
HAL_GPIO_WritePin(OLED_CLK_PORT, OLED_CLK_PIN, GPIO_PIN_SET);
HAL_GPIO_WritePin(OLED_MOSI_PORT, OLED_MOSI_PIN, GPIO_PIN_SET);
HAL_GPIO_WritePin(OLED_DC_PORT, OLED_DC_PIN, GPIO_PIN_SET);
HAL_GPIO_WritePin(OLED_CS1_PORT, OLED_CS1_PIN, GPIO_PIN_SET);
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_CLK_PIN; HAL_GPIO_Init(OLED_CLK_PORT, &g);
g.Pin = OLED_MOSI_PIN; HAL_GPIO_Init(OLED_MOSI_PORT, &g);
g.Pin = OLED_DC_PIN; HAL_GPIO_Init(OLED_DC_PORT, &g);
g.Pin = OLED_CS1_PIN; HAL_GPIO_Init(OLED_CS1_PORT, &g);
g.Pin = OLED_CS2_PIN; HAL_GPIO_Init(OLED_CS2_PORT, &g);
fso_output();
}
/*==== 写字节到屏幕DC=0 指令 / DC=1 数据)====*/
static void oled_write(uint8_t dc, uint8_t dat)
{
lcd_dc(dc);
lcd_cs1(0);
for (int i = 0; i < 8; i++) {
lcd_clk(0);
lcd_mosi(dat & 0x80);
lcd_clk(1);
dat <<= 1;
}
lcd_dc(1);
lcd_cs1(1);
}
/*==== 设置页/列地址 ====*/
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 位 */
}
/*==== 全屏清屏 ====*/
void oled_clear(void)
{
lcd_cs1(0);
rom_cs2(1);
for (uint8_t i = 0; i < 8; i++) {
oled_write(0, 0xb0 + i);
oled_write(0, 0x00);
oled_write(0, 0x10);
for (uint8_t j = 0; j < 128; j++) oled_write(1, 0x00);
}
lcd_cs1(1);
}
/*==== 屏幕初始化SSD1306 兼容序列)====*/
void oled_init(void)
{
HAL_Delay(400); /* 等屏幕/字库上电稳定 */
oled_gpio_init();
lcd_cs1(0);
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 */
lcd_cs1(1);
oled_clear();
}
/*==== 显示 16x16 点阵(汉字)====*/
static void display_graphic_16x16(uint8_t page, uint8_t column, const uint8_t *dp)
{
lcd_cs1(0);
rom_cs2(1);
for (uint8_t j = 2; j > 0; j--) {
lcd_address(column, page);
for (uint8_t i = 0; i < 16; i++) oled_write(1, *dp++);
page++;
}
lcd_cs1(1);
}
/*==== 显示 8x16 点阵ASCII====*/
static void display_graphic_8x16(uint8_t page, uint8_t column, const uint8_t *dp)
{
lcd_cs1(0);
for (uint8_t j = 2; j > 0; j--) {
lcd_address(column, page);
for (uint8_t i = 0; i < 8; i++) oled_write(1, *dp++);
page++;
}
lcd_cs1(1);
}
/*==== 字库 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);
fso_input();
for (int i = 0; i < 8; i++) {
lcd_clk(0);
ret <<= 1;
if (ROM_OUT) ret |= 1;
lcd_clk(1);
}
fso_output();
return ret;
}
/*==== 从字库 IC 连续读 DataLen 字节 ====*/
static void rom_read_n(uint8_t ah, uint8_t am, uint8_t al, uint8_t *buf, uint8_t len)
{
rom_cs2(0);
lcd_cs1(1);
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);
}
/*==== 定位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++;
}
}
}
/*==== 整行显示:计算像素宽度,不足 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; }
}

View File

@ -0,0 +1,18 @@
#ifndef __OLED_H
#define __OLED_H
#include "main.h"
/* 128x64 OLEDSSD1306 兼容指令)+ 晶联讯 GB2312 字库 IC全部 GPIO 模拟 SPI。
* 线CLK=PC12 MOSI=PC9 DC=PC8 CS1=PC7() FSO=PC6(,MCU输入)
* CS2=PB15()
* GB2312/GBK GBK
* 128x6416 4 row 0~3 8 16 ASCII */
void oled_init(void); /* GPIO + 屏幕初始化app_init 调用) */
void oled_clear(void); /* 全屏清屏 */
void oled_locate(uint8_t row, uint8_t col8); /* 定位row 0~3col8 为 8 像素单位 0~15 */
void oled_print(const char *gbk_str); /* 从当前位置显示 GBK 字符串 */
void oled_print_line(const char *gbk_str); /* 整行显示:不足 128px 用空格补齐覆盖旧内容 */
#endif /* __OLED_H */

View File

@ -27,6 +27,7 @@
#include "log_cn.h"
#include "ble_at.h"
#include "hlw8032.h"
#include "oled.h"
#include "reset_log.h"
#include "../W25Q64/w25q64.h"
@ -95,6 +96,12 @@ void app_init(void)
/* HLW8032 电能计量UART5 PD2只累计电量 */
hlw8032_init();
/* OLED 屏幕128x64 + GB2312 字库 IC */
oled_init();
oled_locate(1, 2); oled_print("智能控制器");
oled_locate(2, 2); oled_print("V" FIRMWARE_VERSION_STR);
log_info("> OLED: 初始化完成");
/* 网络模块初始化:按 EEPROM 记忆的上次成功方式优先4G 或 WiFi */
NET_init();
@ -191,6 +198,33 @@ static void app_task(void *arg)
}
}
/*==== OLED 基础信息:电压/电流/功率/电能/继电器/网络1s 刷新 ====*/
static void oled_home_refresh(void)
{
char buf[24];
if (hlw8032_online()) {
snprintf(buf, sizeof(buf), "%.1fV %.2fA",
(double)hlw8032_get_voltage_v(), (double)hlw8032_get_current_a());
oled_locate(0, 0); oled_print_line(buf);
snprintf(buf, sizeof(buf), "%.1fW %.2fkWh",
(double)hlw8032_get_power_w(), (double)hlw8032_get_energy_kwh());
oled_locate(1, 0); oled_print_line(buf);
} else {
oled_locate(0, 0); oled_print_line("计量芯片离线");
oled_locate(1, 0); oled_print_line("");
}
snprintf(buf, sizeof(buf), "开关:%d%d%d%d",
(int)MqttInfoStr.Relay_State[1], (int)MqttInfoStr.Relay_State[2],
(int)MqttInfoStr.Relay_State[3], (int)MqttInfoStr.Relay_State[4]);
oled_locate(2, 0); oled_print_line(buf);
const char *net = !g_net_mqtt_ready ? "未联网" :
(NET_GetActiveBackend() == NET_BACKEND_WIFI) ? "WiFi 已连接" : "4G 已连接";
oled_locate(3, 0); oled_print_line(net);
}
/* 电能日志/上报间隔:想改频率就改这个宏(单位 ms */
#define ENERGY_REPORT_INTERVAL_MS 10000u
@ -219,6 +253,7 @@ static void sys_task(void *arg)
batt_tick = HAL_GetTick();
update_batter();
hlw8032_tick(); /* 攒够约 0.1kWh 写一次 EEPROM */
oled_home_refresh(); /* OLED 基础信息 1s 刷新 */
reset_log_tick((uint32_t)(up_ms / 1000)); /* 共享 RAM 更新运行秒数,热复位不丢 */
}

View File

@ -369,7 +369,7 @@
<MiscControls>--no_multibyte_chars</MiscControls>
<Define>USE_HAL_DRIVER,STM32F103xE</Define>
<Undefine></Undefine>
<IncludePath>../Inc;../Drivers/STM32F1xx_HAL_Driver/Inc;../Drivers/STM32F1xx_HAL_Driver/Inc/Legacy;../Drivers/CMSIS/Device/ST/STM32F1xx/Include;../Drivers/CMSIS/Include;..\m_app;..\HardWare\CAR;..\HardWare\RELAY;..\HardWare\LOG;..\HardWare\IIC;..\HardWare\24C02;..\HardWare\ADC;..\HardWare\LED;..\HardWare\SYSINIT;..\HardWare\UART;..\HardWare\W25Q64;..\HardWare\NETWORK;..\HardWare\WIFI;..\HardWare\MQTT;..\HardWare\FEED_SCALE;..\HardWare\USER_CMD;..\HardWare\HLW8032;..\Middlewares\FreeRTOS\Source\include;..\Middlewares\FreeRTOS\Source\portable\RVDS\ARM_CM3</IncludePath>
<IncludePath>../Inc;../Drivers/STM32F1xx_HAL_Driver/Inc;../Drivers/STM32F1xx_HAL_Driver/Inc/Legacy;../Drivers/CMSIS/Device/ST/STM32F1xx/Include;../Drivers/CMSIS/Include;..\m_app;..\HardWare\CAR;..\HardWare\RELAY;..\HardWare\LOG;..\HardWare\IIC;..\HardWare\24C02;..\HardWare\ADC;..\HardWare\LED;..\HardWare\SYSINIT;..\HardWare\UART;..\HardWare\W25Q64;..\HardWare\NETWORK;..\HardWare\WIFI;..\HardWare\MQTT;..\HardWare\FEED_SCALE;..\HardWare\USER_CMD;..\HardWare\HLW8032;..\HardWare\OLED;..\Middlewares\FreeRTOS\Source\include;..\Middlewares\FreeRTOS\Source\portable\RVDS\ARM_CM3</IncludePath>
</VariousControls>
</Cads>
<Aads>
@ -692,6 +692,11 @@
<FileType>1</FileType>
<FilePath>..\HardWare\HLW8032\hlw8032.c</FilePath>
</File>
<File>
<FileName>oled.c</FileName>
<FileType>1</FileType>
<FilePath>..\HardWare\OLED\oled.c</FilePath>
</File>
</Files>
</Group>
<Group>

View File

@ -62,6 +62,7 @@
| ESP_EN | WiFi 模块使能 | PA6 | 推挽输出,高电平=使能(上电即置高) |
| BT_MODE | 蓝牙 AT/透传切换 | PB0 | 开漏输出,拉低 1s 进 AT 模式ble_at.c 控制) |
| BT_RST | 蓝牙模块复位 | PA7 | 推挽输出,低电平=工作(默认),高电平=复位 |
| OLED 屏幕 | 128x64 + GB2312 字库 IC | CLK=PC12 MOSI=PC9 DC=PC8 CS1=PC7 FSO=PC6 CS2=PB15 | GPIO 模拟 SPI显示电压/电流/功率/电能/继电器/网络 |
| Relay1 | 投喂电机控制 | PB7 | 高电平吸合 |
| Relay2~4 | 备用控制 | PB8/PC0/PC2 | 高电平吸合 |
| I2C软件 | AT24C02 配置存储 | PA11/PA12 | 页写入 |
@ -117,6 +118,13 @@
- **上报**MQTT 就绪且芯片在线1s 内有有效帧)时,每分钟随运行心跳上报一次 `{"energy":%.2f}`;未联网/芯片不在线不上报
- **调试日志**:芯片在线时每分钟日志同时打印电压/电流/功率(`运行时间: ... 电能: x.xxkWh 电压: xxx.xV 电流: x.xxA 功率: xxxx.xW`
## OLED 屏幕显示128x64 + GB2312 字库 IC
- 移植自《485模拟磅秤数据带CRC》的 oledisp 驱动GPIO 模拟 SPI屏幕 CS1 + 字库 CS2 两路片选,字库数据从 FSO 脚读回)
- 字符串直接写中文(工程源文件即 GBK 编码,字库 IC 按 GB2312 地址换算取字模)
- 显示内容(`oled_home_refresh()`sys_task 每 1s 刷新):第 0 行 电压/电流,第 1 行 功率/电能(计量芯片离线时显示"计量芯片离线"),第 2 行 四路继电器状态(`开关:1010`),第 3 行 网络状态WiFi 已连接/4G 已连接/未联网)
- 开机显示"智能控制器 + 版本号"欢迎页,首次刷新后进入常态界面
## 双通道网络4G 优先WiFi 兜底)
网络层 `HardWare/NETWORK/network.c` 对业务层屏蔽底层模组差异,业务只调 `NET_*` 接口4G 后端(`CAR/air780e.c`,模组内置 MQTT与 WiFi 后端(`NETWORK/net_wifi.c`TCP 透传 + 软件 MQTT同构运行时互斥切换。

View File

@ -116,3 +116,5 @@
- `ESP_EN`(PA6)WiFi 模块使能,推挽输出,上电置高=模块工作
- `BT_RST`(PA7):蓝牙复位脚,推挽输出,默认拉低=正常工作(高=复位,预留)
- `BT_MODE`(PB0):蓝牙 AT/透传切换此前版本已实现ble_at.c 开漏控制)
- 新增 OLED 屏幕128x64 + 晶联讯 GB2312 字库 IC`HardWare/OLED/oled.c`GPIO 模拟 SPI
CLK=PC12 MOSI=PC9 DC=PC8 CS1=PC7 FSO=PC6 CS2=PB151s 刷新电压/电流/功率/电能/继电器/网络状态