本帖最后由 lazy 于 2024-10-15 09:15 编辑
熬了两晚终于点亮了1.3寸7789V的屏幕,感谢大家的帮助。
关于LVGL使用问题
https://bbs.ai-thinker.com/forum.php?mod=viewthread&tid=45375&fromuid=16612
上篇帖子遇到的问题已经找到原因了,主要还是SDK版本问题,不够新。我看到新分支上的库是正确的。
https://gitee.com/Ai-Thinker-Open/AiPi-Open-Kits/blob/AiPi-aiThinkerCloud/bl61x_SDK/AiPi_bsp/common/lcd/spi/st7789v_spi.c
主要是st7789v_spi_set_dir这个方法,根据用户需求调整显示方向。
这个是博流官方的
https://gitee.com/bouffalolab/bouffalo_sdk/blob/master/bsp/common/lcd/spi/st7789v_spi.c
int st7789v_spi_set_dir(uint8_t dir, uint8_t mir_flag)
{
uint8_t param;
switch (dir) {
case 0:
if (!mir_flag)
param = 0x00;
else
param = 0x40;
break;
case 1:
if (!mir_flag)
param = 0x20;
else
param = 0xA0;
break;
case 2:
if (!mir_flag)
param = 0x80;
else
param = 0xC0;
break;
case 3:
if (!mir_flag)
param = 0xE0;
else
param = 0x60;
break;
default:
return -1;
break;
}
lcd_spi_transmit_cmd_para(0x36, (void *)¶m, 1);
return dir;
}
这个是安信可的
sdk 是相对旧一些 aithinker_Ai-M6X_SDK,新bl61x_SDK的没问题
https://gitee.com/Ai-Thinker-Open/aithinker_Ai-M6X_SDK/blob/master/bsp/common/lcd/spi/st7789v_spi.c
int st7789v_spi_set_dir(uint8_t dir, uint8_t mir_flag)
{
uint8_t param;
switch (dir) {
case 0:
if (!mir_flag)
param = 0x08;
else
param = 0x48;
break;
case 1:
if (!mir_flag)
param = 0x28;
else
param = 0xA8;
break;
case 2:
if (!mir_flag)
param = 0x88;
else
param = 0xC8;
break;
case 3:
if (!mir_flag)
param = 0xE8;
else
param = 0x68;
break;
default:
return -1;
break;
}
lcd_spi_transmit_cmd_para(0x36, (void *)¶m, 1);
return dir;
}
细心的人可能已经看出来了param这个变量的值不太一样
st7789v_spi_set_dir
函数说明
st7789v_spi_set_dir
函数用于通过 SPI 通信设置 ST7789V LCD 的显示方向。以下是对该函数的详细说明:
函数概述
- 参数:
uint8_t dir
: 指定要设置的方向(取值范围为 0 到 3)。
uint8_t mir_flag
: 一个标志,决定是否镜像显示。
方向映射
函数使用 switch-case 结构根据指定的方向 (dir
) 和镜像标志 (mir_flag
) 确定参数值 (param
)。
- 情况 0:
- 正常:
param = 0x00
- 镜像:
param = 0x40
- 情况 1:
- 正常:
param = 0x20
- 镜像:
param = 0xA0
- 情况 2:
- 正常:
param = 0x80
- 镜像:
param = 0xC0
- 情况 3:
- 正常:
param = 0xE0
- 镜像:
param = 0x60
如果 dir
超出 0 到 3 的范围,函数将返回 -1
作为错误指示。
SPI 命令传输
在确定 param
后,函数调用 lcd_spi_transmit_cmd_para
发送命令 0x36
(对应于内存访问控制),并附带该参数。该命令格式通常定义了 LCD 如何解释像素数据。
返回值
函数在成功时返回设置的方向 (dir
),如果提供了无效的方向,则返回 -1
。
示例用法
以下是如何在代码中使用此函数的简单示例:
<pre class="code-block-wrapper"><code class="hljs code-block-body c">#include <stdio.h>
int main() {
int result;
// 设置方向为 0,且不镜像
result = st7789v_spi_set_dir(0, 0);
if (result == -1) {
printf("错误:无效的方向\n");
} else {
printf("方向设置为 %d\n", result);
}
return 0;
}
</code></pre>
注意事项
- 确保
lcd_spi_transmit_cmd_para
已正确定义,并能够正确处理 SPI 通信。
- 注意有效的
dir
值(0 到 3),并处理可能传入的无效输入。
在 st7789v_spi_set_dir
函数中,参数 param
主要用于设置 ST7789V LCD 的内存访问控制(Memory Access Control)。具体来说,它定义了屏幕的显示方向和是否镜像显示。
param
的含义
param
通过不同的值来控制 LCD 的显示特性,包括以下几个方面:
- 显示方向:通过
param
的值可以设置屏幕内容的旋转方向。例如:
- 0x00:正常方向
- 0x20:向右旋转 90 度
- 0x80:向右旋转 180 度
- 0xE0:向左旋转 90 度
- 镜像显示:镜像功能允许用户将显示内容水平或垂直翻转。
- 结合镜像标志 (
mir_flag
),可以生成不同的 param
值,例如:
- 不镜像时:0x00、0x20、0x80、0xE0
- 镜像时:0x40、0xA0、0xC0、0x60
-
指令解释
对于 ST7789V LCD,0x36 是固定的命令,用于设置内存访问控制(Memory Access Control)。这个命令用于配置屏幕的显示方向、镜像以及颜色排列等特性。
具体用途
当你调用 lcd_spi_transmit_cmd_para 并发送命令 0x36 时,后续的参数(即 param)将决定具体的显示方向和其他相关设置。
示例
例如,如果你想设置屏幕为正常方向且不镜像,你会发送:
- 命令 0x36
参数 0x00
- 如果你想设置为向右旋转 90 度且不镜像,你会发送:
- 命令 0x36
参数 0x20
参数错误
将 ST7789V 的内存访问控制参数改为 0x08、0x28、0x88 或 0xE8,可能会导致不预期的显示效果,因为这些值并不是 ST7789V 文档中定义的有效参数。
可能的影响
- 无效显示:使用这些参数可能会导致屏幕无法正确显示内容,出现乱码或无效像素。
- 错误的显示方向:这些值可能没有明确的定义,可能导致 LCD 以错误的方向显示内容。
- 设备不稳定:在某些情况下,发送无效参数可能会导致设备进入不稳定状态,甚至重启或锁死。
正确的做法
为了确保显示效果正常,建议始终使用 ST7789V 数据手册中规定的有效参数值。有效的参数值通常包括:
0x00
: 正常方向
0x20
: 向右旋转 90 度
0x80
: 向右旋转 180 度
0xE0
: 向左旋转 90 度
- 还有结合镜像的其他有效值。
-
-
使用非标准的参数可能会导致不可预期的行为,因此建议遵循数据手册中的推荐值来设置显示方向和其它特性。
我是怎么一步一步找到这的
根据泽哥的提示,沿着初始化参数开始逐层向代码内部寻找。也对lcd初始化了解深入了一些。
首先是
lv_port_disp_init();
aithinker_Ai-M6X_SDK\components\graphics\lvgl\port\lv_port_disp.c
void lv_port_disp_init(void)
{
/*-------------------------
* Initialize your display
* -----------------------*/
disp_init();
……
/* rotation */
#ifdef LCD_ROTATED_NONE
disp_drv_dsc.rotated = LV_DISP_ROT_NONE;
#elif defined LCD_ROTATED_90
disp_drv_dsc.rotated = LV_DISP_ROT_90;
#elif defined LCD_ROTATED_180
disp_drv_dsc.rotated = LV_DISP_ROT_180;
#elif defined LCD_ROTATED_270
disp_drv_dsc.rotated = LV_DISP_ROT_270;
#endif
/*Used to copy the buffer's content to the display*/
disp_drv_dsc.flush_cb = disp_flush;
……
}
先看初始化过程,调用依次
void disp_init(void)
{
lcd_init(); // 看这个
lcd_async_callback_register(flush_async_callback);
lcd_clear(LCD_COLOR_RGB(0x00, 0X00, 0X00));
}
aithinker_Ai-M6X_SDK\bsp\common\lcd\lcd.c
int lcd_init(void)
{
int res;
……
res = _LCD_FUNC_DEFINE(init); // 这个是初始化
return res;
}
aithinker_Ai-M6X_SDK\bsp\common\lcd\lcd.h
……
#elif defined LCD_SPI_ST7789V
#include "spi/st7789v_spi.h"
#define LCD_INTERFACE_TYPE LCD_INTERFACE_SPI
#define LCD_W ST7789V_SPI_W
#define LCD_H ST7789V_SPI_H
#define LCD_COLOR_DEPTH ST7789V_SPI_COLOR_DEPTH
// 感觉这个好厉害,原来函数可以这样调用
#define _LCD_FUNC_DEFINE(_func, ...) st7789v_spi_##_func(__VA_ARGS__)
……
define _LCD_FUNC_DEFINE(_func, ...) st7789vspi##_func(__VA_ARGS__)
这个宏定义 (#define _LCD_FUNC_DEFINE(_func, ...) st7789v_spi_##_func(__VA_ARGS__)
) 是一个用于简化调用特定函数的宏。我们来详细解析一下这个宏的构成及其用途。
解析宏定义
- 宏名称:
_LCD_FUNC_DEFINE
- 参数:
_func
:这是一个占位符,代表你希望调用的具体函数名。
...
:这是一个变长参数,可以传递任意数量的参数给宏。
- 宏展开:
st7789v_spi_##_func(__VA_ARGS__)
:通过 ##
操作符将 st7789v_spi_
和 _func
连接起来,形成一个完整的函数名。
__VA_ARGS__
会被替换为传入的参数,这样可以将所有的参数传递给实际的 SPI 函数。
用法示例
假设你有几个与 SPI 通信相关的函数,比如 write_data
和 read_data
,它们的实现可能如下:
<pre class="code-block-wrapper"><code class="hljs code-block-body c">void st7789v_spi_write_data(uint8_t data) {
// 发送数据的实现
}
uint8_t st7789v_spi_read_data(void) {
// 读取数据的实现
}
</code></pre>
你可以使用 _LCD_FUNC_DEFINE
宏来简化对这些函数的调用,例如:
<pre class="code-block-wrapper"><code class="hljs code-block-body c">_LCD_FUNC_DEFINE(write_data, 0xFF); // 展开为 st7789v_spi_write_data(0xFF);
uint8_t data = _LCD_FUNC_DEFINE(read_data); // 展开为 st7789v_spi_read_data();
</code></pre>
优点
- 代码可读性:通过使用宏,可以使得函数调用更加一致和简洁。
- 减少重复:避免每次都手动拼接函数名,减少了代码的重复性。
- 灵活性:可以轻松扩展到其他 SPI 函数,只需在需要时使用该宏。
注意事项
- 使用宏时要小心命名冲突,确保
_func
不会与其他函数名冲突。
- 宏的调试和错误提示可能不如正常函数调用清晰,因此在调试过程中可能会增加难度。
aithinker_Ai-M6X_SDK/blob/master/bsp/common/lcd/spi/st7789v_spi.c
int st7789v_spi_init()
{
lcd_spi_init(&spi_para);
for (uint16_t i = 0; i < (sizeof(st7789v_spi_init_cmds) / sizeof(st7789v_spi_init_cmds[0])); i++) {
if (st7789v_spi_init_cmds[i].cmd == 0xFF && st7789v_spi_init_cmds[i].data == NULL && st7789v_spi_init_cmds[i].databytes) {
bflb_mtimer_delay_ms(st7789v_spi_init_cmds[i].databytes);
} else {
lcd_spi_transmit_cmd_para(st7789v_spi_init_cmds[i].cmd, (void *)(st7789v_spi_init_cmds[i].data), st7789v_spi_init_cmds[i].databytes);
}
}
return 0;
}
然后再看
disp_drv_dsc.flush_cb = disp_flush;
这一行代码 disp_drv_dsc.flush_cb = disp_flush;
是用于设置显示驱动的回调函数,通常在图形库或嵌入式系统中使用。下面是对这行代码的详细解释和上下文:
解析
disp_drv_dsc
:
- 这是一个结构体(或类)实例,通常代表显示驱动的描述符。它包含与显示相关的多个配置和回调函数。
flush_cb
:
- 这是
disp_drv_dsc
结构体中的一个成员,表示刷新回调函数(flush callback)。它用于在需要更新显示内容时被调用。
disp_flush
:
- 这是一个函数名,实际用于刷新显示的实现。这个函数通常会处理将新的图像数据传输到显示屏的逻辑。
static void disp_flush(lv_disp_drv_t *disp_drv, const lv_area_t *area, lv_color_t *color_p)
{
static uint8_t rotated_dir = 0;
if (rotated_dir != disp_drv->rotated) {
rotated_dir = disp_drv->rotated;
lcd_set_dir(rotated_dir, 0);
}
p_disp_drv_cb = disp_drv;
lcd_draw_picture_nonblocking(area->x1, area->y1, area->x2, area->y2, (lcd_color_t *)color_p);
}
disp_flush
函数的回调机制通常是在需要刷新显示内容时由图形库自动调用的。具体来说,它会在以下情况下被回调:
1. 需要更新显示时
当应用程序中的某个部分(比如绘制图形、文本,或更新 UI 状态)触发了显示刷新时,图形库会调用 disp_flush
。这通常发生在以下操作后:
2. 定时刷新
如果你实现了定时器或循环逻辑来定期更新屏幕内容,图形库可以在每个周期内调用 disp_flush
。
3. 用户交互
当用户与界面进行交互(如点击按钮,滑动等)时,这些事件可能会导致 UI 更新,从而触发对 disp_flush
的回调。
4. 动画效果
在处理动画或动态效果时,系统可能会在每一帧的更新中调用 disp_flush
,以确保显示的内容是最新的。
示例流程
- 设置回调:在初始化显示驱动时,设置
flush_cb
为 disp_flush
。
- 触发条件:当 UI 发生变化时,图形库会检测到变化。
- 调用回调:库内部逻辑会调用
disp_flush
,并传递相应的参数(如区域和图像数据)。
- 完成通知:在
disp_flush
执行完更新后,需要调用如 lv_disp_flush_ready(drv);
的函数,以通知库刷新完成。
注意事项
- 确保
disp_flush
中的逻辑能够高效地将图像数据传输至显示设备。
- 在完成图像更新后,务必调用相关的完成函数,以保证系统状态的一致性。
调用了lcd_set_dir
aithinker_Ai-M6X_SDK\bsp\common\lcd\lcd.c
/**
* @brief Set display direction and mir
*
* @param dir 0~3 : 0~270 Angle
* @param mir_flag 0:normal 1:Horizontal Mirroring(if support)
* @return int
*/
int lcd_set_dir(uint8_t dir, uint8_t mir_flag)
{
dir %= 4;
lcd_dir = dir;
if (dir == 0 || dir == 2) {
lcd_max_x = LCD_W - 1;
lcd_max_y = LCD_H - 1;
} else {
lcd_max_x = LCD_H - 1;
lcd_max_y = LCD_W - 1;
}
return _LCD_FUNC_DEFINE(set_dir, dir, mir_flag);
}
_LCD_FUNC_DEFINE
回到aithinker_Ai-M6X_SDK\bsp\common\lcd\lcd.h
……
#elif defined LCD_SPI_ST7789V
#include "spi/st7789v_spi.h"
#define LCD_INTERFACE_TYPE LCD_INTERFACE_SPI
#define LCD_W ST7789V_SPI_W
#define LCD_H ST7789V_SPI_H
#define LCD_COLOR_DEPTH ST7789V_SPI_COLOR_DEPTH
#define _LCD_FUNC_DEFINE(_func, ...) st7789v_spi_##_func(__VA_ARGS__)
……
拼接后方法名为:st7789v_spi_set_dir就回到文章开到的方法中了
int st7789v_spi_set_dir(uint8_t dir, uint8_t mir_flag)
{
uint8_t param;
switch (dir) {
case 0:
if (!mir_flag)
param = 0x00;
else
param = 0x40;
break;
case 1:
if (!mir_flag)
param = 0x20;
else
param = 0xA0;
break;
case 2:
if (!mir_flag)
param = 0x80;
else
param = 0xC0;
break;
case 3:
if (!mir_flag)
param = 0xE0;
else
param = 0x60;
break;
default:
return -1;
break;
}
lcd_spi_transmit_cmd_para(0x36, (void *)¶m, 1);
return dir;
}
你以为这就结束了吗
那是不可能的,以上只是其中一个问题。
自己挖的坑自己填
【DIY电子作品】Ai-M61-32SU 手机蓝牙自拍杆
https://bbs.ai-thinker.com/forum.php?mod=viewthread&tid=45370&fromuid=16612
这个是自拍杆的项目,然后呢,在这个项目中我随便选了一个引脚做按钮。
int main(void)
{
board_init();
// gpio初始化
btn_gpio = bflb_device_get_by_name("gpio");
// 默认上拉
bflb_gpio_init(btn_gpio, GPIO_PIN_14, GPIO_INPUT| GPIO_PULLUP | GPIO_SMT_EN | GPIO_DRV_1);
……
// 创建按钮检测任务
xTaskCreate(btn_event, "btn_event", 1024, NULL, 1, NULL);
……
vTaskStartScheduler();
}
然后
int btn_clicked = 0;
// 按钮检测任务
static void btn_event(void* args){
while (1)
{
int status = bflb_gpio_read(btn_gpio, GPIO_PIN_14);
// 检测gpio14是否为低电平,默认上拉高电平
if(status == 0){
// 消除抖动
vTaskDelay(15/portTICK_RATE_MS);
再判断一次
if(status == 0){
// 防止多次触发
if(btn_clicked){
continue;;
}
LOG_I("点击");
btn_clicked = 1;
hid_key_num_t hid_key_num = HID_KEY_NUMBLE_SELFIE_STICK;
// 发送音量-按键进行拍照
xQueueSend(ble_hid_queue, &hid_key_num, portMAX_DELAY);
}
}else{
btn_clicked = 0;
}
}
}
注意这个GPIO_PIN_14待会会考
关于LVGL使用问题
https://bbs.ai-thinker.com/forum.php?mod=viewthread&tid=45375&fromuid=16612
这帖子描述了,使用lvgl驱动7789v 1.3寸屏幕点不亮的问题。
这里引脚定义
/* spi pin, hardware controlled */
#define LCD_SPI_HARD_4_PIN_CLK GPIO_PIN_13 //SCL引脚
#define LCD_SPI_HARD_4_PIN_DAT GPIO_PIN_15 //SDA引脚
/* cs/dc pin, software controlled */
#define LCD_SPI_HARD_4_PIN_CS GPIO_PIN_14 //CS引脚
#define LCD_SPI_HARD_4_PIN_DC GPIO_PIN_16 // DC引脚
#endif
/********** lcd reset configuration ***********/
#if (defined(LCD_RESET_EN) && LCD_RESET_EN)
/* lcd reset signal pin, please leave blank if not needed */
#define LCD_RESET_PIN GPIO_PIN_12 //RES引脚
看到了吗
#define LCD_SPI_HARD_4_PIN_CS GPIO_PIN_14 //CS引脚
又又发现了 GPIO_PIN_14 被定义了
如果在 SPI 通信中将 Chip Select (CS) 引脚定义为上拉,并且一直保持读取状态,会导致以下几个潜在问题:
1. 总是选择设备
- CS 引脚用于选择特定的 SPI 从设备。如果 CS 引脚被上拉并保持高电平,意味着没有任何从设备被选择。在这种情况下,从设备不会响应 SPI 的通信。
2. 数据读取无效
- 在没有选中任何从设备的情况下进行读取操作,所接收到的数据将是无效的或随机的。因为在 SPI 协议中,只有在 CS 引脚处于低电平时,从设备才会响应主设备的命令。
3. 干扰其他 SPI 从设备
- 如果系统中有多个 SPI 从设备,并且某个设备的 CS 引脚被上拉至高电平,那么在进行读取时可能会影响到其他设备的正常工作。这可能导致数据错误或设备冲突。
4. 功耗问题
- 尽管处于空闲状态,但如果 CS 引脚始终保持高电平,某些从设备可能不会进入低功耗模式,导致不必要的功耗增加。
5. 无法进行有效的 SPI 操作
- 由于没有正确地选择从设备,所有后续的 SPI 操作(如发送或接收数据)都将失败,可能导致整个通信流程出错。
解决方案
为了避免这些问题,建议:
- 正确控制 CS 引脚,确保在需要通信时将其拉低。
- 在不需要与 SPI 从设备通信时,可以将 CS 引脚设置为高电平,确保设备处于非选择状态。
到这里你以为就结束了吗
是的结束了,已经点亮了。
最后的最后的最后,写项目之前一定确认自己的sdk时最新的。
因为开发环境不同,依赖库不同都会导致错误的发生。