2023寒假一起练 - 基于 ESP32-S2 模组和扩展板上的外设实现菜单功能
一、项目描述
ESP32-S2 是一款高度集成、高性价比、低功耗、主打安全的单核 Wi-Fi SoC,具备强大的功能和丰富的 IO 接口。本次活动,硬禾提供了 ESP32-S2 WiFi 模块和配套 IO 扩展板,其中 IO 扩展板上包含了下列外设:
- 按键和旋转编码器 (模拟信号)
- 双电位计控制 (数字信号)
- RGB 三色 LED
- 1.44 寸 128*128 LCD (SPI 接口,st7735 芯片)
- MMA7660 三轴姿态传感器
- 电阻加热
- NST112 温度传感器
芯片使用乐鑫 ESP-IDF 开发环境,我们可以通过USB对其编程,作为带wifi的MCU单独使用,也可以烧录AT固件,作为WiFi透传模块与RP2040游戏机套件结合使用。
在本项目中,我使用了下列硬件:
- ESP32-S2 模组
- 按键和旋转编码器 (模拟信号)
- RGB 三色 LED
- 1.44 寸 128*128 LCD (SPI 接口,st7735 芯片)
二、设计思路
首先使用 ESP32-S2 的 SPI 实现控制使用 st7735 芯片的 LCD,在屏幕的主界面展示主菜单,主菜单包含五个条目,模块上电后主菜单默认选中第一个条目,此时条目的背景色为黑色,条目的字体为白色。通过旋转编码器可以实现条目切换,向左旋转则条目向下切换,向右旋转则条目向上切换。此时单击旋转按钮,进入当前条目并展示该条目下的内容,五个条目的内容如下:
- 条目一 - Blue LED,进入该条目,在 LCD 上会周期性地展示蓝色圆圈,同时蓝色 LED 也同步闪烁;
- 条目二 - Green LED,进入该条目,在 LCD 上会周期性地展示绿色正方形,同时绿色 LED 也同步闪烁;
- 条目三 - RED LED,进入该条目,在 LCD 上会周期性地展示红色圆圈,同时红色 LED 也同步闪烁;
- 条目四 - Color Test,进入该条目,在 LCD 上会周期性地全屏展示红绿蓝三条竖行色块;
- 条目五 - Triangle Test,进入该条目,在 LCD 上会显示一个白色的三角形,此时旋转编码器,三角形可以同步地按照顺时针和逆时针方向转动,如果单击旋转编码器,则三角形会连续转动360度之后静止。
在子菜单中单击 S1 按键,会退出当前子界面。
三、硬件连接
- 旋转编码器和按键共用一个模拟输出 Aout,连接到 EPS32-S2 的 GPIO1(ADC1_CH0);
-
LCD 与 ESP32-S2 的管脚连接如下:
128*128 LCD (ST7735) | ESP32-S2 |
LCD_SDA | GPIO21 |
LCD_SCL | GPIO41 |
LCD_CSn | GPIO13 |
LCD_DCx | GPIO17 |
LCD_RESn | GPIO18 |
- RGB LED 与 ESP32-S2 的管脚连接如下:
RGB LED | ESP32-S2 |
BLUE_LED (LED1) | GPIO44 |
GREEN_LED (LED2) | GPIO43 |
RED_LED (LED3) | GPIO12 |
四、软件流程
五、开发环境的搭建
乐鑫为 ESP-IDF 开发环境分别提供了Windows、Linux和macOS平台的安装程序,在本次开发中我使用的是 Windows 开发环境,从https://dl.espressif.cn/dl/esp-idf/?idf=4.4 下载安装了 Espressif-IDE 2.8.1 with ESP-IDF v5.0 离线安装包。
具体的安装过程见:https://docs.espressif.com/projects/esp-idf/zh_CN/latest/esp32s2/get-started/windows-setup.html#id3
工程配置方法如下:
- 创建新项目:
idf.py create-project esp32s2_lcd
- 进入 esp32s2_lcd 目录,设置 ESP32-S2 为目标芯片:
cd esp32s2_lcd idf.py set-target esp32s2
- 运行工程配置工具 menuconfig:
idf.py menuconfig
- 如需使用 USB 烧录 ESP32-S2,将控制台的输出通道改为 USB:
#前往选项 Channel for console output。 Component config ---> ESP System Settings ---> Channel for console output #将默认选项 UART 改为: USB CDC #保存设置,退出 menuconfig 界面。
- 编译工程:
idf.py build
- 运行以下命令,将刚刚生成的二进制文件烧录至您的 ESP32-S2 开发板:
idf.py -p PORT flash
- 如果遇到烧录的程序串口打印速度过快导致 idf.py 无法连接串口进行烧录的话,可以按住模块的 BOOT 按键,然后单击 RST 按钮,此时模块会进入 bootloader,可以重新调用上面的命令进行烧录。
六、代码和功能展示
- LCD st7735 驱动代码
- 代码如下(该款屏幕有蓝边,可以通过设置offset解决):
#define CONFIG_WIDTH 128 // Width - 128px #define CONFIG_HEIGHT 128 // Height - 128px // 设置offset可以消除屏幕蓝边 #define CONFIG_OFFSETX 2 // to fix display issue #define CONFIG_OFFSETY 3 // to fix display issue #define CONFIG_MOSI_GPIO 21 // LCD_SDA - GPIO21 #define CONFIG_SCLK_GPIO 41 // LCD_SCL - GPIO41 #define CONFIG_TFT_CS_GPIO 13 // LCD_CSn - GPIO13 #define CONFIG_DC_GPIO 17 // LCD_DCx - GPIO17 #define CONFIG_RESET_GPIO 18 // LCD_RESn - GPIO18 #define CONFIG_BL_GPIO 33 // LCD 此管脚无法控制,为了通过代码编译,使用一个没有使用的 GPIO33 void lcd_init(void) { uint16_t model = 0x7735; spi_master_init(&dev, CONFIG_MOSI_GPIO, CONFIG_SCLK_GPIO, CONFIG_TFT_CS_GPIO, CONFIG_DC_GPIO, CONFIG_RESET_GPIO, CONFIG_BL_GPIO); lcdInit(&dev, model, CONFIG_WIDTH, CONFIG_HEIGHT, CONFIG_OFFSETX, CONFIG_OFFSETY); } #define HOST_ID SPI2_HOST const static char *TAG = "ST7735"; static const int SPI_Command_Mode = 0; static const int SPI_Data_Mode = 1; static const int TFT_Frequency = SPI_MASTER_FREQ_40M; void spi_master_init(TFT_t *dev, int16_t GPIO_MOSI, int16_t GPIO_SCLK, int16_t TFT_CS, int16_t GPIO_DC, int16_t GPIO_RESET, int16_t GPIO_BL) { esp_err_t ret; ESP_LOGI(TAG, "TFT_CS=%d", TFT_CS); gpio_reset_pin(TFT_CS); gpio_set_direction(TFT_CS, GPIO_MODE_OUTPUT); gpio_set_level(TFT_CS, 1); ESP_LOGI(TAG, "GPIO_DC=%d", GPIO_DC); gpio_reset_pin(GPIO_DC); gpio_set_direction(GPIO_DC, GPIO_MODE_OUTPUT); gpio_set_level(GPIO_DC, 0); ESP_LOGI(TAG, "GPIO_RESET=%d", GPIO_RESET); if (GPIO_RESET >= 0) { gpio_reset_pin(GPIO_RESET); gpio_set_direction(GPIO_RESET, GPIO_MODE_OUTPUT); gpio_set_level(GPIO_RESET, 0); vTaskDelay(pdMS_TO_TICKS(100)); gpio_set_level(GPIO_RESET, 1); } ESP_LOGI(TAG, "GPIO_BL=%d", GPIO_BL); if (GPIO_BL >= 0) { gpio_reset_pin(GPIO_BL); gpio_set_direction(GPIO_BL, GPIO_MODE_OUTPUT); gpio_set_level(GPIO_BL, 0); } spi_bus_config_t buscfg = { .sclk_io_num = GPIO_SCLK, .mosi_io_num = GPIO_MOSI, .miso_io_num = -1, .quadwp_io_num = -1, .quadhd_io_num = -1}; ret = spi_bus_initialize(HOST_ID, &buscfg, SPI_DMA_CH_AUTO); ESP_LOGD(TAG, "spi_bus_initialize=%d", ret); assert(ret == ESP_OK); spi_device_interface_config_t tft_devcfg = { .clock_speed_hz = TFT_Frequency, .spics_io_num = TFT_CS, .queue_size = 7, .flags = SPI_DEVICE_NO_DUMMY, }; spi_device_handle_t tft_handle; ret = spi_bus_add_device(HOST_ID, &tft_devcfg, &tft_handle); ESP_LOGD(TAG, "spi_bus_add_device=%d", ret); assert(ret == ESP_OK); dev->_dc = GPIO_DC; dev->_bl = GPIO_BL; dev->_TFT_Handle = tft_handle; } void lcdInit(TFT_t *dev, uint16_t model, int width, int height, int offsetx, int offsety) { dev->_model = model; dev->_width = width; dev->_height = height; dev->_offsetx = offsetx; dev->_offsety = offsety; dev->_font_direction = DIRECTION0; dev->_font_fill = false; dev->_font_underline = false; ESP_LOGI(TAG, "Your TFT is ST7735"); ESP_LOGI(TAG, "Screen width:%d", width); ESP_LOGI(TAG, "Screen height:%d", height); spi_master_write_comm_byte(dev, 0xC0); // Power Control 1 spi_master_write_data_byte(dev, 0x23); spi_master_write_comm_byte(dev, 0xC1); // Power Control 2 spi_master_write_data_byte(dev, 0x10); spi_master_write_comm_byte(dev, 0xC5); // VCOM Control 1 spi_master_write_data_byte(dev, 0x3E); spi_master_write_data_byte(dev, 0x28); spi_master_write_comm_byte(dev, 0xC7); // VCOM Control 2 spi_master_write_data_byte(dev, 0x86); spi_master_write_comm_byte(dev, 0x36); // Memory Access Control spi_master_write_data_byte(dev, 0xC8); // Right top start, BGR color filter panel C8/68/A8/08 spi_master_write_comm_byte(dev, 0x3A); // Pixel Format Set spi_master_write_data_byte(dev, 0x55); // 65K color: 16-bit/pixel spi_master_write_comm_byte(dev, 0x20); // Display Inversion OFF spi_master_write_comm_byte(dev, 0xB1); // Frame Rate Control spi_master_write_data_byte(dev, 0x00); spi_master_write_data_byte(dev, 0x18); spi_master_write_comm_byte(dev, 0xB6); // Display Function Control spi_master_write_data_byte(dev, 0x08); spi_master_write_data_byte(dev, 0xA2); // REV:1 GS:0 SS:0 SM:0 spi_master_write_data_byte(dev, 0x27); spi_master_write_data_byte(dev, 0x00); spi_master_write_comm_byte(dev, 0x26); // Gamma Set spi_master_write_data_byte(dev, 0x01); spi_master_write_comm_byte(dev, 0xE0); // Positive Gamma Correction spi_master_write_data_byte(dev, 0x0F); spi_master_write_data_byte(dev, 0x31); spi_master_write_data_byte(dev, 0x2B); spi_master_write_data_byte(dev, 0x0C); spi_master_write_data_byte(dev, 0x0E); spi_master_write_data_byte(dev, 0x08); spi_master_write_data_byte(dev, 0x4E); spi_master_write_data_byte(dev, 0xF1); spi_master_write_data_byte(dev, 0x37); spi_master_write_data_byte(dev, 0x07); spi_master_write_data_byte(dev, 0x10); spi_master_write_data_byte(dev, 0x03); spi_master_write_data_byte(dev, 0x0E); spi_master_write_data_byte(dev, 0x09); spi_master_write_data_byte(dev, 0x00); spi_master_write_comm_byte(dev, 0xE1); // Negative Gamma Correction spi_master_write_data_byte(dev, 0x00); spi_master_write_data_byte(dev, 0x0E); spi_master_write_data_byte(dev, 0x14); spi_master_write_data_byte(dev, 0x03); spi_master_write_data_byte(dev, 0x11); spi_master_write_data_byte(dev, 0x07); spi_master_write_data_byte(dev, 0x31); spi_master_write_data_byte(dev, 0xC1); spi_master_write_data_byte(dev, 0x48); spi_master_write_data_byte(dev, 0x08); spi_master_write_data_byte(dev, 0x0F); spi_master_write_data_byte(dev, 0x0C); spi_master_write_data_byte(dev, 0x31); spi_master_write_data_byte(dev, 0x36); spi_master_write_data_byte(dev, 0x0F); spi_master_write_comm_byte(dev, 0x11); // Sleep Out delayMS(120); spi_master_write_comm_byte(dev, 0x29); // Display ON if (dev->_bl >= 0) { gpio_set_level(dev->_bl, 1); } }
- 代码如下(该款屏幕有蓝边,可以通过设置offset解决):
- ADC 采样和按键消抖代码
- 代码如下:
#define A_OUT ADC_CHANNEL_0 // A_OUT - GPIO1 - ADC1_CHANNEL_0 #define ADC_DATA_LEN 20 void adc_init(void) { //-------------ADC1 Init---------------// adc_oneshot_unit_init_cfg_t init_config1 = { .unit_id = ADC_UNIT_1, }; ESP_ERROR_CHECK(adc_oneshot_new_unit(&init_config1, &adc1_handle)); //-------------ADC1 Config---------------// adc_oneshot_chan_cfg_t config = { .bitwidth = ADC_BITWIDTH_DEFAULT, .atten = ADC_ATTEN_DB_11, }; ESP_ERROR_CHECK(adc_oneshot_config_channel(adc1_handle, A_OUT, &config)); //-------------ADC1 Calibration Init---------------// do_calibration1 = example_adc_calibration_init(ADC_UNIT_1, ADC_ATTEN_DB_11, &adc1_cali_handle); } KEY_STATUS get_key_status(int *adc_value) { KEY_STATUS key_status; uint16_t tmp; for (int32_t i = 0; i < ADC_DATA_LEN; i++) { ESP_ERROR_CHECK(adc_oneshot_read(adc1_handle, A_OUT, &adc_raw[i])); // ESP_LOGI(TAG, "ADC%d Channel[%d] Raw Data: %d", ADC_UNIT_1 + 1, A_OUT, adc_raw[i]); if (do_calibration1) { ESP_ERROR_CHECK(adc_cali_raw_to_voltage(adc1_cali_handle, adc_raw[i], &voltage[i])); // ESP_LOGI(TAG, "ADC%d Channel[%d] Cali Voltage: %d mV", ADC_UNIT_1 + 1, A_OUT, voltage[i]); } } for (int32_t i = 0; i <= ADC_DATA_LEN / 2; i++) { for (int32_t j = 0; j < ADC_DATA_LEN - i - 1; j++) { if (adc_raw[j + 1] < adc_raw[j]) { tmp = adc_raw[j + 1]; adc_raw[j + 1] = adc_raw[j]; adc_raw[j] = tmp; } } } if (ADC_DATA_LEN % 2 == 0) { *adc_value = (((adc_raw[ADC_DATA_LEN / 2 - 1] + adc_raw[ADC_DATA_LEN / 2]) / 2)); } else { *adc_value = (adc_raw[ADC_DATA_LEN / 2]); } if (*adc_value > 2000 && *adc_value < 2400) { key_status = KEY_1_PRESSED; } else if (*adc_value > 5800 && *adc_value < 6200) { key_status = KEY_C_PRESSED; } else if (*adc_value > 6600 && *adc_value < 6890) { key_status = KEY_C_LEFT; } else if (*adc_value > 6960 && *adc_value < 7150) { key_status = KEY_C_RIGHT; } else { key_status = KEY_UNKNOWN; } if (key_status != KEY_UNKNOWN) { if ((xTaskGetTickCount() - startTick) > pdMS_TO_TICKS(300)) { startTick = xTaskGetTickCount(); return key_status; } } return KEY_UNKNOWN; }
- 代码如下:
七、功能展示
- 主菜单
- 三角形测试子界面
- 三色块测试子界面
- 蓝色LED测试子界面
八、未来计划
未来计划能够把IO扩展板上的测温芯片和电位计给利用起来。