1. 项目概述
CH347/339W是一款功能强大的USB转多功能接口芯片,能够实现SPI、I2C、JTAG、SWD、UART和GPIO等多种接口协议的转换。本开源项目旨在充分利用这款芯片的多协议支持能力,构建一个完整的外设控制解决方案。作为系列文章的第五篇,本文将重点介绍如何通过CH347的SPI接口驱动ST7798 LCD屏幕(1.8寸,128×160分辨率),实现图片轮询显示功能。
在实际硬件开发中,调试和显示界面往往是不可或缺的环节。传统方案需要为每种显示设备单独设计驱动电路,而通过CH347的SPI接口,我们可以直接连接常见的SPI LCD屏幕,大大简化了硬件设计复杂度。本项目实现的图片轮询功能不仅适用于产品演示,也可作为嵌入式系统的简易显示方案。
2. 硬件连接与配置
2.1 硬件准备清单
要实现本项目的全部功能,需要准备以下硬件组件:
- CH347/339W开发板(建议选用带USB Type-C接口的版本)
- ST7798驱动的1.8寸SPI LCD屏幕(128×160分辨率)
- 杜邦线若干(建议使用彩色线区分功能)
- 5V/3.3V电源(根据LCD屏幕规格选择)
- USB数据线(Type-A to Type-C)
注意:LCD屏幕的供电电压必须与CH347开发板的IO电压匹配。大多数ST7798屏幕工作在3.3V,而CH347的IO电压可通过跳线选择3.3V或5V,务必确认一致后再连接。
2.2 引脚连接示意图
ST7798 LCD屏幕与CH347的SPI接口连接方式如下:
| LCD引脚 | CH347引脚 | 功能说明 |
|---|---|---|
| VCC | 3.3V | 电源正极 |
| GND | GND | 电源地 |
| SCL | SCK | SPI时钟 |
| SDA | MOSI | SPI数据输出 |
| RES | GPIO1 | 复位信号(可配置) |
| DC | GPIO0 | 数据/命令选择(可配置) |
| CS | CS0 | 片选信号 |
在实际连接时,建议先确保电源连接正确,再连接信号线。如果LCD屏幕带有背光控制(BLK引脚),可连接到CH347的另一个GPIO引脚实现亮度控制。
2.3 硬件连接注意事项
-
电源稳定性:虽然CH347可以提供3.3V电源,但当驱动较大尺寸LCD时(如2寸以上),建议使用外部独立电源,避免因电流不足导致显示异常。
-
信号线长度:SPI信号对传输距离敏感,建议杜邦线长度不超过15cm。过长可能导致信号完整性问题,表现为显示花屏或数据错误。
-
GPIO分配:默认使用GPIO0和GPIO1分别控制DC和RESET信号,但这两个引脚可通过软件重新配置。如果与其他功能冲突,可选用其他GPIO引脚。
-
接地处理:确保CH347和LCD屏幕共地,这是信号正常传输的基础。可以在连接信号线前,先单独连接GND线。
3. 软件环境搭建
3.1 开发工具准备
本项目使用以下软件开发工具:
- Visual Studio 2019/2022(社区版即可)
- CH347官方驱动(版本1.8以上)
- OpenCV库(用于图像处理,版本4.5+)
- Qt框架(用于GUI开发,版本5.15+)
安装步骤:
- 从沁恒官网下载CH347驱动并安装
- 通过VS的NuGet包管理器安装OpenCV
- 下载Qt VS Tools插件并配置Qt环境
3.2 项目源码获取
项目源码托管在SourceForge平台,可通过以下命令克隆:
bash复制git clone https://git.code.sf.net/p/ch347-multi-function-software/code ch347-multi-function-software
或者直接下载发布版本:
bash复制wget https://sourceforge.net/projects/ch347-multi-function-software/files/latest/download
3.3 环境配置要点
-
驱动安装验证:
安装完成后,连接CH347设备,在设备管理器中应看到"USB-SPI/I2C/GPIO"设备。如果有感叹号标志,可能需要手动指定驱动路径。 -
库文件配置:
OpenCV和Qt的库路径需要正确添加到VS的项目属性中。特别是OpenCV的world模块包含大部分常用功能,建议使用。 -
编译选项:
本项目使用C++17标准,需要在项目属性中设置。同时建议启用OpenMP支持以加速图像处理。
4. SPI接口实现详解
4.1 SPI初始化配置
CH347的SPI接口初始化是关键步骤,配置不当会导致通信失败。核心初始化代码如下:
cpp复制bool SpiInit() {
mSpiCfgS spi_cfg;
spi_cfg.iMode = 3; // SPI模式3(CPOL=1, CPHA=1)
spi_cfg.iClock = 1; // 30MHz时钟
spi_cfg.iByteOrder = 1; // MSB优先
spi_cfg.iSpiWriteReadInterval = 0;
spi_cfg.iSpiOutDefaultData = 0xFF;
spi_cfg.iChipSelect = 0x0080; // CS0使能
spi_cfg.CS1Polarity = 0; // CS1低电平有效
spi_cfg.CS2Polarity = 0; // CS2低电平有效
spi_cfg.iIsAutoDeativeCS = 0;
spi_cfg.iActiveDelay = 0;
spi_cfg.iDelayDeactive = 0;
if (CH347SPI_Init(m_devIndex, &spi_cfg) == FALSE) {
return false;
}
return true;
}
配置参数解析:
- iMode:SPI工作模式,ST7798通常需要模式3(CPOL=1, CPHA=1)
- iClock:时钟分频设置,1对应30MHz,实际频率会根据屏幕规格调整
- iByteOrder:数据传输顺序,ST7798要求MSB优先
- iChipSelect:片选信号配置,0x0080表示使用CS0
实测发现,ST7798在较高时钟频率下(>20MHz)可能出现数据不稳定,建议初始测试时降低时钟频率,稳定后再逐步提高。
4.2 GPIO控制实现
DC和RESET信号通过GPIO控制,实现代码如下:
cpp复制UCHAR GPIOInit(UCHAR enable, UCHAR dir, UCHAR data) {
UCHAR state;
CH347GPIO_Set(m_devIndex, enable, dir, data);
CH347GPIO_Get(m_devIndex, &dir, &state);
return state;
}
void GPIOSet(UCHAR enable, UCHAR dir, UCHAR data) {
CH347GPIO_Set(m_devIndex, enable, dir, data);
}
使用示例(设置DC引脚为高电平):
cpp复制GPIOSet(0x01, 0x01, 0x01); // GPIO0输出高电平
GPIO配置说明:
- enable:引脚使能掩码(0x01对应GPIO0,0x02对应GPIO1,以此类推)
- dir:方向设置(1为输出,0为输入)
- data:输出电平值(1为高电平,0为低电平)
4.3 SPI数据传输优化
ST7798的数据传输需要先发送命令字节,再发送数据。为提高效率,我们实现批量写入功能:
cpp复制void WriteSpi(std::vector<UCHAR> data) {
CH347SPI_Write(m_devIndex, 0x80, data.size(), 128, data.data());
}
// 示例:发送命令后跟数据
void LcdWriteCommand(UCHAR cmd) {
GPIOSet(0x01, 0x01, 0); // DC=0(命令)
WriteSpi({cmd});
}
void LcdWriteData(const std::vector<UCHAR>& data) {
GPIOSet(0x01, 0x01, 1); // DC=1(数据)
WriteSpi(data);
}
传输优化技巧:
- 合并多次小数据包传输,减少SPI事务开销
- 使用DMA传输(如果CH347支持)提高大数据量传输效率
- 适当增加CS信号的保持时间,确保屏幕能正确处理数据
5. LCD驱动实现
5.1 ST7798初始化序列
ST7798需要特定的初始化序列才能正常工作。以下是典型的初始化流程:
cpp复制void LcdInit() {
// 硬件复位
GPIOSet(0x02, 0x02, 0); // RST=0
Sleep(100);
GPIOSet(0x02, 0x02, 1); // RST=1
Sleep(120);
// 发送初始化命令序列
LcdWriteCommand(0x11); // Sleep out
Sleep(120);
LcdWriteCommand(0x3A); // 颜色模式设置
LcdWriteData({0x55}); // 16位RGB565
LcdWriteCommand(0x36); // 内存访问控制
LcdWriteData({0x08}); // 设置扫描方向
// 更多初始化命令...
LcdWriteCommand(0x29); // 开启显示
}
关键点说明:
- 复位信号(RST)需要保持低电平至少10ms
- 命令之间需要适当延时,特别是Sleep Out命令后需要120ms
- 颜色模式必须与后续图像数据处理方式一致(通常使用RGB565)
5.2 图像显示实现
图像显示主要分为三个步骤:图像加载、格式转换和数据传输。
cpp复制void ShowImage(const std::string& path) {
// 1. 加载图像
cv::Mat img = cv::imread(path);
if(img.empty()) return;
// 2. 调整尺寸并转换格式
cv::resize(img, img, cv::Size(160, 128));
cv::cvtColor(img, img, cv::COLOR_BGR2RGB);
// 3. 设置显示区域
SetWindow(0, 0, 159, 127);
// 4. 转换并发送像素数据
std::vector<UCHAR> pixelData;
for(int y = 0; y < img.rows; y++) {
for(int x = 0; x < img.cols; x++) {
cv::Vec3b pixel = img.at<cv::Vec3b>(y, x);
// RGB565转换
UCHAR high = ((pixel[0] >> 3) << 3) | (pixel[1] >> 5);
UCHAR low = ((pixel[1] >> 2) << 5) | (pixel[2] >> 3);
pixelData.push_back(high);
pixelData.push_back(low);
}
}
LcdWriteData(pixelData);
}
void SetWindow(uint16_t x1, uint16_t y1, uint16_t x2, uint16_t y2) {
LcdWriteCommand(0x2A); // 列地址设置
LcdWriteData({
(UCHAR)(x1 >> 8), (UCHAR)x1,
(UCHAR)(x2 >> 8), (UCHAR)x2
});
LcdWriteCommand(0x2B); // 行地址设置
LcdWriteData({
(UCHAR)(y1 >> 8), (UCHAR)y1,
(UCHAR)(y2 >> 8), (UCHAR)y2
});
LcdWriteCommand(0x2C); // 内存写入
}
图像处理注意事项:
- ST7798的像素顺序可能与OpenCV不同,必要时调整扫描方向
- 大图像传输可能耗时较长,建议添加进度显示
- 透明通道处理:PNG图像需预先去除Alpha通道
5.3 图片轮询功能实现
图片轮询通过Qt的定时器实现,核心逻辑如下:
cpp复制// 在MainWindow类中
void MainWindow::startSlideShow() {
if(m_imageList.empty()) return;
m_currentIndex = 0;
m_timer = new QTimer(this);
connect(m_timer, &QTimer::timeout, this, &MainWindow::showNextImage);
m_timer->start(m_interval * 1000); // 转换为毫秒
}
void MainWindow::showNextImage() {
if(++m_currentIndex >= m_imageList.size()) {
m_currentIndex = 0;
}
ShowImage(m_imageList[m_currentIndex]);
// 更新UI显示
QPixmap pix(m_imageList[m_currentIndex]);
ui->previewLabel->setPixmap(pix.scaled(160, 128));
}
功能扩展建议:
- 添加过渡动画效果(如淡入淡出)
- 支持动态调整轮询顺序(随机/顺序)
- 添加暂停/继续功能
- 实现远程图片加载(需网络支持)
6. 功能测试与调试
6.1 屏幕填充测试
屏幕填充测试是验证硬件连接和基本功能的有效方法:
cpp复制void FillScreen(uint16_t color) {
SetWindow(0, 0, 159, 127);
std::vector<UCHAR> buffer;
UCHAR high = color >> 8;
UCHAR low = color & 0xFF;
// 准备一整屏的数据(128*160=20480像素,每个像素2字节)
for(int i = 0; i < 20480; i++) {
buffer.push_back(high);
buffer.push_back(low);
}
LcdWriteCommand(0x2C); // 内存写入
LcdWriteData(buffer);
}
常用测试颜色:
- 红色:0xF800
- 绿色:0x07E0
- 蓝色:0x001F
- 白色:0xFFFF
- 黑色:0x0000
6.2 常见问题排查
-
屏幕无显示
- 检查电源连接和电压
- 确认背光是否开启
- 验证复位信号时序
- 检查SPI时钟极性设置
-
显示花屏
- 降低SPI时钟频率测试
- 检查数据/命令信号(DC)时序
- 确认颜色格式设置(RGB565)
- 检查内存访问控制寄存器设置
-
图片显示颜色异常
- 确认图像通道顺序(RGB vs BGR)
- 检查RGB565转换算法
- 验证图像是否包含Alpha通道
-
数据传输不稳定
- 缩短信号线长度
- 添加适当的延时
- 检查接地是否良好
6.3 性能优化建议
- 双缓冲机制:准备下一张图片时显示当前图片,减少切换延迟
- 图像预加载:提前加载所有图片到内存,避免IO等待
- SPI DMA传输:如果支持,使用DMA提高数据传输效率
- 图像压缩:对大图片进行适当压缩,减少内存占用
7. 项目扩展与展望
7.1 实时系统信息显示
基于现有框架,可以扩展显示系统实时信息:
cpp复制void ShowSystemInfo() {
// 获取CPU使用率
double cpuUsage = GetCpuUsage();
// 获取内存信息
MEMORYSTATUSEX memInfo;
memInfo.dwLength = sizeof(memInfo);
GlobalMemoryStatusEx(&memInfo);
// 创建信息图像
cv::Mat infoImg(128, 160, CV_8UC3, cv::Scalar(0,0,0));
// 绘制信息
std::string cpuText = "CPU: " + std::to_string((int)cpuUsage) + "%";
cv::putText(infoImg, cpuText, cv::Point(10,30),
cv::FONT_HERSHEY_SIMPLEX, 0.5, cv::Scalar(255,255,255));
// 显示图像
ShowImage(infoImg);
}
可显示的信息包括:
- CPU和内存使用率
- 网络状态
- 系统时间和日期
- 传感器数据(温度、湿度等)
7.2 多屏幕支持
通过修改驱动代码,可以支持多种SPI LCD屏幕:
- 定义屏幕参数结构体:
cpp复制struct LcdSpec {
uint16_t width;
uint16_t height;
uint8_t initCode[20];
// 其他屏幕特定参数
};
- 创建屏幕配置文件:
cpp复制std::map<std::string, LcdSpec> lcdModels = {
{"ST7798", {160, 128, {0x11, 0x3A, ...}}},
{"ILI9341", {240, 320, {0x01, 0x11, ...}}},
// 其他型号
};
- 动态加载配置:
cpp复制void SetLcdModel(const std::string& model) {
if(lcdModels.count(model)) {
m_currentSpec = lcdModels[model];
InitLcd();
}
}
7.3 远程控制功能
通过添加网络模块,可以实现远程控制LCD显示:
- WebSocket服务端实现
- REST API接口设计
- 远程图像上传和显示
- 命令控制接口(切换图片、调整参数等)
7.4 脚本功能扩展
引入脚本引擎(如Lua)可以增强灵活性:
lua复制-- 示例脚本
function onLoad()
setInterval(5) -- 5秒轮询
addImage("image1.png")
addImage("image2.jpg")
end
function onTimer()
showNext()
end
实现思路:
- 集成Lua解释器
- 暴露必要的API给脚本环境
- 实现脚本加载和执行机制
- 添加脚本管理界面
8. 项目总结
通过本项目的开发,我们实现了基于CH347 SPI接口的LCD屏幕驱动方案,主要成果包括:
- 完整的ST7798驱动实现,支持图片显示和轮询功能
- 灵活的GPIO配置,适配不同硬件连接方式
- 图形化界面控制,简化操作流程
- 丰富的调试和测试功能
在实际应用中,这套方案可以用于:
- 嵌入式系统开发调试
- 产品原型演示
- 简易信息显示系统
- 教学实验平台
项目改进方向:
- 增加更多LCD型号支持
- 优化图像处理性能
- 完善远程控制功能
- 增强脚本支持能力
整个CH347多功能接口项目现已实现SPI、I2C、JTAG、SWD、UART和GPIO等主要接口的支持,形成了相对完整的外设控制解决方案。开发者可以基于此快速构建各种硬件原型和工具,大大提升开发效率。