基于CrowPanel ESP32 Display 4.3英寸HMI开发板实现小智AI助手
该项目使用了CrowPanel ESP32 Display 4.3英寸HMI开发板,实现了小智AI语音助手的设计,它的主要功能为:使用ESP32-S3实现以语音方式与大模型进行对话,并且通过屏幕展示对话内容和表情。
标签
嵌入式系统
显示
开发板
接口
Riccck
更新2025-03-13
151

前言

1、 什么是小智:小智AI聊天机器人,以下简称小智

image.png

2、 什么是AI大模型:从国外的ChatGPT到国内各个巨头百花齐放百家争鸣,到最近的DeepSeek国产大模型后来居上,直接比肩元老级别的大模型,可能大多数人都已经在被这两年飞速发展的AI大语言模型冲击到或者实际运用到生产生活中了。可能有的人还是只接触过文字一收一发的模式,但是其实从前的科幻电影场面:人类与机器直接语音对话已经很成熟了,现在更是能够很低成本的实现。

3、 什么是硬禾学堂与CrowPanel屏幕开发板:硬禾学堂是我最近发现的一个极佳的学习研究平台,他们给出了很多活动,活动的显著特点就是,付费购买,完成项目之后则返款。这样的模式促使我为了我的小钱钱不得不专心坐下来进行研究学习。CrowPanel HMI开发板就是平台的一个活动中的一块板卡,我将在这个板卡实现小智的移植来作为我的项目作业提交。

注意!如果你对人工智能不感兴趣,也对AI语音对话不感兴趣,也对ESP32IDF开发/lvgl开发不感兴趣,也对我不感兴趣的话,那么感谢看到这里,接下来建议可以退出啦,毕竟这样的你,看这个一定是感觉十分甚至九分无聊的。

移植流程

1、 首先我声明一下,我只能说是在记录一点经验,这不是教程,更像一种聊天式的分享,因为我自己本来也是一个业余选手。即使现在跑起来了,我还是有很多东西没有搞懂,但是时间紧急,交作业deadline已经到了,跑起来了就不要动了(笑)。

2、 接下来给大家看一下需要准备哪些东西

a) 文档资料,主要是一些datasheet、原理图啊之类的文档

b) 官方资料,比如乐鑫ESP32官方的开发文档,比如CrowPanel官方的示例代码

image.png

c) D老师和C老师

image.png

众所周知,D老师学识渊博,回答的有深度,就是脾气不太好,咨询问题有的时候爱理不理的,咱也不敢说啥

image.png


d) 准备一盒小番茄,吃起来还不错,甜度适中而且很有番茄味,对开发很有帮助,

image.png

e) 这是一盒哈密瓜,晚上超市打折买的,特别熟特别甜

image.png

f) 还可以准备一杯热水,多喝热水,有益健康。

 

3、 其他的废话不多说,项目移植。首先进行可行性分析

那么好,我们直接看小智的硬件需求(面包板Wifi版本)(小智AI聊天机器人面包板DIY硬件清单与接线教程-飞书云文档):

image.png

然后再看看手上开发板的现状(CrowPanel ESP32 Display 4.3英寸HMI开发板):

image.png

下面给出一个简单的直观对比:

平台

主控

规格

屏幕

麦克风

音频功放

备注

小智

ESP32
-S3

16M Flash,
8M PSRAM

OLED
128*32
IIC接口

INMP441

MAX98357A
IIS功放

 

CrowPanel

ESP32
-S3

4M Flash,
2M PSRAM

TFT
480*272
RGB565接口

NS4168
IIS功放

 

从上面的对比来看,我们的CrowPanel板子现状有点惨,首先程序Flash空间和外部扩展SPI RAM空间都如此之小,但是却要背负那么大分辨率的显示屏驱动责任。好在功放虽然型号不同,但是都是标准IIS接口,所以亲测通用,代码的这部分甚至都不用改的。板子上没有麦克风,所以需要外接麦克风,能接的下吗?除此之外小智还使用到了WS2812全彩LED用于指示状态的,我们是否要加呢,有没有地方加呢?带着这些疑问,我们看下规格:

猛的一看接口好像是预留了很多,

image.png

看下来满打满算只有4IO是真正自由的,是不是还不如猛的一看。

image.png

那么4IO够吗,我们看都要干些什么:

首先需要连INMP441麦克风模块,那么我们查一下INMP441的引脚定义可以发现,由于我们是单个麦克风单声道,软件里面写死左或者右之后,L/R引脚可以通过外部高低电平直接固定。只需要SCKWSSD三个IO

image.png

此外需要连接WS2812全彩灯,这是一种单线通信的全彩LED灯,那么顾名思义,它只需要使用一个IO连一根数据线就可以了,接到第一个灯的DIN引脚,可以接单个LED,也可以接一串LED,只需要将灯的DOUT和下一个灯的DIN手拉手串起来即可。

image.png

根据以上分析,我们将空余的4IO刚好用完,接入了麦克风和全彩指示灯。

我们可以直接使用杜邦线从板卡后面的排母或者4P座子接,但是不够优雅,所以不如像我这样做一个简单的扩展板,就是通过排针直插板卡的扩展口,将麦克风和WS2812全彩LED的线路连接在PCB上面完成,麦克风还是直接用淘宝买的模组,因为好买好焊接。L/R引脚预留两个方案,可以上拉也可以下拉。在这个项目我们选择上拉=右声道,可以和扬声器保持一致。

image.png

image.png

整体上来看,除了给的程序空间太小,其他方面都有了,那么这可行性是行还是不行呢?

那么我是怎么做的呢,你不是模块规格不一样吗,那N16R8的模组买回来了

image.png

其他的我不知道行不行,但是朋友,动手方面,骡子一样的动力我是有的。

DIY的焊台,开换

image.png

那么也是二话不说换完了,之后研究了好几天,结论就是我踩了一个大坑,确实做不了,我又给换回来了����。。。

4、 硬件踩坑记录

刚才说到的为什么换上和原版小智一模一样的模组反而不行了?

我们回来看板子原理图:

image.png

此处我们会发现,这个板子将模组全部的引脚都用上了,其中主要消耗就是RGB565屏幕驱动。当我换成N16R8模组我修改引脚到板子实际的引脚然后编译发现总是提醒我,IO35,IO36,IO37已经被占用了,我?不是预留的空闲IO吗?在代码里搜索半天没有发现哪里用了这些IO。上网搜了一番才发现有人和我哦踩过一样的坑,原来规格书有一行小字:

image.png

那么什么是集成Octal SPI PSRAM的模组呢,看下表:

 image.png

那么这样就清晰多了,原来是我白忙活了呀,我以为啥呢。

然后就是不语,只是一味的换回来原装的模块,否则即使其他的跑成功了,但是4个空闲IO只剩下IO38一个了,小智要变聋子了,额咳咳。

这边提醒大家做项目需要换大PSRAM的小伙伴关注一下这里,大PSRAM的模组可用IO要少3个。ESP32系列很多模组都有类似现象,比如ESP32-WROOM-1PSRAM比不带PSRAM的模组少了一个可用IO用于PSRAM的片选;比如ESP32-C3芯片内置4M Flash的型号比不内置Flash的型号少了一个可用IO用于内部Flash的电源,还是多看看规格书吧。。

 

5、 软件移植记录

首先总体来说,这个移植感觉并不难,因为从我的角度来看:

a) 是我第一次接触ESP32 IDF开发

b) 是我第一次接触C++代码

c) 是我第一次接触lvgl开发

d) 是我第一次接触Cmakemenuconfig

e) 是我第一次接触IIS外设,RGB接口,WS2812驱动等等等等

另外我在研究这个项目移植的同时,项目本身也是在进行持续的迭代,所以我也在不断尝试更新。因为我目前还是没有真正研究学习过git代码管理的,所以现在只是自发的下载源码,重复操作移植。最近的新特性比如1.3版本开始官方增加了状态栏在待机状态下显示时钟以及官方的RGB屏幕的支持,1.4版本增加了一些音效。截至当前,最新的版本还是1.4版本。

image.png

那么为了增加代入感,我们直接从零开始,看看都做了什么工作。

首先第一步我们二话不说,要搞到源码。这边我是配置好了git环境,所以在想要存放代码的文件夹我们直接clone就可以了哈:

git clone https://github.com/78/xiaozhi-esp32.git

image.png

如果你的电脑没有git环境,可以选择直接去github上面去点击下载zip压缩包然后解压出来就可以了。

那么这边我们就可以打开VS Code来打开项目了。这边我就不去介绍VS Code怎么配置ESP-IDF扩展相关的内容了,毕竟我也是百度搜到的。我这边安装的是IDF V5.3.2的环境。

如果顺利的话,打开文件夹之后,首先简单的说一下我对工程内容框架的个人总结

image.png

接下来,我是根据我的经验走的,没有看过什么教程之类的,比较随心所欲,大佬勿喷。

这边我们首先看一下状态栏,也就是界面对底下一行,这边展示了一个COM口和一个芯片型号(鼠标悬停可以看到解释)。

我们需要接入板卡,然后获取到是在哪个COM口,这边就可以直接切换上了。

然后芯片我们是esp32-s3的,因此也改一下。

这两个配置好之后就其实如果是原版小智就已经配置完了。这个接下来会介绍。

我们主要的任务无非就是:将软件适配到现有的硬件上去。那么有的朋友就会问了,要修改什么呢?带着这样的疑问,我们打开menuconfig,直接点击小齿轮。首次开启会加载一些配置,有点慢,没关系,视频我会剪掉。

image.png

这里就可视化的展示了全部的工程配置,需要什么功能就使能,不需要什么就禁用,包括一些参数的配置都是在这边进行。

我们先点击Xiaozhi Assistant进入小智相关的配置。这个不是ESP-IDF自身的内容,这个是小智开发者增加的友好的配置界面,动手点一点即可完成一些基本的配置。这边我们就发现原来有一个Board Type,可以选择用的什么板子啊,默认是面包板连线版的小智,那么这边也可以给大家分享一下:这些板子其实都是基于esp32/esp32s3/esp32c3的开发板,你会发现有的是音频编解码方案,有的是iis麦克风与扬声器;有的是oled点阵屏,有的是lcd彩屏,有的还支持触摸屏;有的是可充电的;有的是板子,有的是带外壳的优雅小产品。。对于软件的角度来说,最主要的区别就是这些驱动的引脚不同,用到的外设不同。回到我们手上的CrowPanel板卡,就是一个确定的方案,所以我们需要结合手上的硬件方案去改软件即可。这里我们将其改为鱼鹰科技3.13LCD开发板,原因是这个板卡是目前唯一个同样使用RGB屏幕的板子。在这个基础上改会比较合适。

image.png

另外取消勾选这个“启用音频降噪、增益处理”,应该是可以一定程度减小固件的。

 随后我们继续修改模组相关的配置,因为我们的模组参数是n4r2,表示是用的4MFlash2MPSRAM。这边需要配置一下flash参数、分区表和PSRAM参数:

image.png

image.png

其实到这一步配置就完成了。但是如果就这样回去修改驱动相关的代码结果会发现,flash空间不足。。大家感兴趣的可以自己试。这边我们直接打开准备的资料,ESP32官方的固件大小优化指导进行配置:最小化二进制文件大小- ESP32-S3 - — ESP-IDF编程指南v5.2.4文档

文档前面的部分详细的解释了如何分析固件占用情况,感兴趣的可以看下,但是我不看哈,直接跳转到最后面学习怎么减小固件大小的操作

image.png

根据流程将能配置的配置完,然后自发尝试性的调整一些参数之后,配置部分就完成了。其中需要注意的是我们保留第三个。

接下来我们进行具体代码的修改。首先是回顾刚才改到的禁用的C++异常部分,我们考虑这个修改会对工程造成什么影响。那么主要是会导致C++catchtrythrow这些用法报错。我们直接全局搜索,发现只在thing相关代码影响到。那么没有办法,C++我不懂,C语音我也是个半吊子,所以二话不说,我们将情况给C老师解释一下,让他帮我们在不影响原有功能的情况下移除不支持的写法。C老师也是很给力,直接帮我们写好了,那么我们怎么做,我们应该保持怀疑的态度,拿着C老师的代码去咨询一下D老师点评一下。在得到D老师的肯定之后,我们直接替换掉。

 接下来开始处理具体的适配从哪里开始都行,因为代码是模块化的。

我们可以先处理音频部分。已知我们没有使用专用的音频编解码芯片,而是iis功放;另外IIS功放所接的引脚需要修改,另外IIS是右声道的,需要修改。对于main\audio_codecs\no_audio_codec.cc文件,修改了声道;对于main\boards\kevin-yuying-313lcd\config.h,修改IIS引脚IO,去除未用到的定义。此外还需要在板卡定义的源文件中修改GetAudioCodec虚函数的

接下来处理按钮输入和LED输出部分,先在main\boards\kevin-yuying-313lcd\config.h文件配置按钮和LED的引脚。然后需要调整一下代码。按钮设置为长按用于配网,单击用于激活对话;指示灯设置数量为5个,需要包含circular_strip.h,此为灯串的驱动部分。

1、 按键相关代码

    void InitializeButtons() {

        boot_button_.OnLongPress([this]() {

            auto& app = Application::GetInstance();

            if (app.GetDeviceState() == kDeviceStateStarting && !WifiStation::GetInstance().IsConnected()) {

                ResetWifiConfiguration();

            }

        });

        boot_button_.OnClick([this]() {

            Application::GetInstance().ToggleChatState();

        });

    }
 

2、 LED相关代码新增

    virtual Led* GetLed() override {

        static CircularStrip led(BUILTIN_LED_GPIO, 5);

        return &led;

    }

 

接下来是大头,显示部分。我们一样先处理引脚定义。这边观察一下官方这个rgb屏幕驱动代码,这是一个很标准的常见的rgb屏幕,一般会通过一个三线的SPI进行初始化配置,然后再通过RGB口进行显示通讯。所以代码中有SPI引脚的配置,SPI初始化等。但是我们手上的板卡会发现没有spi初始化部分,所以可以删除掉相关的全部代码。然后配置引脚定义,配置时序参数(参考规格书),处理多余的面板配置部分,直接调用新建函数来初始化屏幕。这里需要关注一些参数的选定,这些在规格书也似乎没有,我是通过官方例程来反向推断出来的。需要注意的是我们的屏幕分辨率不是很高,这边我们直接将lvgl声明的字体字号要改为16

LV_FONT_DECLARE(font_puhui_16_4);
LV_FONT_DECLARE(font_awesome_16_4);

其他部分:

1RGB面板结构

#define GC9503V_LCD_RGB_BUFFER_NUMS             (2)

#define GC9503V_LCD_RGB_BOUNCE_BUFFER_HEIGHT    (8)

 

        ESP_LOGI(TAG, "Install RGB LCD panel driver");

        esp_lcd_panel_handle_t panel_handle = NULL;

        esp_lcd_rgb_panel_config_t rgb_config = {

            .clk_src = LCD_CLK_SRC_PLL160M,

            .timings = GC9503_376_960_PANEL_60HZ_RGB_TIMING(),

            .data_width = 16, // RGB565 in parallel mode, thus 16bit in width

            .bits_per_pixel = 16,

            .num_fbs = GC9503V_LCD_RGB_BUFFER_NUMS,

            .bounce_buffer_size_px = GC9503V_LCD_H_RES * GC9503V_LCD_RGB_BOUNCE_BUFFER_HEIGHT,

            .dma_burst_size = 64,

            .hsync_gpio_num = GC9503V_PIN_NUM_HSYNC,

            .vsync_gpio_num = GC9503V_PIN_NUM_VSYNC,

            .de_gpio_num = GC9503V_PIN_NUM_DE,

            .pclk_gpio_num = GC9503V_PIN_NUM_PCLK,

            .disp_gpio_num = GC9503V_PIN_NUM_DISP_EN,

            .data_gpio_nums = {

                GC9503V_PIN_NUM_DATA0,

                GC9503V_PIN_NUM_DATA1,

                GC9503V_PIN_NUM_DATA2,

                GC9503V_PIN_NUM_DATA3,

                GC9503V_PIN_NUM_DATA4,

                GC9503V_PIN_NUM_DATA5,

                GC9503V_PIN_NUM_DATA6,

                GC9503V_PIN_NUM_DATA7,

                GC9503V_PIN_NUM_DATA8,

                GC9503V_PIN_NUM_DATA9,

                GC9503V_PIN_NUM_DATA10,

                GC9503V_PIN_NUM_DATA11,

                GC9503V_PIN_NUM_DATA12,

                GC9503V_PIN_NUM_DATA13,

                GC9503V_PIN_NUM_DATA14,

                GC9503V_PIN_NUM_DATA15,

            },

            .flags= {

                .fb_in_psram = true, // allocate frame buffer in PSRAM

            }

        };

   

        ESP_LOGI(TAG, "Initialize RGB LCD panel");

   

        (esp_lcd_new_rgb_panel(&rgb_config, &panel_handle));

        (esp_lcd_panel_reset(panel_handle));

        (esp_lcd_panel_init(panel_handle));

 

        display_ = new RgbLcdDisplay(panel_io, panel_handle,

                                  DISPLAY_WIDTH, DISPLAY_HEIGHT, DISPLAY_OFFSET_X, DISPLAY_OFFSET_Y, DISPLAY_MIRROR_X,

                                  DISPLAY_MIRROR_Y, DISPLAY_SWAP_XY,

                                  {

                                      .text_font = &font_puhui_16_4,

                                      .icon_font = &font_awesome_16_4,

                                      .emoji_font = font_emoji_64_init(),

                                  });

    }

2RGB时序相关配置

#define GC9503_376_960_PANEL_60HZ_RGB_TIMING()      \

    {                                               \

        .pclk_hz = GC9503V_LCD_PIXEL_CLOCK_HZ,                \

        .h_res = 480,                               \

        .v_res = 272,                               \

        .hsync_pulse_width = 4,                     \

        .hsync_back_porch = 43,                     \

        .hsync_front_porch = 8,                    \

        .vsync_pulse_width = 4,                     \

        .vsync_back_porch = 12,                     \

        .vsync_front_porch = 8,                    \

        .flags = {                                  \

            .hsync_idle_low = 0,                    \

            .vsync_idle_low = 0,                    \

            .de_idle_high = 0,                      \

            .pclk_active_neg = 1,                   \

            .pclk_idle_high = 0,                    \

        },                                          \

    }

 

3、 lvgl移植相关的结构体

    ESP_LOGI(TAG, "Adding LCD screen");

    const lvgl_port_display_cfg_t display_cfg = {

//        .io_handle = panel_io_,

        .panel_handle = panel_,

        .buffer_size = static_cast<uint32_t>(width_ * 10),

        .double_buffer = true,

        .hres = static_cast<uint32_t>(width_),

        .vres = static_cast<uint32_t>(height_),

        .rotation = {

            .swap_xy = swap_xy,

            .mirror_x = mirror_x,

            .mirror_y = mirror_y,

        },

        .flags = {

            .buff_dma = 1,

            .buff_spiram = 0,

            .sw_rotate = 0,

            .swap_bytes = 0,

            .full_refresh = 0,

            .direct_mode = 0,

        },

    };

 

    const lvgl_port_display_rgb_cfg_t rgb_cfg = {

        .flags = {

            .bb_mode = false,

            .avoid_tearing = false,

        }

    };

   

    display_ = lvgl_port_add_disp_rgb(&display_cfg, &rgb_cfg);

处理完这些,我们回头处理前面的分区表文件。

image.png 

不出意外的话,此时再进行编译是很顺利的,那么连上屏幕下载代码就可以看到小智已经成功运行起来了。

 

备注:优化项目有很多

1、 未激活状态下的提示词增加主动换行

image.png

2、 个人比较喜欢暗黑系的主题,这个可以在SDK配置中直接使能

image.png


成果展示

1、扩展板打板以及焊接成品

 image.png

 image.png

2、指示灯效果,不同颜色表示不同状态;闪烁表示配网,流水灯表示连接等

image.png

image.png

3、小智显示效果

image.png

image.png


附件下载
xiaozhi-esp32.zip
小智移植版源码(基于IDF5.3.2)
团队介绍
业余电子 DIY爱好者
评论
0 / 100
查看更多
硬禾服务号
关注最新动态
0512-67862536
info@eetree.cn
江苏省苏州市苏州工业园区新平街388号腾飞创新园A2幢815室
苏州硬禾信息科技有限公司
Copyright © 2024 苏州硬禾信息科技有限公司 All Rights Reserved 苏ICP备19040198号