<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[【R128】应用开发案例——DBI驱动ST7789V1.3寸LC]]></title><description><![CDATA[<p dir="auto">基于R128-S2设计的全套开发板已上线淘宝百问网韦东山老师个人店进行售卖，包含黑色的DshanMCU-R128s2-R16N16模组和全套的DshanMCU-R128s2-DEVKIT。</p>
<ul>
<li><strong>DshanMCU-R128s2-R16N16模组：39.9元</strong></li>
<li><strong>DshanMCU-R128s2-DEVKIT开发板：59.9元</strong></li>
</ul>
<p dir="auto"><strong>R128开发板购买链接：<a href="https://item.taobao.com/item.htm?spm=a21n57.1.0.0.46b0523cMfarLo&amp;id=736154682975&amp;ns=1&amp;abbucket=5#detail" target="_blank" rel="noopener noreferrer nofollow ugc">https://item.taobao.com/item.htm?spm=a21n57.1.0.0.46b0523cMfarLo&amp;id=736154682975&amp;ns=1&amp;abbucket=5#detail</a></strong></p>
<p dir="auto"><img src="/assets/uploads/files/1696746474623-%E4%B8%BB%E5%9B%BE_01-%E4%B8%BA%E6%99%BA%E8%83%BD%E5%AF%B9%E8%B1%A1-1.jpg" alt="“主图_01”为智能对象-1.jpg" class=" img-responsive img-markdown" /></p>
<h1>DBI驱动ST7789V1.3寸LCD</h1>
<p dir="auto">之前介绍了 R128 平台使用 SPI 驱动显示屏 ST7789V1.3寸 LCD，接下来介绍的是使用 DBI 接口驱动。</p>
<p dir="auto">R128 平台提供了 SPI DBI 的 SPI TFT 接口，具有如下特点：</p>
<ul>
<li>Supports DBI Type C 3 Line/4 Line Interface Mode</li>
<li>Supports 2 Data Lane Interface Mode</li>
<li>Supports data source from CPU or DMA</li>
<li>Supports RGB111/444/565/666/888 video format</li>
<li>Maximum resolution of RGB666 240 x 320@30Hz with single data lane</li>
<li>Maximum resolution of RGB888 240 x 320@60Hz or 320 x 480@30Hz with dual data lane</li>
<li>Supports tearing effect</li>
<li>Supports software flexible control video frame rate</li>
</ul>
<p dir="auto">同时，提供了 SPILCD 驱动框架以供 SPI 屏幕使用。</p>
<p dir="auto">此次适配的SPI屏为 <code>ZJY130S0800TG01</code>，使用的是 DBI 进行驱动。</p>
<p dir="auto">DBI接口的全称是 <code>Display Bus Serial Interface</code> ，在显示屏数据手册中，一般会说这是SPI接口，所以有人会误认为SPI屏可以使用 <code>normal spi</code> 去直接驱动。</p>
<p dir="auto">SPI 接口就是俗称的4线模式，这是因为发送数据时需要额外借助<code>DC</code>线来区分命令和数据，与<code>sclk</code>，<code>cs</code>和<code>sda</code>共四线。</p>
<p dir="auto"><img src="/assets/uploads/files/1699238012747-spi-16932054745452-2.png" alt="spi-16932054745452 (2).png" class=" img-responsive img-markdown" width="726" height="357" /></p>
<p dir="auto">DBI 分为多种接口，包括</p>
<pre><code>0：L3I1
1：L3I2
2：L4I1
3：L4I2
4：D2LI
</code></pre>
<p dir="auto"><code>L3I1</code>和<code>L3I2</code>是三线模式（不需要<code>DC</code>脚），区别是读时序，也就是是否需要额外脚来读寄存器。读写时序图如下：</p>
<ul>
<li>L3I1写时序</li>
</ul>
<p dir="auto"><img src="/assets/uploads/files/1699238040997-l3i1-16932054745453-1.png" alt="L3I1-16932054745453 (1).png" class=" img-responsive img-markdown" width="736" height="302" /></p>
<ul>
<li>L3I1读时序</li>
</ul>
<p dir="auto"><img src="/assets/uploads/files/1699238052505-l3i_rd-16932054745464-1.png" alt="L3I_RD-16932054745464 (1).png" class=" img-responsive img-markdown" width="690" height="298" /></p>
<p dir="auto"><code>L4I1</code>和<code>L4I2</code>是四线模式，与spi接口协议一样，区别是DC脚的控制是否自动化控制，另外I2和I1的区别是读时序，也就是否需要额外脚来读取寄存器。</p>
<ul>
<li>L4I写时序</li>
</ul>
<p dir="auto"><img src="/assets/uploads/files/1699238061308-spi-16932054745452-16987339859554.png" alt="spi-16932054745452-16987339859554.png" class=" img-responsive img-markdown" width="726" height="357" /></p>
<ul>
<li>L4I读时序</li>
</ul>
<p dir="auto"><img src="/assets/uploads/files/1699238074808-spi_read-16932054745465-1.png" alt="spi_read-16932054745465 (1).png" class=" img-responsive img-markdown" width="664" height="318" /></p>
<p dir="auto"><code>D2LI</code>是两data lane模式。发送命令部分时序与读时序与<code>L3I1</code>一致，下图是发送数据时的时序，不同像素格式时钟周期数量不一样。</p>
<ul>
<li>D2LI写时序<br />
<img src="/assets/uploads/files/1699238086844-d2li-16932054745466-1.png" alt="D2LI-16932054745466 (1).png" class=" img-responsive img-markdown" width="742" height="441" /></li>
</ul>
<p dir="auto">可以知道，在3线模式时，发送命令前有1位A0用于指示当前发送的是数据，还是命令。而命令后面接着的数据就没有这个A0位了，代表 SPI 需要在 9 位和 8 位之间来回切换，而在读数据时，更是需要延时 <code>dummy clock</code> 才能读数据，<code>normal spi</code> 都很难，甚至无法实现。所以 <code>normal spi</code> 只能模拟 4 线的 DBI 的写操作。读操作只能通过模拟IO来实现。</p>
<p dir="auto">对于R128这类支持 DBI 接口的CPU，可以选择不去了解 SPI。直接选用 DBI 来驱动屏幕。由于不需要模拟延时和切换数据，屏幕驱动效率将有明显提升。</p>
<p dir="auto"><img src="/assets/uploads/files/1699238130577-image-20231023102211315-2.png" alt="image-20231023102211315 (2).png" class=" img-responsive img-markdown" width="1225" height="885" /></p>
<p dir="auto">引脚配置如下：</p>
<table class="table table-bordered table-striped">
<thead>
<tr>
<th style="text-align:left">R128 Devkit</th>
<th style="text-align:left">TFT 模块</th>
</tr>
</thead>
<tbody>
<tr>
<td style="text-align:left">PA12</td>
<td style="text-align:left">CS</td>
</tr>
<tr>
<td style="text-align:left">PA13</td>
<td style="text-align:left">SCL</td>
</tr>
<tr>
<td style="text-align:left">PA18</td>
<td style="text-align:left">SDA</td>
</tr>
<tr>
<td style="text-align:left">PA9</td>
<td style="text-align:left">BLK</td>
</tr>
<tr>
<td style="text-align:left">PA20</td>
<td style="text-align:left">RES</td>
</tr>
<tr>
<td style="text-align:left">PA19</td>
<td style="text-align:left">DC</td>
</tr>
<tr>
<td style="text-align:left">3V3</td>
<td style="text-align:left">VCC</td>
</tr>
<tr>
<td style="text-align:left">GND</td>
<td style="text-align:left">GND</td>
</tr>
</tbody>
</table>
<h2>载入方案</h2>
<p dir="auto">我们使用的开发板是 R128-Devkit，需要开发 C906 核心的应用程序，所以载入方案选择 <code>r128s2_module_c906</code></p>
<pre><code class="language-bash">$ source envsetup.sh 
$ lunch_rtos 1
</code></pre>
<p dir="auto"><img src="/assets/uploads/files/1699238139273-image-20230802110150203-9.png" alt="image-20230802110150203 (9).png" class=" img-responsive img-markdown" width="1240" height="469" /></p>
<h2>设置 DBI 驱动</h2>
<p dir="auto">屏幕使用的是SPI驱动，所以需要勾选SPI驱动，运行 <code>mrtos_menuconfig</code> 进入配置页面。前往下列地址找到 <code>SPI Devices</code></p>
<pre><code class="language-c">Drivers Options  ---&gt;
    soc related device drivers  ---&gt;
        DBI Devices ---&gt;
        -*- enable dbi driver
</code></pre>
<p dir="auto"><img src="/assets/uploads/files/1699238147547-image-20231031143901502.png" alt="image-20231031143901502.png" class=" img-responsive img-markdown" width="629" height="253" /></p>
<h3>配置 SPI 引脚</h3>
<p dir="auto">DBI同样使用 SPI 控制器，所以需要配置SPI的相关配置。打开你喜欢的编辑器，修改文件：<code>board/r128s2/module/configs/sys_config.fex</code>，在这里我们不需要用到SPI WP引脚，注释掉即可。SPI HOLD 需要作为 DC 脚接入LCD模块。</p>
<pre><code>;----------------------------------------------------------------------------------
;SPI controller configuration
;----------------------------------------------------------------------------------
;Please config spi in dts
[spi1]
spi1_used       = 1
spi1_cs_number  = 1
spi1_cs_bitmap  = 1
spi1_cs0        = port:PA12&lt;6&gt;&lt;0&gt;&lt;3&gt;&lt;default&gt;
spi1_sclk       = port:PA13&lt;6&gt;&lt;0&gt;&lt;3&gt;&lt;default&gt;
spi1_mosi       = port:PA18&lt;6&gt;&lt;0&gt;&lt;3&gt;&lt;default&gt;
spi1_miso       = port:PA21&lt;6&gt;&lt;0&gt;&lt;3&gt;&lt;default&gt;
spi1_hold       = port:PA19&lt;6&gt;&lt;0&gt;&lt;2&gt;&lt;default&gt;
;spi1_wp         = port:PA20&lt;6&gt;&lt;0&gt;&lt;2&gt;&lt;default&gt;
</code></pre>
<p dir="auto"><img src="/assets/uploads/files/1699238157233-image-20231031144020034.png" alt="image-20231031144020034.png" class=" img-responsive img-markdown" width="641" height="283" /></p>
<h2>设置 PWM 驱动</h2>
<p dir="auto">屏幕背光使用的是PWM驱动，所以需要勾选PWM驱动，运行 <code>mrtos_menuconfig</code> 进入配置页面。前往下列地址找到 <code>PWM Devices</code></p>
<pre><code>Drivers Options  ---&gt;
    soc related device drivers  ---&gt;
        PWM Devices ---&gt;
        -*- enable pwm driver
</code></pre>
<p dir="auto"><img src="/assets/uploads/files/1699238269313-image-20230825144408144-4.png" alt="image-20230825144408144 (4).png" class=" img-responsive img-markdown" width="667" height="285" /></p>
<h3>配置 PWM 引脚</h3>
<p dir="auto">打开你喜欢的编辑器，修改文件：<code>board/r128s2/module/configs/sys_config.fex</code>，增加 PWM1 节点</p>
<pre><code>[pwm1]
pwm_used        = 1
pwm_positive    = port:PA9&lt;4&gt;&lt;0&gt;&lt;3&gt;&lt;default&gt;
</code></pre>
<p dir="auto"><img src="/assets/uploads/files/1699238277409-image-20230825150128954-4.png" alt="image-20230825150128954 (4).png" class=" img-responsive img-markdown" width="383" height="77" /></p>
<h2>设置 SPI LCD 驱动</h2>
<p dir="auto">SPI LCD 由专门的驱动管理。运行 <code>mrtos_menuconfig</code> 进入配置页面。前往下列地址找到 <code>SPILCD Devices</code> ，注意同时勾选 <code>spilcd hal APIs test</code> 方便测试使用。</p>
<pre><code>Drivers Options  ---&gt;
    soc related device drivers  ---&gt;
        [*] DISP Driver Support(spi_lcd)
        [*]   spilcd hal APIs test
</code></pre>
<p dir="auto"><img src="/assets/uploads/files/1699238281862-image-20230825150341879-3.png" alt="image-20230825150341879 (3).png" class=" img-responsive img-markdown" width="643" height="365" /></p>
<h2>编写 SPI LCD 显示屏驱动</h2>
<h3>获取屏幕初始化序列</h3>
<p dir="auto">首先询问屏厂提供驱动源码</p>
<p dir="auto"><img src="/assets/uploads/files/1699238287567-image-20231023102239928-1.png" alt="image-20231023102239928 (1).png" class=" img-responsive img-markdown" width="647" height="307" /></p>
<p dir="auto">找到 LCD 的初始化序列代码</p>
<p dir="auto"><img src="/assets/uploads/files/1699238295061-image-20231023102252129-1.png" alt="image-20231023102252129 (1).png" class=" img-responsive img-markdown" width="643" height="319" /></p>
<p dir="auto">找到屏幕初始化的源码</p>
<p dir="auto"><img src="/assets/uploads/files/1699238299851-image-20231023102333476-1.png" alt="image-20231023102333476 (1).png" class=" img-responsive img-markdown" width="852" height="563" /></p>
<p dir="auto">整理后的初始化代码如下：</p>
<pre><code class="language-c">LCD_WR_REG(0x11); // Sleep out 
delay_ms(120);    // Delay 120ms 
//************* Start Initial Sequence **********// 
LCD_WR_REG(0x36);
LCD_WR_DATA8(0x00);

LCD_WR_REG(0x3A);     
LCD_WR_DATA8(0x05);   

LCD_WR_REG(0xB2);     
LCD_WR_DATA8(0x1F);   
LCD_WR_DATA8(0x1F);   
LCD_WR_DATA8(0x00);   
LCD_WR_DATA8(0x33);   
LCD_WR_DATA8(0x33);   

LCD_WR_REG(0xB7);     
LCD_WR_DATA8(0x35);   

LCD_WR_REG(0xBB);     
LCD_WR_DATA8(0x20);   // 2b

LCD_WR_REG(0xC0);     
LCD_WR_DATA8(0x2C);   

LCD_WR_REG(0xC2);     
LCD_WR_DATA8(0x01);   

LCD_WR_REG(0xC3);     
LCD_WR_DATA8(0x01);   

LCD_WR_REG(0xC4);     
LCD_WR_DATA8(0x18);   // VDV, 0x20:0v

LCD_WR_REG(0xC6);     
LCD_WR_DATA8(0x13);   // 0x13:60Hz   

LCD_WR_REG(0xD0);     
LCD_WR_DATA8(0xA4);   
LCD_WR_DATA8(0xA1);   

LCD_WR_REG(0xD6);     
LCD_WR_DATA8(0xA1);   // sleep in后，gate输出为GND

LCD_WR_REG(0xE0);     
LCD_WR_DATA8(0xF0);   
LCD_WR_DATA8(0x04);   
LCD_WR_DATA8(0x07);   
LCD_WR_DATA8(0x04);   
LCD_WR_DATA8(0x04);   
LCD_WR_DATA8(0x04);   
LCD_WR_DATA8(0x25);   
LCD_WR_DATA8(0x33);   
LCD_WR_DATA8(0x3C);   
LCD_WR_DATA8(0x36);   
LCD_WR_DATA8(0x14);   
LCD_WR_DATA8(0x12);   
LCD_WR_DATA8(0x29);   
LCD_WR_DATA8(0x30);   

LCD_WR_REG(0xE1);     
LCD_WR_DATA8(0xF0);   
LCD_WR_DATA8(0x02);   
LCD_WR_DATA8(0x04);   
LCD_WR_DATA8(0x05);   
LCD_WR_DATA8(0x05);   
LCD_WR_DATA8(0x21);   
LCD_WR_DATA8(0x25);   
LCD_WR_DATA8(0x32);   
LCD_WR_DATA8(0x3B);   
LCD_WR_DATA8(0x38);   
LCD_WR_DATA8(0x12);   
LCD_WR_DATA8(0x14);   
LCD_WR_DATA8(0x27);   
LCD_WR_DATA8(0x31);   

LCD_WR_REG(0xE4);     
LCD_WR_DATA8(0x1D);   // 使用240根gate  (N+1)*8
LCD_WR_DATA8(0x00);   // 设定gate起点位置
LCD_WR_DATA8(0x00);   // 当gate没有用完时，bit4(TMG)设为0

LCD_WR_REG(0x21);     

LCD_WR_REG(0x29);     
</code></pre>
<h3>用现成驱动改写 SPI LCD 驱动</h3>
<p dir="auto">选择一个现成的 SPI LCD 改写即可，这里选择 <code>nv3029s.c</code> 驱动来修改</p>
<p dir="auto"><img src="/assets/uploads/files/1699238304911-image-20231017104714827-3.png" alt="image-20231017104714827 (3).png" class=" img-responsive img-markdown" width="378" height="396" /></p>
<p dir="auto">复制这两个驱动，重命名为 <code>st7789v.c</code></p>
<p dir="auto"><img src="/assets/uploads/files/1699238313672-image-20231017104740060-3.png" alt="image-20231017104740060 (3).png" class=" img-responsive img-markdown" width="399" height="170" /></p>
<p dir="auto">先编辑 <code>st7789v.h</code> 将 <code>nv3029s</code> 改成 <code>st7789v</code></p>
<p dir="auto"><img src="/assets/uploads/files/1699238324569-image-20231017104851772-3.png" alt="image-20231017104851772 (3).png" class=" img-responsive img-markdown" width="398" height="190" /></p>
<pre><code class="language-c">#ifndef _ST7789V_H
#define _ST7789V_H

#include "panels.h"

struct __lcd_panel st7789v_panel;

#endif /*End of file*/
</code></pre>
<p dir="auto">编辑 <code>st7789v.c</code> 将 <code>nv3029s</code> 改成 <code>st7789v</code></p>
<p dir="auto"><img src="/assets/uploads/files/1699238333143-image-20231017104942286-3.png" alt="image-20231017104942286 (3).png" class=" img-responsive img-markdown" width="654" height="277" /></p>
<h3>编写初始化序列</h3>
<p dir="auto">先删除 <code>static void LCD_panel_init(unsigned int sel)</code> 中的初始化函数。</p>
<p dir="auto"><img src="/assets/uploads/files/1699238460244-image-20231017105101421-3.png" alt="image-20231017105101421 (3).png" class=" img-responsive img-markdown" width="457" height="101" /></p>
<p dir="auto">然后将屏厂提供的初始化序列复制进来</p>
<p dir="auto"><img src="/assets/uploads/files/1699238466148-image-20231023102641761-1.png" alt="image-20231023102641761 (1).png" class=" img-responsive img-markdown" width="412" height="743" /></p>
<p dir="auto">然后按照 <code>spi_lcd</code> 框架的接口改写驱动接口，具体接口如下</p>
<table class="table table-bordered table-striped">
<thead>
<tr>
<th style="text-align:left">屏厂函数</th>
<th style="text-align:left">SPILCD框架接口</th>
</tr>
</thead>
<tbody>
<tr>
<td style="text-align:left"><code>LCD_WR_REG</code></td>
<td style="text-align:left"><code>sunxi_lcd_cmd_write</code></td>
</tr>
<tr>
<td style="text-align:left"><code>LCD_WR_DATA8</code></td>
<td style="text-align:left"><code>sunxi_lcd_para_write</code></td>
</tr>
<tr>
<td style="text-align:left"><code>delay_ms</code></td>
<td style="text-align:left"><code>sunxi_lcd_delay_ms</code></td>
</tr>
</tbody>
</table>
<p dir="auto">可以直接进行替换</p>
<p dir="auto"><img src="/assets/uploads/files/1699238471625-image-20231023095114146-2.png" alt="image-20231023095114146 (2).png" class=" img-responsive img-markdown" width="492" height="83" /></p>
<p dir="auto">完成后如下</p>
<p dir="auto"><img src="/assets/uploads/files/1699238477944-image-20231023095158411-2.png" alt="image-20231023095158411 (2).png" class=" img-responsive img-markdown" width="723" height="724" /></p>
<p dir="auto">然后对照屏厂提供的驱动修改 <code>address</code> 函数</p>
<p dir="auto"><img src="/assets/uploads/files/1699238482959-image-20231023102549896-1.png" alt="image-20231023102549896 (1).png" class=" img-responsive img-markdown" width="976" height="326" /></p>
<p dir="auto">做如下修改</p>
<pre><code class="language-c">static void address(unsigned int sel, int x, int y, int width, int height)
{
	sunxi_lcd_cmd_write(sel, 0x2B); /* Set row address */
	sunxi_lcd_para_write(sel, (y &gt;&gt; 8) &amp; 0xff);
	sunxi_lcd_para_write(sel, y &amp; 0xff);
	sunxi_lcd_para_write(sel, (height &gt;&gt; 8) &amp; 0xff);
	sunxi_lcd_para_write(sel, height &amp; 0xff);
	sunxi_lcd_cmd_write(sel, 0x2A); /* Set coloum address */
	sunxi_lcd_para_write(sel, (x &gt;&gt; 8) &amp; 0xff);
	sunxi_lcd_para_write(sel, x &amp; 0xff);
	sunxi_lcd_para_write(sel, (width &gt;&gt; 8) &amp; 0xff);
	sunxi_lcd_para_write(sel, width &amp; 0xff);
	sunxi_lcd_cmd_write(sel, 0x2c);
}
</code></pre>
<p dir="auto">完成驱动如下</p>
<pre><code class="language-c">#include "st7789v.h"

static void LCD_power_on(u32 sel);
static void LCD_power_off(u32 sel);
static void LCD_bl_open(u32 sel);
static void LCD_bl_close(u32 sel);
static void LCD_panel_init(u32 sel);
static void LCD_panel_exit(u32 sel);
#define RESET(s, v) sunxi_lcd_gpio_set_value(s, 0, v)
#define power_en(sel, val) sunxi_lcd_gpio_set_value(sel, 0, val)

static struct disp_panel_para info[LCD_FB_MAX];

static void address(unsigned int sel, int x, int y, int width, int height)
{
	sunxi_lcd_cmd_write(sel, 0x2B); /* Set row address */
	sunxi_lcd_para_write(sel, (y &gt;&gt; 8) &amp; 0xff);
	sunxi_lcd_para_write(sel, y &amp; 0xff);
	sunxi_lcd_para_write(sel, (height &gt;&gt; 8) &amp; 0xff);
	sunxi_lcd_para_write(sel, height &amp; 0xff);
	sunxi_lcd_cmd_write(sel, 0x2A); /* Set coloum address */
	sunxi_lcd_para_write(sel, (x &gt;&gt; 8) &amp; 0xff);
	sunxi_lcd_para_write(sel, x &amp; 0xff);
	sunxi_lcd_para_write(sel, (width &gt;&gt; 8) &amp; 0xff);
	sunxi_lcd_para_write(sel, width &amp; 0xff);
	sunxi_lcd_cmd_write(sel, 0x2c);
}

static void LCD_panel_init(unsigned int sel)
{
	if (bsp_disp_get_panel_info(sel, &amp;info[sel])) {
		lcd_fb_wrn("get panel info fail!\n");
		return;
	}

	sunxi_lcd_cmd_write(sel, 0x11); // Sleep out 
	sunxi_lcd_delay_ms(120);    // Delay 120ms 
	//************* Start Initial Sequence **********// 
	sunxi_lcd_cmd_write(sel, 0x36);
	sunxi_lcd_para_write(sel, 0x00);

	sunxi_lcd_cmd_write(sel, 0x3A);     
	sunxi_lcd_para_write(sel, 0x05);   

	sunxi_lcd_cmd_write(sel, 0xB2);     
	sunxi_lcd_para_write(sel, 0x1F);   
	sunxi_lcd_para_write(sel, 0x1F);   
	sunxi_lcd_para_write(sel, 0x00);   
	sunxi_lcd_para_write(sel, 0x33);   
	sunxi_lcd_para_write(sel, 0x33);   

	sunxi_lcd_cmd_write(sel, 0xB7);     
	sunxi_lcd_para_write(sel, 0x35);   

	sunxi_lcd_cmd_write(sel, 0xBB);     
	sunxi_lcd_para_write(sel, 0x20);   // 2b

	sunxi_lcd_cmd_write(sel, 0xC0);     
	sunxi_lcd_para_write(sel, 0x2C);   

	sunxi_lcd_cmd_write(sel, 0xC2);     
	sunxi_lcd_para_write(sel, 0x01);   

	sunxi_lcd_cmd_write(sel, 0xC3);     
	sunxi_lcd_para_write(sel, 0x01);   

	sunxi_lcd_cmd_write(sel, 0xC4);     
	sunxi_lcd_para_write(sel, 0x18);   // VDV, 0x20:0v

	sunxi_lcd_cmd_write(sel, 0xC6);     
	sunxi_lcd_para_write(sel, 0x13);   // 0x13:60Hz   

	sunxi_lcd_cmd_write(sel, 0xD0);     
	sunxi_lcd_para_write(sel, 0xA4);   
	sunxi_lcd_para_write(sel, 0xA1);   

	sunxi_lcd_cmd_write(sel, 0xD6);     
	sunxi_lcd_para_write(sel, 0xA1);   // sleep in后，gate输出为GND

	sunxi_lcd_cmd_write(sel, 0xE0);     
	sunxi_lcd_para_write(sel, 0xF0);   
	sunxi_lcd_para_write(sel, 0x04);   
	sunxi_lcd_para_write(sel, 0x07);   
	sunxi_lcd_para_write(sel, 0x04);   
	sunxi_lcd_para_write(sel, 0x04);   
	sunxi_lcd_para_write(sel, 0x04);   
	sunxi_lcd_para_write(sel, 0x25);   
	sunxi_lcd_para_write(sel, 0x33);   
	sunxi_lcd_para_write(sel, 0x3C);   
	sunxi_lcd_para_write(sel, 0x36);   
	sunxi_lcd_para_write(sel, 0x14);   
	sunxi_lcd_para_write(sel, 0x12);   
	sunxi_lcd_para_write(sel, 0x29);   
	sunxi_lcd_para_write(sel, 0x30);   

	sunxi_lcd_cmd_write(sel, 0xE1);     
	sunxi_lcd_para_write(sel, 0xF0);   
	sunxi_lcd_para_write(sel, 0x02);   
	sunxi_lcd_para_write(sel, 0x04);   
	sunxi_lcd_para_write(sel, 0x05);   
	sunxi_lcd_para_write(sel, 0x05);   
	sunxi_lcd_para_write(sel, 0x21);   
	sunxi_lcd_para_write(sel, 0x25);   
	sunxi_lcd_para_write(sel, 0x32);   
	sunxi_lcd_para_write(sel, 0x3B);   
	sunxi_lcd_para_write(sel, 0x38);   
	sunxi_lcd_para_write(sel, 0x12);   
	sunxi_lcd_para_write(sel, 0x14);   
	sunxi_lcd_para_write(sel, 0x27);   
	sunxi_lcd_para_write(sel, 0x31);   

	sunxi_lcd_cmd_write(sel, 0xE4);     
	sunxi_lcd_para_write(sel, 0x1D);   // 使用240根gate  (N+1)*8
	sunxi_lcd_para_write(sel, 0x00);   // 设定gate起点位置
	sunxi_lcd_para_write(sel, 0x00);   // 当gate没有用完时，bit4(TMG)设为0

	sunxi_lcd_cmd_write(sel, 0x21);     

	sunxi_lcd_cmd_write(sel, 0x29);   

	if (info[sel].lcd_x &lt; info[sel].lcd_y)
		address(sel, 0, 0, info[sel].lcd_x - 1, info[sel].lcd_y - 1);
	else
		address(sel, 0, 0, info[sel].lcd_y - 1, info[sel].lcd_x - 1);
}

static void LCD_panel_exit(unsigned int sel)
{
	sunxi_lcd_cmd_write(sel, 0x28);
	sunxi_lcd_delay_ms(20);
	sunxi_lcd_cmd_write(sel, 0x10);
	sunxi_lcd_delay_ms(20);
	sunxi_lcd_pin_cfg(sel, 0);
}

static s32 LCD_open_flow(u32 sel)
{
	lcd_fb_here;
	/* open lcd power, and delay 50ms */
	LCD_OPEN_FUNC(sel, LCD_power_on, 50);
	/* open lcd power, than delay 200ms */
	LCD_OPEN_FUNC(sel, LCD_panel_init, 200);

	LCD_OPEN_FUNC(sel, lcd_fb_black_screen, 50);
	/* open lcd backlight, and delay 0ms */
	LCD_OPEN_FUNC(sel, LCD_bl_open, 0);

	return 0;
}

static s32 LCD_close_flow(u32 sel)
{
	lcd_fb_here;
	/* close lcd backlight, and delay 0ms */
	LCD_CLOSE_FUNC(sel, LCD_bl_close, 50);
	/* open lcd power, than delay 200ms */
	LCD_CLOSE_FUNC(sel, LCD_panel_exit, 10);
	/* close lcd power, and delay 500ms */
	LCD_CLOSE_FUNC(sel, LCD_power_off, 10);

	return 0;
}

static void LCD_power_on(u32 sel)
{
	/* config lcd_power pin to open lcd power0 */
	lcd_fb_here;
	power_en(sel, 1);

	sunxi_lcd_power_enable(sel, 0);

	sunxi_lcd_pin_cfg(sel, 1);
	RESET(sel, 1);
	sunxi_lcd_delay_ms(100);
	RESET(sel, 0);
	sunxi_lcd_delay_ms(100);
	RESET(sel, 1);
}

static void LCD_power_off(u32 sel)
{
	lcd_fb_here;
	/* config lcd_power pin to close lcd power0 */
	sunxi_lcd_power_disable(sel, 0);
	power_en(sel, 0);
}

static void LCD_bl_open(u32 sel)
{
	sunxi_lcd_pwm_enable(sel);
	/* config lcd_bl_en pin to open lcd backlight */
	sunxi_lcd_backlight_enable(sel);
	lcd_fb_here;
}

static void LCD_bl_close(u32 sel)
{
	/* config lcd_bl_en pin to close lcd backlight */
	sunxi_lcd_backlight_disable(sel);
	sunxi_lcd_pwm_disable(sel);
	lcd_fb_here;
}


/* sel: 0:lcd0; 1:lcd1 */
static s32 LCD_user_defined_func(u32 sel, u32 para1, u32 para2, u32 para3)
{
	lcd_fb_here;
	return 0;
}

static int lcd_set_var(unsigned int sel, struct fb_info *p_info)
{
	return 0;
}

static int lcd_set_addr_win(unsigned int sel, int x, int y, int width, int height)
{
	address(sel, x, y, width, height);
	return 0;
}

static int lcd_blank(unsigned int sel, unsigned int en)
{
	return 0;
}

struct __lcd_panel st7789v_panel = {
    /* panel driver name, must mach the name of lcd_drv_name in sys_config.fex
       */
	.name = "st7789v",
	.func = {
		.cfg_open_flow = LCD_open_flow,
		.cfg_close_flow = LCD_close_flow,
		.lcd_user_defined_func = LCD_user_defined_func,
		.blank = lcd_blank,
		.set_var = lcd_set_var,
		.set_addr_win = lcd_set_addr_win,
	},
};
</code></pre>
<h3>对接驱动框架</h3>
<p dir="auto">完成了屏幕驱动的编写，接下来需要对接到 SPILCD 驱动框架。首先编辑 <code>Kconfig</code></p>
<p dir="auto"><img src="/assets/uploads/files/1699238487802-image-20231017105738155-3.png" alt="image-20231017105738155 (3).png" class=" img-responsive img-markdown" width="368" height="191" /></p>
<p dir="auto">增加 <code>st7789v</code> 的配置</p>
<p dir="auto"><img src="/assets/uploads/files/1699238492643-image-20231017105814814-3.png" alt="image-20231017105814814 (3).png" class=" img-responsive img-markdown" width="465" height="302" /></p>
<pre><code>config LCD_SUPPORT_ST7789V
    bool "LCD support st7789v panel"
    default n
    ---help---
        If you want to support st7789v panel for display driver, select it.
</code></pre>
<p dir="auto">然后编辑 <code>panels.c</code> 在 <code>panel_array</code> 里增加 <code>st7789</code> 驱动的引用</p>
<p dir="auto"><img src="/assets/uploads/files/1699238497283-image-20231017105948156-3.png" alt="image-20231017105948156 (3).png" class=" img-responsive img-markdown" width="382" height="207" /></p>
<p dir="auto">如下图</p>
<p dir="auto"><img src="/assets/uploads/files/1699238502739-image-20231017105919628-3.png" alt="image-20231017105919628 (3).png" class=" img-responsive img-markdown" width="399" height="366" /></p>
<pre><code class="language-c">#ifdef CONFIG_LCD_SUPPORT_ST7789V
    &amp;st7789v_panel,
#endif
</code></pre>
<p dir="auto">之后编辑 <code>panels.h</code> 同样增加引用</p>
<p dir="auto"><img src="/assets/uploads/files/1699238511768-image-20231017110043805-3.png" alt="image-20231017110043805 (3).png" class=" img-responsive img-markdown" width="337" height="113" /></p>
<p dir="auto">如下图</p>
<p dir="auto"><img src="/assets/uploads/files/1699238582091-image-20231017110122397-3.png" alt="image-20231017110122397 (3).png" class=" img-responsive img-markdown" width="420" height="272" /></p>
<pre><code class="language-c">#ifdef CONFIG_LCD_SUPPORT_ST7789V
extern struct __lcd_panel st7789v_panel;
#endif
</code></pre>
<p dir="auto">最后编辑外层的 <code>Makefile</code> 增加编译选项</p>
<p dir="auto"><img src="/assets/uploads/files/1699238590903-image-20231017110204681-3.png" alt="image-20231017110204681 (3).png" class=" img-responsive img-markdown" width="419" height="708" /></p>
<p dir="auto">如下所示</p>
<p dir="auto"><img src="/assets/uploads/files/1699238596872-image-20231017110242997-3.png" alt="image-20231017110242997 (3).png" class=" img-responsive img-markdown" width="473" height="107" /></p>
<pre><code class="language-c">obj-${CONFIG_LCD_SUPPORT_ST7789V} += panels/st7789v.o
</code></pre>
<h3>选择 ST7789V 驱动</h3>
<p dir="auto">在 SPILCD 驱动选择界面可以看到 <code>LCD_FB panels select</code> 选择 SPI 屏幕的驱动</p>
<p dir="auto">进入 <code>LCD_FB panels select</code> 选项</p>
<p dir="auto"><img src="/assets/uploads/files/1699238601025-image-20230825150812435-4.png" alt="image-20230825150812435 (4).png" class=" img-responsive img-markdown" width="665" height="372" /></p>
<p dir="auto">选择并勾选 <code>[*] LCD support st7789v panel</code></p>
<p dir="auto"><img src="/assets/uploads/files/1699238608985-image-20231017110344277-3.png" alt="image-20231017110344277 (3).png" class=" img-responsive img-markdown" width="802" height="329" /></p>
<h3>配置 SPI LCD 引脚</h3>
<p dir="auto">这里是重点部分：打开你喜欢的编辑器，修改文件：<code>board/r128s2/module/configs/sys_config.fex</code></p>
<pre><code>[lcd_fb0]
lcd_used            = 1   
lcd_model_name      = "spilcd"   
lcd_driver_name     = "st7789v" 
; 屏幕规格配置
lcd_x               = 240
lcd_y               = 240  
lcd_width           = 48
lcd_height          = 48
; SPI 速率
lcd_data_speed      = 50
; PWM 背光配置项
lcd_pwm_used        = 1
lcd_pwm_ch          = 1
lcd_pwm_freq        = 5000 
lcd_pwm_pol         = 0 
lcd_backlight       = 100
; 配置 lcd_if = 1 为 DBI 模式，双缓冲
lcd_if              = 1
fb_buffer_num       = 2
; 配置屏幕传输数据的像素格式，这里是 LCDFB_FORMAT_RGB_565
lcd_pixel_fmt       = 10
; 配置 DBI 接口像素格式，这里是 RGB565
lcd_dbi_fmt         = 2
; 配置 DBI 时钟的行为模式，这里是自动停止模式。有数据就有时钟，没发数据就没有时钟
lcd_dbi_clk_mode    = 0
; 屏幕没有 TE 脚，配置 TE 为 0
lcd_dbi_te          = 0
; 配置屏幕 DBI 格式 L4I1
lcd_dbi_if          = 2
; 输入图像数据 RGB 顺序识别设置，这里配置是 RGB 格式
lcd_rgb_order       = 0
; 设置屏的刷新率，单位Hz。当lcd_dbi_te使能时，这个值设置无效。
lcd_fps             = 60
; 使用 SPI1 作为通讯接口
lcd_spi_bus_num     = 1
lcd_frm             = 2
lcd_gamma_en        = 1

lcd_power_num       = 0
lcd_gpio_regu_num   = 0
lcd_bl_percent_num  = 0

;RESET Pin
lcd_gpio_0          = port:PA20&lt;1&gt;&lt;0&gt;&lt;2&gt;&lt;0&gt;
</code></pre>
<h2>编译打包</h2>
<p dir="auto">运行命令 <code>mp</code> 编译打包，可以看到编译了 <code>st7789v.o</code></p>
<p dir="auto"><img src="/assets/uploads/files/1699238615032-image-20231017111015362-3.png" alt="image-20231017111015362 (3).png" class=" img-responsive img-markdown" width="881" height="277" /></p>
<h2>测试</h2>
<p dir="auto">烧录启动之后，屏幕背光启动，但是屏幕全黑。</p>
<p dir="auto"><img src="/assets/uploads/files/1699238627254-image-20231023103606375-1.png" alt="image-20231023103606375 (1).png" class=" img-responsive img-markdown" width="647" height="128" /></p>
<p dir="auto">输入 <code>test_spilcd</code> ，屏幕显示蓝色。</p>
<p dir="auto"><img src="/assets/uploads/files/1699238631937-image-20231031144435046.png" alt="image-20231031144435046.png" class=" img-responsive img-markdown" width="1178" height="825" /></p>
<p dir="auto">输入 <code>lv_examples 1</code> 可以显示 <code>lvgl</code> 界面</p>
<p dir="auto"><img src="/assets/uploads/files/1699238639763-image-20231023103710014-1.png" alt="image-20231023103710014 (1).png" class=" img-responsive img-markdown" width="1175" height="778" /></p>
<h2>常见问题</h2>
<h3>LVGL 颜色异常</h3>
<p dir="auto">这是由于 LVGL 配置的 <code>LV_COLOR_DEPTH</code> 为 32，但是 SPI 屏配置为16位。请修改 <code>lv_conf.h</code>，也请注意 <code>LV_COLOR_16_SWAP</code> 仅有 SPI 需要设置为 1，在使用 DBI 驱动的时候不需要配置为 1。</p>
<p dir="auto"><img src="/assets/uploads/files/1699238805885-image-20231031144514859.png" alt="image-20231031144514859.png" class=" img-responsive img-markdown" width="867" height="156" /></p>
<h3>出现部分花屏</h3>
<p dir="auto"><img src="/assets/uploads/files/1699238810885-image-20231023103039467-2.png" alt="image-20231023103039467 (2).png" class=" img-responsive img-markdown" width="1288" height="794" /></p>
<ul>
<li>检查 <code>address</code> 函数是否正确</li>
<li>检查 <code>sys_config.fex</code> 屏幕配置分辨率是否正确</li>
</ul>
<h3>SPI LCD 颜色相关问题</h3>
<p dir="auto">首先，得先确定显示屏使用的是SPI接口，还是DBI接口，不同的接口，输入数据的解析方式是不一样的。</p>
<p dir="auto">DBI接口的全称是 <code>Display Bus Serial Interface</code> ，在显示屏数据手册中，一般会说这是SPI接口，所以有人会误认为SPI屏可以使用 <code>normal spi</code> 去直接驱动。</p>
<p dir="auto">阅读<code>lcd_dbi_if</code>部分的介绍可以知道，在3线模式时，发送命令前有1位A0用于指示当前发送的是数据，还是命令。而命令后面接着的数据就没有这个A0位了，代表SPI需要在9位和8位之间来回切换，而在读数据时，更是需要延时 <code>dummy clock</code> 才能读数据，<code>normal spi</code> 都很难，甚至无法实现。所以 <code>normal spi</code> 只能模拟4 线的DBI的写操作。</p>
<p dir="auto">对于R128这类支持DBI接口的CPU，可以选择不去了解SPI。如果需要用到SPI去驱动显示屏，必须把显示屏设置成小端。</p>
<h4>RGB565和RGB666</h4>
<p dir="auto">SPI显示屏一般支持RGB444,RGB565和RGB666，RGB444使用的比较少，所以只讨论RGB565和RGB666.</p>
<p dir="auto">RGB565代表一个点的颜色由2字节组成，也就是R（红色）用5位表示，G（绿色）用6位表示，B（蓝色）用5位表示，如下图所示：</p>
<p dir="auto"><img src="/assets/uploads/files/1699238816713-image-20231016100553340-1.png" alt="image-20231016100553340 (1).png" class=" img-responsive img-markdown" width="749" height="137" /></p>
<p dir="auto">RGB666一个点的颜色由3字节组成，每个字节代表一个颜色，其中每个字节的低2位会无视，如下图所示：</p>
<p dir="auto"><img src="/assets/uploads/files/1699238821483-image-20231016100620890-1.png" alt="image-20231016100620890 (1).png" class=" img-responsive img-markdown" width="906" height="118" /></p>
<h4>SPI 接口</h4>
<p dir="auto">因为SPI接口的通讯效率不高，所以建议使用RGB565的显示，以 <code>jlt35031c</code> 显示屏为例，他的显示驱动芯片是 <code>ST7789v</code>，设置显示格式的方式是往 <code>3a</code> 寄存器写入<code>0x55（RGB565</code>）或者 <code>0x66（RGB666）</code>，在 <code>R128SDK</code> 中，已经把 <code>jlt35031c</code> 的通讯格式写死为 <code>0x55</code>，<code>lcd_pixel_fmt</code>配置选项无效：</p>
<pre><code>sunxi_lcd_cmd_write(sel, 0x3a);
sunxi_lcd_para_write(sel, 0x55);
</code></pre>
<p dir="auto">在例程中，输入的数据是 <code>0xff，0x00，0xff，0x00</code>，对于SPI接口，是按字节发送。实际上，例程只需要每次发送2字节即可，因为前后发送的都是相同的ff 00，所以没有看出问题。</p>
<p dir="auto">根据对 <code>565</code> 的数据解析，我们拆分 <code>ff 00</code> 就可以得到红色分量是 <code>0b11111</code>，也就是 <code>31</code>，绿色是<code>0b111000</code>，也就是 <code>56</code>，，蓝色是 <code>0</code>.我们等效转换成 <code>RGB888</code>，有：</p>
<pre><code>R = 31/31*255 = 255
G = 56/63*255 = 226
</code></pre>
<p dir="auto">在调色板输入对应颜色，就可以得到黄色</p>
<p dir="auto"><img src="/assets/uploads/files/1699238827625-image-20231016100913213-1.png" alt="image-20231016100913213 (1).png" class=" img-responsive img-markdown" width="262" height="131" /></p>
<p dir="auto">因为 <code>DBI</code> 通讯效率较高，所以可以使用 <code>RGB565</code> 或者 <code>RGB666</code>，使用 <code>DBI</code> 接口，也就是 <code>lcd_if</code> 设置为<code>1</code>时，驱动会根据 <code>lcd_pixel_fmt</code> 配置寄存器，以 <code>SDK</code> 中的 <code>kld2844b.c</code> 为例，这显示屏的显示驱动也是 <code>ST7789</code>，但是不同的屏幕，厂家封装时已经限制了通讯方式，所以即使是能使用 DBI 接口的驱动芯片的屏幕，或许也用不了DBI。</p>
<pre><code class="language-c">sunxi_lcd_cmd_write(sel, 0x3A); /* Interface Pixel Format */
/* 55----RGB565;66---RGB666 */
if (info[sel].lcd_pixel_fmt == LCDFB_FORMAT_RGB_565 ||
    info[sel].lcd_pixel_fmt == LCDFB_FORMAT_BGR_565) {
    sunxi_lcd_para_write(sel, 0x55);
    if (info[sel].lcd_pixel_fmt == LCDFB_FORMAT_RGB_565)
        rotate &amp;= 0xf7;
    else
        rotate |= 0x08;
} else if (info[sel].lcd_pixel_fmt &lt; LCDFB_FORMAT_RGB_888) {
    sunxi_lcd_para_write(sel, 0x66);
    if (info[sel].lcd_pixel_fmt == LCDFB_FORMAT_BGRA_8888 ||
        info[sel].lcd_pixel_fmt == LCDFB_FORMAT_BGRX_8888 ||
        info[sel].lcd_pixel_fmt == LCDFB_FORMAT_ABGR_8888 ||
        info[sel].lcd_pixel_fmt == LCDFB_FORMAT_XBGR_8888) {
        rotate |= 0x08;
    }
} else {
    sunxi_lcd_para_write(sel, 0x66);
}
</code></pre>
<p dir="auto">对于 DBI 格式，不再是以字节的形式去解析，而是以字的方式去解析，为了统一，软件已经规定了，<code>RGB565</code> 格式时，字大小是2字节，也就是16位，而 <code>RGB666</code> 格式时，字大小是4字节，也就是32位。</p>
<p dir="auto">对于 <code>RGB565</code> 格式，同样是设置为 <code>0xff,0x00</code>。因为屏幕是大端，而芯片存储方式是小端，所以芯片的 DBI 模块，会自动把数据从新排列，也就是实际上 DBI 发送数据时，会先发送<code>0x00</code>，再发送<code>0xff</code>，也就是红色分量为0，绿色分量为 <code>0b000111</code>，也就是7，蓝色分量是 <code>0x11111</code>，也就是31，我们同样转换成RGB888</p>
<pre><code>G = 7/63*255 = 28
B= 31/31*255 = 255
</code></pre>
<p dir="auto">在调色板上输入，可以得到蓝色。</p>
<p dir="auto"><img src="/assets/uploads/files/1699238834495-image-20231016101233907-1.png" alt="image-20231016101233907 (1).png" class=" img-responsive img-markdown" width="263" height="130" /></p>
<p dir="auto">如果是 <code>RGB666</code>，虽然占用的是3个字节，但是没有CPU是3字节对齐的，所以需要一次性输入4字节，然后 DBI 硬件模块，会自动舍弃1个字节，软件同意舍弃了最后一个字节。</p>
<p dir="auto">依旧以例程为例，例程输入了 <code>0xff，0x00，0xff，0x00</code>，为了方便说明，标准为 <code>0xff(1)，0x00(1)，0xff(2)，0x00(2)</code>，其中 <code>0x00(2)</code>会被舍弃掉，然后发送顺序是<code>0xff(2)，0x00(1)，0xff(1)</code>，也就是 <code>0xff(2)</code> 是红色分量，<code>0xff(1)</code> 是蓝色分量，混合可以得到紫色。</p>
<p dir="auto"><img src="/assets/uploads/files/1699238840104-image-20231016101308346-1.png" alt="image-20231016101308346 (1).png" class=" img-responsive img-markdown" width="260" height="127" /></p>
]]></description><link>https://bbs.aw-ol.com/topic/4556/r128-应用开发案例-dbi驱动st7789v1-3寸lc</link><generator>RSS for Node</generator><lastBuildDate>Sun, 16 Aug 2026 19:28:11 GMT</lastBuildDate><atom:link href="https://bbs.aw-ol.com/topic/4556.rss" rel="self" type="application/rss+xml"/><pubDate>Mon, 06 Nov 2023 02:55:19 GMT</pubDate><ttl>60</ttl><item><title><![CDATA[Reply to 【R128】应用开发案例——DBI驱动ST7789V1.3寸LC on Mon, 16 Sep 2024 11:55:45 GMT]]></title><description><![CDATA[<p dir="auto">大佬牛逼，学习了，厉害</p>
]]></description><link>https://bbs.aw-ol.com/post/23978</link><guid isPermaLink="true">https://bbs.aw-ol.com/post/23978</guid><dc:creator><![CDATA[dsc1992]]></dc:creator><pubDate>Mon, 16 Sep 2024 11:55:45 GMT</pubDate></item></channel></rss>