【电子DIY】DIY行车记录仪为游戏机之主控软件开发(10)TFT驱动
代码见:https://github.com/qinyunti/py32f403-nes.git

一.前言

        前面我们已经完成了平台很多的基础功能,现在来实现TFT驱动,毕竟实现游戏机最重要的就是显示部分。

我这里屏幕规格是240x320,驱动是ST7789V,SPI接口。

二.驱动

新增以下文件

St7789.c/h完全可移植无需任何修改,st7789_itf.c/h是st7789驱动的实例实现,需要实现IO和SPI接口等,对外提供接口。St7789_test.c提供刷屏测试。

183343uicjcwhi3mgmgjj8

2.1引脚

PC0-DC

PC1-RESET

PA8-背光

PB15-MOSI AF3

PB14-MISO AF3

PB13-SCK  AF3

PB12-NSS  使用IO方式控制

183343qyxy1ocaizyxzjic

183343b2fut3byb2tge2da

183344i1z95jvcji1j98o1

183344pzgi9ccg73uui1gn

Spi初始化见spi.c

                /* PB12  NSS1   LCD

                 * PB13   SCK  AF3   SPI2 APB1

                 * PB14   MISO AF3

                 * PB15   MOSI AF3

                 */

                        GPIO_InitTypeDef  GPIO_InitStruct;      

                        __HAL_RCC_GPIOB_CLK_ENABLE();   

                        __HAL_RCC_SPI2_CLK_ENABLE();


                        GPIO_InitStruct.Pin = GPIO_PIN_12;

                        GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;              

                        GPIO_InitStruct.Pull = GPIO_PULLUP;                    

                        GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_MEDIUM;   

                        HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);  

                        HAL_GPIO_WritePin(GPIOB, GPIO_PIN_12, GPIO_PIN_SET);

                        GPIO_InitStruct.Pin = GPIO_PIN_13 | GPIO_PIN_14 | GPIO_PIN_15;

                        GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;              

                        GPIO_InitStruct.Pull = GPIO_PULLUP;                    

                        GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;   

                        GPIO_InitStruct.Alternate = GPIO_AF3_SPI1;   

                        HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);  


                        SPI_HandleTypeDef hspi;

                        hspi.Instance = SPI2;

                        uint32_t clk = HAL_RCC_GetPCLK1Freq();

                        uint32_t div = clk/baud;

                        if(div<=2){

                                hspi.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_2;

                        }else if(div<=4){

                                hspi.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_4;

                        }else if(div<=8){

                                hspi.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_8;

                        }else if(div<=16){

                                hspi.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_16;

                        }else if(div<=32){

                                hspi.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_32;

                        }else if(div<=64){

                                hspi.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_64;

                        }else if(div<=128){

                                hspi.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_128;

                        }else{

                                hspi.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_256;

                        }

                        if(mode & 0x01){

                                hspi.Init.CLKPhase = SPI_PHASE_2EDGE;

                        }else{

                                hspi.Init.CLKPhase = SPI_PHASE_1EDGE;

                        }

                        if(mode & 0x02){

                                hspi.Init.CLKPolarity = SPI_POLARITY_HIGH;

                        }else{

                                hspi.Init.CLKPolarity = SPI_POLARITY_LOW;

                        }

                        hspi.Init.CRCCalculation = SPI_CRCCALCULATION_DISABLE;

                        hspi.Init.CRCPolynomial = 0;

                        hspi.Init.DataSize = SPI_DATASIZE_8BIT;

                        hspi.Init.Direction = SPI_DIRECTION_2LINES;

                        hspi.Init.FirstBit = SPI_FIRSTBIT_MSB;

                        hspi.Init.Mode = SPI_MODE_MASTER;

                        HAL_SPI_Init(&hspi);

                        LL_SPI_Enable(SPI2);

Spi传输见spi_trans暂时使用查询方式,后面再优化为DMA方式。

Io初始化lcd.c中

void lcd_bk_ctl(int level)

{

        if(level){

                HAL_GPIO_WritePin(GPIOA, GPIO_PIN_8, GPIO_PIN_SET);

        }else{

                HAL_GPIO_WritePin(GPIOA, GPIO_PIN_8, GPIO_PIN_RESET);

        }

}

void lcd_reset_ctl(int level)

{

        if(level){

                HAL_GPIO_WritePin(GPIOC, GPIO_PIN_1, GPIO_PIN_SET);

        }else{

                HAL_GPIO_WritePin(GPIOC, GPIO_PIN_1, GPIO_PIN_RESET);

        }

}

void lcd_dc_ctl(int level)

{

        if(level){

                HAL_GPIO_WritePin(GPIOC, GPIO_PIN_0, GPIO_PIN_SET);

        }else{

                HAL_GPIO_WritePin(GPIOC, GPIO_PIN_0, GPIO_PIN_RESET);

        }

}

void lcd_init(void)

{

        /* DC PC0

         * RESET PC1

         * BK PA8

         */

        GPIO_InitTypeDef  GPIO_InitStruct;

        __HAL_RCC_GPIOC_CLK_ENABLE();   

        __HAL_RCC_GPIOA_CLK_ENABLE();          

        GPIO_InitStruct.Pin = GPIO_PIN_0 | GPIO_PIN_1;

        GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;              

        GPIO_InitStruct.Pull = GPIO_PULLUP;                    

        GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_MEDIUM;   

        HAL_GPIO_Init(GPIOC, &GPIO_InitStruct);  

        HAL_GPIO_WritePin(GPIOC, GPIO_PIN_0, GPIO_PIN_SET);

        HAL_GPIO_WritePin(GPIOC, GPIO_PIN_1, GPIO_PIN_SET);

        GPIO_InitStruct.Pin = GPIO_PIN_8;

        GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;              

        GPIO_InitStruct.Pull = GPIO_PULLUP;                    

        GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_MEDIUM;   

        HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);  

        HAL_GPIO_WritePin(GPIOA, GPIO_PIN_8, GPIO_PIN_SET);

}

2.2 st7789数据结构

初始化结构

/**

* \struct st7789_cmd_st

* 命令结构体

*/

typedef struct

{

    uint8_t    cmd;         /**< 命令                  */

    uint8_t    data[5];     /**< 参数,最多5个参数       */

    uint8_t    datalen;     /**< 参数长度              */

    uint16_t   delay;       /**< 延时时间              */

} st7789_cmd_st;

写命令,写数接口

/**

* \fn st7789_write_cmd

* 写命令

* \param[in] dev \ref st7789_dev_st

* \param[in] cmd 命令字节

* \retval 0 成功

* \retval 其他值 失败

*/

static int st7789_write_cmd(st7789_dev_st* dev,uint8_t cmd)

{

    uint8_t tmp;

#if ST7789_CHECK_PARAM

    if(dev == (st7789_dev_st*)0)

    {

        return -1;

    }

    if(dev->set_dcx == (st7789_set_dcx_pf)0)

    {

        return -1;

    }

    if(dev->write == (st7789_spi_write_pf)0)

    {

        return -1;

    }

#endif

    tmp = cmd;

    dev->enable(1);

    dev->set_dcx(0);

    dev->write(&tmp,1);

    dev->enable(0);

    return 0;

}

/**

* \fn st7789_write_data

* 写数据

* \param[in] dev \ref st7789_dev_st

* \param[in] data 待写入数据

* \param[in] len 待写入数据长度

* \retval 0 成功

* \retval 其他值 失败

*/

static int st7789_write_data(st7789_dev_st* dev,uint8_t* data, uint32_t len)

{

#if ST7789_CHECK_PARAM

    if(dev == (st7789_dev_st*)0)

    {

        return -1;

    }

    if(dev->set_dcx == (st7789_set_dcx_pf)0)

    {

        return -1;

    }

    if(dev->write == (st7789_spi_write_pf)0)

    {

        return -1;

    }

#endif

    dev->enable(1);

    dev->set_dcx(1);

    dev->write(data,len);

    dev->enable(0);

    return 0;

}

2.3st7789初始化

st7789.c中

s_st7789_cmd_init_list中是初始化序列定义

st7789_init时执行初始化序列

具体寄存器含义参考规格书,这里不再赘述

static st7789_cmd_st s_st7789_cmd_init_list[]=

{

    {ST7789_CMD_SLPOUT,{0   },0,120},  /**< SLPOUT (11h): Sleep Out */

    {ST7789_CMD_MADCTL,{0   },1,0},    /**< MADCTL (36h): Memory Data Access Control */

    {ST7789_CMD_COLMOD,{0x55},1,0},    /**< 16bit/pixel 65K         */

    //{ST7789_CMD_PORCTRL,{0x0C,0x0C,0x00,0x33,0x33},5,0},  /**< 默认值 */

    //{ST7789_CMD_GCTRL,{0x35},1,0},                        /**< 默认值 */

    {ST7789_CMD_VCOMS,{0x28},1,0},                          /**< 0x28(1.1V) 默认值是0x20(0.9) */

    {ST7789_CMD_INVON,{0x00},0,0},

    //{ST7789_CMD_LCMCTRL,{0x2C},1,0},                      /**< 默认值       */

    //{ST7789_CMD_VDVVRHEN,{0x01,0xFF},2,0},                /**< 默认值       */

    //{ST7789_CMD_VRHS,{0x0B},1,0},                         /**< 默认值       */

    //{ST7789_CMD_VDVS,{0x20},1,0},                         /**< 默认值       */

    //{ST7789_CMD_FRCTRL2,{0x0F},1,0},                      /**< 默认值  (60Hz帧率)   */

    //{ST7789_CMD_PWCTRL1,{0xA4,0xA1},2,0},                 /**< 默认值       */

    //{ST7789_CMD_PVGAMCTRL,{0xD0,0xD1,0x08,0x0F,0x11,0x2A,0x36,0x55,0x44,0x3A,0x0B,0x06,0x11,0x20},2,0},

    //{ST7789_CMD_NVGAMCTRL,{0xD0,0x02,0x07,0x0A,0x0B,0x18,0x34,0x43,0x4A,0x2B,0x1B,0x1C,0x22,0x1F},2,0},

    //{0xE4,{0x1D,0,0},3,0},

    //{0x21,{0,0,0},0,0},

    //{ST7789_CMD_NORON, {0,0},0,0},  /**< NORON (13h): Normal Display Mode On */

    {ST7789_CMD_DISPON,{0},0,0},    /**< DISPON (29h): Display On */

};

/**

* \fn st7789_init

* 初始化

* \param[in] dev \ref st7789_dev_st

* \retval 0 成功

* \retval 其他值 失败

*/

int st7789_init(st7789_dev_st* dev)

{

#if ST7789_CHECK_PARAM

    if(dev == (st7789_dev_st*)0)

    {

        return -1;

    }

#endif

    if(dev->init != 0)

    {

        dev->init();

    }

    dev->set_reset(1);

    dev->delay(10);

    dev->set_reset(0);

    dev->delay(10);

    dev->set_reset(1);

    dev->delay(120);

                if(dev->buffer != 0){

                        memset(dev->buffer,0,sizeof(ST7789_HSIZE*ST7789_VSIZE*2));

                }

    /* 初始化序列 */

    for(uint32_t i=0; i<sizeof(s_st7789_cmd_init_list)/sizeof(s_st7789_cmd_init_list[0]); i++)

    {

        st7789_write_cmd(dev, s_st7789_cmd_init_list.cmd);

        if(s_st7789_cmd_init_list.datalen > 0)

        {

            st7789_write_data(dev, s_st7789_cmd_init_list.data,s_st7789_cmd_init_list.datalen);

            if(s_st7789_cmd_init_list.delay > 0)

            {

                dev->delay(s_st7789_cmd_init_list.delay);

            }

        }

    }

    return 0;

}

2.4 写显存

设置窗口

/**

* \fn st7789_set_windows

* 设置窗口范围(行列地址)

* \param[in] dev \ref st7789_dev_st

* \param[in] data 待写入数据

* \param[in] len 待写入数据长度

* \retval 0 成功

* \retval 其他值 失败

*/

static int st7789_set_windows(st7789_dev_st* dev, uint16_t x0, uint16_t x1, uint16_t y0, uint16_t y1)

{

    uint8_t data[4];

    st7789_write_cmd(dev, ST7789_CMD_CASET);

    data[0] = (x0>>8) & 0xFF;  /* 列开始地址 大端 */

    data[1] = x0 & 0xFF;

    data[2] = (x1>>8) & 0xFF;  /* 列结束地址 大端 */

    data[3] = x1 & 0xFF;

    st7789_write_data(dev, data, 4);

    st7789_write_cmd(dev, ST7789_CMD_RASET);

    data[0] = (y0>>8) & 0xFF;  /* 行开始地址 大端 */

    data[1] = y0 & 0xFF;

    data[2] = (y1>>8) & 0xFF;  /* 行结束地址 大端 */

    data[3] = y1 & 0xFF;

    st7789_write_data(dev, data, 4);

    return 0;

}

写显存数据

/**

* \fn st7789_sync

* 现存写入st7789

* \param[in] dev \ref st7789_dev_st

* \paran[in] x0 列开始地址

* \paran[in] x1 列结束地址

* \paran[in] y0 行开始地址

* \paran[in] y1 行结束地址

* \paran[in] buffer 待写入数据

* \paran[in] len 待写入数据长度

* \retval 0 成功

* \retval 其他值 失败

*/

int st7789_sync(st7789_dev_st* dev, uint16_t x0, uint16_t x1, uint16_t y0, uint16_t y1, uint16_t* buffer, uint32_t len)

{

    (void)dev;

    st7789_set_windows(dev, x0, x1, y0, y1);

    st7789_write_cmd(dev,ST7789_CMD_RAMWR);

    st7789_write_data(dev, (uint8_t*)buffer, len);

    return 0;

}

三.接口适配

St7789_itf.c中以下实例

/******************************************************************************

*                        以下是ST7789设备实例

*

******************************************************************************/

/* 设备实例 */

static st7789_dev_st s_st7789_itf_dev =

{

    .set_dcx = port_st7789_set_dcx,

    .set_reset = port_st7789_set_reset,

    .write = port_st7789_spi_write,

    .enable = port_st7789_spi_enable,

    .delay = port_st7789_delay_ms,

    .init = port_st7789_init,

    .deinit = port_st7789_deinit,

    .buffer = (uint16_t*)0,

};

适配上述接口

static void port_st7789_set_dcx(uint8_t val)

{

        if(val){

                HAL_GPIO_WritePin(GPIOC, GPIO_PIN_0, GPIO_PIN_SET);

        }else{

                HAL_GPIO_WritePin(GPIOC, GPIO_PIN_0, GPIO_PIN_RESET);

        }

}               

static void port_st7789_set_bk(uint8_t val)

{

        if(val){

                HAL_GPIO_WritePin(GPIOA, GPIO_PIN_8, GPIO_PIN_SET);

        }else{

                HAL_GPIO_WritePin(GPIOA, GPIO_PIN_8, GPIO_PIN_RESET);

        }

}

static void port_st7789_set_reset(uint8_t val)

{

        if(val){

                HAL_GPIO_WritePin(GPIOC, GPIO_PIN_1, GPIO_PIN_SET);

        }else{

                HAL_GPIO_WritePin(GPIOC, GPIO_PIN_1, GPIO_PIN_RESET);

        }

}

static void port_st7789_spi_write(uint8_t* buffer, uint32_t len)

{

        spi_trans(1,0,buffer,0,len,1);

}   

static void port_st7789_spi_enable(uint8_t val)

{

        (void)val;

}   

static void port_st7789_delay_ms(uint32_t t)

{

        vTaskDelay((t*1000/configTICK_RATE_HZ));

}

static void port_st7789_init(void)

{

        /* DC PC0

         * RESET PC1

         * BK PA8

         */

        GPIO_InitTypeDef  GPIO_InitStruct;

        __HAL_RCC_GPIOC_CLK_ENABLE();   

        __HAL_RCC_GPIOA_CLK_ENABLE();          

        GPIO_InitStruct.Pin = GPIO_PIN_0 | GPIO_PIN_1;

        GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;              

        GPIO_InitStruct.Pull = GPIO_PULLUP;                    

        GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_MEDIUM;   

        HAL_GPIO_Init(GPIOC, &GPIO_InitStruct);  

        HAL_GPIO_WritePin(GPIOC, GPIO_PIN_0, GPIO_PIN_SET);

        HAL_GPIO_WritePin(GPIOC, GPIO_PIN_1, GPIO_PIN_SET);

        GPIO_InitStruct.Pin = GPIO_PIN_8;

        GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;              

        GPIO_InitStruct.Pull = GPIO_PULLUP;                    

        GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_MEDIUM;   

        HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);  

        HAL_GPIO_WritePin(GPIOA, GPIO_PIN_8, GPIO_PIN_SET);


        port_st7789_set_bk(0);

        port_st7789_set_dcx(1);


        port_st7789_set_reset(0);

        vTaskDelay((100*1000/configTICK_RATE_HZ));

        port_st7789_set_reset(1);

        vTaskDelay((100*1000/configTICK_RATE_HZ));

        port_st7789_set_bk(1);

}           

static void port_st7789_deinit(void)

{

}   

对外提供操作接口

/******************************************************************************

*                        以下是对外操作接口

*

******************************************************************************/

/**

* \fn st7789_itf_init

* 初始化

* \retval 0 成功

* \retval 其他值 失败

*/

int st7789_itf_init(void)

{

    return st7789_init(&s_st7789_itf_dev);

}

/**

* \fn st7789_itf_deinit

* 解除初始化

* \retval 0 成功

* \retval 其他值 失败

*/

int st7789_itf_deinit(void)

{

    return st7789_deinit(&s_st7789_itf_dev);

}

/**

* \fn st7789_itf_sync

* 刷新显示

* \retval 0 成功

* \retval 其他值 失败

*/

int st7789_itf_sync(void)

{

    return st7789_sync(&s_st7789_itf_dev, 0, ST7789_HSIZE-1, 0, ST7789_VSIZE-1, s_st7789_itf_dev.buffer, ST7789_HSIZE*ST7789_VSIZE*2);

}

/**

* \fn st7789_itf_set_pixel

* 写点

* \param[in] x x坐标位置

* \param[in] y y坐标位置

* \param[in] rgb565 颜色

*/

void st7789_itf_set_pixel(uint16_t x, uint16_t y, uint16_t rgb565)

{

    //if(x >= ST7789_HSIZE)

    //{

    //    return -1;

    //}

    //if(y >= ST7789_VSIZE)

    //{

    //    return -1;

    //}

    s_st7789_itf_dev.buffer[y*ST7789_HSIZE + x] = (uint16_t)((rgb565>>8)&0xFF) | (uint16_t)((rgb565<<8) & 0xFF00);

}

/**

* \fn st7789_itf_set_pixel_0

* 写点

* \param[in] offset 偏移位置

* \param[in] rgb565 颜色

*/

void st7789_itf_set_pixel_0(uint32_t offset, uint16_t rgb565)

{

    s_st7789_itf_dev.buffer[offset] = (uint16_t)((rgb565>>8)&0xFF) | (uint16_t)((rgb565<<8) & 0xFF00);

}

/**

* \fn st7789_itf_get_pixel

* 读点

* \param[in] x x坐标位置

* \param[in] y y坐标位置

* \return rgb565颜色

*/

uint16_t st7789_itf_get_pixel(uint16_t x, uint16_t y)

{

    uint16_t color = s_st7789_itf_dev.buffer[y*ST7789_HSIZE + x];

    return ((uint16_t)(color>>8) | (uint16_t)(color<<8));

}

/**

* \fn st7789_itf_fill_direct

* 直接填充区域

* \param[in] x x开始坐标位置

* \param[in] w 宽度

* \param[in] y y开始坐标位置

* \param[in] h 高度

* \param[in] buffer rgb565颜色缓存区

*/

void st7789_itf_fill_direct(uint16_t x, uint16_t w, uint16_t y, uint16_t h, uint16_t* buffer)

{

                st7789_sync(&s_st7789_itf_dev, x, x+w-1, y, y+h-1, buffer, w*h*2);

}

/**

* \fn st7789_itf_set_pixel_direct

* 直接写点

* \param[in] x x坐标位置

* \param[in] y y坐标位置

* \param[in] rgb565 颜色

*/

void st7789_itf_set_pixel_direct(uint16_t x, uint16_t y, uint16_t rgb565)

{

                uint16_t tmp = rgb565;

                st7789_sync(&s_st7789_itf_dev, x, x, y, y, &tmp, 2);

}

四.测试

St7789_test.c中

static void rgb_test(void)

{

    for(int x=0;x<240;x++)

    {

        for(int y=0;y<320;y++)

        {

            st7789_itf_set_pixel(x, y, 0xF800);

        }

    }

    st7789_itf_sync();

                vTaskDelay((1000*1000/configTICK_RATE_HZ));

    for(int x=0;x<240;x++)

    {

        for(int y=0;y<320;y++)

        {

            st7789_itf_set_pixel(x, y, 0x07E0);

        }

    }

    st7789_itf_sync();

                vTaskDelay((1*1000/configTICK_RATE_HZ));

    for(int x=0;x<240;x++)

    {

        for(int y=0;y<320;y++)

        {

            st7789_itf_set_pixel(x, y, 0x001F);

        }

    }

    st7789_itf_sync();

                vTaskDelay((1*1000/configTICK_RATE_HZ));

}

static void rgb_test_direct(void)

{

    for(int x=0;x<240;x++)

    {

        for(int y=0;y<320;y++)

        {

            st7789_itf_set_pixel_direct(x, y, 0xF800);

        }

    }

                vTaskDelay((1000*1000/configTICK_RATE_HZ));

    for(int x=0;x<240;x++)

    {

        for(int y=0;y<320;y++)

        {

            st7789_itf_set_pixel_direct(x, y, 0x07E0);

        }

    }

                vTaskDelay((1*1000/configTICK_RATE_HZ));

    for(int x=0;x<240;x++)

    {

        for(int y=0;y<320;y++)

        {

            st7789_itf_set_pixel_direct(x, y, 0x001F);

        }

    }

                vTaskDelay((1*1000/configTICK_RATE_HZ));

}

int st7789_test(void)

{

    st7789_itf_init();

    rgb_test();

    uint32_t start;

    uint32_t end;

    uint32_t ftime = 0;

    while(1)

    {

        start = xTaskGetTickCount();

        for(int i=0;i<100;i++)

        {

            rgb_test_direct();

        }

        end = xTaskGetTickCount();

        ftime = (end - start);

        uint32_t fps = (ftime*2+100)/(100*2);  /* 刷新一次的时间uS */

        if(fps > 0)

        {

            xprintf("FPS:%d\r\n",1000000/fps);

        }

        else

        {

            xprintf("FPS:%d\r\n",0);

        }

    }

}

在shell任务中调用

st7789_test进行RGB刷屏测试,效果如下

183344ay1bsonzqn94mgme

183345o6rz8skkvsxxl8ka

183345e2ww2x8x0gl02kxo

五.总结

以上实现了TFT的驱动,可以进行显示,为NES移植做好了准备。后面还需要从两个方面优化,1:SPI使用DMA,2:实现PSRAM驱动,实现帧缓存提高效率。