得捷电子 Follow me 第2期任务提交汇总 全程esp-idf实现方案
[复制链接]
视频:https://training.eeworld.com.cn/video/38569
大家好,这里是日历,我参与了EEWORLD的“跟着我一起做”活动,采用了espidf方案进行全程开发。在这个项目中,我选择使用了Adafruit ESP32-S3 TFT Feather板,它搭载了强大的ESP32-S3控制器,并提供丰富的外设接口,包括液晶屏、WS2812B RGB灯珠以及i2c接口。
活动内容涉及多个领域,主要包括屏幕显示、网络通信、音乐播放、数据检测与记录等。通过这次活动,我深入学习了espidf的开发方式,同时掌握了在Adafruit ESP32-S3 TFT Feather板上使用不同外设的方法。
总算是板子9底月到货了然后我成功的拖更了一整个月(doge),这块Adafruit ESP32-S3 TFT Feather的常规写法各位的方案我看大多是CircuitPython 或者少数arduino的,但是笔者习惯了使用esp-idf方案写esp32的板子的程序的原因,这块板子也稍微扒拉了下引脚配置然后用espidf+lvgl方案完成这块板子的程序
首先说回来最麻烦的屏幕配置部分
MOSI 35
CLK 36
CS 7
DC 39
RESET 40
BLK 45
lvgl的移植部分各位可以直接使用esp官方的组件库的lvgl移植内容,但是在lvgl_esp32_drivers这个依赖的st7789.c的内容我根据arduino那边的对这块屏幕的配置更改了下具体如下
/**
* [url=home.php?mod=space&uid=1307177]@File[/url] st7789.c
*
* Mostly taken from lbthomsen/esp-idf-littlevgl github.
*/
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "sdkconfig.h"
#include "esp_log.h"
#include "st7789.h"
#include "disp_spi.h"
#include "driver/gpio.h"
/*********************
* DEFINES
*********************/
#define TAG "st7789"
/**********************
* TYPEDEFS
**********************/
/*The LCD needs a bunch of command/argument values to be initialized. They are stored in this struct. */
typedef struct {
uint8_t cmd;
uint8_t data[16];
uint8_t databytes; //No of data in data; bit 7 = delay after set; 0xFF = end of cmds.
} lcd_init_cmd_t;
/**********************
* STATIC PROTOTYPES
**********************/
static void st7789_set_orientation(uint8_t orientation);
static void st7789_send_color(void *data, size_t length);
/**********************
* STATIC VARIABLES
**********************/
/**********************
* MACROS
**********************/
/**********************
* GLOBAL FUNCTIONS
**********************/
void st7789_init(void)
{
lcd_init_cmd_t st7789_init_cmds[] = {
{ST7789_SWRESET, {0}, 0x80}, // Software reset
{ST7789_SLPOUT, {0}, 0x80}, // Out of sleep mode
{ST7789_COLMOD, {0x55}, 1}, // Set color mode
{ST7789_MADCTL, {0x00}, 1}, // Memory access control
{ST7789_CASET, {0x00, 0x00, 0x00, 0xEF}, 4}, // Column address set
{ST7789_RASET, {0x00, 0x00, 0x01, 0x3F}, 4}, // Row address set
{ST7789_INVON, {0}, 0}, // Hack - possibly inverted mode
{ST7789_NORON, {0}, 0x80}, // Normal display on
{ST7789_DISPON, {0}, 0x80}, // Main screen turn on
{0, {0}, 0xFF} // End marker
};
//Initialize non-SPI GPIOs
gpio_reset_pin(ST7789_DC);//esp-idf v5.0和v4.3有差异
gpio_set_direction(ST7789_DC, GPIO_MODE_OUTPUT);
#if !defined(ST7789_SOFT_RST)
esp_rom_gpio_pad_select_gpio(ST7789_RST);
gpio_set_direction(ST7789_RST, GPIO_MODE_OUTPUT);
#endif
//Reset the display
#if !defined(ST7789_SOFT_RST)
gpio_set_level(ST7789_RST, 0);
vTaskDelay(100 / portTICK_PERIOD_MS);
gpio_set_level(ST7789_RST, 1);
vTaskDelay(100 / portTICK_PERIOD_MS);
#else
st7789_send_cmd(ST7789_SWRESET);
#endif
printf("ST7789 initialization.\n");
//Send all the commands
uint16_t cmd = 0;
while (st7789_init_cmds[cmd].databytes!=0xff) {
st7789_send_cmd(st7789_init_cmds[cmd].cmd);
st7789_send_data(st7789_init_cmds[cmd].data, st7789_init_cmds[cmd].databytes&0x1F);
if (st7789_init_cmds[cmd].databytes & 0x80) {
vTaskDelay(100 / portTICK_PERIOD_MS);
}
cmd++;
}
st7789_set_orientation(CONFIG_LV_DISPLAY_ORIENTATION);
}
/* The ST7789 display controller can drive up to 320*240 displays, when using a 240*240 or 240*135
* displays there's a gap of 80px or 40/52/53px respectively. 52px or 53x offset depends on display orientation.
* We need to edit the coordinates to take into account those gaps, this is not necessary in all orientations. */
void st7789_flush(lv_disp_drv_t * drv, const lv_area_t * area, lv_color_t * color_map)
{
uint8_t data[4] = {0};
uint16_t offsetx1 = area->x1;
uint16_t offsetx2 = area->x2;
uint16_t offsety1 = area->y1;
uint16_t offsety2 = area->y2;
offsetx1 += 40;
offsetx2 += 40;
offsety1 += 53;
offsety2 += 53;
/*Column addresses*/
st7789_send_cmd(ST7789_CASET);
data[0] = (offsetx1 >> 8) & 0xFF;
data[1] = offsetx1 & 0xFF;
data[2] = (offsetx2 >> 8) & 0xFF;
data[3] = offsetx2 & 0xFF;
st7789_send_data(data, 4);
/*Page addresses*/
st7789_send_cmd(ST7789_RASET);
data[0] = (offsety1 >> 8) & 0xFF;
data[1] = offsety1 & 0xFF;
data[2] = (offsety2 >> 8) & 0xFF;
data[3] = offsety2 & 0xFF;
st7789_send_data(data, 4);
/*Memory write*/
st7789_send_cmd(ST7789_RAMWR);
size_t size = (size_t)lv_area_get_width(area) * (size_t)lv_area_get_height(area);
st7789_send_color((void*)color_map, size * 2);
}
/**********************
* STATIC FUNCTIONS
**********************/
void st7789_send_cmd(uint8_t cmd)
{
disp_wait_for_pending_transactions();
gpio_set_level(ST7789_DC, 0);
disp_spi_send_data(&cmd, 1);
}
void st7789_send_data(void * data, uint16_t length)
{
disp_wait_for_pending_transactions();
gpio_set_level(ST7789_DC, 1);
disp_spi_send_data(data, length);
}
static void st7789_send_color(void * data, size_t length)
{
disp_wait_for_pending_transactions();
gpio_set_level(ST7789_DC, 1);
disp_spi_send_colors(data, length);
}
#define ST77XX_MADCTL_MY 0x80
#define ST77XX_MADCTL_MX 0x40
#define ST77XX_MADCTL_MV 0x20
#define ST77XX_MADCTL_ML 0x10
#define ST77XX_MADCTL_RGB 0x00
static void st7789_set_orientation(uint8_t orientation)
{
uint8_t madctl = 0;
ESP_LOGI(TAG, "0x36 command value: 0x%02X", madctl);
madctl = ST77XX_MADCTL_MX | ST77XX_MADCTL_MV | ST77XX_MADCTL_RGB;
st7789_send_cmd(ST7789_MADCTL);
st7789_send_data((void *)&madctl, 1);
}
特别注意的是屏幕的分辨率水平240,垂直135已经配置过,如果颜色出现了问题,请查看下lvgl的有一个CONFIG_LV_COLOR_16_SWAP的配置
在配置完屏幕驱动项后理应就能正常内容,这个时候就可以开始完成
任务一、控制屏幕显示中文(必做任务)
首先使用lvgl构建页面内容,笔者是使用了gui guider构建配置,相关的移植gui guider方法请各位自行查找或者看下我的程序
特别提醒中文显示的话需要去lvgl字体转换网站Online font converter - TTF or WOFF fonts to C array | LVGL
把你需要转的中文字体导入并且选定要转换的文本,最终结果输出为一个 .c文件保存
此次笔者使用了一个网上找的字体和guiguider默认案例音乐播放器显示完成的第一个任务
rt
任务二、网络功能使用(必做任务)
这个任务二其实就很简单
ap是构建热点,sta是作为子设备
那么直接照抄官方的程序就可以了
这个地方po出笔者的程序
sta模式:
#include <string.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/event_groups.h"
#include "esp_system.h"
#include "esp_wifi.h"
#include "esp_event.h"
#include "esp_log.h"
#include "nvs_flash.h"
#include "lwip/err.h"
#include "lwip/sys.h"
#include "main.h"
static const char *TAG = "wifi station";
#define EXAMPLE_ESP_WIFI_SSID "TP-LINK_CC60"
#define EXAMPLE_ESP_WIFI_PASS "stc15f104w"
#define EXAMPLE_ESP_MAXIMUM_RETRY 5
#define ESP_WIFI_SCAN_AUTH_MODE_THRESHOLD WIFI_AUTH_WPA_WPA2_PSK
/* FreeRTOS event group to signal when we are connected*/
static EventGroupHandle_t s_wifi_event_group;
/* The event group allows multiple bits for each event, but we only care about two events:
* - we are connected to the AP with an IP
* - we failed to connect after the maximum amount of retries */
#define WIFI_CONNECTED_BIT BIT0
#define WIFI_FAIL_BIT BIT1
static int s_retry_num = 0;
static void event_handler(void* arg, esp_event_base_t event_base,
int32_t event_id, void* event_data)
{
if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_START) {
esp_wifi_connect();
} else if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_DISCONNECTED) {
if (s_retry_num < EXAMPLE_ESP_MAXIMUM_RETRY) {
esp_wifi_connect();
s_retry_num++;
ESP_LOGI(TAG, "retry to connect to the AP");
} else {
xEventGroupSetBits(s_wifi_event_group, WIFI_FAIL_BIT);
}
ESP_LOGI(TAG,"connect to the AP fail");
} else if (event_base == IP_EVENT && event_id == IP_EVENT_STA_GOT_IP) {
ip_event_got_ip_t* event = (ip_event_got_ip_t*) event_data;
ESP_LOGI(TAG, "got ip:" IPSTR, IP2STR(&event->ip_info.ip));
s_retry_num = 0;
xEventGroupSetBits(s_wifi_event_group, WIFI_CONNECTED_BIT);
}
}
void wifi_init_sta(void)
{
esp_err_t ret = nvs_flash_init();
if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND) {
ESP_ERROR_CHECK(nvs_flash_erase());
ret = nvs_flash_init();
}
ESP_ERROR_CHECK(ret);
ESP_LOGI(TAG, "ESP_WIFI_MODE_STA");
s_wifi_event_group = xEventGroupCreate();
ESP_ERROR_CHECK(esp_netif_init());
ESP_ERROR_CHECK(esp_event_loop_create_default());
esp_netif_create_default_wifi_sta();
wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
ESP_ERROR_CHECK(esp_wifi_init(&cfg));
esp_event_handler_instance_t instance_any_id;
esp_event_handler_instance_t instance_got_ip;
ESP_ERROR_CHECK(esp_event_handler_instance_register(WIFI_EVENT,
ESP_EVENT_ANY_ID,
&event_handler,
NULL,
&instance_any_id));
ESP_ERROR_CHECK(esp_event_handler_instance_register(IP_EVENT,
IP_EVENT_STA_GOT_IP,
&event_handler,
NULL,
&instance_got_ip));
wifi_config_t wifi_config = {
.sta = {
.ssid = EXAMPLE_ESP_WIFI_SSID,
.password = EXAMPLE_ESP_WIFI_PASS,
/* Authmode threshold resets to WPA2 as default if password matches WPA2 standards (pasword len => 8).
* If you want to connect the device to deprecated WEP/WPA networks, Please set the threshold value
* to WIFI_AUTH_WEP/WIFI_AUTH_WPA_PSK and set the password with length and format matching to
* WIFI_AUTH_WEP/WIFI_AUTH_WPA_PSK standards.
*/
.threshold.authmode = ESP_WIFI_SCAN_AUTH_MODE_THRESHOLD,
.sae_pwe_h2e = WPA3_SAE_PWE_BOTH,
},
};
ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA) );
ESP_ERROR_CHECK(esp_wifi_set_config(WIFI_IF_STA, &wifi_config) );
ESP_ERROR_CHECK(esp_wifi_start() );
ESP_LOGI(TAG, "wifi_init_sta finished.");
/* Waiting until either the connection is established (WIFI_CONNECTED_BIT) or connection failed for the maximum
* number of re-tries (WIFI_FAIL_BIT). The bits are set by event_handler() (see above) */
EventBits_t bits = xEventGroupWaitBits(s_wifi_event_group,
WIFI_CONNECTED_BIT | WIFI_FAIL_BIT,
pdFALSE,
pdFALSE,
portMAX_DELAY);
/* xEventGroupWaitBits() returns the bits before the call returned, hence we can test which event actually
* happened. */
if (bits & WIFI_CONNECTED_BIT) {
ESP_LOGI(TAG, "connected to ap SSID:%s password:%s",
EXAMPLE_ESP_WIFI_SSID, EXAMPLE_ESP_WIFI_PASS);
} else if (bits & WIFI_FAIL_BIT) {
ESP_LOGI(TAG, "Failed to connect to SSID:%s, password:%s",
EXAMPLE_ESP_WIFI_SSID, EXAMPLE_ESP_WIFI_PASS);
} else {
ESP_LOGE(TAG, "UNEXPECTED EVENT");
}
xTaskNotify(xtask_lvgl,1,eSetBits);
}
只需要在main里面调用void wifi_init_sta(void)就可以
ap:
/* WiFi softAP Example
This example code is in the Public Domain (or CC0 licensed, at your option.)
Unless required by applicable law or agreed to in writing, this
software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
CONDITIONS OF ANY KIND, either express or implied.
*/
#include <string.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_mac.h"
#include "esp_wifi.h"
#include "esp_event.h"
#include "esp_log.h"
#include "nvs_flash.h"
#include "lwip/err.h"
#include "lwip/sys.h"
/* The examples use WiFi configuration that you can set via project configuration menu.
If you'd rather not, just change the below entries to strings with
the config you want - ie #define EXAMPLE_WIFI_SSID "mywifissid"
*/
#define EXAMPLE_ESP_WIFI_SSID CONFIG_ESP_WIFI_SSID
#define EXAMPLE_ESP_WIFI_PASS CONFIG_ESP_WIFI_PASSWORD
#define EXAMPLE_ESP_WIFI_CHANNEL CONFIG_ESP_WIFI_CHANNEL
#define EXAMPLE_MAX_STA_CONN CONFIG_ESP_MAX_STA_CONN
static const char *TAG = "wifi softAP";
static void wifi_event_handler(void* arg, esp_event_base_t event_base,
int32_t event_id, void* event_data)
{
if (event_id == WIFI_EVENT_AP_STACONNECTED) {
wifi_event_ap_staconnected_t* event = (wifi_event_ap_staconnected_t*) event_data;
ESP_LOGI(TAG, "station "MACSTR" join, AID=%d",
MAC2STR(event->mac), event->aid);
} else if (event_id == WIFI_EVENT_AP_STADISCONNECTED) {
wifi_event_ap_stadisconnected_t* event = (wifi_event_ap_stadisconnected_t*) event_data;
ESP_LOGI(TAG, "station "MACSTR" leave, AID=%d",
MAC2STR(event->mac), event->aid);
}
}
void wifi_init_softap(void)
{
esp_err_t ret = nvs_flash_init();
if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND) {
ESP_ERROR_CHECK(nvs_flash_erase());
ret = nvs_flash_init();
}
ESP_ERROR_CHECK(ret);
ESP_ERROR_CHECK(esp_netif_init());
ESP_ERROR_CHECK(esp_event_loop_create_default());
esp_netif_create_default_wifi_ap();
wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
ESP_ERROR_CHECK(esp_wifi_init(&cfg));
ESP_ERROR_CHECK(esp_event_handler_instance_register(WIFI_EVENT,
ESP_EVENT_ANY_ID,
&wifi_event_handler,
NULL,
NULL));
wifi_config_t wifi_config = {
.ap = {
.ssid = EXAMPLE_ESP_WIFI_SSID,
.ssid_len = strlen(EXAMPLE_ESP_WIFI_SSID),
.channel = EXAMPLE_ESP_WIFI_CHANNEL,
.password = EXAMPLE_ESP_WIFI_PASS,
.max_connection = EXAMPLE_MAX_STA_CONN,
#ifdef CONFIG_ESP_WIFI_SOFTAP_SAE_SUPPORT
.authmode = WIFI_AUTH_WPA3_PSK,
.sae_pwe_h2e = WPA3_SAE_PWE_BOTH,
#else /* CONFIG_ESP_WIFI_SOFTAP_SAE_SUPPORT */
.authmode = WIFI_AUTH_WPA2_PSK,
#endif
.pmf_cfg = {
.required = true,
},
},
};
if (strlen(EXAMPLE_ESP_WIFI_PASS) == 0) {
wifi_config.ap.authmode = WIFI_AUTH_OPEN;
}
ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_AP));
ESP_ERROR_CHECK(esp_wifi_set_config(WIFI_IF_AP, &wifi_config));
ESP_ERROR_CHECK(esp_wifi_start());
ESP_LOGI(TAG, "wifi_init_softap finished. SSID:%s password:%s channel:%d",
EXAMPLE_ESP_WIFI_SSID, EXAMPLE_ESP_WIFI_PASS, EXAMPLE_ESP_WIFI_CHANNEL);
}
任务3:控制WS2812B(必做任务)
比较简单,说下笔者的方案
esp官方依赖封装按键绑定以色轮输出rgb值的任务
使用官方案例的rmt 驱动ws2812b
要注意提前打开ws2812b的电源控制引脚
然后色轮输出rgb值的任务向lvgl任务发送任务通知
通过位操作把rgb通道存到任务通知的uint_t32
lvgl任务显示一个lv_led组件显示实际灯的颜色
任务4 分任务2:WS2812B效果控制
这个地方偷个 懒改个灯珠数量和针脚所以如下
是不是太偷懒了()
再写个任务四吧()
分任务1:日历&时钟
注册个ntp客户端去请求ntp时间
这个地方使用的esp idf的sntp(sample ntp)案例
把重要的几行程序提一下
void ntp_gettime(){
char strftime_buf[64];
time_t now;
struct tm timeinfo;
time(&now);
localtime_r(&now, &timeinfo);
// Is time set? If not, tm_year will be (1970 - 1900).
if (timeinfo.tm_year < (2016 - 1900)) {
ESP_LOGI(TAG, "Time is not set yet. Connecting to WiFi and getting time over NTP.");
esp_sntp_config_t config = ESP_NETIF_SNTP_DEFAULT_CONFIG("pool.ntp.org");
esp_netif_sntp_init(&config);
esp_netif_sntp_start();
// update 'now' variable with current time
time(&now);
}
setenv("TZ", "CST-8", 1);
tzset();
while (1)
{
time(&now);
localtime_r(&now, &timeinfo);
strftime(strftime_buf, sizeof(strftime_buf), "%c", &timeinfo);
ESP_LOGI(TAG, "The current date/time in Shanghai is: %s", strftime_buf);
xQueueSend(ntp_time,(void*)strftime_buf, 0);
vTaskDelay(pdMS_TO_TICKS(1000));
}
}
把获取ntp时间封装位rtos任务,每1s请求一次时间
通过队列发送到lvgl任务
效果如图
任务5:通过网络控制WS2812B(可选任务,非必做)
写一个色盘网页从ws发送rgb到esp32s3上
esp32s3上websocket使用ws_echo_server案例改一下接受并解析rgb json文件
通过任务队列发送给ws2812的显示任务里
后记:
■ 分任务3:数据检测与记录——按一定时间间隔连续记录温度/亮度信息,保存到SD卡,并可以通过按键调用查看之前的信息,并在屏幕上绘图
建议搭配器件:Adafruit ESP32-S3 TFT Feather、光传感器、温度传感器、微型 SD卡模块
■ 分任务4:音乐播放功能——实现音乐播放器功能,可以在屏幕上显示列表信息和音乐信息,使用按键进行切换,使用扬声器进行播放
建议搭配器件:Adafruit ESP32-S3 TFT Feather、音频放大器、扬声器
■ 分任务5:AI功能应用——结合运动传感器,完成手势识别功能,至少要识别三种手势(如水平左右、前后、垂直上下、水平画圈、垂直画圈,或者更复杂手势
建议搭配器件:Adafruit ESP32-S3 TFT Feather、运动传感器
这几个任务我这现在要不就是缺能和这块板子适配的元件(i2s codec比如),要不就是这段时间稍微忙点等短时间我可能会做一下补一下
只管稍微说一下在espidf上跑这几个功能的思路:
sd卡使用sdio 1line/4line或者spisd卡都可,去案例找一下引脚配置就可
iic自行找自己使用的外设
音乐播放功能可以使用esp-adf的组件自行配置adc/codec,楼主测试过es8311和es7210都是适配没问题,使用pipline或者element方法都可以写一下这个功能的还算是比较简单的
AI功能应用的话mpu6050的数据读取不做另行说明,但是这地方使用ai相关配置我还真的不太了解,如果各位有了解请麻烦分享一下
代码包下载地址:https://download.eeworld.com.cn/detail/eew_MsNISU/629815
心得体会
这次活动让我更深入地了解了ESP32-S3的开发生态和丰富的外设支持。通过实际操作,我积累了更多的硬件开发经验,同时也感受到了espidf在实际项目中的灵活性和强大功能。希望未来的活动能够继续涵盖更多领域,激发更多创意和技术探讨。感谢EEWORLD和DigiKey提供这样一个学习与交流的平台!
|