BA4RDK

欢迎来到我的博客

博客园 首页 新随笔 联系 订阅 管理

最近一直在寻找老式的点阵屏
独爱那种显示效果,于是上淘宝寻宝
但大多数都是边框很大 或者很厚的屏幕
终于,在我的苦苦寻找下 找到了这款 又大又薄的屏幕!

虽然是块无资料的屏幕 但是这个价格和颜值 简直不要太香!!!
OMG!受不鸟了!我直接下单!
3676023-20260821172654951-481684172

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

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

e234b1bbba34ee97b4d4da2c7adf3bfe

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

可以显示不大于 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工艺了
e5dca468475569a8b89b9e10da851dc7
80e7d7517702357ae0bde5af72fb005c

屏幕的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,数据位出错 → 花屏 / 局部错乱 / 偶发显示异常。

这里焊接好后 写了一个显示立方体的代码 测试一下效果

47d59939910cd24100b9c404aa823bca

肉眼实际观看 非常的清晰漂亮!

测试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块 留着以后做项目使用!!!
如果这篇文章对你有帮助的话 帮小祁点个推荐呗 嘻嘻!!!!!!!!!!!!!!!

posted on 2026-08-21 18:28  BA4RDK  阅读(43)  评论(0)    收藏  举报