单片机/MCU论坛
直播中

知之为知之zhl

5年用户 107经验值
擅长:可编程逻辑 电源/新能源
私信 关注
[文章]

RA4M2 SCI SPI 驱屏极限测试

开发环境

  • Keil MDK: 5.42.0
  • ARM-GCC: 10.3.1
  • FSP: 5.1.0
  • FreeRTOS: 10.6.1

环境搭建

首先在RASC中新建工程,选择芯片为开发板上的型号 R7FA4M2D3CFP,编译工具链选择 keil MDK

RA4M2 SCI SPI 驱屏极限测试_figure_1.png

不使用 TrustZone 功能
RA4M2 SCI SPI 驱屏极限测试_figure_2.png

选用 FreeRTOS 的工程

RA4M2 SCI SPI 驱屏极限测试_figure_3.png

屏幕选用的是很常见的 ST7789 驱动芯片的 240*240 分辨率的小方屏,搭配之前自己画的一个兼容 Arduino 尺寸和引脚的 屏幕底板,原理图如下
RA4M2 SCI SPI 驱屏极限测试_figure_4.png

然后查看 RA4M2 开发板的原理图,得知连接到 MCU 的各个引脚
RA4M2 SCI SPI 驱屏极限测试_figure_5.png

可以得到硬件IO的连接如下

IO 功能
P600 SCK
P602 MOSI
P603 CS
P114 DC
P601 BLK
P102 RES

创建一个线程命名为lcd_thread,在这个线程下面配置一个 spilcd 的 thread,设置 SPI9 的 stack,SPI9 是 SCI9 中配置得到的,同时配置一个周期 10ms 的定时器,使用的是agt定时器外设,定时器周期设置为1s,可以拿来算fps用
RA4M2 SCI SPI 驱屏极限测试_figure_6.png

然后需要设置下载算法,修改RAM的地址为0x20000000,并且添加如下三个下载算法

RA4M2 SCI SPI 驱屏极限测试_figure_7.png

修改lcd_thread的代码如下,实现一个点灯的效果来验证

void lcd_thread_entry(void * pvParameters)
{
    FSP_PARAMETER_NOT_USED(pvParameters);

    /* TODO: add your own code here */
    while(1)
    {
        static uint8_t state = 0;
        state = !state;
        R_IOPORT_PinWrite(g_ioport.p_ctrl, BSP_IO_PORT_04_PIN_05, state);
        vTaskDelay(pdMS_TO_TICKS(1000));
    }
}

开发板上的LED灯会一秒闪烁一次,至此基本的工程已经搭建完成,下面开始验证SPI的通信

RA4M2 SCI SPI 驱屏极限测试_figure_8.png

点亮屏幕

小屏幕用的是很常见的 ST7789 驱动芯片,根据手册编写初始化序列的代码并编写了一个简单的fps计算程序,大致流程如下:

  1. 向屏幕发送初始化序列,完成屏幕的初始化
  2. 拉低CS、拉高DC,进入全刷模式,不断填充红、绿、蓝三色
  3. 在lcd_thread中fps变量不断自增
  4. 在1s的定时器中断内记录fps的大小
#define RED 0xF800
#define BLUE 0x001F
#define GREEN 0x07E0
#define WHITE 0xFFFF
#define BLACK 0x0000
#define GRAY 0x8430
#define PINK 0xFE19

#define PIN_RES BSP_IO_PORT_01_PIN_02
#define PIN_DC BSP_IO_PORT_01_PIN_14
#define PIN_CS BSP_IO_PORT_06_PIN_03
#define PIN_BL BSP_IO_PORT_06_PIN_01

#define LCD_W 240
#define LCD_H 240

uint8_t lcd_buff[LCD_H][LCD_W][2];
static volatile bool transfer_complete = false;

volatile uint32_t fps;

void lcd_reset(void);
void lcd_spisend(uint8_t data);
void lcd_sendcmd(uint8_t cmd);
void lcd_senddata(int data);
void lcd_push_buff(void);
void lcd_init(void);
void lcd_setPos(uint16_t Xstart, uint16_t Ystart, uint16_t Xend, uint16_t Yend);
void lcd_clear_buff(uint16_t color);
void lcd_clear(uint16_t color);
void lcd_enter_brushmode(void);
void lcd_setup(void);
void lcd_loop(void);

void lcd_reset(void)
{
    R_IOPORT_PinWrite(g_ioport.p_ctrl, PIN_RES, BSP_IO_LEVEL_LOW);
    vTaskDelay(pdMS_TO_TICKS(100));
    R_IOPORT_PinWrite(g_ioport.p_ctrl, PIN_RES, BSP_IO_LEVEL_HIGH);
    vTaskDelay(pdMS_TO_TICKS(100));
}

void lcd_spisend(uint8_t data) {
    fsp_err_t err = FSP_SUCCESS;
    transfer_complete = false;
    R_IOPORT_PinWrite(g_ioport.p_ctrl, PIN_CS, BSP_IO_LEVEL_LOW);
    err = R_SCI_SPI_Write(spilcd_spi9.p_ctrl, &data, 1, SPI_BIT_WIDTH_8_BITS);
    assert(FSP_SUCCESS == err);
    while (transfer_complete == false) {
    }
    R_IOPORT_PinWrite(g_ioport.p_ctrl, PIN_CS, BSP_IO_LEVEL_HIGH);
}

void lcd_sendcmd(uint8_t cmd) {
    R_IOPORT_PinWrite(g_ioport.p_ctrl, PIN_DC, BSP_IO_LEVEL_LOW);
    lcd_spisend(cmd);
    R_IOPORT_PinWrite(g_ioport.p_ctrl, PIN_DC, BSP_IO_LEVEL_HIGH);
}

void lcd_senddata(int data) { lcd_spisend((uint8_t)data); }

void lcd_push_buff(void) {
    transfer_complete = false;
    R_SCI_SPI_Write(spilcd_spi9.p_ctrl, (uint8_t* )lcd_buff, LCD_W * LCD_H, SPI_BIT_WIDTH_8_BITS);
    while (transfer_complete == false) { }
    transfer_complete = false;
    R_SCI_SPI_Write(spilcd_spi9.p_ctrl, (uint8_t* )lcd_buff + LCD_W * LCD_H, LCD_W * LCD_H, SPI_BIT_WIDTH_8_BITS);
    while (transfer_complete == false) { }
}

void lcd_init(void) {
    lcd_reset();

    lcd_sendcmd(0x11);
    vTaskDelay(pdMS_TO_TICKS(100));

    lcd_sendcmd(0x09);
    lcd_senddata(0x00);
    lcd_senddata(0x00);
    lcd_sendcmd(0xB1);
    lcd_senddata(0x05);
    lcd_senddata(0x3C);
    lcd_senddata(0x3C);
    lcd_sendcmd(0xB2);
    lcd_senddata(0x05);
    lcd_senddata(0x3C);
    lcd_senddata(0x3C);
    lcd_sendcmd(0xB3);
    lcd_senddata(0x05);
    lcd_senddata(0x3C);
    lcd_senddata(0x3C);
    lcd_senddata(0x05);
    lcd_senddata(0x3C);
    lcd_senddata(0x3C);
    lcd_sendcmd(0xB4);
    lcd_senddata(0x03);
    lcd_sendcmd(0xC0);
    lcd_senddata(0x28);
    lcd_senddata(0x08);
    lcd_senddata(0x04);
    lcd_sendcmd(0xC1);
    lcd_senddata(0XC0);
    lcd_sendcmd(0xC2);
    lcd_senddata(0x0D);
    lcd_senddata(0x00);
    lcd_sendcmd(0xC3);
    lcd_senddata(0x8D);
    lcd_senddata(0x2A);
    lcd_sendcmd(0xC4);
    lcd_senddata(0x8D);
    lcd_senddata(0xEE);
    lcd_sendcmd(0xC5);
    lcd_senddata(0x1A);
    lcd_sendcmd(0x36);
    lcd_senddata(0x00);
    lcd_sendcmd(0xE0);
    lcd_senddata(0x04);
    lcd_senddata(0x22);
    lcd_senddata(0x07);
    lcd_senddata(0x0A);
    lcd_senddata(0x2E);
    lcd_senddata(0x30);
    lcd_senddata(0x25);
    lcd_senddata(0x2A);
    lcd_senddata(0x28);
    lcd_senddata(0x26);
    lcd_senddata(0x2E);
    lcd_senddata(0x3A);
    lcd_senddata(0x00);
    lcd_senddata(0x01);
    lcd_senddata(0x03);
    lcd_senddata(0x13);
    lcd_sendcmd(0xE1);
    lcd_senddata(0x04);
    lcd_senddata(0x16);
    lcd_senddata(0x06);
    lcd_senddata(0x0D);
    lcd_senddata(0x2D);
    lcd_senddata(0x26);
    lcd_senddata(0x23);
    lcd_senddata(0x27);
    lcd_senddata(0x27);
    lcd_senddata(0x25);
    lcd_senddata(0x2D);
    lcd_senddata(0x3B);
    lcd_senddata(0x00);
    lcd_senddata(0x01);
    lcd_senddata(0x04);
    lcd_senddata(0x13);
    lcd_sendcmd(0x3A);
    lcd_senddata(0x05);
    lcd_sendcmd(0x21);

    lcd_sendcmd(0x29);
}

void lcd_setPos(uint16_t Xstart, uint16_t Ystart, uint16_t Xend, uint16_t Yend) {
    lcd_sendcmd(0x2a);
    lcd_senddata((Xstart) >> 8);
    lcd_senddata((Xstart) & 0xff);
    lcd_senddata((Xend) >> 8);
    lcd_senddata((Xend) & 0xff);
    lcd_sendcmd(0x2b);
    lcd_senddata((Ystart) >> 8);
    lcd_senddata((Ystart) & 0xff);
    lcd_senddata((Yend) >> 8);
    lcd_senddata((Yend)&0xff);
    lcd_sendcmd(0x2C);
}

void lcd_clear_buff(uint16_t color) {
    for (size_t j = 0; j < LCD_H; j++) {
        for (size_t i = 0; i < LCD_W; i++) {
            lcd_buff[j][i][0] = (uint8_t)(color >> 8);
            lcd_buff[j][i][1] = (uint8_t)color;
        }
    }
}

void lcd_clear(uint16_t color) {
    lcd_clear_buff(color);
    lcd_push_buff();
}

void lcd_enter_brushmode(void) {
    lcd_setPos(0, 0, LCD_W-1, LCD_H-1);
    R_IOPORT_PinWrite(g_ioport.p_ctrl, PIN_CS, BSP_IO_LEVEL_LOW);
}

void lcd_setup(void) {
    fsp_err_t err = FSP_SUCCESS;
    err = R_SCI_SPI_Open(spilcd_spi9.p_ctrl, spilcd_spi9.p_cfg);
    err += R_AGT_Open(spilcd_timer0.p_ctrl, spilcd_timer0.p_cfg);
    err += R_AGT_Start(spilcd_timer0.p_ctrl);
    assert(FSP_SUCCESS == err);

    lcd_init();
    lcd_enter_brushmode();
}

void lcd_loop(void) {
    static uint16_t rgb;
    rgb++;
    rgb %= 3;
    if (rgb == 1)
    {
        lcd_clear_buff(RED);
    } else if (rgb == 2)
    {
        lcd_clear_buff(GREEN);
    } else
    {
        lcd_clear_buff(BLUE);
    }
    lcd_push_buff();
}

/* lcd_thread entry function */
/* pvParameters contains TaskHandle_t */
void lcd_thread_entry(void* pvParameters) {
    FSP_PARAMETER_NOT_USED(pvParameters);
    R_IOPORT_Open(g_ioport.p_ctrl, g_ioport.p_cfg);
    R_AGT_Open(spilcd_timer0.p_ctrl, spilcd_timer0.p_cfg);
    R_AGT_Start(spilcd_timer0.p_ctrl);
    lcd_setup();
    /* TODO: add your own code here */
    while (1) {
        lcd_loop();
        fps++;
    }
}

void sci_spi_callback(spi_callback_args_t* p_args) {
    if (SPI_EVENT_TRANSFER_COMPLETE == p_args->event) {
        transfer_complete = true;
    }
}
static uint32_t fps_record = 0;
void spilcd_timer0_callback(timer_callback_args_t* p_args) {
    if (TIMER_EVENT_CYCLE_END == p_args->event) {
        fps_record = fps;
        fps = 0;
        static uint8_t state = 0;
        state = !state;
        R_IOPORT_PinWrite(g_ioport.p_ctrl, BSP_IO_PORT_04_PIN_05, state);
    }
}

烧录到开发板中后可以看到屏幕上面已经不断的刷新三种颜色,用逻辑分析仪抓包查看波形可以看到SPI的输出,稳定的25Mbps
RA4M2 SCI SPI 驱屏极限测试_figure_9.png

拍摄原因所以不是单色的,可以看到屏幕的刷新也是非常快
RA4M2 SCI SPI 驱屏极限测试_figure_10.png

在keil MDK的debug界面可以看出来fps稳定在16
RA4M2 SCI SPI 驱屏极限测试_figure_11.png

写在最后

完成本文的实验中发现DTC+SPI的这种驱动方式似乎并不是很适合这种分辨率的小屏幕驱动,查阅文档发现 DTC单次发送最大支持的字节数为65536,也就是支持不到240*240*2=115200字节那么大的数量,所以需要分两次来发送
RA4M2 SCI SPI 驱屏极限测试_figure_12.png

本文主要是对于SCI SPI驱动屏幕进行了测试,查阅文档得知 SCI 上的 SPI 确实最多可以跑到 25Mbps
RA4M2 SCI SPI 驱屏极限测试_figure_13.png

而仅有的一个 SPI 专用接口可以跑到 50Mbps
RA4M2 SCI SPI 驱屏极限测试_figure_14.png

理论上直接使用专用 SPI 接口的话应该可以获得更好的驱屏效果

更多回帖

发帖
×
20
完善资料,
赚取积分