开发环境
- Keil MDK: 5.42.0
- ARM-GCC: 10.3.1
- FSP: 5.1.0
- FreeRTOS: 10.6.1
环境搭建
首先在RASC中新建工程,选择芯片为开发板上的型号 R7FA4M2D3CFP,编译工具链选择 keil MDK

不使用 TrustZone 功能

选用 FreeRTOS 的工程

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

然后查看 RA4M2 开发板的原理图,得知连接到 MCU 的各个引脚

可以得到硬件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用

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

修改lcd_thread的代码如下,实现一个点灯的效果来验证
void lcd_thread_entry(void * pvParameters)
{
FSP_PARAMETER_NOT_USED(pvParameters);
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的通信

点亮屏幕
小屏幕用的是很常见的 ST7789 驱动芯片,根据手册编写初始化序列的代码并编写了一个简单的fps计算程序,大致流程如下:
- 向屏幕发送初始化序列,完成屏幕的初始化
- 拉低CS、拉高DC,进入全刷模式,不断填充红、绿、蓝三色
- 在lcd_thread中fps变量不断自增
- 在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();
}
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();
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

拍摄原因所以不是单色的,可以看到屏幕的刷新也是非常快

在keil MDK的debug界面可以看出来fps稳定在16

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

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

而仅有的一个 SPI 专用接口可以跑到 50Mbps

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