【已解决】熬了两晚终于点亮了1.3寸7789V的屏幕

[复制链接]
查看43 | 回复6 | 昨天 09:15 | 显示全部楼层 |阅读模式

本帖最后由 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 的显示特性,包括以下几个方面:

  1. 显示方向:通过 param 的值可以设置屏幕内容的旋转方向。例如:
    • 0x00:正常方向
    • 0x20:向右旋转 90 度
    • 0x80:向右旋转 180 度
    • 0xE0:向左旋转 90 度
  2. 镜像显示:镜像功能允许用户将显示内容水平或垂直翻转。
    • 结合镜像标志 (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 文档中定义的有效参数。

可能的影响

  1. 无效显示:使用这些参数可能会导致屏幕无法正确显示内容,出现乱码或无效像素。
  2. 错误的显示方向:这些值可能没有明确的定义,可能导致 LCD 以错误的方向显示内容。
  3. 设备不稳定:在某些情况下,发送无效参数可能会导致设备进入不稳定状态,甚至重启或锁死。

正确的做法

为了确保显示效果正常,建议始终使用 ST7789V 数据手册中规定的有效参数值。有效的参数值通常包括:

  • 0x00: 正常方向
  • 0x20: 向右旋转 90 度
  • 0x80: 向右旋转 180 度
  • 0xE0: 向左旋转 90 度
  • 还有结合镜像的其他有效值。

使用非标准的参数可能会导致不可预期的行为,因此建议遵循数据手册中的推荐值来设置显示方向和其它特性。

我是怎么一步一步找到这的

企业微信截图_20241014161253.png

根据泽哥的提示,沿着初始化参数开始逐层向代码内部寻找。也对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__)) 是一个用于简化调用特定函数的宏。我们来详细解析一下这个宏的构成及其用途。

解析宏定义

  1. 宏名称_LCD_FUNC_DEFINE
    • 这个名称表明它是一个与 LCD 功能相关的宏。
  2. 参数
    • _func:这是一个占位符,代表你希望调用的具体函数名。
    • ...:这是一个变长参数,可以传递任意数量的参数给宏。
  3. 宏展开
    • st7789v_spi_##_func(__VA_ARGS__):通过 ## 操作符将 st7789v_spi__func 连接起来,形成一个完整的函数名。
    • __VA_ARGS__ 会被替换为传入的参数,这样可以将所有的参数传递给实际的 SPI 函数。

用法示例

假设你有几个与 SPI 通信相关的函数,比如 write_dataread_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>

优点

  1. 代码可读性:通过使用宏,可以使得函数调用更加一致和简洁。
  2. 减少重复:避免每次都手动拼接函数名,减少了代码的重复性。
  3. 灵活性:可以轻松扩展到其他 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; 是用于设置显示驱动的回调函数,通常在图形库或嵌入式系统中使用。下面是对这行代码的详细解释和上下文:

解析

  1. disp_drv_dsc
    • 这是一个结构体(或类)实例,通常代表显示驱动的描述符。它包含与显示相关的多个配置和回调函数。
  2. flush_cb
    • 这是 disp_drv_dsc 结构体中的一个成员,表示刷新回调函数(flush callback)。它用于在需要更新显示内容时被调用。
  3. 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,以确保显示的内容是最新的。

示例流程

  1. 设置回调:在初始化显示驱动时,设置 flush_cbdisp_flush
  2. 触发条件:当 UI 发生变化时,图形库会检测到变化。
  3. 调用回调:库内部逻辑会调用 disp_flush,并传递相应的参数(如区域和图像数据)。
  4. 完成通知:在 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 引脚设置为高电平,确保设备处于非选择状态。

到这里你以为就结束了吗

是的结束了,已经点亮了。

15449417-f1dc-4971-a55f-b0d607455c60.jpg

最后的最后的最后,写项目之前一定确认自己的sdk时最新的。

因为开发环境不同,依赖库不同都会导致错误的发生。

回复

使用道具 举报

WT_0213 | 昨天 09:18 | 显示全部楼层
真不错
回复

使用道具 举报

爱笑 | 昨天 10:09 | 显示全部楼层
用心做好保姆工作
回复

使用道具 举报

putin | 昨天 12:11 | 显示全部楼层
可以
回复

使用道具 举报

bzhou830 | 昨天 14:57 | 显示全部楼层
真不错
选择去发光,而不是被照亮
回复

使用道具 举报

干簧管 | 昨天 19:32 | 显示全部楼层
l厉害
回复

使用道具 举报

沈夜 | 昨天 20:31 | 显示全部楼层
最后改了哪了 没看懂
回复 支持 反对

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则