nodemcu 使用 Lua 编写/移植 ST7735S 0.96 寸彩色 LCD 屏幕驱动(SPI 通信)

本站提供了很多现成的屏幕驱动供大家使用,也写了很多文章教大家如何绘图、显示中文、动画制作等。现在,让我们来自己编写/移植一款彩色屏幕的驱动,这样大家以后也可以把驱动移植到其它平台,或者举一反三,自行学会编写其它型号的屏幕驱动。当然,你也可以移植到其它语言比如 nodejs,Lua,C 来驱动此屏幕。本站已经实现了 C, NodeJS, Lua, Python 各种语言的版本,可以运行在单片机,树莓派,ESP8266/32等各种平台。

先介绍一下 ST7735S LCD 屏幕,这个屏幕的主控型号是使用的 ST7735S,分辨率是 160*80,接口是 SPI。它是 LCD 屏幕,色彩和可视角度都比 TFT 好多了,尤其是色彩鲜艳度和细腻度。此款屏幕可以接 3.3V 或者 5V。注意,不接背光接口它也能亮,只是会发生闪烁现象和屏幕灰暗,所以看到背光闪烁多半是因为电压不够或者没有接背光导致的。它支持 262K 颜色,即 565 格式,使用 2 个字节表示 RGB 颜色。

上面的图片是本店中出售的 ST7735S SPI 接口的屏幕,分辨率是 160*80。

驱动例程

我们是按照厂家给的驱动例程来移植的,所以大家在阅读此文章时,请先查看这篇文章 树莓派使用 Python 编写/移植 ST7735S 0.96 寸彩色 LCD 屏幕驱动(SPI 通信) 里面的 驱动移植部分。

硬件接线

下面是 nodemcu 的引脚图。

程序的开头已经注释了如何接线,请大家自己对照 nodemcu 的引脚图接线。屏幕的 VCC 和 GND 是电源,记得也要接,可以接 3.3V。因为搞电子模块的大家都知道电源是必备的,之前的文章就省略了,然后很多人问我屏幕为什么不亮,我也是很无奈啊。

注意:请一定以屏幕的实际引脚编号为准,如你不清楚如何接,请本店咨询客服,电源反接必烧毁,本店概不负责,屏幕会产生大量热量,注意烫伤手。

在本店购买的屏幕除非特殊说明,默认接 3.3V 电压,防止烧坏。其它地方购买的屏幕,请一定注意电源,烧坏本人不负责。

移植到 nodemcu

前篇文章我们讲解了如何在树莓派上使用 Python 移植驱动。下面我们把它移植到 nodemcu 上。ESP8266 自带了硬件 SPI,所以我们移植起来非常简单,只需要把 SPI 通信部分替换成硬件 SPI,然后将 C 代码改写成 Lua 就行了。上完整代码:

-- Hardware SPI CLK  = GPIO14
-- Hardware SPI MOSI = GPIO13
-- Hardware SPI MISO = GPIO12 (not used)
cs  = 8 -- GPIO15, pull-down 10k to GND
dc  = 4 -- GPIO2
res = 0 -- GPIO16
bus = 1

node.setcpufreq(node.CPU160MHZ)

function spi_begin (id)
  spi.setup(id, spi.MASTER, spi.CPOL_LOW, spi.CPHA_LOW, 8, 2)
  -- we won't be using the HSPI /CS line, so disable it again
  gpio.mode(cs, gpio.INPUT, gpio.PULLUP)
  -- setup command and data pin.
  gpio.mode(dc, gpio.OUTPUT)
  gpio.mode(res, gpio.OUTPUT)
end

function spi_write (id, data)
  spi.send(id, data)
end

function delay_ms (ms)
  tmr.delay(ms * 1000)
end

function lcd_writeCmd(cmd)
  gpio.write(dc, 0)
  spi_write(bus, cmd)
end

function lcd_writeData (data)
  gpio.write(dc, 1)
  spi_write(bus, data)
end

function lcd_reset ()
  gpio.write(res, 0)
  delay_ms(100)
  gpio.write(res, 1)
end

function lcd_init ()
  lcd_reset()
  lcd_writeCmd(0x11)
  delay_ms(120)
  lcd_writeCmd(0x21)

  lcd_writeCmd(0xB1)
  lcd_writeData(0x05)
  lcd_writeData(0x3A)
  lcd_writeData(0x3A)

  lcd_writeCmd(0xB2)
  lcd_writeData(0x05)
  lcd_writeData(0x3A)
  lcd_writeData(0x3A)

  lcd_writeCmd(0xB3)
  lcd_writeData(0x05)
  lcd_writeData(0x3A)
  lcd_writeData(0x3A)
  lcd_writeData(0x05)
  lcd_writeData(0x3A)
  lcd_writeData(0x3A)

  lcd_writeCmd(0xB4)
  lcd_writeData(0x03)

  lcd_writeCmd(0xC0)
  lcd_writeData(0x62)
  lcd_writeData(0x02)
  lcd_writeData(0x04)

  lcd_writeCmd(0xC1)
  lcd_writeData(0xC0)

  lcd_writeCmd(0xC2)
  lcd_writeData(0x0D)
  lcd_writeData(0x00)

  lcd_writeCmd(0xC3)
  lcd_writeData(0x8D)
  lcd_writeData(0x6A)

  lcd_writeCmd(0xC4)
  lcd_writeData(0x8D)
  lcd_writeData(0xEE)

  lcd_writeCmd(0xC5)
  lcd_writeData(0x0E)

  lcd_writeCmd(0xE0)
  lcd_writeData(0x10)
  lcd_writeData(0x0E)
  lcd_writeData(0x02)
  lcd_writeData(0x03)
  lcd_writeData(0x0E)
  lcd_writeData(0x07)
  lcd_writeData(0x02)
  lcd_writeData(0x07)
  lcd_writeData(0x0A)
  lcd_writeData(0x12)
  lcd_writeData(0x27)
  lcd_writeData(0x37)
  lcd_writeData(0x00)
  lcd_writeData(0x0D)
  lcd_writeData(0x0E)
  lcd_writeData(0x10)

  lcd_writeCmd(0xE1)
  lcd_writeData(0x10)
  lcd_writeData(0x0E)
  lcd_writeData(0x03)
  lcd_writeData(0x03)
  lcd_writeData(0x0F)
  lcd_writeData(0x06)
  lcd_writeData(0x02)
  lcd_writeData(0x08)
  lcd_writeData(0x0A)
  lcd_writeData(0x13)
  lcd_writeData(0x26)
  lcd_writeData(0x36)
  lcd_writeData(0x00)
  lcd_writeData(0x0D)
  lcd_writeData(0x0E)
  lcd_writeData(0x10)

  lcd_writeCmd(0x3A)
  lcd_writeData(0x05)

  lcd_writeCmd(0x36)
  lcd_writeData(0xC8)

  lcd_writeCmd(0x29)
end

function lcd_setRegion(x1, y1, x2, y2)
  lcd_writeCmd(0x2a)
  lcd_writeData(0x00)
  lcd_writeData(x1 + 0x1A)
  lcd_writeData(0x00)
  lcd_writeData(x2 + 0x1A)

  lcd_writeCmd(0x2b)
  lcd_writeData(0x00)
  lcd_writeData(y1 + 1)
  lcd_writeData(0x00)
  lcd_writeData(y2 + 1)
  lcd_writeCmd(0x2c)
end

function lcd_fill (color1, color2)
  lcd_setRegion(0, 0, 79, 159)
  for i=1,160 do
    for j=1,80 do
      lcd_writeData(color1)
      lcd_writeData(color2)
    end
    tmr.wdclr()
  end
end

function main()
  spi_begin(bus)
  lcd_init()

  lcd_fill(0xf8, 0x00) -- red
  lcd_fill(0x07, 0xe0) -- green
  lcd_fill(0x00, 0x1f) -- blue
  lcd_fill(0xff, 0xe0) -- yellow
  lcd_fill(0xff, 0xff) -- white
end

main()

可以看到,代码基本和 C 是差不多的,只有 2 处不同,第一个是我们使用 ESP8266 的硬件 SPI 替换了模拟 SPI,延时函数用的是定时器模块。ESP8266 的硬件 SPI 使用方法请查看官方文档,没什么好说的。使用定时器是因为我们没法在应用层准确的知道执行时间,因为这是 Lua 解释器,指令的执行时间是不确定的。

第二处不同是我们在 lcd_fill 函数内增加了一个 tmr.wdclr() 函数调用。这个函数去查看 api 就知道是手动重置看门狗计时器。因为在 nodemcu 上默认开启了看门狗,而我们的清屏函数需要发送整屏的像素数据,并且是同步阻塞的。SPI 的速率没那么快,这就导致还没等我们发送整屏的数据后,看门狗被触发了,导致芯片被硬重启。然后你就会发现你的 ESP8266 一直在无限重启,屏幕总是在刷新到一半的时候就停止。所以我们在每次刷新完一行数据后,就手动重置看门狗,这样就不会无限重启了。虽然对刷新速度有一点影响,不过总算比不能用好。

将上面的代码保存为 init.lua 文件,上传到 nodemcu,屏幕的接线在程序开头就写了,大家自己对照 nodemcu 的引脚图接。然后复位就能看到屏幕在刷屏了。

到这里,最基本的驱动移植已经做完了,能点亮就成功了一大半了,剩下的就是如何封装更友好的图形 api,比如点、线、面、图片、文字之类的。在树莓派上我们有 pillow 这么好用的图形库,但是在 nodemcu 上没有啊,点、线、面这些简单的图形 api 还好说,可以使用现成的算法函数,代码也不多,但是文字和图片就没办法了。如果要显示文字,必须要有字库,英文的字库比较小,实现起来也不是很难,并且网上已经有很多点阵的英文字库了,nodemcu 的 u8g 已经集成了很多的英文字库,可以直接拿来用。中文的话,大家老老实实去取点阵。图片建议在 PC 上先预处理好,保存成像素数据文件后直接刷屏。

再看看其它同类文章吧!

有想说的评论一句吧!