打印
[单片机芯片]

在沁恒CH32V307上使用4.0英寸SPI串口TFT液晶屏显示模块

[复制链接]
3265|1
手机看帖
扫描二维码
随时随地手机跟帖
跳转到指定楼层
楼主
tpgf|  楼主 | 2024-8-2 10:30 | 只看该作者 |只看大图 回帖奖励 |倒序浏览 |阅读模式
最近做项目需要一块大显示屏作为机器人的face,在tb上看了看选购了这块:



官方给的例程算是比较全面,奈何我们用到的核心板——沁恒CH32V307(逐飞科技)——比较“小众”,其实官方给出了CH32F1、F2系列的例程,但因为我们用的是seekfree的库,通过Mounriver Studio开发,所以需要自己手动移植。

一、移植效果
目前我已经移植成功了,如下:




4.0英寸SPI串口TFT液晶显示屏

首先我们随便找个官方给的demo(可以去官方下载),从中把lcd.c/.h,FONT.h,GUI.c/.h,test.c/.h添加到Mounriver的工程中,然后会出现一堆错误,原因主要出在SPI的库不同,所以我们第一步先修改SPI库,使二者兼容。

在此之前,我先说明一下我的引脚分配:

//================电源接线==============================================================//
//     LCD模块                 CH32单片机
//      VCC          接        DC5V/3.3V    //电源
//      GND          接          GND        //电源地
//=============液晶屏数据线接线==========================================================//
//    SDI(MOSI)      接          A7        //液晶屏SPI总线数据写信号
//    SDO(MISO)      接                    //液晶屏SPI总线数据读信号,如果不需要读可以不接线
//=============液晶屏控制线接线==========================================================//
//       LED         接          D0        //液晶屏背光控制信号(如果不需要控制可以不接)
//       SCK         接          A5        //液晶屏SPI总线时钟信号
//      LCD_RS       接          D7        //液晶屏数据/命令控制信号
//      LCD_RST      接          B7        //液晶屏复位控制信号
//      LCD_CS       接          D4        //液晶屏片选控制信号
//===============触摸屏触接线===========================================================//
//如果模块不带触摸功能, 或者带有触摸功能但是不需要触摸功能,则不需要进行触摸屏接线
//     CTP_INT       接          B4        //电容触摸屏触摸中断信号
//     CTP_SDA       接          B5        //电容触摸屏IIC总线数据信号
//     CTP_RST       接          B6        //电容触摸屏触摸复位信号
//     CTP_SCL       接          B3        //电容触摸屏IIC总线时钟信号

二、修改SPI文件
在官方写的SPI.h中可以发现只有三个函数:

u8 SPI_WriteByte(SPI_TypeDef* SPIx,u8 Byte);
void SPI1_Init(void);
void SPI_SetSpeed(SPI_TypeDef* SPIx,u8 SpeedSet);
这三个函数的调用主要发生在lcd.h中,我们进入SPI.c阅读下他们的意思,便可以根据zf的库重写/替换一下这三个函数。

第一个函数 u8 SPI_WriteByte(SPI_TypeDef* SPIx,u8 Byte) 的意思是用SPIx写一个字节的数据,返回值为SPIx->DR,它可以用zf的void spi_write_8bit (spi_index_enum spi_n, uint8_t data)平替,但需要注意的是后者是没有返回值的,所以我们要重写一下这个函数:

u8 SPI_WriteByte(spi_index_enum spi_n, uint8_t data)
{
    uint8_t spi_write_8bit (spi_n,data);
    return ((SPI_TypeDef *)(spi_index[spi_n]))->DATAR;
}
这样做的好处是,由于lcd.c中调用了很多官方库中的spi函数,我们直接以官方库函数的名称命名,就省得去lcd.c中把所有的 u8 SPI_WriteByte(SPI_TypeDef* SPIx,u8 Byte) 换成 void spi_write_8bit (spi_index_enum spi_n, uint8_t data) 了。(但还是要改实参,也没省啥事)

第二个函数 void SPI1_Init(void) 是初始化硬件SPI,用zf库中的 void spi_init  (spi_index_enum spi_n, spi_mode_enum mode, uint32_t baud, spi_pin_enum sck_pin, spi_pin_enum mosi_pin, spi_pin_enum miso_pin, gpio_pin_enum cs_pin) 平替就可以。

我们需要把lcd.c中的 void LCD_Init(void) 函数中的 SPI1_Init(); 改成 spi_init(SPI_1, 0, 10*1000*1000, SPI1_SCK_A5, SPI1_MOSI_A7, SPI1_MISO_A6, D4) ;  其中的实参需要根据你自己的引脚分配进行修改,模式、速率也可以自己修改。(实践发现,第三个参数——波特率,严重影响lcd刷屏速率,有条件的话可以尽量调快点。)

第三个函数 void SPI_SetSpeed(SPI_TypeDef* SPIx,u8 SpeedSet) 是更改SPI速度的函数,进入到内部可以发现,若SpeedSet传入为1则进行2分频,其他则为8分频。但找遍了zf的SPI库,并没有发现与之相似的函数,那我们就自己来重写一下吧:

/*****************************************************************************
* @name       :void SPI_SetSpeed(spi_index_enum SPIx, uint8_t SpeedSet)
* @date       :2024-05-08
* @function   :设置spi速度
* @parameters :
* @retvalue   :None
******************************************************************************/
void SPI_SetSpeed(spi_index_enum SPIx, uint8_t SpeedSet)
{
    uint32_t spi_baudrate_div; // SPI波特率分频值

    // 根据SpeedSet的值选择分频
    if(SpeedSet == 1)
    {
        spi_baudrate_div = SPI_BaudRatePrescaler_2; // 二分频
    }
    else
    {
        spi_baudrate_div = SPI_BaudRatePrescaler_8; // 八分频
    }

    // 根据SPIx选择对应的SPI模块并设置速度
    switch(SPIx)
    {
        case SPI_1:
            // 设置SPI1的速度
            SPI1->CTLR1 |=  spi_baudrate_div; // 设置新的分频值
            break;
        case SPI_2:
            // 设置SPI2的速度
            SPI2->CTLR1 |=  spi_baudrate_div; // 设置新的分频值
            break;
        // 如果有更多的SPI模块,可以继续添加case
        default:
            // 如果传入了不支持的SPIx,可以添加错误处理
            break;
    }
}

使用效果与官方提供的是一样的。

三、修改lcd文件
对于lcd的修改主要在 .h 文件中。对端口宏定义的修改(我是根据我的引脚分配来的):

#define LED      D0       //背光控制引脚
#define LCD_CS   D4       //片选引脚
#define LCD_RS   D7       //寄存器/数据选择引脚
#define LCD_RST  B7       //复位引脚
对IO定义直接操作寄存器,快速IO操作:

//GPIO置位(拉高)
#define        LCD_CS_SET  ((GPIO_TypeDef*)gpio_group[(LCD_CS>>5)])->BSHR  = (uint16_t)(1 << (LCD_CS & 0x0F))    //片选端口
#define        LCD_RS_SET        ((GPIO_TypeDef*)gpio_group[(LCD_RS>>5)])->BSHR  = (uint16_t)(1 << (LCD_RS & 0x0F))    //数据/命令
#define        LCD_RST_SET        ((GPIO_TypeDef*)gpio_group[(LCD_RST>>5)])->BSHR  = (uint16_t)(1 << (LCD_RST & 0x0F))   //复位
//GPIO复位(拉低)                                                            
#define        LCD_CS_CLR  ((GPIO_TypeDef*)gpio_group[(LCD_CS>>5)])->BCR   = (uint16_t)(1 << (LCD_CS & 0x0F))     //片选端口
#define        LCD_RS_CLR        ((GPIO_TypeDef*)gpio_group[(LCD_RS>>5)])->BCR   = (uint16_t)(1 << (LCD_RS & 0x0F))     //数据/命令
#define        LCD_RST_CLR        ((GPIO_TypeDef*)gpio_group[(LCD_RST>>5)])->BCR   = (uint16_t)(1 << (LCD_RST & 0x0F))    //复位
比较费事的是官方用了一个这样的操作:

#define        LCD_LED PBout(LED) //LCD背光
一层一层往底倒会发现:

#define PBout(n)   BIT_ADDR(GPIOB_ODR_Addr,n)  //输出
#define BIT_ADDR(addr, bitnum)   MEM_ADDR(BITBAND(addr, bitnum))
#define GPIOB_ODR_Addr    (GPIOB_BASE+12) //0x40010C0C
这是位带操作,实现51类似的GPIO控制功能。但是查阅资料发现,相较于ARM内核,RISC-V内核并没有位带操作,所以我们来自己模拟一下:

/**************自定义位带操作***********/
typedef struct { //根据芯片内存大小端设置,当前为小端模式,大端反过来
    uint32_t bit0 :1;
    uint32_t bit1 :1;
    uint32_t bit2 :1;
    uint32_t bit3 :1;
    uint32_t bit4 :1;
    uint32_t bit5 :1;
    uint32_t bit6 :1;
    uint32_t bit7 :1;
    uint32_t bit8 :1;
    uint32_t bit9 :1;
    uint32_t bit10 :1;
    uint32_t bit11 :1;
    uint32_t bit12 :1;
    uint32_t bit13 :1;
    uint32_t bit14 :1;
    uint32_t bit15 :1;
    uint32_t bit16 :1;
    uint32_t bit17 :1;
    uint32_t bit18 :1;
    uint32_t bit19 :1;
    uint32_t bit20 :1;
    uint32_t bit21 :1;
    uint32_t bit22 :1;
    uint32_t bit23 :1;
    uint32_t bit24 :1;
    uint32_t bit25 :1;
    uint32_t bit26 :1;
    uint32_t bit27 :1;
    uint32_t bit28 :1;
    uint32_t bit29 :1;
    uint32_t bit30 :1;
    uint32_t bit31 :1;
} GPIO_REG;
#define PDout(n)    (((GPIO_REG *)(&(GPIOD->OUTDR)))->bit##n)
#define PDin(n)     (((GPIO_REG *)(&(GPIOD->INDR)))->bit##n)
#define        LCD_LED PDout(0) //LCD背光

至此lcd.h文件就差不多修改完了,接下来修改lcd.c文件,涉及到的改动较少。

第一,把所有SPI函数的实参SPI1改成SPI_1,把所有的 delay_ms() 改成 system_delay_ms()。

第二 ,修改一下lcd的gpio初始化函数:

void LCD_GPIOInit(void)
{
    gpio_init(D0, GPO, 0, GPIO_PIN_CONFIG);
    gpio_init(D4, GPO, 0, GPIO_PIN_CONFIG);
    gpio_init(D7, GPO, 0, GPIO_PIN_CONFIG);
    gpio_init(B7, GPO, 0, GPIO_PIN_CONFIG);
        LCD_LED=1;  //点亮背光
}
在这里你不需要开启RCC时钟,也不需要定义结构体,直接无脑调用 gpio_init() 就完事儿了。

至此,修改工作基本完成,别忘了把包含的头文件改成 #include "zf_common_headfile.h" ,还有GUI和test文件里的一些小细节(如延时函数什么的)。

至于电容触摸屏的修改,其与文章所述过程类似,需要此功能的小伙伴仿照着自行修改吧。
————————————————

                            版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。

原文链接:https://blog.csdn.net/yipenmian/article/details/138726946

使用特权

评论回复
沙发
micoccd| | 2024-8-14 14:39 | 只看该作者
实现效果咋样,刷新率如何

使用特权

评论回复
发新帖 我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则

2028

主题

15903

帖子

13

粉丝