简介

  • 板子: Esp 32s开发板
  • 屏幕: 2.8‘’ TFT LCD (ILI9341、SPI
  • IDE: Arduino
  • 库:Ucglib
    玩Arduino也有一阵子了,B站小电视和天气始终带火了0.96寸的OLED,上手简单,确实好用,但有个短处就是太小了,而且不支持触摸。。。。那这次这个稍微大一点又支持触摸的吧(事实证明就是完虐自己)

首先就是某宝搜索arduino TFT屏幕(Arduino创客多用这种TFT屏幕),找了下五六十就有焊好排针的,还支持触摸啊,然后我选了个尺寸最大的,等了几天到手发现不是给Arduino玩耍的。。而是树莓派专属的屏幕(没有树莓派的哭泣ing,就是3.5寸蓝色电路板,26个排母的那个。。而且卖家的资料里压根都是七八年前的资料,也没有关于这款屏幕的资料。。。网络上也没有适配arduino的。。。);于是又买了一个稍微小一点2.8寸TFT屏幕,等了三四天开心的取快递,然后接线,上arduino Uno。。。咋整也没反应,就背光LED傻傻的亮着(白屏)。。。。整了一周多,查各种资料,博客、论坛、社区,心态都没了。。。。终于某天我掏出了esp8266和esp32的开发板进行尝试-。-最终在esp32上成功运行显示目标程序

为什么失败

  • Arduino Uno
    这个板子接上屏幕后尝试次数最多,我怀疑是我接线问题、程序不对。。。最后人傻了,在LCDwiki的概述中说

当我们直接将没有板载电平转换模块的SPI显示模块接到Arduino上运行时,会发现根本不能运行。
这是因为SPI模块的引脚只能输入3.3V高电平,而Arduino输出的高电平为5V。要想成功运行,有两种方法:短接法和外接电平转换模块法。
短接法的优点是操作简单,只需短接,不需要再外接器件,缺点是运行时,模块发热量较大,可能会影响模块的使用寿命。
外接电平转换模块法为常规操作,优点是运行时模块发热量小,运行稳定,缺点是操作稍微复杂(需要外接电平转换模块),增加成本(需要额外购买电平转换模块)。
综上所述,推荐使用外接电平转换模块法。

所以这里就告诉我们两条路:

  1. 电压转换:买 5V转换3.3V模块,某宝下单即可,路数别买少了;于是我就掏出了我的ESP系列开发板(3.3V电平)
  2. 短接法:我怂,不采取
  • Esp 8266
    这个板子失败的时候我人傻了。。。。先是板子死了两块。。。(于是我又下单买了不少开发板。。。早知道直接买电平转换模块了,死因不明。。。怀疑是垃圾开发板采用垃圾闪存寿命短吧),第三款新板子到手后尽量减少烧写次数,终于,屏幕有了一丢丢动作,是真的真的只有一丢丢动作(全屏扫描只扫描了几十条就从头开始扫描了。。压根没扫完)。。。。这里根据esp32的结果我猜是垃圾内存不足。。。

失败是成功之母

  • 选对屏幕:驱动芯片、通信类型、适配产品和平台、成功案例
    驱动芯片关系到成败,不是常用的有可供直接使用的库不买,自己能写库的大佬忽略。。。通信类型大部分都是SPI,需要留意自己的板子有没有预留接口,我买的是可以插2.54排针的屏幕,要是焊接功夫NB,可以直接焊接那种细细软软的接口倒是可以省一笔费用。。(焊坏就哭吧)
    适配性的话主要是屏幕能不能支持自己手上的板子,不能的买回来就吃灰。。。而成功案例至少你可以让它成功点亮,而不是只点亮背板LED
  • 资料筛选
    找了Arduino中文社区、野火、CSDN等平台,垃圾信息一大堆,你吵吵我,我抄抄你。。。。Arduino中文社区那个目前还在开贴,我。。。想找个成功案例都要哭啊,最后找到一个LCD WiKi可NB了,感谢LCD wiki的站长

2.8’’ TFT SPI ILI9341

esp32 st7789 LCD屏硬件电路设计 esp32接lcd屏幕_2d

接口定义

序号 引脚标号 说明
1 VCC 5V/3.3V电源输入
2 GND 接地
3 CS 液晶屏片选信号,低电平使能
4 RESET 液晶屏复位信号,低电平复位
5 DC/RS 液晶屏寄存器/数据选择信号,低电平:寄存器,高电平:数据
6 SDI(MOSI) SPI总线写数据信号
7 SCK SPI总线时钟信号
8 LED 背光控制,高电平点亮,如无需控制则接3.3V常亮
9 SDO(MISO) SPI总线读数据信号,如无需读取功能则可不接
(以下为触摸屏信号线接线,如无需触摸或者模块本身不带触摸功能,可不连接)
10 T_CLK 触摸SPI总线时钟信号
11 T_CS 触摸屏片选信号,低电平使能
12 T_DIN 触摸SPI总线输入
13 T_DO 触摸SPI总线输出
14 T_IRQ 触摸屏中断信号,检测到触摸时为低电平

产品参数

显示颜色 RGB 65K彩色
SKU 带触摸:MSP2807、无触摸:MSP2806
尺寸 2.8(inch)
类型 TFT
驱动芯片 ILI9341
分辨率 320*240 (Pixel)
模块接口 4-wire SPI interface
有效显示区域(AA区) 43.2x57.6(mm)
模块PCB底板尺寸 50.0x86.0(mm)
工作温度 -20℃~60℃
存储温度 -30℃~70℃
VCC电源电压 3.3V~5V
逻辑IO口电压 3.3V(TTL)

产品介绍

2.8寸彩屏,支持16BIT RGB 65K色显示,显示色彩丰富
320X240分辨率,可选触摸功能
采用SPI串行总线,只需几个IO即可点亮显示
带SD卡槽方便扩展实验

IO映射

TFT LCD

ESP32

VCC

3V3

GND

GND

CS

P5

RESET

P4

DC

P17

MOSI

P23

SCK

P18

LED

VIN(最好加一个电阻)

MISO

P19(非必需,可以不接)

点亮过程

首先Arduino配置Esp32的开发环境请参考我的一篇博客,然后下载Ucglib库

HelloWorld

引脚连接顺序已在Ucglib_ILI9341_18x240x320_SWSPI ucg(/*sclk=*/ 18, /*data=*/ 23, /*cd=*/ 17, /*cs=*/ 5, /*reset=*/4); //ESP32中写明,按照该顺序连接后下载下面的程序,rst后屏幕首先后扫屏,一遍,然后在顶部显示 Hello World!

代码

#include <SPI.h>
#include "Ucglib.h"

Ucglib_ILI9341_18x240x320_SWSPI ucg(/*sclk=*/ 18, /*data=*/ 23, /*cd=*/ 17, /*cs=*/ 5, /*reset=*/4); //ESP32

void setup(void)
{
  delay(1000);
  ucg.begin(UCG_FONT_MODE_TRANSPARENT);
  ucg.clearScreen();
}

void loop(void)
{
  ucg.setFont(ucg_font_ncenR12_tr);
  ucg.setColor(255, 255, 255);
  ucg.setColor(1, 255, 0,0);
  
  ucg.setPrintPos(0,25);
  ucg.print("Hello World!");

  delay(500);  
}

效果图片

心力憔悴。。。。整了好几天才点亮,放弃了十几次吧。尝试了几十次终于亮了。。。。怀疑自己不存的,有点饿

做完有点心累,晚上回来继续写

esp32 st7789 LCD屏硬件电路设计 esp32接lcd屏幕_2d_02

Box3D

下载下面的程序,rst后屏幕首先后扫屏一遍,然后在中间显示一个彩色立方体,然后不断旋转(帧率肉眼可见。。。。)

代码

#include <SPI.h>
#include "Ucglib.h"

Ucglib_ILI9341_18x240x320_SWSPI ucg(/*sclk=*/ 18, /*data=*/ 23, /*cd=*/ 17, /*cs=*/ 5, /*reset=*/4);      //nodemcu-esp32s,没反应可下载后重新插拔电源,succeed

// define a 3d point structure 
struct pt3d 
{
  ucg_int_t x, y, z;
};

struct surface
{
  uint8_t p[4];
  int16_t z;
};

struct pt2d 
{
  ucg_int_t x, y;
  unsigned is_visible;
};


// define the point at which the observer looks, 3d box will be centered there
#define MX (ucg.getWidth()/2)
#define MY (ucg.getHeight()/2)

// define a value that corresponds to "1"
#define U 64

// eye to screen distance (fixed)
#define ZS U

// cube edge length is 2*U
struct pt3d cube[8] =
{
  { -U, -U, U}, 
  { U, -U, U}, 
  { U, -U, -U}, 
  { -U, -U, -U}, 
  { -U, U, U}, 
  { U, U, U}, 
  { U, U, -U}, 
  { -U, U, -U}, 
};

// define the surfaces
struct surface cube_surface[6] = 
{
  { {0, 1, 2, 3}, 0 },  // bottom
  { {4, 5, 6, 7}, 0 },  // top
  { {0, 1, 5, 4}, 0 },  // back
  
  { {3, 7, 6, 2}, 0 },  // front
  { {1, 2, 6, 5}, 0 },  // right
  { {0, 3, 7, 4}, 0 },  // left
};

// define some structures for the copy of the box, calculation will be done there
struct pt3d cube2[8];
struct pt2d cube_pt[8];

// will contain a rectangle border of the box projection into 2d plane
ucg_int_t x_min, x_max;
ucg_int_t y_min, y_max;

int16_t sin_tbl[65] = {
0,1606,3196,4756,6270,7723,9102,10394,11585,12665,13623,14449,15137,15679,16069,16305,16384,16305,16069,15679,
15137,14449,13623,12665,11585,10394,9102,7723,6270,4756,3196,1606,0,-1605,-3195,-4755,-6269,-7722,-9101,-10393,
-11584,-12664,-13622,-14448,-15136,-15678,-16068,-16304,-16383,-16304,-16068,-15678,-15136,-14448,-13622,-12664,-11584,-10393,-9101,-7722,
-6269,-4755,-3195,-1605,0};

int16_t cos_tbl[65] = {
16384,16305,16069,15679,15137,14449,13623,12665,11585,10394,9102,7723,6270,4756,3196,1606,0,-1605,-3195,-4755,
-6269,-7722,-9101,-10393,-11584,-12664,-13622,-14448,-15136,-15678,-16068,-16304,-16383,-16304,-16068,-15678,-15136,-14448,-13622,-12664,
-11584,-10393,-9101,-7722,-6269,-4755,-3195,-1605,0,1606,3196,4756,6270,7723,9102,10394,11585,12665,13623,14449,
15137,15679,16069,16305,16384};


void copy_cube(void)
{
  uint8_t i;
  for( i = 0; i < 8; i++ )
  {
    cube2[i] = cube[i];
  }
}

void rotate_cube_y(uint16_t w)
{
  uint8_t i;
  int16_t x, z;
  /*
    x' = x * cos(w) + z * sin(w)
    z' = - x * sin(w) + z * cos(w)  
  */
  for( i = 0; i < 8; i++ )
  {
    x = ((int32_t)cube2[i].x * (int32_t)cos_tbl[w] + (int32_t)cube2[i].z * (int32_t)sin_tbl[w])>>14;
    z = (- (int32_t)cube2[i].x * (int32_t)sin_tbl[w] + (int32_t)cube2[i].z * (int32_t)cos_tbl[w])>>14;
    //printf("%d: %d %d --> %d %d\n", i, cube2[i].x, cube2[i].z, x, z);
    cube2[i].x = x;
    cube2[i].z = z;
  }  
}

void rotate_cube_x(uint16_t w)
{
  uint8_t i;
  int16_t y, z;
  for( i = 0; i < 8; i++ )
  {
    y = ((int32_t)cube2[i].y * (int32_t)cos_tbl[w] + (int32_t)cube2[i].z * (int32_t)sin_tbl[w])>>14;
    z = (- (int32_t)cube2[i].y * (int32_t)sin_tbl[w] + (int32_t)cube2[i].z * (int32_t)cos_tbl[w])>>14;
    cube2[i].y = y;
    cube2[i].z = z;
  }  
}

void trans_cube(uint16_t z)
{
  uint8_t i;
  for( i = 0; i < 8; i++ )
  {
    cube2[i].z += z;
  }  
}

void reset_min_max(void)
{
  x_min = 0x07fff;
  y_min = 0x07fff;
  x_max = -0x07fff;
  y_max = -0x07fff;
}

// calculate xs and ys from a 3d value
void convert_3d_to_2d(struct pt3d *p3, struct pt2d *p2)
{
  int32_t t;
  p2->is_visible = 1;
  if ( p3->z >= ZS )
  {
    t = ZS;
    t *= p3->x;
    t <<=1;
    t /= p3->z;
    if ( t >= -MX && t <= MX-1 )
    {
      t += MX;
      p2->x = t;
      
      if ( x_min > t )
  x_min = t;
      if ( x_max < t )
  x_max = t;  
      
      t = ZS;
      t *= p3->y;
      t <<=1;
      t /= p3->z;
      if ( t >= -MY && t <= MY-1 )
      {
  t += MY;
  p2->y = t;
  
  if ( y_min > t )
    y_min = t;
  if ( y_max < t )
    y_max = t;  
      }
      else
      {
  p2->is_visible = 0;
      }
    }
    else
    {
      p2->is_visible = 0;
    }
  }
  else
  {
    p2->is_visible = 0;
  }
}

void convert_cube(void)
{
  uint8_t i;
  reset_min_max();
  for( i = 0; i < 8; i++ )
  {
    convert_3d_to_2d(cube2+i, cube_pt+i);
    //printf("%d: %d %d\n", i, cube_pt[i].x, cube_pt[i].y);
  }  
}

void calculate_z(void)
{
  uint8_t i, j;
  uint16_t z;
  for( i = 0; i < 6; i++ )
  {
    z = 0;
    for( j = 0; j < 4; j++ )
    {
      z+=cube2[cube_surface[i].p[j]].z;      
    }
    z/=4;
    cube_surface[i].z = z;
    //printf("%d: z=%d\n", i, z);
  }  
}

void draw_cube(void)
{
  uint8_t i, ii;
  uint8_t skip_cnt = 3;   /* it is known, that the first 3 surfaces are invisible */
  int16_t z, z_upper;
  
  
  z_upper = 32767;
  for(;;)
  {
    ii = 6;
    z = -32767;
    for( i = 0; i < 6; i++ )
    {
      if ( cube_surface[i].z <= z_upper )
      {
  if ( z < cube_surface[i].z )
  {
    z = cube_surface[i].z;
    ii = i;
  }
      }
    }
    
    if ( ii >= 6 )
      break;
    //printf("%d z=%d upper=%d\n", ii, z, z_upper);
    z_upper = cube_surface[ii].z;
    cube_surface[ii].z++;
    
    if ( skip_cnt > 0 )
    {
      skip_cnt--;
    }
    else
    {
      ucg.setColor(0, ((ii+1)&1)*255,(((ii+1)>>1)&1)*255,(((ii+1)>>2)&1)*255);
      ucg.drawTetragon( 
  cube_pt[cube_surface[ii].p[0]].x, cube_pt[cube_surface[ii].p[0]].y,
  cube_pt[cube_surface[ii].p[1]].x, cube_pt[cube_surface[ii].p[1]].y,
  cube_pt[cube_surface[ii].p[2]].x, cube_pt[cube_surface[ii].p[2]].y,
  cube_pt[cube_surface[ii].p[3]].x, cube_pt[cube_surface[ii].p[3]].y);
    }
  }
}



void calc_and_draw(uint16_t w, uint16_t v)
{
  copy_cube();
  rotate_cube_y(w);
  rotate_cube_x(v);
  trans_cube(U*8);
  convert_cube();
  calculate_z();
  draw_cube();
}


void setup(void)
{
  delay(1000);
  ucg.begin(UCG_FONT_MODE_TRANSPARENT);
  ucg.setRotate90();
  ucg.clearScreen();
  ucg.setFont(ucg_font_ncenR10_tr);
  ucg.setPrintPos(0,25);
  ucg.setColor(255, 255, 255);
  ucg.print("Ucglib Box3D");
  Serial.begin(9600);
    Serial.println("sr");
}

uint16_t w = 0;
uint16_t v = 0;

void loop(void)
{  
  Serial.println("loop");
  calc_and_draw(w, v>>3);

  v+=3;
  v &= 511;

  w++;
  w &= 63;  
  delay(30);  
  
  ucg.setColor(0,0,0);
  ucg.drawBox(x_min, y_min, x_max-x_min+1, y_max-y_min+1);
}