# 240X240显示屏(显示动图)

## 240x240 显示屏 - 显示动图

- 效果展示, 由于 gif 有压缩，实际效果参考视频

![](https://cdn.zhiyanbang.com/md16/gif.gif)

## 前期准备

### 第一步

这个动画是由7张 240 x 240 图片组成的，所以需要先准备 7张图片。

老许将相关图片放出来，大家保存就可以了


| 图片名称 | 图片 |
|--------|--------|
| 1.png | ![](https://cdn.zhiyanbang.com/pic/1.png) |
| 2.png | ![](https://cdn.zhiyanbang.com/pic/2.png) |
| 3.png | ![](https://cdn.zhiyanbang.com/pic/3.png) |
| 4.png | ![](https://cdn.zhiyanbang.com/pic/4.png) |
| 5.png | ![](https://cdn.zhiyanbang.com/pic/5.png) |
| 6.png | ![](https://cdn.zhiyanbang.com/pic/6.png) |
| 7.png | ![](https://cdn.zhiyanbang.com/pic/7.png) |


## 第二步

与上节教程一样，准备相关的 raw 文件，是由图片转为 RGB565

相关的批量转换 raw 的```nodejs```逻辑代码，老许给大家放出来了。pngs2raws.js

```javascript
const fs = require('fs');
const PNG = require('pngjs').PNG;

// 异步函数，将PNG转换为RGB565 RAW格式
async function makeRaw(inputPath, outputPath) {
  return new Promise((resolve, reject) => {
    const png = new PNG({
      filterType: -1 // 禁用过滤以避免潜在的性能问题
    });

    let data = [];
    const readStream = fs.createReadStream(inputPath).pipe(png);

    png.on('parsed', function() {
      // 转换图片数据到RGB565格式
      const width = png.width;
      const height = png.height;
      for (let y = 0; y < height; y++) {
        for (let x = 0; x < width; x++) {
          const idx = (png.width * y + x) << 2;
          const r = png.data.readUInt8(idx);
          const g = png.data.readUInt8(idx + 1);
          const b = png.data.readUInt8(idx + 2);
          const rgb565 = ((r >> 3) << 11) | ((g >> 2) << 5) | (b >> 3);
          data.push((rgb565 >> 8) & 0xFF, rgb565 & 0xFF);
        }
      }

      // 转换为Buffer并写入文件
      const buffer = Buffer.from(data);
      fs.writeFileSync(outputPath, buffer);
      console.log(`Conversion completed for ${inputPath}`);
      resolve();
    });

    png.on('error', (err) => {
      console.error(`Failed to process file: ${inputPath}`, err);
      reject(err);
    });
  });
}

async function convertAllImages(pathPrefix) {
  for(let i = 0; i < 7; i++) {
    const Ipath = `${pathPrefix}${i+1}.png`;
    const Opath = `${pathPrefix}${i+1}.raw`;
    console.log(Ipath);
    try {
      await makeRaw(Ipath, Opath);
    } catch (err) {
      console.error(`Error converting image ${Ipath}: `, err);
    }
  }
}

// 调用异步函数转换所有图片
convertAllImages('xxx/Desktop/');
```

## 硬件链接

- 和上节课的连接方法一致


| 器件 | 数量 |
|--------|--------|
| 240X240 st7789显示屏 | Cell |
| ESP32 | 1 |
| 杜邦线 | 若干 |

![](https://cdn.zhiyanbang.com/md16/WeChat0b3c71a437fd5d59fae6086851550e37.jpg)

## 软件代码

### 准备类库

```javascript

const LCD_WIDTH = 240;
const LCD_HEIGHT = 240;
const COLSTART = 0;
const ROWSTART = 0;
// spi, dc, ce, rst, callback
function init(spi, dc, ce, rst, callback) {
    function cmd(c, d) {
        dc.reset();
        spi.write(c, ce);
        if (d !== undefined) {
            dc.set();
            spi.write(d, ce);
        }
    }

    if (rst) {
        digitalPulse(rst, 0, 10);
    } else {
        cmd(0x01); //Software reset
    }

    const ST7789_INIT_CODE = [
        // CMD, D0,D1,D2...
        [0x11, 0], //SLPOUT (11h):

        [0x36, 0x00], // MADCTL
        // These 2 rotate the screen by 180 degrees
        //0x36,0xC0],     // MADCTL
        //0x37,[0,80]],   // VSCSAD (37h): Vertical Scroll Start Address of RAM

        [0x3A, 0x55], // COLMOD - interface pixel format - 16bpp
        [0xB2, [0xC, 0xC, 0, 0x33, 0x33]], // PORCTRL (B2h): Porch Setting
        [0xB7, 0], // GCTRL (B7h): Gate Control
        [0xBB, 0x3E], // VCOMS (BBh): VCOM Setting 
        [0xC2, 1], // VDVVRHEN (C2h): VDV and VRH Command Enable
        [0xC3, 0x19], // VRHS (C3h): VRH Set 
        [0xC4, 0x20], // VDVS (C4h): VDV Set
        [0xC5, 0xF], // VCMOFSET (C5h): VCOM Offset Set .
        [0xD0, [0xA4, 0xA1]], // PWCTRL1 (D0h): Power Control 1 
        [0xe0, [0x70, 0x15, 0x20, 0x15, 0x10, 0x09, 0x48, 0x33, 0x53, 0x0B, 0x19, 0x15, 0x2a, 0x2f]], // PVGAMCTRL (E0h): Positive Voltage Gamma Control
        [0xe1, [0x70, 0x15, 0x20, 0x15, 0x10, 0x09, 0x48, 0x33, 0x53, 0x0B, 0x19, 0x15, 0x2a, 0x2f]], // NVGAMCTRL (E1h): Negative Voltage Gamma Contro
        [0x21, 0], // INVON (21h): Display Inversion On
        [0x29, 0], // DISPON (29h): Display On
        [0x13, 0],
        // End
        [0, 0] // 255/*DATA_LEN = 255 => END*/
    ];

    setTimeout(function() {
        cmd(0x11, 0); //Exit Sleep
        setTimeout(function() {
            ST7789_INIT_CODE.forEach(function(e) {
                cmd(e[0], e[1]);
            });
            if (callback) callback();
        }, 20);
    }, 120);
}


function main(spi, dc, ce, rst, callback) {
    var g = Graphics.createCallback(LCD_WIDTH, LCD_HEIGHT, 16, {
        setPixel: function(x, y, c) {
            ce.reset();
            spi.write(0x2A, dc);
            spi.write((COLSTART + x) >> 8, COLSTART + x, (COLSTART + x) >> 8, COLSTART + x);
            spi.write(0x2B, dc);
            spi.write((ROWSTART + y) >> 8, ROWSTART + y, (ROWSTART + y) >> 8, (ROWSTART + y));
            spi.write(0x2C, dc);
            spi.write(c >> 8, c);
            ce.set();
        },
        fillRect: function(x1, y1, x2, y2, c) {
            ce.reset();
            spi.write(0x2A, dc);
            spi.write((COLSTART + x1) >> 8, COLSTART + x1, (COLSTART + x2) >> 8, COLSTART + x2);
            spi.write(0x2B, dc);
            spi.write((ROWSTART + y1) >> 8, ROWSTART + y1, (ROWSTART + y2) >> 8, (ROWSTART + y2));
            spi.write(0x2C, dc);
            spi.write({
                data: String.fromCharCode(c >> 8, c),
                count: (x2 - x1 + 1) * (y2 - y1 + 1)
            });
            ce.set();
        }
    });
    init(spi, dc, ce, rst, callback);
    g.showImg = function(filename) {
      ce.reset();
      var x2 = LCD_WIDTH - 1;
      var y2 = LCD_HEIGHT - 1;
      spi.write(0x2A, dc);
      spi.write(0,0,x2>>8,x2);
      spi.write(0x2B, dc);
      spi.write(0,0,y2>>8,y2);
      spi.write(0x2C, dc);
      var fd = E.openFile(filename,'r');
      var d = fd.read(3072);
      while (d !== undefined) {
        spi.write(d);
        d = fd.read(3072);
      }
      fd.close();
      ce.set();
    };
    return g;
}

```

### 下载多个raw文件

同理连接网络后，先将图片文件，一个一个存入 ESP32 的 flash闪存

```javascript
const ssid = "laoxu";  // wifi名称
const password = 'laoxu123'; // wifi 密码
const wifi = require('Wifi');
const fs = require("fs");

try {
  console.log(fs.readdirSync());
} catch (e) { //'Uncaught Error: Unable to mount media : NO_FILESYSTEM'
  console.log('Formatting FS - only need to do once');
  E.flashFatFS({ format:true });
}

function downloadData(filename) {
  require('http').get("http://cdn.zhiyanbang.com/pic/"+filename, function(res) {
    var i = 0;
    res.on('data', function(data) {
      i+=1;
      console.log('download:' + filename + " chunk =>" + i + '...');
      fs.appendFileSync(filename, data);
    });
    res.on('close', function(data) {
      console.log("Connection closed");
      console.log(fs.readdirSync());
    });
  });
}


function do_connect() {
  wifi.connect(ssid, {password: password}, function() {
      console.log('Connected to Wifi.  IP address is:', wifi.getIP().ip);
  });
}


const statusData = wifi.getStatus();
console.log(statusData);

if (statusData.station !== "connected") {
  do_connect();
} else {
  downloadData("1.raw");
  // downloadData("2.raw");
  // downloadData("3.raw");
  // downloadData("4.raw");
  // downloadData("5.raw");
  // downloadData("6.raw");
  // downloadData("7.raw");
}

```

### 渲染GIF

- 我们已经掌握了一个图片的渲染，多个图片的渲染，直接使用我们的setinterval函数去一张一张渲染即可。

```javascript
SPI2.setup({
  sck: D18,     // SCK 引脚
  mosi: D23,    // MOSI 引脚
  baud: 40000000,
  mode: 3,
});

// spi, dc, ce, rst, callback

// console.log('--->', SPI2);
var g = main(SPI2,
             D13, //dc
             D5, //ce
             D12, //rst
             function() {
  var i = 0;
  setInterval(() => {
    i += 1;
    var num = (i % 7) + 1;
    console.log(num);
    g.showImg(`${num}.raw`);
  }, 20);
});
```

## 加速显示

刷入支持 lcd_spi_unbuf固件，更改库方法,修改main 函数

> [!TIP]
>  如何刷固件？<a href="https://www.zhiyanbang.com/website/md/37?content_id=F34CgNUxhLBngjb9" target="_blank">Espruino ESP32 开发环境搭建</a>


固件下载地址：

> [!NOTE]
> <a href="https://cdn.zhiyanbang.com/md24/espruino_2v24.1_esp32.zip" target="_blank" download="true">点击进行下载</a>  支持lcd_spi_unbuf 的espruino固件

```javascript
function main(spi, dc, ce, rst, callback) {
   var g = lcd_spi_unbuf.connect(spi, {
            dc: dc,
            cs: ce,
            height: LCD_HEIGHT,
            width: LCD_WIDTH,
            colstart: COLSTART,
            rowstart: ROWSTART
        });
        init(spi, dc, ce, rst, callback);
 
        g.lcd_on = function(s) {
            if (s) {
                dc.reset();
                spi.write(0x29, ce);
            } else {
                dc.reset();
                spi.write(0x28, ce);
            }
        };
 
        g.lcd_inv = function(s) {
            if (s) {
                //Display Inversion On
                dc.reset();
                spi.write(0x21, ce);
            } else {
                //Display Inversion off
                dc.reset();
                spi.write(0x20, ce);
            }
        };
    g.showImg = function(filename) {
      ce.reset();
      var x2 = LCD_WIDTH - 1;
      var y2 = LCD_HEIGHT - 1;
      spi.write(0x2A, dc);
      spi.write(0,0,x2>>8,x2);
      spi.write(0x2B, dc);
      spi.write(0,0,y2>>8,y2);
      spi.write(0x2C, dc);
      var fd = E.openFile(filename,'r');
      var d = fd.read(3072);
      while (d !== undefined) {
        spi.write(d);
        d = fd.read(3072);
      }
      fd.close();
      ce.set();
    };
    return g;
}

```


