FastBond3挑战部分-基于USB HS的一线通高刷监控副屏设计
使用USB HS 转 SPI、I2C、UART 通讯芯片、STM32单片机、HS3001温湿度传感器制作一个高刷新率、高自由度的电脑一线通监控屏。
标签
嵌入式系统
显示
USB
pomin
更新2024-09-26
530

背景介绍

现在的电脑外设制造商现在接连不断的推出带屏幕的一些外设,主要面向给极客玩家、DIY 玩家,装扮自己的电脑主机和桌面,置于显示屏下方的小监控屏就是其中很受欢迎的一类产品,但存在些许不足之处,分析如下:

该场景下的“监控小屏”类外设当前主要有以下三种方案:

序号

实现方式

成本

性能

操作

1

通过 AIDA64 获取电脑的状态信息,需要Wi-Fi支持

一般

一般

较麻烦

2

USB 一线通,使用MCU驱屏,类串口屏思路实现

一般

较低

一般

3

使用 HDMI 屏幕,直接作为小拓展屏

简单

以上三种方案或成本较高、或性能局限、或操作麻烦,针对上述问题,本创意首次提出了如下方案

  • 使用 USB HS 转 SPI、I2C、UART专用芯片搭配 IO 拓展芯片实现高速驱屏、电容触摸。
  • 使用 I2C 环境传感器获取当前室内温湿度,可拓展工作台温湿度监控等功能。
  • 直接使用电脑运行 LVGL,无 Flash 限制!无 RAM 限制!搭配 LVGL 设计器实现炫酷界面!
  • 通过 WMI 等方式直接读取电脑状态,无需反复配网、配置,直接运行 exe 完成操作。

依靠上述方案实现了一个低成本、高性能、易操作的监控小屏项目。具体实现且听我娓娓道来。。

硬件设计

器件列表

器件

功能

HS3001

温湿度传感器

STM32G030F6P6

UART 转 PWM、GPIO 拓展功能

CH347T

USB HS 转高速 SPI、I2C、UART 专用芯片

触摸屏

屏幕为 ST7789 驱动芯片,TOUCH 为 FT6236

硬件设计框图



方案介绍如下:

  • 通过 USB 转高速 SPI(60Mbps)驱动 SPI 小屏幕显示,实现高刷新率
  • 通过 USB 转 I2C 读取 TOUCH 芯片、温湿度芯片实现触摸、监测环境温湿度的功能
  • 通过 USB 转高速 UART(6Mbps)和STM32通讯,实现IO、PWM拓展功能

IO 拓展器硬件设计

对于 STM32 端的 IO 分配使用 CubeMX 来完成分配,如下所示:

  • PA13、PA14 作 SWD 烧录引脚
  • PA0、PA1、PA4、PA5 作为 GPIO 输出引脚、PA6、PA7、PA11、PA12 作为 GPIO 输入引脚
  • PB1 作为 PWM 输出引脚、频率为 10KHz
  • PA2、PA3 作为 USART1 通信引脚与 CH347T 通讯



原理图设计

原理图采用 KiCAD 进行设计,CH347T、FPC 等部分封装为手动创建,非系统原理图库,在文末的附件压缩包中可以找到。



原理图中,主要分为

  • Type-C 及供电电路
  • CH347T 与 STM32 通过 UART 连接
  • CH347T 与 HS3001 通过 I2C 连接
  • 触摸屏通过 SPI、I2C、GPIO、PWM 与 CH347T 和 STM32 连接
  • PMOS 控制 STM32 电源电路——当 CH347T 建立 USB 连接后 ACT 拉低,STM32 工作;断开连接(但不断电)后 ACT 拉高,STM32 不工作,LCD_BL 拉低,实现了电脑休眠时自动熄灭屏幕的功能

PCB 设计

PCB 当然也是用的 KiCAD 进行的设计,采用双层板设计


外壳设计

同时也设计了一个简单的外壳,HS3001 位置留了缝隙以供空气传递,外壳和屏幕全贴合,并且留有 Type-C 的接口槽孔。



软件设计

IO 拓展器软件设计

modbus 是工业中常用的一种标准的通信协议,有二进制变量(线圈、离散量)和双字节变量(输入寄存器、保持寄存器)四种类型,在工业中广泛用于 IO 控制、数据同步等许多应用,在本项目中的 IO 操作、PWM 占空比设置十分合适,上下位机的代码也可以利用开源库,易于实现。

IO 拓展器采用 modbus 协议与 PC 端软件通讯,来完成对 MCU 的 IO 输入输出、PWM 占空比进行读写操作,本项目是在 STM32G030F6P6 端移植了 FreeModbus 的协议栈以实现 PC 和 STM32 的通讯,由于篇幅原因,对于移植过程不做赘述,核心的代码如下:

    // 使能 modbus 需要的 RS485 串口和定时器
eMBInit(MB_RTU, id, &huart2, baud, &htim17);
// 使能 modbus 协议栈
eMBEnable();
// 使能 PWM
HAL_TIM_PWM_Start(&htim14, TIM_CHANNEL_1);
while (1)
{
// modbus 轮训
  eMBPoll();
// 如果被主机读
 if (usSRegInRead) {
        usSRegInRead = 0;
}
// 如果被主机写
     if (usSRegHoldWrite) {
HAL_GPIO_WritePin(OUT0_GPIO_Port, OUT0_Pin, IO_OUT0 ? GPIO_PIN_SET : GPIO_PIN_RESET);
HAL_GPIO_WritePin(OUT1_GPIO_Port, OUT1_Pin, IO_OUT1 ? GPIO_PIN_SET : GPIO_PIN_RESET);
HAL_GPIO_WritePin(OUT2_GPIO_Port, OUT2_Pin, IO_OUT2 ? GPIO_PIN_SET : GPIO_PIN_RESET);
HAL_GPIO_WritePin(OUT3_GPIO_Port, OUT3_Pin, IO_OUT3 ? GPIO_PIN_SET : GPIO_PIN_RESET);
// PWM 占空比
__HAL_TIM_SetCompare(&htim14, TIM_CHANNEL_1, PWM_DUTY);
usSRegHoldWrite = 0;
}
IO_IN0 = (USHORT)HAL_GPIO_ReadPin(IN0_GPIO_Port, IN0_Pin);
IO_IN1 = (USHORT)HAL_GPIO_ReadPin(IN1_GPIO_Port, IN1_Pin);
IO_IN2 = (USHORT)HAL_GPIO_ReadPin(IN2_GPIO_Port, IN2_Pin);
IO_IN3 = (USHORT)HAL_GPIO_ReadPin(IN3_GPIO_Port, IN3_Pin);
}

PC 端软件设计

IO 拓展器通讯 API

与 IO 拓展器的通讯使用 libmodbus 来实现,对于读 IO 输入对应为 modbus 读输入寄存器操作,写 IO 输出和 PWM 占空比对应写保持寄存器操作,具体代码如下

#include "driver.h"
#include <modbus.h>

modbus_t* g_ctx;
char g_comx[1024];

void exio_set_com_global(const char* comx) {
    strcpy(g_comx, comx);
}

static int exio_open(void) {
    int addr = 1;
    g_ctx = modbus_new_rtu(g_comx, 115200, 'N', 8, 1);
    if (g_ctx == NULL) {
        fprintf(stderr, "Unable to create the libmodbus context\n");
        return -1;
    }
    modbus_set_slave(g_ctx, addr);
    if (modbus_connect(g_ctx) == -1) {
        fprintf(stderr, "Connection failed: %s\n", modbus_strerror(errno));
        modbus_free(g_ctx);
        return -1;
    }
    return 0;
}
static void exio_close(void) {
    modbus_close(g_ctx);
    modbus_free(g_ctx);
}
int exio_set_out(int index, uint16_t value) {
    int rc;
    rc = exio_open();
    if (rc == -1) {
       return -1;
    }
    rc = modbus_write_register(g_ctx, index, value);
    if (rc == -1) {
        fprintf(stderr, "Failed to write holding register: %s\n", modbus_strerror(errno));
        exio_close();
        return -1;
    }
    exio_close();
    return 0;
}
int exio_get_in(int index, uint16_t* p_value) {
    int nb = 1;
    int rc;
    rc = exio_open();
    if (rc == -1) {
        exio_close();
        return -1;
    }
    rc = modbus_read_input_registers(g_ctx, index, nb, p_value);
    if (rc == -1) {
        fprintf(stderr, "Failed to read input registers: %s\n", modbus_strerror(errno));
        exio_close();
        return -1;
    }
    exio_close();
    return 0;
}

SPI、I2C 通讯 API


对于 SPI、I2C 的通讯,沁恒已经封装成了 DLL 库,只需调用即可,但仍较为复杂,本项目对其进行了一些简化封装,具体代码比较繁多,具体代码可见附件。


驱动封装


对于触摸屏的驱动芯片,也就是 ST7789,采用四线 SPI 通讯方式,其中 D/CX 引脚为 IO 拓展器的 IO 输出脚,用前文的 libmodbus 进行操作,其他的 SCL、SDA、CSX 为 SPI 标准信号线,使用 CH347T 库进行操作,搭配操作实现高速刷屏,代码较为繁琐,可以查看附件



对于触摸屏的 FT6236 触摸芯片和 HS3001 温湿度传感器,都采用 I2C 的通讯方式,使用 CH347T 库进行操作,这里给出 HS3001 读取温湿度的例子

从手册中可以看到 HS3001 的 I2C 8-bit 的地址为 0x88,读取温湿度数据时需要先写一个 0x00 唤醒 HS3001 请求测量,然后再读取温湿度数据,代码如下

void HS3003_Read(float* t, float* h) {
    uint8_t wr_buf[4] = { 0X88, 0, 0, 0, };
    uint8_t rd_buf[4] = { 0, 0, 0, 0, };
    uint16_t humi, temp;

    i2c_writeread(wr_buf, 2, rd_buf, 0);
    Sleep(50);
    wr_buf[0] = 0x89;
    i2c_writeread(wr_buf, 1, rd_buf, 4);
    Sleep(200);

    if ((rd_buf[0] & HS3003_MASK_STATUS) != HS3003_STATUS_VALID) {
        printf("timeout\r\n");
    }
    humi = rd_buf[0];
    humi <<= 8;
    humi |= rd_buf[1];
    humi &= HS3003_MASK_HUMI;

    temp = rd_buf[2];
    temp <<= 8;
    humi |= rd_buf[3];
    temp &= HS3003_MASK_TEMP;
    temp >>= 2;

    *h= HS3003_CALC_HUMI(humi);
    *t= HS3003_CALC_TEMP(temp);
}

对于 PC 的状态信息读取采用 Windows 给出的一些 API 和开源的一些库代码来完成,目前支持了 CPU 温度/占用率、GPU 温度/占用率、主板温度、内存占用率这几个信息的读取,代码比较繁多,具体代码细节可以查看附件。


至此,完成了所有硬件信息的读写,然后就是如何将其展示出来并且可供用户操作了,本项目采用了现在最流行的 LVGL 来作为界面库使用。


对接 LVGL 驱动框架

对于 LVGL 的移植,主要包含有显示驱动接口和输入驱动(在此为触摸)接口。

对于显示驱动接口,本质就是一个对于显存的操作,往显存中填颜色,然后使用 SPI 全局刷新到屏幕上面,具体接口代码如下:

static void lcd_refresh(void) {
    spi_write16b_stream((uint16_t*)clr, LCD_H * LCD_W * 2, LCD_W * 2);
}

static void disp_flush(lv_disp_drv_t * disp_drv, const lv_area_t * area, lv_color_t * color_p)
{
    if(disp_flush_enabled) {
        int32_t x;
        int32_t y;
        for(y = area->y1; y <= area->y2; y++) {
            for(x = area->x1; x <= area->x2; x++) {
                clr[y][x] = color_p->full;
                color_p++;
            }
        }
        lcd_refresh();
    }
    lv_disp_flush_ready(disp_drv);
}

对于输入驱动接口,本质就是 xy 坐标和一个按下状态标志位,读取一下 FT6236 的寄存器即可

static void touchpad_read(lv_indev_drv_t * indev_drv, lv_indev_data_t * data)
{
    static lv_coord_t last_x = 0;
    static lv_coord_t last_y = 0;
    FT6236_Scan(&touch_x, &touch_y, &touch_sta);
    if(touchpad_is_pressed()) {
        touchpad_get_xy(&last_x, &last_y);
        data->state = LV_INDEV_STATE_PR;
    }
    else {
        data->state = LV_INDEV_STATE_REL;
    }
    data->point.x = last_x;
    data->point.y = last_y;
}

LVGL 界面设计

界面设计是使用的 GUI Guider 进行的设计,图标来自 iconfont,图标不做商业用途,整体设计界面如下:


主程序的代码量比较大,

实物展示

PCBA,和2.4 寸的触摸屏铁框完美契合



装上外壳,完美贴合



放在桌面上



和小米温湿度计的合影





物料清单
KiCad文件
使用说明
全屏
附件下载
上位机工程.zip
KiCAD工程.zip
团队介绍
个人
评论
0 / 100
查看更多
硬禾服务号
关注最新动态
0512-67862536
info@eetree.cn
江苏省苏州市苏州工业园区新平街388号腾飞创新园A2幢815室
苏州硬禾信息科技有限公司
Copyright © 2024 苏州硬禾信息科技有限公司 All Rights Reserved 苏ICP备19040198号