---
video: //player.bilibili.com/player.html?isOutside=true&aid=113225991525459&bvid=BV1KuxseNETw&cid=500001592209944&p=1&high_quality=1&autoplay=0 

---

# 240x240 显示屏 - 显示图片

-  效果展示

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

## 前期图片准备

### 第一步

准备一张图片，图片的分辨率为 240 X 240 ，老许将图片给大家放出来了，直接保存即可

![](https://cdn.zhiyanbang.com/pic/pic.png)


### 第二步

### 图片转换RGB565

RGB565是一种颜色编码格式，用于在存储和传输时减少数据量。它使用16位（2字节）来表示一个颜色值，其中：

- R（红色）：5位，可以表示0到31的值（2^5=32种红色调）
- G（绿色）：6位，可以表示0到63的值（2^6=64种绿色调）
- B（蓝色）：5位，可以表示0到31的值（2^5=32种蓝色调）


这种格式总共可以表示大约65,536种不同的颜色（即32∗64∗32）。RGB565格式在嵌入式系统和移动设备中广泛使用，因为它在保持相对较高颜色精度的同时减少了所需的存储空间。

老许给大家准备了一个```nodejs```的脚本，大家按照以下方法使用即可。

### 安装pngjs库

- js基础的相关教程老许之前已经讲过，如何安装node，搭建环境<a target="_blank" href="https://www.zhiyanbang.com/website/md/38?content_id=kHWyIKVFlZ7dBYZe">点击这里</a> 

- 安装好nodejs 后，在一个空的文件夹中输入命令

```bash
$ npm init
```

```bash
$ npm i pngjs
```

### 第三步

准备脚本并且运行

- png => raw，png2raw.js

```javascript

  const fs = require('fs');
  const PNG = require('pngjs').PNG;
  
  // 读取PNG文件路径
  const inputPath = '/xxx/pic.png';
  const outputPath = '/xxx/pic.raw'; // 输出RAW文件路径
  
  // 创建PNG对象并加载文件
  const png = new PNG({
    filterType: -1 // 禁用过滤以避免潜在的性能问题
  });
  
  let data = [];
  
  fs.createReadStream(inputPath).pipe(png);
  
  // 当PNG图片被解析后，转换每个像素的颜色值
  png.on('parsed', function() {
    // 遍历PNG图片的每个像素
    for (let y = 0; y < png.height; y++) {
      for (let x = 0; x < png.width; x++) {
        // 获取RGBA值
        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);
        // 转换为RGB565格式
        const rgb565 = ((r >> 3) << 11) | ((g >> 2) << 5) | (b >> 3);
        // 将RGB565值添加到数据数组
        data.push((rgb565 >> 8) & 0xFF); // 高字节
        data.push(rgb565 & 0xFF);        // 低字节
      }
    }
  
    // 将数据数组转换为Buffer
    const buffer = Buffer.from(data);
  
    // 写入RAW文件
    fs.writeFileSync(outputPath, buffer);
  
    console.log('PNG to RGB565 RAW conversion completed.');
  });
```
- 运行脚本

```bash
$ node png2raw.js
```
老许已经帮大家上传到了服务器上。

<a href="https://cdn.zhiyanbang.com/pic/pic.raw" target="_blank" download>https://cdn.zhiyanbang.com/pic/pic.raw</a> 

## 硬件链接

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

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



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

## 软件

### 下载图片

-  连接网络后，先将图片文件存入 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);
      downloadData("pic.raw");
  });
}


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

if (statusData.station !== "connected") {
  do_connect();
} else {
  downloadData("pic.raw");
}

```

### 查看图片是否下载成功

```javascript
const fs = require("fs");
console.log(fs.readdirSync());
```
![](https://cdn.zhiyanbang.com/md16%2FWeChate8f5a63c9fa3f525f4095ecd234cbdb7.jpg)

> [!WARNING]
> 如果文件写入失败，想要清空ESP32中的flash闪存，则需要运行以下命令，达到清空flash闪存的目的。

```javascript
E.flashFatFS({ format:true });
```


### 渲染图片

- 添加类库，注意g.showImg的方法，老许帮大家已经写好了。直接使用即可

```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;
}
```
- 渲染图片

```javascript

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


var g = main(SPI2,D13,D5,D12,
 function() {
    g.showImg('pic.raw');
  }
);
```

## 加速显示

刷入支持 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固件


-  修改库文件的 `main` 函数

```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;
}

```





