基于 ESP32-S2 模组和扩展板上的外设实现菜单功能
使用 ESP32-S2 模组连接到扩展板上并读取扩展板上的按键和旋转编码器状态,根据按键和编码器的状态控制 LCD 上的菜单切换,支持进入子界面和退出子界面等功能。
标签
嵌入式系统
2023寒假在家练
topgear
更新2023-03-27
673

内容介绍

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

四、软件流程

FjfYawtFpxd4gaZ3vi4HL7iFmAIq

五、开发环境的搭建

乐鑫为 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,可以重新调用上面的命令进行烧录。

六、代码和功能展示

  1.  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);
      	}
      }
      
      
  2. 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;
      }
      

七、功能展示

  • 主菜单

Fsmvrk7nnuzjZXqzloxew95ievef

  • 三角形测试子界面

FvtBd2T9mwHyIYtP_IkinQtw7Ihe

  • 三色块测试子界面

Fkn0x47zUXiRIfhBqFzqOnZl1fNH

  • 蓝色LED测试子界面

FqbuI9Y-CRKDbRHTAiHUened2-Yy

八、未来计划

未来计划能够把IO扩展板上的测温芯片和电位计给利用起来。

附件下载

esp32s2_lcd.zip
ESP-IDF 工程源代码

团队介绍

在职工程师
团队成员
topgear

评论

0 / 100
查看更多
目录
硬禾服务号
关注最新动态
0512-67862536
info@eetree.cn
江苏省苏州市苏州工业园区新平街388号腾飞创新园A2幢815室
苏州硬禾信息科技有限公司
Copyright © 2023 苏州硬禾信息科技有限公司 All Rights Reserved 苏ICP备19040198号