707N彩屏仓屏显调试
📎 原始文档:https://www.kdocs.cn/l/ctX5e9qrEXTe 707N彩屏屏显调试
1.常规带RAM屏的驱动基本配置:
📌屏驱动C文件基本配置部分
#define LCD_DRIVE_CONFIG:屏幕接口配置,其中“单线SPI+DC屏”建议使用SPI_4WIRE_RGB565_1T8B模式,707的D1引脚接屏DC。其它按照701配置即可。
#define SCR_X 0 :2A指令的偏移,并非画面偏移
#define SCR_Y 0 :2B指令的偏移,并非画面偏移
LCD_BLOCK_H:分块高度,分块的操作流程在gpu_task.c开放了一部分。
.fps = 65:数据接口速率,如SPI的速率。
初始化指令、进出休眠接口、纯色debug模式、ID、LOGO配置等等请参考9B71的屏驱动代码配置方式即
具体调用指令顺序请跟踪lcd_drv_init()代码。
💡GRAM屏的数据顺序
CPU ->GRAM -> 屏幕
- CPU发送2A/2B/2C/3C指定区域写到GRAM:这一步是将图像数据写入图形内存(GRAM)。不同的区域(如2A、2B、2C、3C)可能代表不同的图像层或图形对象,CPU将这些数据传输到GRAM中,以便后续处理。
- Scanline扫描:显示控制器会逐行扫描GRAM中的数据,并将其转换为屏幕上可见的图像。这个过程通常是通过逐行读取内存中的像素数据,并将其发送到显示设备来完成的。

💡TE信号同步
- FMARK信号:scanline到某一个屏幕垂直方向的某一个位置,触发中断信号输出,即TE信号,用于通知CPU发同步发送显示数据。
- 1TE模式:超前模式,即1TE发一帧数据,比如TE是60Hz,实际显示是60FPS。
- 2TE模式:滞后模式,即2TE发一帧数据,比如TE是60Hz,实际显示是30FPS。
📌屏幕调切线常用寄存器(仅供参考,具体查看屏IC的数据手册):
0x35:设置FMARK信号垂直位置,如果有一条固定切线,一般是推数据稍微超前了一下scanline,因此需要加大FMARK信号的位置让数据总体滞后。tear line
0x36:通过SPI获取屏幕scanline的位置,只有部分屏幕支持,代替TE功能。
0xC6:设置scanline的刷新频率,也是TE频率,也是GRAM显示到显示屏的频率。
📌2TE模式消除切线的方法(整屏刷):
- 2倍的TE周期 > CPU写到GRAM的时间 > 1倍的TE周期
- 确保W与R永远不相交
- SPI速率要适中,不能太快和太慢
- 触发SPI数据可能要稍微滞后一点scanline,如SPI推屏稍微滞后一点TE中断,或者触发TE的位置往下调(0x35)
如上图示为2TE推屏模式,上方为spi数据,下方为TE信信号,1触发A帧发送SPI,并要在2-3之间结束,即C需要在2-3之间,然后3再次触发下一帧即B帧发送SPI,D要在4-5之间,以此类推。
📌1TE模式消除切线的方法(整屏刷):
- CPU写到GRAM的时间 < 1倍的TE周期
- 确保W与R永远不相交
- SPI速度尽量调高
- 触发SPI数据可能要稍微超前一点scanline,如SPI推屏稍微超前一点TE中断,或者触发TE的位置往上调(0x35)
如上图示为1TE推屏模式,上方为spi数据,下方为TE信信号,1触发A帧发送SPI,并要在1-2之间结束,即C需要在1-2之间,然后2再次触发下一帧即B帧发送SPI,D要在2-3之间,以此类推。
📌局部刷
核心思路只要保证W与R永远不相交即可,具体何时触发需要程序软件去预估scanline位置,具体代码可参考#define TCFG_LCD_TE_USED_PEND与#define TCFG_LCD_TE_QUICK_SYNC所包含代码。
参考资料:
https://www.bilibili.com/read/cv18740493/
https://www.bilibili.com/read/cv28161505/
📌添加9307与3030屏驱动案例
- 在屏触摸驱动文件添加屏幕型号对应触摸配置信息,根据实际情况去调试是否使用镜像翻转,以及设置好触摸范围。 cst816.c
// 触摸信息配置
#if (defined TCFG_LCD_NB3030_172X320 && TCFG_LCD_NB3030_172X320)
#define X_MIRROR 1
#define Y_MIRROR 0
#define VK_X 320
#define VK_Y 172
#define TP_FPS 60
#elif (defined TCFG_LCD_GC9307_172X320 && TCFG_LCD_GC9307_172X320)
#define X_MIRROR 1
#define Y_MIRROR 0
#define VK_X 320
#define VK_Y 172
#define TP_FPS 60
- 配置好TP 的中断和复位IO
//*********************************************************************************//
// tp 配置 //
//*********************************************************************************//
#define TCFG_TOUCH_PANEL_ENABLE 1
#define TCFG_TP_CST816D_ENABLE 1
#define TCFG_TP_SLEEP_EN 1
#define TCFG_TP_INT_IO IO_PORTB_01 //TP中断脚
#define TCFG_TP_RESET_IO IO_PORTA_02 //TP复位脚
- LCD添加屏驱动的宏
//*********************************************************************************//
// LCD 配置 //
//*********************************************************************************//
#define TCFG_SPI_LCD_ENABLE 1//spi lcd开关
#define TCFG_LCD_NB3030_172X320 1
#define TCFG_LCD_GC9307_172X320 0
- LCD配置TE,根据实际情况开关,有些屏幕没有TE引出,可以关闭TCFG_LCD_TE_USED_PEND与这TCFG_LCD_TE_QUICK_SYNC两个宏,如小屏的9307。
#define TCFG_LCD_TE_USED_PEND 0//1
#define TCFG_LCD_TE_QUICK_SYNC 0//1//te优化策略
如果关闭TE出现编译不过,添加空函数变量:
volatile int64_t dc_te_absolute_us;
volatile int64_t dc_start_absolute_us;
volatile int lcd_spi_last_us = 25 * 1000;
u32 lcd_get_te_frame_period_us(void)
{
return 16000;
}
- LCD与TP供电口配置,如果电路图是PC1供电,芯片内部PC1与PC2是双绑的,需要将pin_en与pin_en_ex两个引脚同时配置,如去除PC1供电:
LCD_SPI_PLATFORM_DATA_BEGIN(lcd_spi_data) = {
.pin_reset = IO_PORTB_00,//IO_PORTC_03,
.pin_en = NO_CONFIG_PORT,//IO_PORTC_01,
.pin_en_ex = NO_CONFIG_PORT,//IO_PORTC_02,
.pin_te = TCFG_LCD_TE_IO,
.pin_bl = TCFG_LCD_BL_IO,
LCD_SPI_PLATFORM_DATA_END()
};
- LCD其它引脚配置更改
#define TCFG_LCD_PIN_RESET IO_PORTB_00//IO_PORTA_03 // LCD RESET
#define TCFG_LCD_PIN_CS IO_PORTA_07//IO_PORTA_04 // LCD CS
#define TCFG_LCD_PIN_BL NO_CONFIG_PORT // LCD BLCKLIGHT
#define TCFG_LCD_PIN_DC IO_PORTA_09//IO_PORTA_02 // LCD DC
#define TCFG_LCD_PIN_EN NO_CONFIG_PORT // LCD EN
#define TCFG_LCD_PIN_TE IO_PORTC_00 // LCD TE
- lcd_conf.h新添加LCD_WIDTH与LCD_HEIGHT宏
#include "app_config.h"
#if defined(TCFG_LCD_NB3030_172X320) && TCFG_LCD_NB3030_172X320
#define LCD_WIDTH 320
#define LCD_HEIGHT 172
#endif
#if defined(TCFG_LCD_GC9307_172X320) && TCFG_LCD_GC9307_172X320
#define LCD_WIDTH 320
#define LCD_HEIGHT 172
#endif
- 添加设置TE的相位,用于切线与帧率优化,根据实际情况调试
#if (defined TCFG_LCD_NB3030_172X320 && TCFG_LCD_NB3030_172X320)
const int lcd_te_display_line = 0x80;
#elif (defined TCFG_LCD_GC9307_172X320 && TCFG_LCD_GC9307_172X320)
const int lcd_te_display_line = 0x80;
- makefile或者cbp添加屏幕驱动文件
cpu/br35/ui_driver/lcd_drive/lcd_spi/lcd_spi_gc9307_RQ_BOE_172x320.c \
cpu/br35/ui_driver/lcd_drive/lcd_spi/oled_spi_nb3030_172x320.c \
📌LCD初始化
lcd_drv_init()
📌JL框架合成推屏
以分3块双BUF举例
| GPU任务 | 合成分块1 | 合成分块2 | 合成分块3 | 合成分块1 | 合成分块2 | 合成分块3 | 合成分块1 |
|---|---|---|---|---|---|---|---|
| DMA推屏 | 推屏分块3 | 推屏分块1 | 推屏分块2 | 推屏分块3 | 推屏分块1 | 推屏分块2 | 推屏分块3 |
| UI任务 | 用户层JL UI,包括动画,拿资源,配置下一帧的任务链ui_platform.c | 用户层UI,配置下一帧任务链 |
// GPU任务
static void jlgpu_task(void *p)
{
int ret;
int msg[32] = {0};
while (1) {
ret = os_taskq_pend(NULL, msg, ARRAY_SIZE(msg));
if (ret != OS_TASKQ) {
continue;
}
switch (msg[0]) {
case GPU_MSG_DRAW:
IO_GPU_HIGH();
pJLGPUMultTaskList_t mult_list = (pJLGPUMultTaskList_t)msg[2];
jlgpu_scheduler_kick_start(mult_list, &__this->dc);
break;
case GPU_MSG_OTHER:
break;
default:
break;
}
}
}
UI任务配置GPU的任务链在:ui_platform.c文件,第三方框架可参考。
📌第三方推屏接口(2A、2B、2C、3C)
/* ------------------------------------------------------------------------------------*/
/**
* @brief lcd_set_draw_area 设置LCD显示区域(2A, 2B命令)
*
* @param xstart X起始像素坐标
* @param xend X结束像素坐标
* @param ystart Y起始像素坐标
* @param yend Y结束像素坐标
*/
/* ------------------------------------------------------------------------------------*/
void lcd_set_draw_area(int xstart, int xend, int ystart, int yend);
/* ------------------------------------------------------------------------------------*/
/**
* @brief lcd_draw DBI硬件写显示数据到LCD, 非阻塞接口,与lcd_set_draw_area配合使用
*
* @param buf 待写出数据
* @param xstart X起始像素坐标
* @param xend X结束像素坐标
* @param ystart Y起始像素坐标
* @param yend Y结束像素坐标
*/
/* ------------------------------------------------------------------------------------*/
void lcd_draw(u8 *buf, int xstart, int xend, int ystart, int yend);
/* ------------------------------------------------------------------------------------*/
/**
* @brief lcd_draw_continue DBI硬件以续传方式写显示数据到LCD, 非阻塞接口,与lcd_set_draw_area、lcd_draw配合使用
*
* @param buf 待写出数据
* @param xstart X起始像素坐标
* @param xend X结束像素坐标
* @param ystart Y起始像素坐标
* @param yend Y结束像素坐标
*/
/* ------------------------------------------------------------------------------------*/
void lcd_draw_continue(u8 *buf, int xstart, int xend, int ystart, int yend);
2.横屏设计
💡方法一:LCD寄存器90/270旋转(UI横屏设计,横屏推送)
寄存器设置值得一提的是,这类LCD一般有一个指令用于设置行列地址转换的,可以变更LCD的横竖屏。比如ST7789的0x36指令参数的MV位:

尽管这在我们看起来是切换了横竖屏,但对于扫描线scanline而言,始终与LCD的排线互相垂直,手册中也有提到这一点,扫描线与MV位无关。
ST7789手册关于MV和TE的描述
这里看似在说TE信号,实际上TE信号就是由扫描线触发的,因此可得知扫描线不受MV的影响。
也就是说,当你设置了MV,试图把竖屏当横屏或者横屏当做竖屏使用时,扫描线依旧是垂直的.旋转后传输路径以及斜切现象
上方cpu为常规推屏,下方cpu为经过MV90/270翻转后推屏,可以看出与scanline方向不一致
左图为常规推屏两线不相交、右图为MV翻转后出现切切现象
- 解决斜切的理论方法 让总体的分块推的方向保持和Scan Line方向一致,即第一块、到第二块、到第三块的方向与scanline一致,分块不能太大也不能太小,太大达不到scanline与推屏错开的效果,太小绘制效率会下降,需要互相兼顾,选取合适的分块数。并且分块需要是原图的X方向分块,不能是Y方向,才能确保更改数据部分与scanline不发生相交。
此外,要注意部分小屏幕,如172*320的NB3030和GC9307,如上图所示,红色部分才是真正的显示屏部分,位于GRAM的中间,屏幕左上起始点相对GRAM起始点偏移34,针对这种小屏需要给2A或者2B指令一个34偏移,一般默认是要给2A偏移,MV翻转后则给2B偏移,具体实操参考下文例子。
📌X轴分块+MV硬件旋转修(未验证过)
以 NB3030和GC9307为例
- 软件使能X轴分块:#define DRAW_X_DIV_EN 1
- 调整X轴分块刷的总体方向(从左至右或者从右至左):#define DRAW_R_TO_L 0
- 给屏驱动初始化修改0x36寄存器,使能MV,如下:
#define MY 0//1 //Y反向(画面)
#define MX 1//0 //X反向(画面)
#define MV 1 //90旋转(画面)
#define ML 0//1 //LCD垂直反向(SCANLINE)
#define BGR 1 //大小端颜色
#define MH 0//0 //LCD水平反向(SCANLINE)
_BEGIN_, 0x36, (MY<<7)|(MX<<6)|(MV<<5)|(ML<<4)|(BGR<<3)|(MH<<2), _END_,
- 设置好绘制区域的偏移,一般LCD寄存器使能MV后2B可能需要偏移,如172*320 9307需偏移34,所以给SCR_Y 34
#define SCR_X 0 // 2A偏移
#define SCR_Y 34 // 2B偏移
#define SCR_W LCD_WIDTH // 画面宽度
#define SCR_H LCD_HEIGHT // 画面高度
💡方法二:CPU软件旋转90/270度(UI横屏设计 竖屏推送)
同样需要让总体的分块推的方向保持和Scan Line方向一致,需要原图(即以UI工程的图为准)的X方向分块,软件端作旋转,从而实现推屏与scanline同方向并且互相错开:
📌X轴分块+软件旋转修改如下(已验证过)
以 NB3030和GC9307为例
- 软件使能X轴分块:#define DRAW_X_DIV_EN 1
- 调整X轴分块刷的总体方向(从左至右或者从右至左):#define DRAW_R_TO_L 0
- 使能BUF软件旋转:#define DRAW_BUF_ROTATE 1
- 旋转90度还是270度:#define RBUF_270 1
// 消除屏幕切线优化
#define DRAW_X_DIV_EN 1//UI横屏设计,使能X方向分块,让分块buf与scanline扫描方向一致
#if DRAW_X_DIV_EN
#define DRAW_R_TO_L 0//X方向反向分块刷
#define DRAW_BUF_ROTATE 1//使能BUF旋转
#define RBUF_270 1//1:270 0:90
#endif
- 设置好绘制区域的偏移,小屏2A可能需要偏移,如172*320 9307需偏移34,所以给SCR_X 34
#define SCR_X 34 // 2A偏移
#define SCR_Y 0 // 2B偏移
#define SCR_W LCD_WIDTH // 画面宽度
#define SCR_H LCD_HEIGHT // 画面高度
📌具体横屏代码参考 #define DRAW_X_DIV_EN所包含的代码。
3.sram buf 做90/270度旋转,使用GPU与CPU的区别
9307 320172 90/270度条件下测试,一共分9块。
sw_rgb565buf_rotate():CPU软件实现,测试耗时260us9 = 2.3ms。
hw_rgb565buf_rotate():GPU硬件实现,测试耗时1.1ms*9 = 10ms。
以下为CPU与GPU具体实现BUF 90/270度方法举例:
// CPU方法
static void sw_rgb565buf_rotate(u16 * rot_buf, u16 * src_color_p, int area_w, int area_h){
uint32_t invert = (area_w * area_h) - 1;
uint32_t initial_i = ((area_w - 1) * area_h);
for(u16 y = 0; y < area_h; y++) {
uint32_t i = initial_i + y;
#if(RBUF_270)
i = invert - i;
#endif
for(u16 x = 0; x < area_w; x++) {
rot_buf[i] = *(src_color_p++);
#if(RBUF_270)
i += area_h;
#else
i -= area_h;
#endif
}
}
}
// GPU方法
static void hw_rgb565buf_rotate(u16 * rot_buf, u16 * src_color_p, int area_w, int area_h){
// 输出区域
struct rect mask_r_temp = {0};
struct rect * mask_r = &mask_r_temp;
mask_r->left = 0;
mask_r->top = 0;
mask_r->width = area_w;
mask_r->height = area_h;
struct rect disp_r_temp = {0};
struct rect * disp_r = &disp_r_temp;
disp_r->left = 0;
disp_r->top = 0;
disp_r->width = area_h;
disp_r->height = area_w;
uint8_t * mask_buf = src_color_p;
uint8_t * disp_buf = rot_buf;
int in_format = GPU_FORMAT_RGB565;
u16 color =0xff;
int r = ((color >> 11) & 0x1f) << 3;
int g = ((color >> 5) & 0x3f) << 2;
int b = ((color) & 0x1f) << 3;
// printf("%s (%d %d %d %d) (%d %d %d %d) %x %x (%d %d %d)", __func__,
// mask_r->left, mask_r->top, mask_r->width, mask_r->height,
// disp_r->left, disp_r->top, disp_r->width, disp_r->height,
// (int)mask_buf, (int)disp_buf, r, g, b);
gpu_out_params_t out_param = { 0 };
gpu_basic_params_t basic_param = { 0 };
gpu_transform_params_t transform_param = {0};
out_param.data = (uint8_t *)disp_buf;
out_param.win_x_min = disp_r->left;
out_param.win_x_max = disp_r->left + disp_r->width ;
out_param.win_y_min = disp_r->top;
out_param.win_y_max = disp_r->top + disp_r->height ;
out_param.stride = disp_r->width * 2;
out_param.format = GPU_FORMAT_RGB565;
basic_param.act_x_min = disp_r->left;//mask_r->left ;
basic_param.act_x_max = disp_r->left + disp_r->width;//mask_r->left + mask_r->width;
basic_param.act_y_min = disp_r->top;//mask_r->top ;
basic_param.act_y_max = disp_r->top + disp_r->height;//mask_r->top + mask_r->height;
// basic_param.act_x_min = draw_r->left ;
// basic_param.act_x_max = draw_r->left + draw_r->width ;
// basic_param.act_y_min = draw_r->top ;
// basic_param.act_y_max = draw_r->top + draw_r->height;
// printf("basic:{%d %d %d %d}", basic_param.act_x_min, basic_param.act_x_max, basic_param.act_y_min, basic_param.act_y_max);
basic_param.alpha = 255;
basic_param.red = r;
basic_param.green = g;
basic_param.blue = b;
basic_param.layer_en = 1;
basic_param.blend_mode = 1;
basic_param.global_alpha = 128; //范围:0-128
basic_param.premult = 0;
gpu_texture_params_t texture_param = { 0 };
texture_param.adr_mode = 1;
texture_param.data = mask_buf;
texture_param.format = in_format;
texture_param.compress_mode = 0;
texture_param.stride = (mask_r->width * gpu_get_format_bpp(in_format) + 7) / 8 ;
texture_param.compress_size = mask_r->width;
texture_param.big_end = 0;
float zoom_w = (float)1 / 1.0;
float zoom_h = (float)1 / 1.0;
gpu_matrix_t matrix = {0};
gpu_matrix_set_identity(&matrix);
float tran_x = area_h;//area_w;//10;//(float)mask_r->left;
float tran_y = 0;//(float)mask_r->top;
gpu_matrix_translate(&matrix, -tran_x, -tran_y);
// gpu_matrix_scale(&matrix, zoom_w, zoom_h);
static float aaaa = -3.14159/2.0;
// g_printf("11 %f %f %f %f %f %f", matrix.m[0][0],matrix.m[0][1],matrix.m[0][2],matrix.m[1][0],matrix.m[1][1],matrix.m[1][2]);
gpu_matrix_rotate(&matrix, aaaa);
// g_printf("22 %f %f %f %f %f %f", matrix.m[0][0],matrix.m[0][1],matrix.m[0][2],matrix.m[1][0],matrix.m[1][1],matrix.m[1][2]);
// printf("%s %f %f %f %f\n", __func__, zoom_w, zoom_h, tran_x, tran_y);
transform_param.M00 = matrix.m[0][0];
transform_param.M01 = matrix.m[0][1];
transform_param.M02 = matrix.m[0][2] - 0.5 * transform_param.M00 - 0.5 * transform_param.M01 + 0.5;
transform_param.M10 = matrix.m[1][0];
transform_param.M11 = matrix.m[1][1];
transform_param.M12 = matrix.m[1][2] - 0.5 * transform_param.M10 - 0.5 * transform_param.M11 + 0.5;
transform_param.fg_x_min = 0;
transform_param.fg_x_max = mask_r->width;
transform_param.fg_y_min = 0;
transform_param.fg_y_max = mask_r->height;
transform_param.sample_mode = 0;
transform_param.shift_sel = 6;
uint8_t mode = GPU_TEXTURE_AFFINE;
gpu_reset_all_regs(mode);
gpu_texture_affine(&basic_param, &texture_param, &transform_param);
gpu_set_out_layer(&out_param);
gpu_isr_sem_clr();
gpu_run();
gpu_wait_done();
}
解释:90/270旋转这是特殊情况,可以不考虑cos'sin'以及浮点运算,CPU只是做交换的简易运算,主频高,因此会比较快速;而硬件运算会复杂很多,包括了三角函数以及浮点,矩阵变换,滤波等,所以时间会比CPU偏慢,这种情况只针对90/270特殊角度下成立,其它角度下依旧是GPU比旋转比软件要快的。目前小屏横屏90/270旋转还是用软件旋转方案比较现实和可靠。
4.彩屏仓开发板其它配置
修改波特率到1M,PC1输出用于debug
#define TCFG_DEBUG_UART_TX_PIN IO_PORTC_01//IO_PORTA_11//IO_PORTB_03 // 输出IO
#define TCFG_DEBUG_UART_BAUDRATE 1000000//0x1e8480 // 波特率
7076内置FLASH容量改成8M
#define CONFIG_FLASH_SIZE 0x800000//0x1000000 // flash容量