联盛德 HLK-W806 (四): 软件SPI和硬件SPI驱动ST7735液晶LCD
目录
- 联盛德 HLK-W806 (一): Ubuntu20.04下的开发环境配置, 编译和烧录说明
- 联盛德 HLK-W806 (二): Win10下的开发环境配置, 编译和烧录说明
- 联盛德 HLK-W806 (三): 免按键自动下载和复位
- 联盛德 HLK-W806 (四): 软件SPI和硬件SPI驱动ST7735液晶LCD
- 联盛德 HLK-W806 (五): W801开发板上手报告
ST7735介绍
ST7735是用于驱动最大162x132像素的TFT驱动芯片, 396(128*3色)x162线输出, 可以直接以SPI协议, 或者8位/9位/16位并行连接外部控制器.
显示数据可以存储在片内的132 x 162 x 18 bits内存中, 显示内存的读写不需要外部时钟驱动.
使用ST7735的128x160 TFT LCD模块
连接
ST7735的LCD模块有128x128, 128x160等不同分辨率, 对外的接线除了VCC和GND外有6根
- SCL SPI时钟, 对应上位机SPI的SCK
- SDA SPI数据输入, 对应上位机SPI的MOSI
- RES 重启, 低电平有效, 工作时处于高电平
- DC 命令模式和数据模式切换位, 低电平为命令模式, 高电平为数据模式
- CS 片选信号, 对应上位机SPI的CS
- BL 背光, 高电平亮, 低电平灭
如果使用软件SPI, IO口可以随便选择, 如果是硬件SPI, 其中的CS, SCK, MOSI 和 MISO(ST7735未使用)只能使用特定的IO口, 根据W806的手册有以下可选项
- CS: B4, B14
- SCK: B1, B2, B15, B24
- MOSI: B5, B17, B26, PA7
- MISO: B0, B3, B16, B25
对应本测试的连接方式为
- B10 -> RES, RESET
- B11 -> DC, CD
- B14 -> CS, Chip Select
- B15 -> SCK, SCL, CLK, Clock
- B16 -> BL, Back Light
- B17 -> MOSI, SDA
- GND -> GND
- 3.3V -> VCC
ST7735的控制
ST7735的控制分普通IO部分和命令/数据传输部分
- 普通IO部分为外围控制相关的接口, 包括 BL背光, RES重置复位, DC命令数据模式切换
- 命令和数据传输部分为显示控制相关的接口, 有8080并口模式和SPI串口模式, 其中并口还可以区分为8位/9位/16位传输, 因为手里这个模块是串口的, 所以只测试了串口模式. 以下为SPI串口模式的说明
软件SPI方式
软件SPI方式需要初始化全部IO, 都使用GPIO_MODE_OUTPUT
+GPIO_NOPULL
方式
void ST7735_GPIO_Init(void)
{
GPIO_InitTypeDef GPIO_InitStruct = {0};
__HAL_RCC_GPIO_CLK_ENABLE();
GPIO_InitStruct.Pin = ST7735_CS_PIN;
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT;
GPIO_InitStruct.Pull = GPIO_NOPULL;
HAL_GPIO_Init(ST7735_CS_PORT, &GPIO_InitStruct);
GPIO_InitStruct.Pin = ST7735_SCK_PIN;
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT;
GPIO_InitStruct.Pull = GPIO_NOPULL;
HAL_GPIO_Init(ST7735_SCK_PORT, &GPIO_InitStruct);
GPIO_InitStruct.Pin = ST7735_MOSI_PIN;
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT;
GPIO_InitStruct.Pull = GPIO_NOPULL;
HAL_GPIO_Init(ST7735_MOSI_PORT, &GPIO_InitStruct);
GPIO_InitStruct.Pin = ST7735_BL_PIN;
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT;
GPIO_InitStruct.Pull = GPIO_NOPULL;
HAL_GPIO_Init(ST7735_BL_PORT, &GPIO_InitStruct);
GPIO_InitStruct.Pin = ST7735_RES_PIN;
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT;
GPIO_InitStruct.Pull = GPIO_NOPULL;
HAL_GPIO_Init(ST7735_RES_PORT, &GPIO_InitStruct);
GPIO_InitStruct.Pin = ST7735_DC_PIN;
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT;
GPIO_InitStruct.Pull = GPIO_NOPULL;
HAL_GPIO_Init(ST7735_DC_PORT, &GPIO_InitStruct);
}
对应的传输实现
static void ST7735_TransmitByte(uint8_t dat)
{
uint8_t i;
ST7735_CS_LOW;
for (i = 0; i < 8; i++)
{
ST7735_SCK_LOW;
if (dat & 0x80)
{
ST7735_MOSI_HIGH;
}
else
{
ST7735_MOSI_LOW;
}
ST7735_SCK_HIGH;
dat <<= 1;
}
ST7735_CS_HIGH;
}
硬件SPI方式
硬件SPI方式只需要初始化IO接口部分
void ST7735_GPIO_Init(void)
{
GPIO_InitTypeDef GPIO_InitStruct = {0};
__HAL_RCC_GPIO_CLK_ENABLE();
GPIO_InitStruct.Pin = ST7735_BL_PIN;
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT;
GPIO_InitStruct.Pull = GPIO_NOPULL;
HAL_GPIO_Init(ST7735_BL_PORT, &GPIO_InitStruct);
GPIO_InitStruct.Pin = ST7735_RES_PIN;
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT;
GPIO_InitStruct.Pull = GPIO_NOPULL;
HAL_GPIO_Init(ST7735_RES_PORT, &GPIO_InitStruct);
GPIO_InitStruct.Pin = ST7735_DC_PIN;
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT;
GPIO_InitStruct.Pull = GPIO_NOPULL;
HAL_GPIO_Init(ST7735_DC_PORT, &GPIO_InitStruct);
}
数据传输直接调用SDK提供的SPI传输方法
static void ST7735_TransmitByte(uint8_t dat)
{
ST7735_CS_LOW;
HAL_SPI_Transmit(&hspi, &dat, 1, 100);
ST7735_CS_HIGH;
}
使用BUFFER提升刷新速度
在硬件SPI下, 因为接口实现是以批量数据为基础的, 批量写入数据比按单字节或双字节写入效率要高得多, 在代码中可以申请一小段内存增加buffer, 在绘制中通过buffer去调用SPI, 可以充分利用硬件SPI的传输速度.
声明buffer区
static uint8_t st7735_buf[ST7735_BUF_SIZE];
static uint16_t st7735_buf_pt = 0;
写入buffer和清空buffer
static void ST7735_WriteBuff(uint8_t* buff, size_t buff_size)
{
while (buff_size--)
{
st7735_buf[st7735_buf_pt++] = *buff++;
if (st7735_buf_pt == ST7735_BUF_SIZE)
{
ST7735_Transmit(st7735_buf, st7735_buf_pt, HAL_MAX_DELAY);
st7735_buf_pt = 0;
}
}
}
static void ST7735_FlushBuff(void)
{
if (st7735_buf_pt > 0)
{
ST7735_Transmit(st7735_buf, st7735_buf_pt, HAL_MAX_DELAY);
st7735_buf_pt = 0;
}
}
在区域填充中使用buffer
void ST7735_Fill(uint16_t x_start, uint16_t y_start, uint16_t x_end, uint16_t y_end, uint16_t color)
{
uint16_t i,j;
ST7735_SetAddrWindow(x_start, y_start, x_end - 1, y_end - 1);
for(i = y_start; i < y_end; i++)
{
for( j = x_start; j < x_end; j++)
{
ST7735_WriteBuff((uint8_t *)&color, 2);
}
}
ST7735_FlushBuff();
}
字体的绘制和代码结构
这里只说英文字体, 中文字体也是一样的原理, 但是中文字体的规模要大得多. 在LCD显示中, 字体在数据中也是存储的点阵, 所以对应不同font和不同size, 实际上要准备不同的字库, 每个字库在代码中是一个数组, 数组中每个元素代表了字符在其中一行(或者一行的一部分)点阵.
- 数组结构有两种类型, 一种是uint16_t, 一个变量对应的就是点阵的一行, 因此字体最大宽度是16像素, 另一种是不管字体多宽都用uint8_t, 一个变量对应点阵的一行或者一行的一部分, 因此字体的最大宽度不限.
- 数值顺序也有两种类型, 一种是按位从高到低对应像素从左到右, 另一种是按位从低到高对应像素从左到右, 在使用字库时需要区别, 否则无法正常显示
字体的代码结构
很多网上的代码例子, 将字库放在.h头文件中, 因为字库本身是一个大数组, 因此放在头文件中只能在调用的最外层include, 不能多处使用, 否则编译会包变量重复定义错误. 建议使用的字库结构为: 在.c文件中定义字库, 在.h文件中将其声明为extern, 这样就可以多处调用了. 例如:
fonts.h 中声明字体类型
typedef struct {
const uint8_t width;
const uint8_t height;
const uint8_t order;
const uint8_t *data;
} FontDef;
extern FontDef Font_6x12;
font.c 中定义
#include "st7735_fonts.h"
static const uint8_t Font6x12 [] = {
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /*" ",0*/
0x00,0x00,0x04,0x04,0x04,0x04,0x04,0x00,0x00,0x04,0x00,0x00, /*"!",1*/
0x14,0x14,0x0A,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /*""",2*/
...
};
测试代码
控制背光
ST7735_BackLight_On();
ST7735_BackLight_Off();
填充色块
ST7735_Fill(0, 0, ST7735_WIDTH, ST7735_HEIGHT, ST7735_RED);
输出字符
ST7735_DrawString(5, y, (const char *)"0123456789ABCDE", Font_6x12, ST7735_YELLOW, ST7735_RED);
输出图片
ST7735_DrawImage(0, 0, 128, 160, (uint16_t *)testimage1);
在主程序中引用st7735.h并初始化就可以调用了
main.c
#include <stdio.h>
#include "wm_hal.h"
#include "st7735.h"
#include "testimg2.h"
void Error_Handler(void);
SPI_HandleTypeDef hspi;
static void SPI_Init(void)
{
hspi.Instance = SPI;
hspi.Init.Mode = SPI_MODE_MASTER;
hspi.Init.CLKPolarity = SPI_POLARITY_LOW;
hspi.Init.CLKPhase = SPI_PHASE_1EDGE;
hspi.Init.NSS = SPI_NSS_SOFT;
hspi.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_2;
hspi.Init.FirstByte = SPI_LITTLEENDIAN;
if (HAL_SPI_Init(&hspi) != HAL_OK)
{
Error_Handler();
}
}
int main(void)
{
uint8_t y = 10;
SystemClock_Config(CPU_CLK_240M);
ST7735_GPIO_Init();
SPI_Init();
ST7735_Init();
while(1)
{
ST7735_BackLight_On();
ST7735_Fill(0, 0, ST7735_WIDTH, ST7735_HEIGHT, ST7735_RED);
HAL_Delay(500);
ST7735_Fill(0, 0, ST7735_WIDTH, ST7735_HEIGHT, ST7735_YELLOW);
HAL_Delay(500);
ST7735_Fill(0, 0, ST7735_WIDTH, ST7735_HEIGHT, ST7735_GREEN);
HAL_Delay(500);
ST7735_Fill(0, 0, ST7735_WIDTH, ST7735_HEIGHT, ST7735_CYAN);
HAL_Delay(500);
ST7735_Fill(0, 0, ST7735_WIDTH, ST7735_HEIGHT, ST7735_MAGENTA);
HAL_Delay(500);
ST7735_Fill(0, 0, ST7735_WIDTH, ST7735_HEIGHT, ST7735_ORANGE);
HAL_Delay(500);
ST7735_Fill(0, 0, ST7735_WIDTH, ST7735_HEIGHT, ST7735_BROWN);
HAL_Delay(500);
ST7735_Fill(0, 0, ST7735_WIDTH, ST7735_HEIGHT, ST7735_BLUE);
HAL_Delay(500);
y = 10;
ST7735_DrawString(5, y, (const char *)"0123456789ABCDE", Font_6x12, ST7735_YELLOW, ST7735_RED);
HAL_Delay(1000);
ST7735_DrawImage(0, 0, 128, 160, (uint16_t *)testimage1);
HAL_Delay(1000);
ST7735_DrawImage(0, 0, 128, 160, (uint16_t *)testimage2);
HAL_Delay(1000);
ST7735_BackLight_Off();
HAL_Delay(1000);
}
}
另外需要在wm_hal_msp.c中增加SPI的初始化方法
wm_hal_msp.c
void HAL_SPI_MspInit(SPI_HandleTypeDef* hspi)
{
__HAL_RCC_SPI_CLK_ENABLE();
__HAL_AFIO_REMAP_SPI_CS(ST7735_CS_PORT, ST7735_CS_PIN);
__HAL_AFIO_REMAP_SPI_CLK(ST7735_SCK_PORT, ST7735_SCK_PIN);
__HAL_AFIO_REMAP_SPI_MOSI(ST7735_MOSI_PORT, ST7735_MOSI_PIN);
}
void HAL_SPI_MspDeInit(SPI_HandleTypeDef* hspi)
{
__HAL_RCC_SPI_CLK_DISABLE();
HAL_GPIO_DeInit(ST7735_CS_PORT, ST7735_CS_PIN);
HAL_GPIO_DeInit(ST7735_SCK_PORT, ST7735_SCK_PIN);
HAL_GPIO_DeInit(ST7735_MOSI_PORT, ST7735_MOSI_PIN);
}
测试数据
在128x160的LCD中, 通过以下代码在主进程中测试两幅128x160图片无间断循环刷新
for (uint16_t i = 0; i < 1000; i++)
{
ST7735_DrawImage(0, 0, 128, 160, (uint16_t *)testimage1);
ST7735_DrawImage(0, 0, 128, 160, (uint16_t *)testimage2);
}
printf("done");
记录得到的结果为
[2021-11-28 08:06:33.267]
RX:done
[2021-11-28 08:07:12.256]
RX:done
[2021-11-28 08:07:51.232]
RX:done
[2021-11-28 08:08:30.204]
RX:done
[2021-11-28 08:09:09.181]
RX:done
[2021-11-28 08:09:48.166]
RX:done
[2021-11-28 08:10:27.145]
RX:done
[2021-11-28 08:11:06.113]
RX:done
[2021-11-28 08:11:45.145]
RX:done
[2021-11-28 08:12:24.073]
RX:done
可以看到间隔基本上在39秒左右, 根据这个结果计算得全屏刷新率为 2000/39 = 51.2 FPS
相关代码
以上的代码位于Github: wm-sdk-w806/tree/dev/demo/spi/st7735_lcd
联盛德 HLK-W806 (四): 软件SPI和硬件SPI驱动ST7735液晶LCD的更多相关文章
- 联盛德 HLK-W806 (九): 软件SPI和硬件SPI驱动ST7789V液晶LCD
目录 联盛德 HLK-W806 (一): Ubuntu20.04下的开发环境配置, 编译和烧录说明 联盛德 HLK-W806 (二): Win10下的开发环境配置, 编译和烧录说明 联盛德 HLK-W ...
- 联盛德 HLK-W806 (十一): 软件SPI和硬件SPI驱动ST7567液晶LCD
目录 联盛德 HLK-W806 (一): Ubuntu20.04下的开发环境配置, 编译和烧录说明 联盛德 HLK-W806 (二): Win10下的开发环境配置, 编译和烧录说明 联盛德 HLK-W ...
- 联盛德 HLK-W806 (八): 4线SPI驱动SSD1306/SSD1315 128x64 OLED液晶屏
目录 联盛德 HLK-W806 (一): Ubuntu20.04下的开发环境配置, 编译和烧录说明 联盛德 HLK-W806 (二): Win10下的开发环境配置, 编译和烧录说明 联盛德 HLK-W ...
- 联盛德 HLK-W806 (一): Ubuntu20.04下的开发环境配置, 编译和烧录说明
目录 联盛德 HLK-W806 (一): Ubuntu20.04下的开发环境配置, 编译和烧录说明 联盛德 HLK-W806 (二): Win10下的开发环境配置, 编译和烧录说明 联盛德 HLK-W ...
- 联盛德 HLK-W806 (五): W801开发板上手报告
目录 联盛德 HLK-W806 (一): Ubuntu20.04下的开发环境配置, 编译和烧录说明 联盛德 HLK-W806 (二): Win10下的开发环境配置, 编译和烧录说明 联盛德 HLK-W ...
- 联盛德 HLK-W806 (二): Win10下的开发环境配置, 编译和烧录说明
目录 联盛德 HLK-W806 (一): Ubuntu20.04下的开发环境配置, 编译和烧录说明 联盛德 HLK-W806 (二): Win10下的开发环境配置, 编译和烧录说明 联盛德 HLK-W ...
- 联盛德 HLK-W806 (三): 免按键自动下载和复位
目录 联盛德 HLK-W806 (一): Ubuntu20.04下的开发环境配置, 编译和烧录说明 联盛德 HLK-W806 (二): Win10下的开发环境配置, 编译和烧录说明 联盛德 HLK-W ...
- 联盛德 HLK-W806 (六): I2C驱动SSD1306 128x64 OLED液晶屏
目录 联盛德 HLK-W806 (一): Ubuntu20.04下的开发环境配置, 编译和烧录说明 联盛德 HLK-W806 (二): Win10下的开发环境配置, 编译和烧录说明 联盛德 HLK-W ...
- 联盛德 HLK-W806 (七): 兼容开发板 LuatOS Air103
目录 联盛德 HLK-W806 (一): Ubuntu20.04下的开发环境配置, 编译和烧录说明 联盛德 HLK-W806 (二): Win10下的开发环境配置, 编译和烧录说明 联盛德 HLK-W ...
随机推荐
- pycharm输入代码后,没有补全提示
安装pycharm后,输入代码后,没有补全提示 首先检查是否关闭了代码提示,如下图,将红框中"Power Save Mode"前的勾去掉 第二步,如果在输入某些代码时还是没有补全提 ...
- MyBatis 中实现SQL语句中in的操作 (11)
MyBatis 中实现SQL语句中in的操作 概括:应用myBatis实现SQL查询中IN的操作 1.数据库结构及其数据 2.mapper.xml文件 <?xml version="1 ...
- NXOpen.CAM.CAMSetup.CopyObjects的使用
复制CAM对象 Public Function CopyObjects(ByVal view As NXOpen.CAM.CAMSetup.View, ByVal objectsToBeMoved() ...
- Hadoop面试题(四)——YARN
1.简述hadoop1与hadoop2 的架构异同 1)加入了yarn解决了资源调度的问题. 2)加入了对zookeeper的支持实现比较可靠的高可用. 2.为什么会产生 yarn,它解决了什么问题, ...
- 正则表达式: NFA引擎匹配原理
NFA引擎匹配原理 1 为什么要了解引擎匹配原理 一个个音符杂乱无章的组合在一起,弹奏出的或许就是噪音,同样的音符经过作曲家的手,就可以谱出非常动听的乐曲,一个演奏者同样可以照着乐谱奏出动 ...
- MIPI归纳---为什么阻抗为100欧姆
根据LVDS(Low Voltage Differential Signaling)电平定义的. LVDS差分信号PN两线最大幅度是350mV,内部一个恒流源电流是3.5mA.于是终端匹配电阻是100 ...
- FPGA基础之锁存器与触发器的设计
转载:https://blog.csdn.net/lg2lh/article/details/39081061 一.锁存器 首先设计锁存器的时候应该清楚什么是锁存器,锁存器其实是对电平信号敏感的,一定 ...
- linux shell 提示符
当我们打开或者登陆到一个终端的时候都会显示一长串提示符 void@void-ThinkPad-E450:~$ 提示符一般包含当前登陆的用户名 ,主机名,以及当前工作路径路径,最后都是以 $ 或者 # ...
- Android编译执行envsetup.sh,产生工具命令m、mm、mmm、mmma、tapas 、croot、cgrep、jgrep、 resgrep、godir
一般来说编译一个sdk或者一个比较大的工程项目,第一步都是执行 envsetup.sh这个脚本,比如编译android,qt源码以及其他一些嵌入式的sdk. 而且执行的时候需要特别注意使用 sourc ...
- Python ImportError: cannot import name ABC
Python 3.5.2 测试可以运行 import sys from abc import ABC,abstractmethod class MyBase(ABC): @abstractmethod ...