正点原子ESP32S3开发板Arduino(Platformio)下使用经验记录

一、用8080接口驱动OLED屏幕

正点原子的0.96 OLED 默认设置的是8080接口,但是其ESP32S3开发板手册里教学使用OLED的时候,却建议更改电阻焊接方式,将屏幕调整成IIC接口。这个很不人性,太坑了。

于是我尝试使用U8g2库函数,也能成功驱动了屏幕。有几个要点记录一下:

1. U8g2主要调用这条语句来初始化

U8G2_SSD1306_128X64_NONAME_F_8080 u8g2(U8G2_R0, 4, 5, 6, 7, 15, 16, 17, 18, /*enable=*/48, /*cs=*/47, /*dc=*/38, /*reset=*/255);

2. 因为开发板的RESET引脚是通过xl9555扩展出来的,所以上面的函数中先设为255,之后在setup函数中手动添加语句进行复位。

void setup() {

  xl9555_io_config(OV_RESET, OUTPUT);
  /* OLED 模块的复位引脚连接到 XL9555 器件的 P05 引脚 */
  xl9555_pin_set(OV_RESET, HIGH); /* 默认拉高复位引脚 */
  /* 进行复位 */
  OLED_RST(0);
  delay(100);
  OLED_RST(1);
  delay(100);

  u8g2.begin();
}

二、使用Platformio

用Arduino IDE查看函数定义时经常会找不到信息,而用vscode+Platformio就要方便很多。关于Platformio.ini的正确配置查了好就资料,总算调试成功了,记录如下:

[env:esp32s3]
platform = espressif32
board = esp32-s3-devkitc-1
framework = arduino
monitor_speed = 115200
monitor_port = COM5
monitor_rts = 0
monitor_dtr = 0
board_build.arduino.partitions = default_16MB.csv
build_flags = -DBOARD_HAS_PSRAM
board_build.arduino.memory_type = qio_opi
board_upload.flash_size = 16MB
lib_extra_dirs = ~/Documents/Arduino/libraries
targets = upload, monitor

三、LVGL

因为开发板配套的2.4寸spi屏幕不带触摸,所以通过按键实现lvgl的屏幕控制。程序如下:

#include <lvgl.h>
#include "xl9555.h"

#if LV_USE_TFT_ESPI
#include <TFT_eSPI.h>
#endif

#include <examples/lv_examples.h>

/*Set to your screen resolution and rotation*/
#define TFT_HOR_RES 240
#define TFT_VER_RES 320
#define TFT_ROTATION LV_DISPLAY_ROTATION_90

static lv_group_t *group;

/*LVGL draw into this buffer, 1/10 screen size usually works well. The size is in bytes*/
#define DRAW_BUF_SIZE (TFT_HOR_RES * TFT_VER_RES / 10 * (LV_COLOR_DEPTH / 8))
uint32_t draw_buf[DRAW_BUF_SIZE / 4];

// 全局变量:存储按钮对象
static lv_obj_t *btn1, *btn2, *btn3;

void btn_event_cb(lv_event_t *e) {
  lv_event_code_t code = lv_event_get_code(e);
  lv_obj_t *btn = (lv_obj_t *)lv_event_get_target(e);
  lv_obj_t *label = lv_obj_get_child(btn, 0);

  if (code == LV_EVENT_PRESSED) {   
    // 根据不同的按钮执行不同的操作
    if (btn == btn1) {
      Serial.println("Button 1 clicked!");
      lv_label_set_text(label, "Btn1 Clicked!");
    } else if (btn == btn2) {
      Serial.println("Button 2 clicked!");
      lv_label_set_text(label, "Btn2 Clicked!");
    } else if (btn == btn3) {
      Serial.println("Button 3 clicked!");
      lv_label_set_text(label, "Btn3 Clicked!");
    }
  }
  if(code == LV_EVENT_RELEASED) {
    // 恢复按钮颜色
    if (btn == btn1) {
      lv_label_set_text(label, "Button 1");
      lv_obj_set_style_bg_color(btn, lv_palette_main(LV_PALETTE_BLUE), 0);
    } else if (btn == btn2) {
      lv_label_set_text(label, "Button 2");
      lv_obj_set_style_bg_color(btn, lv_palette_main(LV_PALETTE_BLUE), 0);
    } else if (btn == btn3) {
      lv_label_set_text(label, "Button 3");
      lv_obj_set_style_bg_color(btn, lv_palette_main(LV_PALETTE_BLUE), 0);
    }
  }
}

void create_ui() {
  // 创建第一个按钮
  btn1 = lv_button_create(lv_screen_active());
  lv_obj_set_size(btn1, 100, 40);
  lv_obj_set_pos(btn1, 20, 20);
  lv_obj_set_style_bg_color(btn1, lv_palette_main(LV_PALETTE_BLUE), 0);
  lv_obj_set_style_bg_color(btn1, lv_palette_main(LV_PALETTE_RED), LV_STATE_PRESSED);
  lv_obj_set_style_radius(btn1, 10, 0);

  lv_obj_t *label1 = lv_label_create(btn1);
  lv_label_set_text(label1, "Button 1");
  lv_obj_center(label1);

  lv_obj_add_event_cb(btn1, btn_event_cb, LV_EVENT_ALL, NULL);

  // 创建第二个按钮
  btn2 = lv_button_create(lv_screen_active());
  lv_obj_set_size(btn2, 100, 40);
  lv_obj_set_pos(btn2, 20, 80);
  lv_obj_set_style_bg_color(btn2, lv_palette_main(LV_PALETTE_BLUE), 0);
  lv_obj_set_style_bg_color(btn2, lv_palette_main(LV_PALETTE_GREEN), LV_STATE_PRESSED);
  lv_obj_set_style_radius(btn2, 10, 0);

  lv_obj_t *label2 = lv_label_create(btn2);
  lv_label_set_text(label2, "Button 2");
  lv_obj_center(label2);

  lv_obj_add_event_cb(btn2, btn_event_cb, LV_EVENT_ALL, NULL);

  // 创建第三个按钮
  btn3 = lv_button_create(lv_screen_active());
  lv_obj_set_size(btn3, 100, 40);
  lv_obj_set_pos(btn3, 20, 140);
  lv_obj_set_style_bg_color(btn3, lv_palette_main(LV_PALETTE_BLUE), 0);
  lv_obj_set_style_bg_color(btn3, lv_palette_main(LV_PALETTE_YELLOW), LV_STATE_PRESSED);
  lv_obj_set_style_radius(btn3, 10, 0);

  lv_obj_t *label3 = lv_label_create(btn3);
  lv_label_set_text(label3, "Button 3");
  lv_obj_center(label3);

  lv_obj_add_event_cb(btn3, btn_event_cb, LV_EVENT_ALL, NULL);

  // 启用按键导航
  group = lv_group_create();

  // 设置组属性
  lv_group_set_wrap(group, true);  // 启用循环导航

  // 将按钮添加到组中(按顺序)
  lv_group_add_obj(group, btn1);
  lv_group_add_obj(group, btn2);
  lv_group_add_obj(group, btn3);

  // 设置第一个按钮为初始焦点
  lv_group_focus_obj(btn1);
  
  // 设置按钮的聚焦样式
  lv_obj_set_style_border_color(btn1, lv_palette_main(LV_PALETTE_RED), LV_STATE_FOCUSED);
  lv_obj_set_style_border_width(btn1, 3, LV_STATE_FOCUSED);

  lv_obj_set_style_border_color(btn2, lv_palette_main(LV_PALETTE_RED), LV_STATE_FOCUSED);
  lv_obj_set_style_border_width(btn2, 3, LV_STATE_FOCUSED);

  lv_obj_set_style_border_color(btn3, lv_palette_main(LV_PALETTE_RED), LV_STATE_FOCUSED);
  lv_obj_set_style_border_width(btn3, 3, LV_STATE_FOCUSED);

  // 添加说明标签
  lv_obj_t *info_label = lv_label_create(lv_screen_active());
  lv_label_set_text(info_label, "Use UP/DOWN to navigate\nPress ENTER to select");
  lv_obj_set_pos(info_label, 20, 200);

  Serial.println("UI created with 3 buttons");
}

/* 改进的按键读取函数 - 在回调中直接处理焦点转移 */
void my_keypad_read(lv_indev_t *indev, lv_indev_data_t *data) {
  static unsigned long last_press_time = 0;
  static uint32_t last_key = 0;
  static bool key_down = false;
  
  // 读取按键状态
  bool key0_pressed = (xl9555_get_pin(KEY0) == 0);
  bool key1_pressed = (xl9555_get_pin(KEY1) == 0);
  bool key2_pressed = (xl9555_get_pin(KEY2) == 0);
  bool key3_pressed = (xl9555_get_pin(KEY3) == 0);
  
  // 检查是否有按键按下
  bool any_key_pressed = key0_pressed || key1_pressed || key2_pressed || key3_pressed;
  
  // 去抖动处理 - 200ms 去抖动时间
  unsigned long now = millis();
  if (any_key_pressed != key_down) {
    if (now - last_press_time > 200) { // 去抖动延迟
      key_down = any_key_pressed;
      last_press_time = now;
      
      if (key_down) {
        // 按键按下
        if (key1_pressed) {
          // ENTER 键 - 发送点击事件
          data->state = LV_INDEV_STATE_PRESSED;
          data->key = LV_KEY_ENTER;
          last_key = LV_KEY_ENTER;
          Serial.println("ENTER key pressed (will trigger click)");
        } 
        else if (key0_pressed) {
          // DOWN 键 - 移动焦点到下一个
          if (group) {
            lv_group_focus_next(group);
            Serial.println("DOWN key pressed - moved focus to next");
          }
          // 我们不需要发送按键事件给LVGL,因为我们手动处理了焦点
          data->state = LV_INDEV_STATE_PRESSED;
          data->key = 0; // 设置为0,表示不处理其他操作
          last_key = 0;
        }
        else if (key2_pressed) {
          // UP 键 - 移动焦点到上一个
          if (group) {
            lv_group_focus_prev(group);
            Serial.println("UP key pressed - moved focus to previous");
          }
          data->state = LV_INDEV_STATE_PRESSED;
          data->key = 0; // 设置为0,表示不处理其他操作
          last_key = 0;
        }
        else if (key3_pressed) {
          // ESC 键
          data->state = LV_INDEV_STATE_PRESSED;
          data->key = LV_KEY_ESC;
          last_key = LV_KEY_ESC;
          Serial.println("ESC key pressed");
        }
      } else {
        // 按键释放
        data->state = LV_INDEV_STATE_RELEASED;
        data->key = last_key;
        Serial.println("Key released");
      }
    } else {
      // 在去抖动期间,保持状态不变
      if (key_down) {
        data->state = LV_INDEV_STATE_PRESSED;
        data->key = last_key;
      } else {
        data->state = LV_INDEV_STATE_RELEASED;
        data->key = 0;
      }
    }
  } else {
    // 按键状态没有变化
    if (key_down) {
      data->state = LV_INDEV_STATE_PRESSED;
      data->key = last_key;
    } else {
      data->state = LV_INDEV_STATE_RELEASED;
      data->key = 0;
    }
  }
}

/*use Arduinos millis() as tick source*/
static uint32_t my_tick(void) {
  return millis();
}

void setup() {
  Serial.begin(115200);
  Serial.println("Starting setup...");

  xl9555_init();
  xl9555_io_config(KEY0 | KEY1 | KEY2 | KEY3, INPUT);
    /* 初始化 LCD 屏需要用到的引脚 */
  xl9555_io_config(SLCD_PWR, OUTPUT);
  xl9555_io_config(SLCD_RST, OUTPUT);

  lv_init();

  /*Set a tick source so that LVGL will know how much time elapsed. */
  lv_tick_set_cb(my_tick);

  lv_display_t *disp = lv_tft_espi_create(TFT_HOR_RES, TFT_VER_RES, draw_buf, sizeof(draw_buf));
  lv_display_set_rotation(disp, TFT_ROTATION);

  /*Initialize the (dummy) input device driver*/
  lv_indev_t *indev = lv_indev_create();
  lv_indev_set_type(indev, LV_INDEV_TYPE_KEYPAD);
  lv_indev_set_read_cb(indev, my_keypad_read);

  create_ui();

  // 将输入设备与组关联
  lv_indev_set_group(indev, group);
}

void loop() {
  lv_timer_handler(); /* let the GUI do its work */
  delay(5);           /* let this time pass */
}

四、SD卡

这里遇到一个巨坑。我在sdcard.cpp文件中写了SD卡的初始化函数。一开始将SPIClass在函数内作为局部变量声明。

//重要,一定要将SPIClass设为全局变量,并在头文件中extern
SPIClass sdcard_spi;
void sdcard_init(void)
{
    sdcard_spi.begin(SD_SCK, SD_MISO, SD_MOSI, SD_CS);
    if (!SD.begin(SD_CS, sdcard_spi))
    {
        Serial.println("Card Mount Failed");
        return;
    }
}

然后在lv_conf.h中将Arduino对于SD卡的支持打开

/*API for Arduino LittleFs. */
#define LV_USE_FS_ARDUINO_ESP_LITTLEFS 1
#if LV_USE_FS_ARDUINO_ESP_LITTLEFS
    #define LV_FS_ARDUINO_ESP_LITTLEFS_LETTER 'E'     /*Set an upper cased letter on which the drive will accessible (e.g. 'A')*/
#endif

/*API for Arduino Sd. */
#define LV_USE_FS_ARDUINO_SD 1
#if LV_USE_FS_ARDUINO_SD
    #define LV_FS_ARDUINO_SD_LETTER 'A'     /*Set an upper cased letter on which the drive will accessible (e.g. 'A')*/
#endif

/*LODEPNG decoder library*/
#define LV_USE_LODEPNG 1

/*BMP decoder library*/
#define LV_USE_BMP 1

/* JPG + split JPG decoder library.
 * Split JPG is a custom format optimized for embedded systems. */
#define LV_USE_TJPGD 1

在ino主文件调用sdcard_init()函数后,调用lv_image_set_src(bg_img, "A:/background.bin");来设置背景图片。结果程序一直重启。我怀疑过是不是内存不够,SD卡初始化语句的位置是不是不对等等原因,卡了三天。最后发现是因为没有把SPIClass声明为全局变量,原因是:

  • SD 库在 begin() 时,内部保存了传入的 SPIClass 对象的引用。它记住了这个SPI通道,以便后续所有读写操作(openreadseek等)都通过它来与硬件通信。

  • 当sdcard_init()函数结束,栈内存被释放,这个指针就变成了“悬空指针”。下次调用 SD.open(),库通过这个无效指针去访问SPI硬件,导致内存访问违规,系统崩溃重启。

五、图片加载

图片可以通过多种方式加载。包括直接保留bmp、png、jpg原格式,从littlefs和sd卡加载;也可以通过lvgl的图形转换工具先转为bin文件后再加载;还可以转为c文件后加载。实测下来,直接通过原格式加载,由于涉及到解码需要占用大量内存,对图片大小的要求很严格,分辨率稍微大点的图片就会很慢或失败。而转为c文件不方便放到littlefs或sd卡中。还是先转为bin文件后加载效果最好。

bool load_background()
{
  // 方法一:从文件系统加载图像文件
  if (!LittleFS.exists("/background.bin"))
  {
     Serial.println("背景图片文件不存在");
     return false;
   }
   Serial.println("找到背景图片");
   bg_img = lv_image_create(lv_screen_active());
   lv_image_set_src(bg_img, "E:/background.bin");
   return true;

  // 方法二:从SD卡加载图像文件
  if (!SD.exists("/background.bin"))
  {
    Serial.println("背景图片文件不存在");
    return false;
  }
  Serial.println("找到背景图片");
  bg_img = lv_image_create(lv_screen_active());
  lv_image_set_src(bg_img, "A:/background.bin");
  return true;

  // 方法三:使用C语言方式声明图像
   LV_IMG_DECLARE(background);
   bg_img = lv_image_create(lv_screen_active());
   lv_image_set_src(bg_img, &background); // 使用声明的图像
   return true;
}

六、驱动4.3寸RGB触摸屏

利用esp32_display_panel这个库文件的lcd_single_rgb和touch_i2c官方示例,稍微改一下引脚和屏幕参数,再结合lvgl库的官方示例就能比较容易地驱动正点原子的4.3寸800X480的RGB触摸屏。

#include "display.h"
#include "touch.h"
#include <lvgl.h>

#include <examples/lv_examples.h>
//#include <demos/lv_demos.h>

/*Set to your screen resolution and rotation*/
#define TFT_HOR_RES 800
#define TFT_VER_RES 480
#define TFT_ROTATION LV_DISPLAY_ROTATION_0

/*LVGL draw into this buffer, 1/10 screen size usually works well. The size is in bytes*/
#define DRAW_BUF_SIZE (TFT_HOR_RES * TFT_VER_RES / 20 * (LV_COLOR_DEPTH / 8))
uint32_t draw_buf[DRAW_BUF_SIZE / 4];

/* LVGL calls it when a rendered image needs to copied to the display*/
void my_disp_flush(lv_display_t *disp, const lv_area_t *area, uint8_t *px_map) {
  uint32_t w = lv_area_get_width(area);
  uint32_t h = lv_area_get_height(area);

  bool success = display->drawBitmap(
    area->x1,  // 起始 X 坐标
    area->y1,  // 起始 Y 坐标
    w,         // 区块宽度
    h,         // 区块高度
    px_map,    // LVGL 渲染好的像素数据
    0          // 非阻塞模式(根据库说明)
  );

  /*Call it to tell LVGL you are ready*/
  lv_display_flush_ready(disp);
}

/*Read the touchpad*/
void my_touchpad_read(lv_indev_t *indev, lv_indev_data_t *data) {
  touch->readRawData(1, 0, EXAMPLE_TOUCH_READ_PERIOD_MS);
  std::vector<TouchPoint> points;
  touch->getPoints(points);
  if (points.empty()) {
    data->state = LV_INDEV_STATE_RELEASED;
  } else {
    data->state = LV_INDEV_STATE_PRESSED;
    data->point.x = points[0].x;
    data->point.y = points[0].y;
    Serial.printf("Touch point: x %d, y %d, strength %d\n", points[0].x, points[0].y, points[0].strength);
  }
}

/*use Arduinos millis() as tick source*/
static uint32_t my_tick(void) {
  return millis();
}

void setup() {
  String LVGL_Arduino = "Hello Arduino! ";
  LVGL_Arduino += String('V') + lv_version_major() + "." + lv_version_minor() + "." + lv_version_patch();

  Serial.begin(115200);
  Serial.println(LVGL_Arduino);

  display_init();
  touch_init();
  lv_init();

  /*Set a tick source so that LVGL will know how much time elapsed. */
  lv_tick_set_cb(my_tick);

  lv_display_t *disp;
  /*create a display yourself*/
  disp = lv_display_create(TFT_HOR_RES, TFT_VER_RES);
  lv_display_set_flush_cb(disp, my_disp_flush);
  lv_display_set_buffers(disp, draw_buf, NULL, sizeof(draw_buf), LV_DISPLAY_RENDER_MODE_PARTIAL);

  /*Initialize the (dummy) input device driver*/
  lv_indev_t *indev = lv_indev_create();
  lv_indev_set_type(indev, LV_INDEV_TYPE_POINTER); /*Touchpad should have POINTER type*/
  lv_indev_set_read_cb(indev, my_touchpad_read);

  //   lv_obj_t *label = lv_label_create(lv_screen_active());
  //   lv_label_set_text(label, "Hello Arduino, I'm LVGL!");
  //   lv_obj_align(label, LV_ALIGN_CENTER, 0, 0);

  lv_example_anim_1();

  Serial.println("Setup done");
}

void loop() {
  lv_timer_handler(); /* let the GUI do its work */
  delay(5);           /* let this time pass */
}

复杂几何的多球近似MATLAB类及多球模型的比较 MATLAB类Approxi提供了一个框架,用于使用具有迭代缩放的聚集球体模型来近似解剖体积模型,以适应目标体积和模型比较。专为骨科、生物力学和计算几何应用而开发。 MATLAB class for multi-sphere approximation of complex geometries and comparison of multi-sphere models 主要特点: 球体模型生成 1.多球体模型生成:与Sihaeri的聚集球体算法的接口 2.音量缩放 基于体素的球体模型和参考几何体的交集。 迭代缩放球体模型以匹配目标体积。 3.模型比较:不同模型体素占用率的频率分析(多个评分指标) 4.几何分析:原始曲面模型和球体模型之间的顶点到最近邻距离映射(带颜色编码结果)。 如何使用: 1.代码结构:Approxi类可以集成到相应的主脚本中。代码的关键部分被提取到单独的函数中以供重用。 2.导入:将STL(或网格)导入MATLAB,并确保所需的函数,如DEM clusteredSphere(populateSpheres)和inpolyhedron,已添加到MATLAB路径中 3.生成多球体模型:使用DEM clusteredSphere方法从输入网格创建多球体模型 4.运行体积交点:计算多球体模型和参考几何体之间的基于体素的交点,并调整多球体模型以匹配目标体积 5.比较和可视化模型:比较多个多球体模型的体素频率,并计算多球体模型与原始表面模型之间的距离,以进行2D/3D可视化 使用案例: 骨科和生物力学体积建模 复杂结构的多球模型形状近似 基于体素拟合度量的模型选择 基于距离的患者特定几何形状和近似值分析 优点: 复杂几何的多球体模型 可扩展模型(基于体素)-自动调整到目标体积 可视化就绪输出(距离图)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值