最近一直在寻找老式的点阵屏
独爱那种显示效果,于是上淘宝寻宝
但大多数都是边框很大 或者很厚的屏幕
终于,在我的苦苦寻找下 找到了这款 又大又薄的屏幕!
虽然是块无资料的屏幕 但是这个价格和颜值 简直不要太香!!!
OMG!受不鸟了!我直接下单!

屏幕一拿到手 我就爱不释手了 太符合我心中的那个白月光屏幕了!

但是棘手的问题来了 我该怎么去点亮这个屏幕呢?
正当我疑惑之际,我翻看了该屏幕店铺的评论区 发现还是有卧龙凤雏的!
大佬终究是大佬!

这里大佬已经给出了引脚定义 我们直接照搬
经过我缜密严谨的调查 终于查出了这块屏幕的厂家和控制器
晶联讯电子 液晶模块 JLX12896G-946-BN

可以显示不大于 128×96 点阵单色或 4 灰度级的图片,或显示 8 个×6 行=48
个的 1616 点阵的汉字,或显示 16 个×6 行=96 个的 816 点阵的英文、数字、符号。或显示 21 个
×16 行的 5*8 点阵的英文、数字、符号。
这是一款12896像素的屏幕 并非卖家所叙述的128128像素,
所用控制器型号为 UC1617S
屏幕型号为 JLX12896G-946-BN
可选则SPI/8080/I2C驱动
庆幸的是 找到的厂家说明书里给了示例代码 只不过是51单片机写的 我们需要转换成STM32的代码
有了参考 接下来的工程也就简单了 只不过是照葫芦画瓢
废话不多说 直接展示代码
uc1617s_lcd.c
点击查看代码
/**
* @file uc1617s_lcd.c
* @brief JLX12896G-946-BN (UC1617S) 128x96 单色 STN LCD —— STM32 HAL I2C 驱动实现
*
* 数据格式要点:
* UC1617S 是 4 级灰阶控制器,内部 RAM 恒为 2bit/像素(4 像素 = 1 字节)。
* 本驱动用一个 1bit 帧缓冲(96x16 字节),刷屏时把每字节(8像素)展开成
* 2 字节灰阶(每像素 2bit,0=白 3=黑)再经 I2C 写入,对照说明书 write_mono_data。
*
* 内存映射:uc1617s_fb[y][x>>3] 的 bit(7-(x&7)) 对应坐标 (x,y),
* 即字节内 bit7=最左像素,行优先、从左到右。
*/
#include "UC1617s.h"
#include "Font.h"
#include <string.h>
static I2C_HandleTypeDef *lcd_i2c = NULL;
/* 帧缓冲:96 行 x 16 列字节(每字节 = 8 水平像素) */
uint8_t uc1617s_fb[UC1617S_HEIGHT][UC1617S_COL_BYTES];
/* ==================== 复位引脚 ==================== */
static void rst_low(void) { HAL_GPIO_WritePin(UC1617S_RST_PORT, UC1617S_RST_PIN, GPIO_PIN_RESET); }
static void rst_high(void) { HAL_GPIO_WritePin(UC1617S_RST_PORT, UC1617S_RST_PIN, GPIO_PIN_SET); }
/* ==================== I2C 底层 ==================== */
/* UC1617S 用两个从机地址区分命令/数据:写命令走 CMD 地址,写数据走 DATA 地址 */
static HAL_StatusTypeDef i2c_tx(uint16_t addr, uint8_t *buf, uint16_t len)
{
return HAL_I2C_Master_Transmit(lcd_i2c, addr, buf, len, 100);
}
HAL_StatusTypeDef UC1617S_WriteCmd(uint8_t cmd)
{
return i2c_tx(UC1617S_I2C_ADDR_CMD, &cmd, 1);
}
HAL_StatusTypeDef UC1617S_WriteData(uint8_t dat)
{
return i2c_tx(UC1617S_I2C_ADDR_DATA, &dat, 1);
}
HAL_StatusTypeDef UC1617S_WriteDataBuf(uint8_t *buf, uint16_t len)
{
return i2c_tx(UC1617S_I2C_ADDR_DATA, buf, len);
}
/* ==================== 地址 / 数据 ==================== */
/* 设置写入地址。行 row(0~95) 会自动加上 UC1617S_COM_START 偏移;
* 列地址 ca 是灰阶字节单位(0~31),每个灰阶字节 = 4 像素。
* 发送顺序对照说明书 lcd_address():先列,再行高4位,再行低4位。 */
void UC1617S_SetAddr(uint8_t row, uint8_t ca)
{
uint8_t ra = row + UC1617S_COM_START; /* 软件行 -> 硬件 COM 行 */
UC1617S_WriteCmd(0x00 | (ca & 0x1F)); /* 列地址 0x00~0x1F */
UC1617S_WriteCmd(0x70 | (ra >> 4)); /* 行地址高 4 位 0x70~0x77 */
UC1617S_WriteCmd(0x60 | (ra & 0x0F)); /* 行地址低 4 位 0x60~0x6F */
}
/* 1 字节单色(8像素) -> 2 字节灰阶(4像素x2bit)并写入。
* 忠实复刻说明书 write_mono_data:每个像素 1bit -> 2bit(0->00,1->11)。
*
* 位展开过程:每次循环处理一个像素(2bit),
* g >>= 2 把已放好的像素往右挪一位给新像素腾位置,
* mono & 0x80 取当前最左像素,为 1 则写入 0b11(黑),否则留 0b00(白)。
* 8 个像素分两轮、每轮 4 个,最终得到 2 个灰阶字节。 */
void UC1617S_WriteMono(uint8_t mono)
{
uint8_t gray[2];
for (int j = 0; j < 2; j++) { /* 2 个灰阶字节 */
uint8_t g = 0;
for (int i = 0; i < 4; i++) { /* 每字节 4 个像素 */
g >>= 2;
if (mono & 0x80) g |= 0xC0; /* 0xC0 = 0b11 = 黑色 */
mono <<= 1;
}
gray[j] = g;
}
UC1617S_WriteDataBuf(gray, 2);
}
/* ==================== 初始化 ==================== */
static void lcd_init_seq(void)
{
/* 初始化序列,对照 JLX12896G-946-BN 说明书 LCD_INITIAL() */
UC1617S_WriteCmd(0xE2); /* System reset */
HAL_Delay(10);
UC1617S_WriteCmd(0x27); /* 温度补偿 */
UC1617S_WriteCmd(0x2B); /* 面板负载 */
UC1617S_WriteCmd(0x2F); /* 电荷泵 */
UC1617S_WriteCmd(0x81); /* 设置 Vop(对比度) */
UC1617S_WriteCmd(0x50); /* Vop 值(说明书 0x68 会背景发黑,实测 0x50 合适) */
UC1617S_WriteCmd(0xA9); /* 行频率 */
UC1617S_WriteCmd(0xC8); /* N 行反转 */
UC1617S_WriteCmd(0x89); /* RAM 访问控制(数据写入方式) */
UC1617S_WriteCmd(0xC4); /* MY=0, MX=0 扫描方向(0xC2 = 180° 旋转) */
UC1617S_WriteCmd(0xF1); /* 设置 COM 结束地址 */
UC1617S_WriteCmd(UC1617S_COM_END);
UC1617S_WriteCmd(0xAD); /* 显示开启 */
}
void UC1617S_Init(I2C_HandleTypeDef *hi2c)
{
lcd_i2c = hi2c;
/* 硬件复位:先拉低再拉高,让控制器进入已知状态 */
rst_low();
HAL_Delay(200);
rst_high();
HAL_Delay(500);
lcd_init_seq();
UC1617S_Clear(UC1617S_WHITE);
UC1617S_Refresh();
}
void UC1617S_DisplayOn(void) { UC1617S_WriteCmd(0xAD); }
void UC1617S_DisplayOff(void) { UC1617S_WriteCmd(0xAC); }
/* 卡死恢复:复位主机 I2C 外设 + 硬件复位 LCD + 重跑初始化 + 重刷画面 */
void UC1617S_Recover(void)
{
if (lcd_i2c == NULL) return;
/* 1. 复位主机 I2C 外设,清 BUSY/错误标志 */
HAL_I2C_DeInit(lcd_i2c);
HAL_I2C_Init(lcd_i2c);
/* 2. 硬件复位 LCD 从机(和断电等效) */
rst_low();
HAL_Delay(200);
rst_high();
HAL_Delay(500);
/* 3. 重跑初始化 + 重写画面 */
lcd_init_seq();
UC1617S_Refresh();
}
/* ==================== 帧缓冲 / 绘图 ==================== */
void UC1617S_Clear(uint8_t color)
{
memset(uc1617s_fb, color ? 0xFF : 0x00, sizeof(uc1617s_fb));
}
/* 把帧缓冲整屏刷到 LCD(行优先:每行 16 字节单色 -> 32 字节灰阶)。
*
* 用一次“窗口”命令框定整屏后,数据写入地址会按“列->行”自动递增,
* 所以每行只需连续发 32 字节灰阶数据,不用重复设地址,刷屏更快。 */
int UC1617S_Refresh(void)
{
uint8_t gray[32]; /* 单行展开后的灰阶缓冲:16 单色字节 x 2 = 32 字节 */
/* 设置整屏窗口 */
UC1617S_WriteCmd(0xF4); UC1617S_WriteCmd(0x00); /* 列起始 0 */
UC1617S_WriteCmd(0xF6); UC1617S_WriteCmd(0x1F); /* 列结束 31 */
UC1617S_WriteCmd(0xF5); UC1617S_WriteCmd(UC1617S_COM_START); /* 行起始 */
UC1617S_WriteCmd(0xF7); UC1617S_WriteCmd(UC1617S_COM_END); /* 行结束 */
UC1617S_WriteCmd(0xF9); /* 窗口外模式 */
for (int row = 0; row < UC1617S_HEIGHT; row++) {
/* 把这一行 16 个单色字节展开成 32 个灰阶字节(逻辑同 UC1617S_WriteMono) */
for (int c = 0; c < UC1617S_COL_BYTES; c++) {
uint8_t m = uc1617s_fb[row][c]; /* 取 8 个像素 */
for (int j = 0; j < 2; j++) { /* 拆成 2 个灰阶字节 */
uint8_t g = 0;
for (int i = 0; i < 4; i++) { /* 每字节 4 像素,1bit -> 2bit */
g >>= 2;
if (m & 0x80) g |= 0xC0;
m <<= 1;
}
gray[c * 2 + j] = g;
}
}
if (UC1617S_WriteDataBuf(gray, 32) != HAL_OK)
return -1;
}
return 0;
}
void UC1617S_DrawPixel(int16_t x, int16_t y, uint8_t color)
{
if (x < 0 || x >= UC1617S_WIDTH || y < 0 || y >= UC1617S_HEIGHT) return;
if (color)
uc1617s_fb[y][x >> 3] |= (0x80 >> (x & 7)); /* 置位 = 黑 */
else
uc1617s_fb[y][x >> 3] &= ~(0x80 >> (x & 7)); /* 清零 = 白 */
}
void UC1617S_DrawLine(int16_t x0, int16_t y0, int16_t x1, int16_t y1, uint8_t color)
{
/* Bresenham 直线算法 */
int16_t dx = (x1 > x0) ? (x1 - x0) : (x0 - x1);
int16_t dy = (y1 > y0) ? (y1 - y0) : (y0 - y1);
int16_t sx = (x0 < x1) ? 1 : -1;
int16_t sy = (y0 < y1) ? 1 : -1;
int16_t err = dx - dy;
for (;;) {
UC1617S_DrawPixel(x0, y0, color);
if (x0 == x1 && y0 == y1) break;
int16_t e2 = err * 2;
if (e2 > -dy) { err -= dy; x0 += sx; }
if (e2 < dx) { err += dx; y0 += sy; }
}
}
void UC1617S_DrawRect(int16_t x, int16_t y, int16_t w, int16_t h, uint8_t color)
{
/* 四条边:上、下、左、右 */
UC1617S_DrawLine(x, y, x + w - 1, y, color);
UC1617S_DrawLine(x, y + h - 1, x + w - 1, y + h - 1, color);
UC1617S_DrawLine(x, y, x, y + h - 1, color);
UC1617S_DrawLine(x + w - 1, y, x + w - 1, y + h - 1, color);
}
void UC1617S_FillRect(int16_t x, int16_t y, int16_t w, int16_t h, uint8_t color)
{
/* 逐像素填充(可优化为按行 memset 位操作,当前量级足够) */
for (int16_t j = 0; j < h; j++)
for (int16_t i = 0; i < w; i++)
UC1617S_DrawPixel(x + i, y + j, color);
}
/* 位图:bmp 为行优先、每字节 8 水平像素、bit7=最左 */
void UC1617S_DrawBitmap(int16_t x, int16_t y, const uint8_t *bmp,
int16_t w, int16_t h, uint8_t color)
{
for (int16_t j = 0; j < h; j++) {
for (int16_t i = 0; i < w; i++) {
/* 定位到 (j 行, i 列) 对应的字节和位,为 1 才画点 */
if (bmp[j * ((w + 7) / 8) + (i >> 3)] & (0x80 >> (i & 7)))
UC1617S_DrawPixel(x + i, y + j, color);
}
}
}
void UC1617S_DrawChar(int16_t x, int16_t y, char ch, uint8_t reverse)
{
if (ch < ' ' || ch > '~') ch = ' '; /* 非 ASCII 可打印字符替换为空格 */
const uint8_t *glyph = font5x7[ch - ' '];
for (int col = 0; col < 5; col++) { /* 5 列 */
uint8_t line = glyph[col]; /* 每列 7 bit,bit0=顶部 */
if (reverse) line = ~line; /* 反白显示 */
for (int row = 0; row < 7; row++) {
if (line & (1 << row))
UC1617S_DrawPixel(x + col, y + row, UC1617S_BLACK);
else
UC1617S_DrawPixel(x + col, y + row, UC1617S_WHITE); /* 清背景,避免叠影 */
}
}
}
void UC1617S_DrawString(int16_t x, int16_t y, const char *str, uint8_t reverse)
{
while (*str) {
UC1617S_DrawChar(x, y, *str, reverse);
x += 6; /* 5 宽 + 1 间隔 */
str++;
}
}
uc1617s_lcd.h
点击查看代码
/**
* @file uc1617s_lcd.h
* @brief JLX12896G-946-BN (UC1617S) 128x96 单色 STN LCD —— STM32 HAL I2C 驱动
*
* 屏幕:JLX12896G-946-BN,控制器 UC1617S (Solomon Systech)
* 分辨率:128 x 96,接口:I2C(也支持 SPI/6800/8080,本驱动只用 I2C)
*
* 接线(I2C 模式,对照说明书 3.2 引脚表):
* BM1(pin8)=VSS, BM0(pin9)=VDD -> 选 I2C 模式
* SDA = WR0/WR (pin11),SCL = CDRS (pin12)
* CS(pin13) 接地,RST(pin14) 接 MCU 一个 GPIO
*
* MCU 侧:STM32F405 I2C1,PB6=SCL,PB7=SDA
*/
#ifndef __UC1617S_H
#define __UC1617S_H
#include "stm32f4xx_hal.h"
/* ==================== 硬件配置 ==================== */
/* I2C 从机地址:命令 0x7C、数据 0x7E(即 7-bit 地址 0x3E/0x3F 左移 1 位) */
#define UC1617S_I2C_ADDR_CMD 0x7C
#define UC1617S_I2C_ADDR_DATA 0x7E
/* 复位引脚 —— 按你的实际接线修改 */
#define UC1617S_RST_PORT GPIOA
#define UC1617S_RST_PIN GPIO_PIN_8
/* ==================== 显示参数 ==================== */
#define UC1617S_WIDTH 128
#define UC1617S_HEIGHT 96
#define UC1617S_COL_BYTES (UC1617S_WIDTH / 8) /* 16 字节/行(1bit 帧缓冲) */
/*
* 面板 COM 起始地址。
* 数据手册示例:COM 结束=95,从 COM0 开始,无偏移(下面这组默认值)。
* 若你的面板实际只接了 COM32~127(之前调试发现),请改成 START=32 / END=127。
*/
#define UC1617S_COM_START 0
#define UC1617S_COM_END 95
/* 单色颜色:1=点亮(黑),0=熄灭(白) */
#define UC1617S_BLACK 1
#define UC1617S_WHITE 0
/* ==================== 帧缓冲 ====================
* 每字节 = 8 个水平像素,bit7 = 最左,行优先排列
* 共 96 x 16 = 1536 字节
*/
extern uint8_t uc1617s_fb[UC1617S_HEIGHT][UC1617S_COL_BYTES];
/* ==================== 底层驱动 ==================== */
void UC1617S_Init(I2C_HandleTypeDef *hi2c);
HAL_StatusTypeDef UC1617S_WriteCmd(uint8_t cmd);
HAL_StatusTypeDef UC1617S_WriteData(uint8_t dat);
HAL_StatusTypeDef UC1617S_WriteDataBuf(uint8_t *buf, uint16_t len);
void UC1617S_SetAddr(uint8_t row, uint8_t ca); /* 行(0~95)+COM偏移,列地址 ca(0~31, 灰阶字节=4像素) */
void UC1617S_WriteMono(uint8_t mono); /* 1 字节(8像素) -> 2 字节灰阶并写入 */
void UC1617S_DisplayOn(void);
void UC1617S_DisplayOff(void);
void UC1617S_Recover(void); /* 硬件复位 */
/* ==================== 绘图(操作帧缓冲,Refresh 才上屏) ==================== */
void UC1617S_Clear(uint8_t color);
int UC1617S_Refresh(void);
void UC1617S_DrawPixel(int16_t x, int16_t y, uint8_t color);
void UC1617S_DrawLine(int16_t x0, int16_t y0, int16_t x1, int16_t y1, uint8_t color);
void UC1617S_DrawRect(int16_t x, int16_t y, int16_t w, int16_t h, uint8_t color);
void UC1617S_FillRect(int16_t x, int16_t y, int16_t w, int16_t h, uint8_t color);
void UC1617S_DrawBitmap(int16_t x, int16_t y, const uint8_t *bmp,
int16_t w, int16_t h, uint8_t color);
/* 内置 5x7 字库(完整 ASCII),reverse=1 反色 */
void UC1617S_DrawChar(int16_t x, int16_t y, char ch, uint8_t reverse);
void UC1617S_DrawString(int16_t x, int16_t y, const char *str, uint8_t reverse);
#endif /* __UC1617S_LCD_H */
为了便于接线 直接用嘉立创画了一个PCB 可以直接焊接固定屏幕
也是看出来了 嘉立创已经开始全面使用OSP工艺了


屏幕的FPC金手指 是0.4mm宽 两个金手指中心间隔是0.8mm
在验证电路的时候 我发现 I2C的上拉电阻 我用1K阻值的正常 如果再大一点的阻值 例如2K,4.7K等 都会造成屏幕显示不正常
I2C 的 SDA/SCL 是开漏输出,芯片只能主动拉低,拉高全靠上拉电阻给总线电容充电:
上升时间 t_r ≈ 0.8473 × R_pullup × C_bus
- C_bus 是总线寄生电容 = 走线电容 + 引脚电容 + 屏幕模块输入电容
- R_pullup 越大 → 充电越慢 → 上升沿越缓
如果在 SCL 采样点,SDA 电平还没爬过 VIH(≈ 0.7·VDD),就会被读成 0,数据位出错 → 花屏 / 局部错乱 / 偶发显示异常。
这里焊接好后 写了一个显示立方体的代码 测试一下效果

肉眼实际观看 非常的清晰漂亮!
测试DEMO代码也给大家了 可以拿回去测试 有很多动态展示哦!
字库代码
点击查看代码
#ifndef __FONT_H
#define __FONT_H
#include "stm32f4xx_hal.h"
/* ==================== 5x7 内置字库(完整 ASCII) ==================== */
static const uint8_t font5x7[][5] = {
{0x00,0x00,0x00,0x00,0x00}, /* ' ' */
{0x00,0x00,0x5F,0x00,0x00}, /* '!' */
{0x00,0x07,0x00,0x07,0x00}, /* '"' */
{0x14,0x7F,0x14,0x7F,0x14}, /* '#' */
{0x24,0x2A,0x7F,0x2A,0x12}, /* '$' */
{0x23,0x13,0x08,0x64,0x62}, /* '%' */
{0x36,0x49,0x55,0x22,0x50}, /* '&' */
{0x00,0x05,0x03,0x00,0x00}, /* ''' */
{0x00,0x1C,0x22,0x41,0x00}, /* '(' */
{0x00,0x41,0x22,0x1C,0x00}, /* ')' */
{0x14,0x08,0x3E,0x08,0x14}, /* '*' */
{0x08,0x08,0x3E,0x08,0x08}, /* '+' */
{0x00,0x50,0x30,0x00,0x00}, /* ',' */
{0x08,0x08,0x08,0x08,0x08}, /* '-' */
{0x00,0x60,0x60,0x00,0x00}, /* '.' */
{0x20,0x10,0x08,0x04,0x02}, /* '/' */
{0x3E,0x51,0x49,0x45,0x3E}, /* '0' */
{0x00,0x42,0x7F,0x40,0x00}, /* '1' */
{0x42,0x61,0x51,0x49,0x46}, /* '2' */
{0x21,0x41,0x45,0x4B,0x31}, /* '3' */
{0x18,0x14,0x12,0x7F,0x10}, /* '4' */
{0x27,0x45,0x45,0x45,0x39}, /* '5' */
{0x3C,0x4A,0x49,0x49,0x30}, /* '6' */
{0x01,0x71,0x09,0x05,0x03}, /* '7' */
{0x36,0x49,0x49,0x49,0x36}, /* '8' */
{0x06,0x49,0x49,0x29,0x1E}, /* '9' */
{0x00,0x36,0x36,0x00,0x00}, /* ':' */
{0x00,0x56,0x36,0x00,0x00}, /* ';' */
{0x08,0x14,0x22,0x41,0x00}, /* '<' */
{0x14,0x14,0x14,0x14,0x14}, /* '=' */
{0x00,0x41,0x22,0x14,0x08}, /* '>' */
{0x02,0x01,0x51,0x09,0x06}, /* '?' */
{0x32,0x49,0x79,0x41,0x3E}, /* '@' */
{0x7E,0x11,0x11,0x11,0x7E}, /* 'A' */
{0x7F,0x49,0x49,0x49,0x36}, /* 'B' */
{0x3E,0x41,0x41,0x41,0x22}, /* 'C' */
{0x7F,0x41,0x41,0x22,0x1C}, /* 'D' */
{0x7F,0x49,0x49,0x49,0x41}, /* 'E' */
{0x7F,0x09,0x09,0x09,0x01}, /* 'F' */
{0x3E,0x41,0x49,0x49,0x7A}, /* 'G' */
{0x7F,0x08,0x08,0x08,0x7F}, /* 'H' */
{0x00,0x41,0x7F,0x41,0x00}, /* 'I' */
{0x20,0x40,0x41,0x3F,0x01}, /* 'J' */
{0x7F,0x08,0x14,0x22,0x41}, /* 'K' */
{0x7F,0x40,0x40,0x40,0x40}, /* 'L' */
{0x7F,0x02,0x0C,0x02,0x7F}, /* 'M' */
{0x7F,0x04,0x08,0x10,0x7F}, /* 'N' */
{0x3E,0x41,0x41,0x41,0x3E}, /* 'O' */
{0x7F,0x09,0x09,0x09,0x06}, /* 'P' */
{0x3E,0x41,0x51,0x21,0x5E}, /* 'Q' */
{0x7F,0x09,0x19,0x29,0x46}, /* 'R' */
{0x46,0x49,0x49,0x49,0x31}, /* 'S' */
{0x01,0x01,0x7F,0x01,0x01}, /* 'T' */
{0x3F,0x40,0x40,0x40,0x3F}, /* 'U' */
{0x1F,0x20,0x40,0x20,0x1F}, /* 'V' */
{0x3F,0x40,0x38,0x40,0x3F}, /* 'W' */
{0x63,0x14,0x08,0x14,0x63}, /* 'X' */
{0x07,0x08,0x70,0x08,0x07}, /* 'Y' */
{0x61,0x51,0x49,0x45,0x43}, /* 'Z' */
{0x00,0x7F,0x41,0x41,0x00}, /* '[' */
{0x02,0x04,0x08,0x10,0x20}, /* '\' */
{0x00,0x41,0x41,0x7F,0x00}, /* ']' */
{0x04,0x02,0x01,0x02,0x04}, /* '^' */
{0x40,0x40,0x40,0x40,0x40}, /* '_' */
{0x00,0x01,0x02,0x04,0x00}, /* '`' */
{0x20,0x54,0x54,0x54,0x78}, /* 'a' */
{0x7F,0x48,0x44,0x44,0x38}, /* 'b' */
{0x38,0x44,0x44,0x44,0x20}, /* 'c' */
{0x38,0x44,0x44,0x48,0x7F}, /* 'd' */
{0x38,0x54,0x54,0x54,0x18}, /* 'e' */
{0x08,0x7E,0x09,0x01,0x02}, /* 'f' */
{0x0C,0x52,0x52,0x52,0x3E}, /* 'g' */
{0x7F,0x08,0x04,0x04,0x78}, /* 'h' */
{0x00,0x44,0x7D,0x40,0x00}, /* 'i' */
{0x20,0x40,0x44,0x3D,0x00}, /* 'j' */
{0x7F,0x10,0x28,0x44,0x00}, /* 'k' */
{0x00,0x41,0x7F,0x40,0x00}, /* 'l' */
{0x7C,0x04,0x18,0x04,0x78}, /* 'm' */
{0x7C,0x08,0x04,0x04,0x78}, /* 'n' */
{0x38,0x44,0x44,0x44,0x38}, /* 'o' */
{0x7C,0x14,0x14,0x14,0x08}, /* 'p' */
{0x08,0x14,0x14,0x18,0x7C}, /* 'q' */
{0x7C,0x08,0x04,0x04,0x08}, /* 'r' */
{0x48,0x54,0x54,0x54,0x20}, /* 's' */
{0x04,0x3F,0x44,0x40,0x20}, /* 't' */
{0x3C,0x40,0x40,0x20,0x7C}, /* 'u' */
{0x1C,0x20,0x40,0x20,0x1C}, /* 'v' */
{0x3C,0x40,0x30,0x40,0x3C}, /* 'w' */
{0x44,0x28,0x10,0x28,0x44}, /* 'x' */
{0x0C,0x50,0x50,0x50,0x3C}, /* 'y' */
{0x44,0x64,0x54,0x4C,0x44}, /* 'z' */
{0x00,0x08,0x36,0x41,0x00}, /* '{' */
{0x00,0x00,0x7F,0x00,0x00}, /* '|' */
{0x00,0x41,0x36,0x08,0x00}, /* '}' */
{0x08,0x04,0x08,0x10,0x08}, /* '~' */
};
static const uint8_t hanzi[][32] = {
0x20,0x00,0x10,0xFC,0x10,0x84,0xFC,0x88,0x04,0x88,0x08,0x90,0x10,0x88,0x38,0x88,
0x54,0x84,0x92,0x84,0x10,0xC4,0x10,0xA8,0x10,0x90,0x10,0x80,0x10,0x80,0x10,0x80,/*"祁",0*/
0x02,0x20,0x02,0x10,0x02,0x08,0x3F,0xE0,0x02,0x20,0x02,0x20,0x02,0x20,0x02,0x20,
0xFF,0xFC,0x02,0x04,0x02,0x04,0x02,0x04,0x02,0x04,0x02,0x28,0x02,0x10,0x02,0x00,/*"书",1*/
0x10,0x00,0x10,0x00,0x10,0xFE,0xFE,0x10,0x20,0x10,0x28,0x10,0x48,0x10,0x7E,0x10,
0x09,0xFE,0x08,0x10,0x0E,0x10,0xF8,0x10,0x48,0x10,0x08,0x10,0x08,0x10,0x08,0x10,/*"轩",2*/
};
#endif
DEMO动画代码
点击查看代码
/**
* @file Demo.c
* @brief UC1617S 128x96 单色屏动态演示实现
*
* 六个场景循环播放,全部在内存帧缓冲上绘制,每帧调用一次 UC1617S_Refresh() 上屏。
* 刷新受 I2C 带宽限制约 5~8 fps,动画按此节奏设计。
* 三角函数用泰勒级数自实现(无 libm 依赖),浮点由 STM32F405 硬件 FPU 加速。
*/
#include "Demo.h"
#include "UC1617s.h"
#include "Font.h"
#include <string.h>
#define PI_F 3.14159265358979f
#define FRAME_MS 50 /* 目标帧间隔(刷屏本身约 100ms,会自然拉长) */
/* ==================== 场景选择 ====================
* 在这里填数字即可锁定要显示的场景:
* 0 = 自动轮播(循环播放下面 1~6)
* 1 = 弹跳名字
* 2 = 3D 旋转立方体
* 3 = 重力弹球
* 4 = 星空穿梭
* 5 = 生命游戏
* 6 = 跑马灯
*/
#define DEMO_SCENE 0
/* ==================== 基础工具 ==================== */
static int32_t rnd_seed = 0x1A2B3C4D;
static int32_t rnd_next(void)
{
rnd_seed = rnd_seed * 1103515245 + 12345;
return (rnd_seed >> 16) & 0x7FFF;
}
static int32_t rnd_range(int32_t lo, int32_t hi)
{
if (hi <= lo) return lo;
return lo + (rnd_next() % (hi - lo + 1));
}
/* 单精度正弦(角度任意,泰勒级数 + 象限归约,无 libm) */
static float fast_sin(float a)
{
while (a > PI_F) a -= 2.0f * PI_F;
while (a < -PI_F) a += 2.0f * PI_F;
float sign = 1.0f;
if (a < 0) { a = -a; sign = -1.0f; }
if (a > PI_F * 0.5f) a = PI_F - a;
float a2 = a * a, a3 = a2 * a, a5 = a3 * a2, a7 = a5 * a2, a9 = a7 * a2;
return sign * (a - a3 / 6.0f + a5 / 120.0f - a7 / 5040.0f + a9 / 362880.0f);
}
static float fast_cos(float a) { return fast_sin(a + PI_F * 0.5f); }
static void draw_circle(int16_t cx, int16_t cy, int16_t r, uint8_t c)
{
int16_t x = 0, y = r, d = 3 - 2 * r;
while (y >= x) {
UC1617S_DrawPixel(cx + x, cy + y, c); UC1617S_DrawPixel(cx + y, cy + x, c);
UC1617S_DrawPixel(cx - y, cy + x, c); UC1617S_DrawPixel(cx - x, cy + y, c);
UC1617S_DrawPixel(cx - x, cy - y, c); UC1617S_DrawPixel(cx - y, cy - x, c);
UC1617S_DrawPixel(cx + y, cy - x, c); UC1617S_DrawPixel(cx + x, cy - y, c);
x++;
if (d > 0) { y--; d += 4 * (x - y) + 10; }
else { d += 4 * x + 6; }
}
}
static void fill_circle(int16_t cx, int16_t cy, int16_t r, uint8_t c)
{
for (int16_t dy = -r; dy <= r; dy++)
for (int16_t dx = -r; dx <= r; dx++)
if (dx * dx + dy * dy <= r * r)
UC1617S_DrawPixel(cx + dx, cy + dy, c);
}
/* 读帧缓冲某一点(生命游戏用) */
static inline uint8_t fb_get(int16_t x, int16_t y)
{
if (x < 0 || x >= UC1617S_WIDTH || y < 0 || y >= UC1617S_HEIGHT) return 0;
return (uc1617s_fb[y][x >> 3] >> (7 - (x & 7))) & 1;
}
/* 16x16 汉字(Font.h 内置:0=祁 1=书 2=轩),MSB 在前 */
static void draw_hanzi(int16_t x, int16_t y, uint8_t idx, uint8_t c)
{
const uint8_t *g = hanzi[idx];
for (int16_t r = 0; r < 16; r++) {
uint16_t b = (uint16_t)(g[r * 2] << 8) | g[r * 2 + 1];
for (int16_t col = 0; col < 16; col++)
if (b & (0x8000 >> col)) UC1617S_DrawPixel(x + col, y + r, c);
}
}
/* ==================== 场景 1:弹跳名字(DVD 式) ==================== */
static int32_t b_x, b_y, b_dx, b_dy;
static void bounce_init(void) { b_x = 40; b_y = 40; b_dx = 2; b_dy = 1; }
static void bounce_frame(void)
{
UC1617S_Clear(UC1617S_WHITE);
UC1617S_DrawRect(0, 0, UC1617S_WIDTH, UC1617S_HEIGHT, UC1617S_BLACK);
UC1617S_DrawString(4, 4, "UC1617S", 0);
UC1617S_DrawString(82, 4, "128x96", 0);
b_x += b_dx; b_y += b_dy;
if (b_x <= 1) { b_x = 1; b_dx = 2; }
if (b_x >= UC1617S_WIDTH - 48 - 1) { b_x = UC1617S_WIDTH - 49; b_dx = -2; }
if (b_y <= 18) { b_y = 18; b_dy = 1; }
if (b_y >= UC1617S_HEIGHT - 16 - 1) { b_y = UC1617S_HEIGHT - 17; b_dy = -1; }
draw_hanzi(b_x, b_y, 0, UC1617S_BLACK);
draw_hanzi(b_x + 16, b_y, 1, UC1617S_BLACK);
draw_hanzi(b_x + 32, b_y, 2, UC1617S_BLACK);
}
/* ==================== 场景 2:3D 旋转立方体 ==================== */
static float cube_ax, cube_ay, cube_az;
static void cube_init(void) { cube_ax = 0.3f; cube_ay = 0.5f; cube_az = 0.1f; }
static void cube_frame(void)
{
UC1617S_Clear(UC1617S_WHITE);
const int16_t cx = 63, cy = 47;
const float F = 85.0f;
/* 呼吸式缩放(复用已归约到 0~2π 的旋转角,避免角度无界增长) */
float h = 20.0f * (1.0f + 0.12f * fast_sin(cube_ax * 2.0f));
float ca = fast_cos(cube_ax), sa = fast_sin(cube_ax);
float cb = fast_cos(cube_ay), sb = fast_sin(cube_ay);
float cg = fast_cos(cube_az), sg = fast_sin(cube_az);
int16_t vx[8], vy[8];
for (int i = 0; i < 8; i++) {
float x = (i & 1) ? h : -h;
float y = (i & 2) ? h : -h;
float z = (i & 4) ? h : -h;
float y1 = y * ca - z * sa; /* 绕 X */
float z1 = y * sa + z * ca;
float x2 = x * cb + z1 * sb; /* 绕 Y */
float z2 = -x * sb + z1 * cb;
float x3 = x2 * cg - y1 * sg; /* 绕 Z */
float y3 = x2 * sg + y1 * cg;
float p = F / (F - z2); /* 透视投影 */
vx[i] = cx + (int16_t)(x3 * p);
vy[i] = cy + (int16_t)(y3 * p);
}
static const uint8_t edge[12][2] = {
{0,1},{1,3},{3,2},{2,0},
{4,5},{5,7},{7,6},{6,4},
{0,4},{1,5},{2,6},{3,7}
};
for (int e = 0; e < 12; e++)
UC1617S_DrawLine(vx[edge[e][0]], vy[edge[e][0]],
vx[edge[e][1]], vy[edge[e][1]], UC1617S_BLACK);
cube_ax += 0.020f; if (cube_ax > 2.0f * PI_F) cube_ax -= 2.0f * PI_F;
cube_ay += 0.027f; if (cube_ay > 2.0f * PI_F) cube_ay -= 2.0f * PI_F;
cube_az += 0.013f; if (cube_az > 2.0f * PI_F) cube_az -= 2.0f * PI_F;
}
/* ==================== 场景 3:重力弹球 ==================== */
typedef struct { float px, py, vx, vy; int16_t r; } ball_t;
static ball_t balls[3];
static void balls_init(void)
{
for (int i = 0; i < 3; i++) {
balls[i].px = (float)rnd_range(20, 100);
balls[i].py = (float)rnd_range(15, 60);
balls[i].vx = (float)rnd_range(-30, 30) / 10.0f;
balls[i].vy = (float)rnd_range(-20, 20) / 10.0f;
balls[i].r = 6 + i * 3; /* 6 / 9 / 12 */
}
}
static void balls_frame(void)
{
UC1617S_Clear(UC1617S_WHITE);
const float g = 0.5f;
for (int i = 0; i < 3; i++) {
ball_t *b = &balls[i];
b->vy += g;
if (b->vy > 6.0f) b->vy = 6.0f;
b->px += b->vx;
b->py += b->vy;
if (b->px < b->r) { b->px = b->r; b->vx = -b->vx * 0.92f; }
if (b->px > 127 - b->r) { b->px = 127 - b->r; b->vx = -b->vx * 0.92f; }
if (b->py < b->r) { b->py = b->r; b->vy = -b->vy * 0.92f; }
if (b->py > 95 - b->r) { b->py = 95 - b->r; b->vy = -b->vy * 0.92f; }
int16_t x = (int16_t)b->px, y = (int16_t)b->py;
if (i == 0) { /* 实心 */
fill_circle(x, y, b->r, UC1617S_BLACK);
} else if (i == 1) { /* 双环 */
draw_circle(x, y, b->r, UC1617S_BLACK);
draw_circle(x, y, b->r - 3, UC1617S_BLACK);
} else { /* 甜甜圈 */
fill_circle(x, y, b->r, UC1617S_BLACK);
fill_circle(x, y, b->r - 4, UC1617S_WHITE);
}
}
}
/* ==================== 场景 4:星空穿梭 ==================== */
#define NSTAR 42
typedef struct { float a, r, v; } star_t;
static star_t stars[NSTAR];
static void stars_init(void)
{
for (int i = 0; i < NSTAR; i++) {
stars[i].a = (float)rnd_next() / 32767.0f * 2.0f * PI_F;
stars[i].r = (float)rnd_range(1, 40);
stars[i].v = 0.6f + (float)(rnd_next() % 20) / 10.0f;
}
}
static void stars_frame(void)
{
UC1617S_Clear(UC1617S_WHITE);
const int16_t cx = 63, cy = 47;
for (int i = 0; i < NSTAR; i++) {
star_t *s = &stars[i];
s->r += s->v;
if (s->r > 75.0f) {
s->r = 0.5f + (float)(rnd_next() % 10) / 10.0f;
s->a = (float)rnd_next() / 32767.0f * 2.0f * PI_F;
s->v = 0.6f + (float)(rnd_next() % 20) / 10.0f;
}
float ca = fast_cos(s->a), sa = fast_sin(s->a);
UC1617S_DrawPixel(cx + (int16_t)(ca * s->r), cy + (int16_t)(sa * s->r), UC1617S_BLACK);
UC1617S_DrawPixel(cx + (int16_t)(ca * (s->r - 2)), cy + (int16_t)(sa * (s->r - 2)), UC1617S_BLACK);
UC1617S_DrawPixel(cx + (int16_t)(ca * (s->r - 4)), cy + (int16_t)(sa * (s->r - 4)), UC1617S_BLACK);
}
}
/* ==================== 场景 5:康威生命游戏 ==================== */
static uint8_t life_next[UC1617S_HEIGHT][UC1617S_COL_BYTES];
static int32_t life_gen;
static void life_init(void)
{
for (int16_t y = 0; y < UC1617S_HEIGHT; y++)
for (int16_t x = 0; x < UC1617S_WIDTH; x++)
UC1617S_DrawPixel(x, y, (rnd_next() & 7) == 0 ? UC1617S_BLACK : UC1617S_WHITE);
memcpy(life_next, uc1617s_fb, sizeof(uc1617s_fb)); /* 把初始帧当对比基准 */
life_gen = 0;
}
static int life_neighbors(int16_t x, int16_t y)
{
int n = 0;
for (int dy = -1; dy <= 1; dy++)
for (int dx = -1; dx <= 1; dx++) {
if (dx == 0 && dy == 0) continue;
int16_t xx = (x + dx + UC1617S_WIDTH) % UC1617S_WIDTH; /* 环形边界 */
int16_t yy = (y + dy + UC1617S_HEIGHT) % UC1617S_HEIGHT;
n += fb_get(xx, yy);
}
return n;
}
static void life_frame(void)
{
int changed = 0;
int pop = 0;
for (int16_t y = 0; y < UC1617S_HEIGHT; y++) {
for (int16_t c = 0; c < UC1617S_COL_BYTES; c++) {
uint8_t b = 0;
for (int k = 0; k < 8; k++) {
int16_t x = c * 8 + k;
int n = life_neighbors(x, y);
int cur = fb_get(x, y);
int v = cur ? (n == 2 || n == 3) : (n == 3);
b = (uint8_t)((b << 1) | v);
pop += v;
}
if (life_next[y][c] != b) changed = 1;
life_next[y][c] = b;
}
}
memcpy(uc1617s_fb, life_next, sizeof(uc1617s_fb));
life_gen++;
/* 只在「全灭」或「彻底静止」时才重新播种,画面连续不突兀 */
if (pop == 0 || (changed == 0 && life_gen > 30))
life_init();
}
/* ==================== 场景 6:跑马灯 ==================== */
static int32_t mar_x;
static const char *mar_msg = "HELLO UC1617S * 128 x 96 * STM32F405 * QI SHU XUAN * ";
static void marquee_init(void) { mar_x = UC1617S_WIDTH; }
static void marquee_frame(void)
{
UC1617S_Clear(UC1617S_WHITE);
UC1617S_DrawLine(0, 40, 127, 40, UC1617S_BLACK);
UC1617S_DrawLine(0, 53, 127, 53, UC1617S_BLACK);
int32_t w = (int32_t)strlen(mar_msg) * 6;
mar_x -= 2;
if (mar_x < -w) mar_x = UC1617S_WIDTH;
UC1617S_DrawString(mar_x, 44, mar_msg, 0);
}
/* ==================== 场景调度 ==================== */
typedef enum {
SC_BOUNCE = 0, SC_CUBE, SC_BALLS, SC_STARS, SC_LIFE, SC_MARQUEE, SC_NUM
} scene_t;
static scene_t cur_scene = SC_BOUNCE;
static uint32_t scene_start = 0;
static uint32_t last_frame = 0;
static uint32_t scene_duration(scene_t s)
{
switch (s) {
case SC_BOUNCE: return 6000;
case SC_CUBE: return 8000;
case SC_BALLS: return 6000;
case SC_STARS: return 6000;
case SC_LIFE: return 9000;
case SC_MARQUEE: return 6000;
default: return 5000;
}
}
/* 每个场景的帧间隔(ms)。生命游戏放慢,方便看清每一代演化 */
static uint32_t scene_frame_ms(scene_t s)
{
switch (s) {
case SC_LIFE: return 200;
default: return FRAME_MS;
}
}
static void enter_scene(scene_t s)
{
cur_scene = s;
scene_start = HAL_GetTick();
switch (s) {
case SC_BOUNCE: bounce_init(); break;
case SC_CUBE: cube_init(); break;
case SC_BALLS: balls_init(); break;
case SC_STARS: stars_init(); break;
case SC_LIFE: life_init(); break;
case SC_MARQUEE: marquee_init(); break;
default: break;
}
}
void Demo_Init(void)
{
/* 用上电时刻的 SysTick 计数做随机种子,避免每次开机图案都一样 */
rnd_seed = (int32_t)((HAL_GetTick() * 1664525u + 1013904223u) ^ (SysTick->VAL << 8));
if (rnd_seed == 0) rnd_seed = 0x1A2B3C4D;
if (DEMO_SCENE >= 1 && DEMO_SCENE <= 6)
enter_scene((scene_t)(DEMO_SCENE - 1));
else
enter_scene(SC_BOUNCE);
last_frame = HAL_GetTick();
}
void Demo_Update(void)
{
uint32_t now = HAL_GetTick();
uint32_t fms = scene_frame_ms(cur_scene);
if (now - last_frame < fms)
HAL_Delay(fms - (now - last_frame));
last_frame = HAL_GetTick();
switch (cur_scene) {
case SC_BOUNCE: bounce_frame(); break;
case SC_CUBE: cube_frame(); break;
case SC_BALLS: balls_frame(); break;
case SC_STARS: stars_frame(); break;
case SC_LIFE: life_frame(); break;
case SC_MARQUEE: marquee_frame(); break;
default: break;
}
// ★ 刷屏 + 卡死检测,就加在这里
if (UC1617S_Refresh() != 0) UC1617S_Recover();
if (DEMO_SCENE == 0 &&
HAL_GetTick() - scene_start >= scene_duration(cur_scene))
enter_scene((scene_t)((cur_scene + 1) % SC_NUM));
}
这块屏幕就介绍到这里 我真怕它被卖光 我直接又下单了5块 留着以后做项目使用!!!
如果这篇文章对你有帮助的话 帮小祁点个推荐呗 嘻嘻!!!!!!!!!!!!!!!
浙公网安备 33010602011771号