这个警告怎么处理 warning: initializing 'const uint8_t *' (aka 'const unsigned char
时间: 2024-02-06 17:12:29 浏览: 781
*') with an expression of type 'const char *' discards qualifiers [-Wincompatible-pointer-types-discards-qualifiers]?
这个警告是因为你尝试使用一个指向const char类型的指针来初始化一个指向const uint8_t类型的指针,这会导致const限定符丢失。解决这个警告的方法是将指向const char类型的指针转换为指向const uint8_t类型的指针。你可以使用强制类型转换来实现这个目的,如下所示:
```c++
const char* str = "hello";
const uint8_t* ptr = reinterpret_cast<const uint8_t*>(str);
```
这样,你就可以使用指向const char类型的指针来初始化指向const uint8_t类型的指针,而不会丢失const限定符了。但是,请注意,这种类型转换可能会导致未定义行为,因此请谨慎使用。
相关问题
Arduino:1.8.19 (Windows 10), 开发板:"ESP32 Dev Module, Disabled, Disabled, Default 4MB with spiffs (1.2MB APP/1.5MB SPIFFS), 240MHz (WiFi/BT), QIO, 80MHz, 4MB (32Mb), 115200, Core 1, Core 1, None, Disabled, Disabled" D:\TFT\Demo_Arduino\Demo_UNO_Hardware_SPI\Example_10_touch_pen\touch_pen\touch_pen.ino: In function 'void show_color_select_menu()': touch_pen:94:16: error: invalid conversion from 'const char*' to 'uint8_t*' {aka 'unsigned char*'} [-fpermissive] 94 | show_string("OK",CENTER,COLORBOXSIZE/2+4,2,RED, BLACK,1); | ^~~~ | | | const char* D:\TFT\Demo_Arduino\Demo_UNO_Hardware_SPI\Example_10_touch_pen\touch_pen\touch_pen.ino:74:27: note: initializing argument 1 of 'void show_string(uint8_t*, int16_t, int16_t, uint8_t, uint16_t, uint16_t, boolean)' 74 | void show_string(uint8_t *str,int16_t x,int16_t y,uint8_t csize,uint16_t fc, uint16_t bc,boolean mode) | ~~~~~~~~~^~~ D:\TFT\Demo_Arduino\Demo_UNO_Hardware_SPI\Example_10_touch_pen\touch_pen\touch_pen.ino: In function 'void show_pen_size_select_menu()': touch_pen:113:16: error: invalid conversion from 'const char*' to 'uint8_t*' {aka 'unsigned char*'} [-fpermissive] 113 | show_string("OK",CENTER,COLORBOXSIZE/2+4,2,RED, BLACK,1); | ^~~~ | | | const char* D:\TFT\Demo_Arduino\Demo_UNO_Hardware_SPI\Example_10_touch_pen\touch_pen\touch_pen.ino:74:27: note: initializing argument 1 of 'void show_string(uint8_t*, int16_t, int16_t, uint8_t, uint16_t, uint16_t, boolean)' 74 | void show_string(uint8_t *str,int16_t x,int16_t y,uint8_t csize,uint16_t fc, uint16_t bc,boolean mode) | ~~~~~~~~~^~~ D:\TFT\Demo_Arduino\Demo_UNO_Hardware_SPI\Example_10_touch_pen\touch_pen\touch_pen.ino: In function 'void show_main_menu()': touch_pen:124:16: error: invalid conversion from 'const char*' to 'uint8_t*' {aka '
<think>好的,我现在需要解决用户在Arduino项目中遇到的编译错误:invalid conversion from 'const char*' to 'uint8_t*',具体是在show_string函数中。这个问题看起来是关于指针类型转换的错误。让我仔细分析一下。
首先,用户提到的错误信息显示,在代码中将一个const char*类型的指针传递给了一个期望uint8_t*类型参数的函数。C++是类型安全的,不允许隐式转换从const char*到uint8_t*,尤其是在涉及到const的情况下。所以需要显式转换或者调整函数参数类型。
接下来,我需要查看用户提供的代码示例。用户引用了两个引用:引用[1]和引用[2]。在引用[2]中,错误出现在Adafruit_SPIDevice.cpp文件的transferBytes函数调用,其中传递了prefix_buffer和nullptr。错误的原因是transferBytes期望的是uint8_t*类型的参数,但传入的可能是一个const uint8_t*,导致类型不匹配。类似的情况可能出现在用户的show_string函数中,即传递了const char*而函数参数是uint8_t*。
可能的解决方案包括:
1. 修改函数参数类型:如果函数show_string不需要修改传入的字符串,可以将参数声明为const uint8_t*,或者更准确地说,使用const char*,并在内部进行必要的类型转换。例如:
void show_string(const char* str) {
// 使用reinterpret_cast<uint8_t*>(const_cast<char*>(str))进行转换
}
但这种方法需要确保函数内部不会修改字符串内容,否则会导致未定义行为。
2. 显式类型转换:在调用函数时,将const char*显式转换为uint8_t*。例如:
show_string(reinterpret_cast<uint8_t*>(const_cast<char*>("Hello")));
但这种方法需要谨慎,因为它去除了const修饰符,如果函数试图修改字符串内容,会导致问题。
3. 使用中间变量:如果字符串内容需要修改,可以将其复制到一个非const的数组中,然后传递该数组。例如:
char buffer[] = "Hello";
show_string(reinterpret_cast<uint8_t*>(buffer));
这样buffer是可修改的,转换是安全的。
此外,用户可能需要检查函数show_string的原型是否正确声明。例如,如果函数应该接受const char*,则直接修改参数类型为const char*,并在内部处理相应的操作。如果涉及到底层硬件操作,可能需要保持参数为uint8_t*,但需要确保传入的数据是可变的。
还需要注意Arduino中字符串常量的处理。例如,字符串字面量如"Hello"是const char*类型,不能直接转换为非const的指针。因此,在调用函数时,必须确保类型匹配,或者进行适当的转换。
总结,解决方案的关键在于正确处理const修饰符和指针类型转换,确保类型安全,同时满足函数的需求。需要根据函数的具体用途来决定是调整参数类型还是进行显式转换。</think>### Arduino ESP32编译错误解决方案:无效转换从'const char*'到'uint8_t*'
#### 错误原因分析
编译错误`invalid conversion from 'const char*' to 'uint8_t*'`表明代码中存在**类型不匹配**问题。具体表现为:
1. 函数`show_string`的形参类型为`uint8_t*`(即`unsigned char*`),但实际传入的是`const char*`类型的字符串字面量(如`"Hello"`)。
2. C++不允许将`const`指针隐式转换为非`const`指针[^2]。
#### 解决方法
根据代码场景选择以下方案:
##### 方案1:修改函数参数类型(推荐)
如果`show_string`不需要修改字符串内容:
```cpp
void show_string(const uint8_t* str) { // 或 const char*
// 函数内部操作
}
```
调用时直接传递字符串字面量:
```cpp
show_string(reinterpret_cast<const uint8_t*>("Hello")); // 类型匹配
```
##### 方案2:显式强制类型转换
若必须保留`uint8_t*`参数类型(需确保不修改字符串):
```cpp
show_string(reinterpret_cast<uint8_t*>(const_cast<char*>("Hello")));
```
⚠️ 注意:此操作会移除`const`属性,若函数尝试修改字符串内容,可能导致程序崩溃。
##### 方案3:使用可变缓冲区
如果需要动态修改字符串内容:
```cpp
char buffer[] = "Hello"; // 非const数组
show_string(reinterpret_cast<uint8_t*>(buffer)); // 安全转换
```
#### 补充建议
1. **检查函数原型**:确认`show_string`是否需要`uint8_t*`参数。若仅用于显示字符串,更推荐使用`const char*`类型。
2. **硬件库兼容性**:部分Arduino库(如SPI)强制要求`uint8_t*`参数时,需参考库文档确认是否支持`const`修饰符。
../Middle/MALLOC/MALLOC.c(29): error: incompatible pointer to integer conversion initializing 'uint8_t' (aka 'unsigned char') with an expression of type 'uint16_t[1280]' (aka 'unsigned short[1280]') [-Wint-conversion]
<think>嗯,用户遇到了一个编译错误,错误信息是关于指针和整数转换不兼容的问题。让我仔细看一下错误信息的内容。错误发生在MALLOC.c文件的第29行,提示是用uint16_t数组初始化uint8_t类型的指针时出现了类型不兼容的问题。用户可能是在动态内存分配或者初始化某个内存池的时候出错了。
首先,我需要理解错误的具体原因。错误信息提到“incompatible pointer to integer conversion”,也就是说,用户可能试图将一个数组(可能是uint16_t类型)赋值给一个uint8_t类型的变量,而这里可能是一个指针或者整数类型的变量。因为C语言中数组名会被隐式转换为指针,所以如果用户把一个uint16_t数组赋值给一个uint8_t指针,类型不匹配就会导致这个错误。
接下来,我需要考虑用户代码的结构。例如,用户可能在MALLOC.c中定义了一个内存池,比如:
uint16_t mem1base[MEM1_MAX_SIZE]; // 假设MEM1_MAX_SIZE是1280
然后在某个地方试图将这个数组赋值给一个uint8_t类型的变量:
uint8_t *memory_pool = mem1base;
这时候,mem1base作为uint16_t数组,其类型是uint16_t*,而memory_pool是uint8_t*,直接赋值会导致类型不兼容的错误,编译器会警告。
这种情况下,正确的做法应该是进行显式的类型转换,告诉编译器用户确实需要将uint16_t指针转换为uint8_t指针,可能涉及到字节寻址的问题。或者,用户可能原本应该使用uint8_t数组来初始化指针,但错误地用了uint16_t数组。
另一种可能是用户在结构体或联合体中定义变量时,错误地混合了不同的类型,导致初始化时的类型不匹配。例如,定义了一个结构体成员为uint8_t,但试图用uint16_t数组来初始化它。
接下来,我需要回顾用户提供的错误信息中的具体代码行:../Middle/MALLOC/MALLOC.c(29): error: incompatible pointer to integer conversion initializing 'uint8_t' (aka 'unsigned char') with an expression of type 'uint16_t[1280]' (aka 'unsigned short[1280]') [-Wint-conversion]
这里的关键点在于初始化(initializing)一个uint8_t类型的变量,使用了uint16_t数组的表达式。可能用户是声明了一个uint8_t类型的变量,但试图将一个uint16_t数组的地址赋值给它,例如:
uint8_t memory_pool = mem1base; // mem1base是uint16_t数组
这里的问题在于memory_pool是一个uint8_t类型的变量(即一个字节),而mem1base是一个数组名,转换为指针后是uint16_t*类型。将一个指针赋值给一个uint8_t类型的整数变量,会导致指针到整数的转换,而类型不匹配,所以编译器报错。
正确的做法应该是将memory_pool声明为指针类型,例如:
uint8_t *memory_pool = (uint8_t *)mem1base;
或者,如果用户确实需要将数组的地址存储在一个变量中,应该使用正确的指针类型:
uint16_t *memory_pool = mem1base;
但根据错误信息,用户的目标变量是uint8_t类型,这可能意味着用户需要以字节为单位操作内存,而mem1base是uint16_t数组,每个元素占两个字节。因此,可能需要显式转换指针类型,以便正确地进行字节级别的访问。
总结可能的情况和解决方案:
1. 用户可能错误地将一个uint16_t数组赋值给uint8_t类型的变量,而不是指针。需要将变量声明为指针,并进行显式类型转换。
2. 用户可能在初始化时混淆了指针和数组的类型,需要检查变量声明和赋值的类型是否一致。
3. 如果用户确实需要使用uint8_t指针来访问uint16_t数组的内存,需要确保类型转换正确,并且了解这样做可能带来的对齐和访问问题。
此外,用户可能在内存管理模块中定义了不同内存池,例如使用不同的数据类型来管理堆内存,这时候需要确保类型的一致性。例如,malloc的实现可能需要字节指针来管理内存块,而用户可能错误地使用其他类型的数组作为基础内存池。
可能的代码示例:
错误的代码:
uint16_t mem1base[1280];
uint8_t *mem1 = mem1base; // 错误:uint16_t* 转换为 uint8_t* 需要显式转换
正确的代码:
uint16_t mem1base[1280];
uint8_t *mem1 = (uint8_t *)mem1base; // 显式转换指针类型
或者,如果用户需要使用uint16_t类型的数组作为内存池,但希望以字节为单位进行管理,可能需要重新定义数组为uint8_t类型:
uint8_t mem1base[1280 * sizeof(uint16_t)]; // 根据需求调整大小
但这样可能不太直观,所以显式转换更常见。
另外,检查是否在结构体或变量声明中错误地使用了类型。例如,在结构体中有一个uint8_t类型的成员,但试图将数组地址赋给它:
struct {
uint8_t *pool;
} malloc_info;
malloc_info.pool = mem1base; // mem1base是uint16_t数组,需要转换
此时应改为:
malloc_info.pool = (uint8_t *)mem1base;
总之,解决方案的核心在于正确地进行指针类型转换,或者调整变量/数组的类型以匹配。同时,用户需要明确自己是要以字节还是其他单位来管理内存,选择合适的类型和转换方式。</think>在C语言中,将不同类型的指针直接赋值会导致类型不兼容错误。错误 **`incompatible pointer to integer conversion initializing 'uint8_t' with 'uint16_t[1280]'`** 表明您尝试将 `uint16_t` 数组的地址(指针类型)赋值给一个 `uint8_t` 类型的变量(整数类型),或尝试隐式转换指针类型。以下是逐步分析和解决方案:
---
### **错误原因分析**
1. **类型不匹配**
- `uint16_t[1280]` 是 `unsigned short` 类型的数组,其地址类型为 `uint16_t*`(指针)。
- 您试图将此指针赋值给一个 `uint8_t` 类型(`unsigned char`)的变量,导致 **指针到整数的隐式转换**,这是未定义行为。
2. **典型场景**
- 在动态内存管理(如 `malloc` 实现)中,若内存池定义为 `uint16_t` 数组,但试图通过 `uint8_t*` 指针操作时,未显式转换指针类型。
---
### **解决方案**
#### **方法1:显式强制类型转换**
若需要将 `uint16_t` 数组的地址赋值给 `uint8_t*` 指针(例如以字节为单位管理内存),需显式转换指针类型。
**错误代码示例:**
```c
uint16_t mem1base[1280]; // 定义uint16_t数组
uint8_t *memory_pool = mem1base; // 错误:隐式转换uint16_t*到uint8_t*
```
**修正代码:**
```c
uint16_t mem1base[1280];
uint8_t *memory_pool = (uint8_t *)mem1base; // 显式强制类型转换
```
---
#### **方法2:统一内存池类型**
若内存池需要以字节为单位操作,直接定义为 `uint8_t` 数组:
```c
uint8_t mem1base[1280 * sizeof(uint16_t)]; // 按需调整大小(如1280个uint16_t占2560字节)
uint8_t *memory_pool = mem1base; // 类型匹配,无需转换
```
---
#### **方法3:检查变量声明**
确保目标变量是**指针类型**而非整数类型。
**错误示例:**
```c
uint16_t mem1base[1280];
uint8_t memory_pool = mem1base; // 错误:将指针赋值给uint8_t整数变量
```
**正确声明:**
```c
uint16_t mem1base[1280];
uint8_t *memory_pool = (uint8_t *)mem1base; // 目标变量必须是指针类型
```
---
### **深入理解**
1. **指针类型转换的意义**
- `uint16_t*` 指针的步长为2字节(每次 `+1` 移动2字节),而 `uint8_t*` 指针的步长为1字节。
- 显式转换后,通过 `uint8_t*` 指针访问内存时,可逐字节操作,但需自行处理对齐问题。
2. **内存对齐警告**
若 `uint16_t` 数组的地址未按2字节对齐,强制转换为 `uint8_t*` 可能导致性能下降或硬件异常(某些架构严格要求对齐)。
---
### **验证步骤**
1. 检查报错行代码,确认赋值操作的目标变量是否为指针类型。
2. 添加显式类型转换 `(uint8_t *)` 或调整数组类型。
3. 重新编译,观察错误是否消除。
4. 若涉及硬件内存操作(如DMA),需验证地址对齐是否符合要求。
---
### **扩展建议**
- **使用联合体(Union)处理多类型访问**
若需同一块内存以不同数据类型访问,可定义联合体:
```c
union MemoryPool {
uint16_t as_uint16[1280];
uint8_t as_uint8[2560]; // 1280*2字节
} mem_pool;
uint8_t *byte_ptr = mem_pool.as_uint8; // 直接获取字节指针
```
- **启用编译器警告**
在Keil中开启 `-Wall` 和 `-Wpedantic` 选项,提前捕获类型不匹配问题。
---
通过显式类型转换或统一内存池类型,可解决此错误。核心原则是 **确保指针类型与操作的内存单元一致**,避免隐式类型转换的风险。
阅读全文
相关推荐









/**
* @brief 传感器外参标定库
* @author ZhengfengGu. any question please send mail to
*[email protected]
* @date 2025-5-22
* @version V1.0.0
* @copyright Copyright (c) 2003-无固定期限 杭州士腾科技有限公司
*********************************************************************
* @attention
* 重要信息:
* @par 修改日志:
*
* Date Version Author Description
* 2025/5/22 1.0.0 ZhengfengGu 创建初始版本
*
*
**********************************************************************
*/
#pragma once
#include <common/sys_logger.h>
#include
#include <rclcpp/rclcpp.hpp>
#include <sensor_msgs/msg/imu.hpp>
#include <sensor_msgs/msg/laser_scan.hpp>
#include <string>
#include "calib_cfg/calib_cfg.h"
#include "calib_common/time.h"
#include "calibration_component/calibration_def.h"
#include "geometry_msgs/msg/twist.hpp"
#include "sensor/sensor_dispatch.h"
// #include "mrp_interfaces/msg/motor_data.hpp"
#include "mrp_interfaces/msg/motor_status_data.hpp"
#include "mrp_interfaces/msg/motor_control_data.hpp"
namespace mrp {
namespace calib {
// template <typename NodeT = rclcpp::Node::SharedPtr>
class CalibrationComponent {
public:
/**
* @brief 传感器外参标定库.
* @param[in] i_lg [mrp::common::LoggerPtr] 日志库指针
* @param[in] i_init_param [InitExternalParam]
* i_init_param.laser: 激光初始外参
* i_init_param.laser.x: 激光x轴偏移
* ...
* i_init_param.imu: IMU初始外参
* i_init_param.imu.x: IMU x轴偏移
* ...
* 初始外参(机械安装值),不标定的传感器初始外参可以不赋值
* @param[in] i_robot_param 机器人参数
* i_robot_param.model 0:差速, 1:阿克曼, 2:单舵轮
* i_robot_param.l:轴距,
* i_robot_param.d:舵轮距离中心偏移,
* i_robot_param.steer_offset:舵轮零偏(一般给0)
*/
CalibrationComponent(
mrp::common::LoggerPtr i_lg,
mrp_interfaces::calibration_component::InitExternalParam i_init_param,
mrp_interfaces::calibration_component::RobotParam i_robot_param);
~CalibrationComponent();
/*
* @brief
* 单舵轮模型添加里程计数据,在标定雷达和里程计时,需要实时添加车辆控制信息,
* 时间戳必须准确添加
* @param[in] header [std_msgs::msg::Header]
* @param[in] msg [mrp_interfaces::calibration_component::SingleSteerControl]
* 里程计数据
* @return void
*/
void AddSensorData(
const std_msgs::msg::Header& header,
const std::vector<mrp_interfaces::msg::MotorStatusData>& motor_datas);
/*
* @brief
* 添加雷达数据
* @param[in] msg [sensor_msgs::msg::LaserScan::SharedPt] 雷达数据
* @return void
*/
void AddSensorData(const sensor_msgs::msg::LaserScan::SharedPtr msg);
/*
* @brief
* 添加IMU数据
* @param[in] msg [sensor_msgs::msg::Imu::SharedPtr msg] IMU数据
* @return void
*/
void AddSensorData(const sensor_msgs::msg::Imu::SharedPtr msg);
/**
* @breif 复位状态,开始标定
* @param[in] i_calib_mode [CalibMode]
* i_calib_mode.mode:标定模式,0:激光+里程计+IMU,
* 1:激光+里程计,2:IMU
* i_calib_mode.move_dist:移动距离 (单位m)
* @return error_code [int] -1:模式错误 -2:机器人模型错误
* @retval int.
*/
int StartCalibration(
mrp_interfaces::calibration_component::CalibMode i_calib_mode);
/**
* @brief 停止标定
*/
void StopCalibration();
/*
* @brief 获取单舵轮模型标定运动指令
* @param[out] cmd_vel
* [mrp_interfaces::calibration_component::SingleSteerControl] 标定运动指令
* @retval void
*/
void GetCalibrationMoveCommand(
std::vector<mrp_interfaces::msg::MotorControlData>& motor_datas);
/**
* @brief 获取标定状态和结果
* @param[out] calib_result [std::map<std::string,
* mrp_interfaces::calibration_component::ExternalParam>] 标定结果
* first: 外设类型(可选值: "lidar", "odom", "imu", "camera")
* second: 外参结构体(其中舵角零偏在second.yaw中)
* @return calib_status [int] 0:未开始 3:进行中 5:完成 6:失败
* 状态为 NONE = 0,
* WAITING,
* INITIALIZING,
* RUNNING,
* PAUSED,
* FINISHED,
* FAILED
* @retval int
*/
int GetCalibrationStatusAndResult(
std::map<std::string,
mrp_interfaces::calibration_component::ExternalParam>&
calib_result);
void DebugPrint();
private:
void CalcuteOdom(const sensor::MotorData& msg);
enum class ActionStatus {
NONE = 0,
WAITING,
INITIALIZING,
RUNNING,
PAUSED,
FINISHED,
FAILED
};
std::shared_ptr<CalibCfg> config_;
mrp::common::LoggerPtr logger_;
std::shared_ptr<sensor::SensorDispatch> sensor_dispatch_;
std::deque<sensor::MotorData> odom_buffer_;
struct {
double x = 0.0;
double y = 0.0;
double yaw = 0.0;
} current_odom_;
bool is_forward_phase_ = true, is_left_phase_ = true;
double origin_x = 0, origin_y = 0, origin_yaw = 0;
bool move_initialized_ = false;
std::deque<common::Time> control_times_;
};
} // namespace calib
} // namespace mrp 逐行翻译
#include "camera_pins.h"
#include <WiFi.h>
#include "esp_camera.h"
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
// Edge Impulse模型库(需手动添加到项目目录)
#include "shibie_inferencing.h"
#include "edge-impulse-sdk/dsp/image/image.hpp"
#include "esp_task_wdt.h"
#include "freertos/semphr.h"
#include <SD_MMC.h> // SD卡库(使用SPI或SDMMC接口)
#include <SPIFFS.h>
#include <SD.h> // SPI接口SD卡库(新增)
#include <Time.h> // 时间函数库(新增)
#include "esp_task_wdt.h"
#include "freertos/semphr.h"
// 功能开关
#define ENABLE_INFERENCE 1
#define ENABLE_HTTP_SERVER 1
#define ENABLE_OLED_DISPLAY 1
#define ENABLE_SD_CARD 1 // 启用SD卡功能
#define SUPPORT_OBJECT_DETECTION 0
// 摄像头配置
#define CAMERA_MODEL_AI_THINKER
#define XCLK_FREQ_HZ 2000000 // 降低时钟频率
#define FRAME_SIZE FRAMESIZE_QVGA // 320x240分辨率
#define JPEG_QUALITY 12
#define MAX_CAPTURE_RETRIES 3
// 图像尺寸
#define EI_CAMERA_COLS 320
#define EI_CAMERA_ROWS 240
#define MODEL_INPUT_WIDTH EI_CLASSIFIER_INPUT_WIDTH
#define MODEL_INPUT_HEIGHT EI_CLASSIFIER_INPUT_HEIGHT
// OLED配置
#define OLED_WIDTH 128
#define OLED_HEIGHT 64
#define OLED_RESET -1
#define OLED_SDA 21
#define OLED_SCL 22
Adafruit_SSD1306 display(OLED_WIDTH, OLED_HEIGHT, &Wire, OLED_RESET);
// WiFi配置
const char* ssid = "88888888";
const char* password = "11111111";
// SD卡配置(SPI接口)
#define SD_MOUNT_POINT "/sdcard"
#define SD_PIN_CS 15 // 改为常用的SPI CS引脚(原5可能冲突)
// 全局变量
WiFiServer server(80);
static bool is_initialised = false;
static bool wifi_connected = false;
static bool sd_ready = false;
uint8_t* model_buf = NULL;
camera_fb_t* fb = NULL;
SemaphoreHandle_t camera_mutex = NULL;
SemaphoreHandle_t sd_mutex = NULL;
// 摄像头配置
static camera_config_t camera_config = {
.pin_pwdn = PWDN_GPIO_NUM,
.pin_reset = RESET_GPIO_NUM,
.pin_xclk = XCLK_GPIO_NUM,
.pin_sscb_sda = SIOD_GPIO_NUM,
.pin_sscb_scl = SIOC_GPIO_NUM,
.pin_d7 = Y9_GPIO_NUM,
.pin_d6 = Y8_GPIO_NUM,
.pin_d5 = Y7_GPIO_NUM,
.pin_d4 = Y6_GPIO_NUM,
.pin_d3 = Y5_GPIO_NUM,
.pin_d2 = Y4_GPIO_NUM,
.pin_d1 = Y3_GPIO_NUM,
.pin_d0 = Y2_GPIO_NUM,
.pin_vsync = VSYNC_GPIO_NUM,
.pin_href = HREF_GPIO_NUM,
.pin_pclk = PCLK_GPIO_NUM,
.xclk_freq_hz = XCLK_FREQ_HZ,
.ledc_timer = LEDC_TIMER_0,
.ledc_channel = LEDC_CHANNEL_0,
.pixel_format = PIXFORMAT_JPEG,
.frame_size = FRAME_SIZE,
.jpeg_quality = JPEG_QUALITY,
.fb_count = 1,
.fb_location = CAMERA_FB_IN_PSRAM,
.grab_mode = CAMERA_GRAB_WHEN_EMPTY,
};
/* -------------------------- 时间初始化 -------------------------- */
// 初始化时间(使用NTP同步)
void init_time() {
configTime(8 * 3600, 0, "pool.ntp.org", "time.nist.gov"); // 北京时间(UTC+8)
Serial.println("等待时间同步...");
time_t now = time(nullptr);
int retry_count = 0;
// 等待时间同步,超时则跳过
while (now < 1609459200 && retry_count < 10) { // 等待同步到2021年之后
delay(1000);
Serial.print(".");
now = time(nullptr);
retry_count++;
}
if (now >= 1609459200) {
Serial.println("\n时间同步完成");
struct tm* tm_info = localtime(&now);
char time_str[26];
strftime(time_str, 26, "%Y-%m-%d %H:%M:%S", tm_info);
Serial.print("当前时间: ");
Serial.println(time_str);
} else {
Serial.println("\n时间同步超时,使用默认时间");
}
}
/* -------------------------- SD卡操作 -------------------------- */
#ifdef ENABLE_SD_CARD
bool sd_init() {
sd_mutex = xSemaphoreCreateMutex();
if (!sd_mutex) {
Serial.println("SD卡互斥锁创建失败");
return false;
}
Serial.println("初始化SD卡...");
// 尝试SDMMC模式(仅适用于支持SDMMC的板子)
if (!SD_MMC.begin("/sdcard", true)) { // 1线模式
Serial.println("SDMMC模式失败,尝试SPI模式...");
// SPI模式初始化(低速模式提高兼容性)
if (!SD.begin(SD_PIN_CS, SPI, 1000000)) { // 1MHz低速率
Serial.println("SPI模式初始化失败,检查:");
Serial.println("1. 引脚连接是否正确(CS=" + String(SD_PIN_CS) + ", SCK=18, MOSI=23, MISO=19)");
Serial.println("2. SD卡是否为FAT32格式");
Serial.println("3. 尝试更换SD卡");
return false;
}
}
// 验证SD卡读写功能
File testFile = SD.open("/test.txt", FILE_WRITE);
if (testFile) {
testFile.println("SD卡测试成功");
testFile.close();
// 读取测试
testFile = SD.open("/test.txt", FILE_READ);
if (testFile) {
String content = testFile.readString();
testFile.close();
SD.remove("/test.txt"); // 删除测试文件
Serial.println("SD卡读写验证成功: " + content);
}
} else {
Serial.println("SD卡写入测试失败");
return false;
}
// 创建目录
if (!SD.exists("/logs")) SD.mkdir("/logs");
if (!SD.exists("/images")) SD.mkdir("/images");
sd_ready = true;
Serial.println("SD卡初始化成功");
return true;
}
// 写入日志到SD卡
void sd_log(const char* message) {
if (!sd_ready) return;
if (xSemaphoreTake(sd_mutex, 1000 / portTICK_PERIOD_MS) != pdTRUE) return;
time_t now = time(nullptr);
struct tm* tm = localtime(&now);
char filename[32];
sprintf(filename, "/logs/%04d%02d%02d.txt",
tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
File file = SD.open(filename, FILE_APPEND);
if (file) {
char timestamp[32];
sprintf(timestamp, "[%02d:%02d:%02d] ",
tm->tm_hour, tm->tm_min, tm->tm_sec);
file.print(timestamp);
file.println(message);
file.close();
}
xSemaphoreGive(sd_mutex);
}
// 保存图像到SD卡
bool sd_save_image(const uint8_t* data, size_t size) {
if (!sd_ready || !data || size == 0) return false;
if (xSemaphoreTake(sd_mutex, 1000 / portTICK_PERIOD_MS) != pdTRUE) return false;
time_t now = time(nullptr);
struct tm* tm = localtime(&now);
char filename[64];
sprintf(filename, "/images/img_%04d%02d%02d_%02d%02d%02d.jpg",
tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday,
tm->tm_hour, tm->tm_min, tm->tm_sec);
File file = SD.open(filename, FILE_WRITE);
if (!file) {
xSemaphoreGive(sd_mutex);
return false;
}
bool success = (file.write(data, size) == size);
file.close();
xSemaphoreGive(sd_mutex);
if (success) {
Serial.printf("图像保存到: %s\n", filename);
sd_log((String("图像保存: ") + filename).c_str());
}
return success;
}
// 从SD卡读取图像
size_t sd_read_image(const char* filename, uint8_t* buffer, size_t max_size) {
if (!sd_ready || !buffer || max_size == 0) return 0;
if (xSemaphoreTake(sd_mutex, 1000 / portTICK_PERIOD_MS) != pdTRUE) return 0;
File file = SD.open(filename, FILE_READ);
if (!file) {
xSemaphoreGive(sd_mutex);
return 0;
}
size_t size = file.size();
if (size > max_size) size = max_size;
size_t read = file.read(buffer, size);
file.close();
xSemaphoreGive(sd_mutex);
return read;
}
#endif
/* -------------------------- 图像处理函数 -------------------------- */
bool convert_jpeg_to_rgb888_from_sd(const char* jpeg_path, uint8_t* rgb_data, size_t rgb_size) {
if (!sd_ready || !rgb_data) return false;
File file = SD.open(jpeg_path, FILE_READ);
if (!file) return false;
size_t jpeg_size = file.size();
if (jpeg_size == 0 || jpeg_size > 1024*200) {
file.close();
return false;
}
uint8_t* jpeg_buf = (uint8_t*)ps_malloc(jpeg_size);
if (!jpeg_buf) {
file.close();
return false;
}
size_t read = file.read(jpeg_buf, jpeg_size);
file.close();
if (read != jpeg_size) {
free(jpeg_buf);
return false;
}
bool success = fmt2rgb888(jpeg_buf, jpeg_size, PIXFORMAT_JPEG, rgb_data);
free(jpeg_buf);
return success;
}
// 备用缩放函数(当默认缩放失败时使用)
bool backup_resize_rgb888(const uint8_t* src, uint32_t src_width, uint32_t src_height,
uint8_t* dst, uint32_t dst_width, uint32_t dst_height) {
if (!src || !dst || src_width == 0 || src_height == 0 || dst_width == 0 || dst_height == 0) {
return false;
}
if (((uintptr_t)dst) % 4 != 0) {
Serial.println("错误:输出缓冲区未4字节对齐");
return false;
}
float x_ratio = (float)src_width / (float)dst_width;
float y_ratio = (float)src_height / (float)dst_height;
for (uint32_t y = 0; y < dst_height; y++) {
for (uint32_t x = 0; x < dst_width; x++) {
float src_x = x * x_ratio;
float src_y = y * y_ratio;
uint32_t x1 = (uint32_t)src_x;
uint32_t y1 = (uint32_t)src_y;
uint32_t x2 = (x1 < src_width - 1) ? x1 + 1 : x1;
uint32_t y2 = (y1 < src_height - 1) ? y1 + 1 : y1;
float fx = src_x - x1;
float fy = src_y - y1;
for (uint8_t c = 0; c < 3; c++) {
uint8_t v11 = src[(y1 * src_width + x1) * 3 + c];
uint8_t v12 = src[(y2 * src_width + x1) * 3 + c];
uint8_t v21 = src[(y1 * src_width + x2) * 3 + c];
uint8_t v22 = src[(y2 * src_width + x2) * 3 + c];
uint8_t v1 = (uint8_t)((1 - fx) * v11 + fx * v21);
uint8_t v2 = (uint8_t)((1 - fx) * v12 + fx * v22);
dst[(y * dst_width + x) * 3 + c] = (uint8_t)((1 - fy) * v1 + fy * v2);
}
}
}
return true;
}
/* -------------------------- 工具函数 -------------------------- */
void oled_print(const char* line1, const char* line2 = "", const char* line3 = "") {
#ifdef ENABLE_OLED_DISPLAY
display.clearDisplay();
display.setTextSize(1);
display.setTextColor(SSD1306_WHITE);
display.setCursor(0, 0);
display.println(line1);
if (strlen(line2) > 0) {
display.setCursor(0, 16);
display.println(line2);
}
if (strlen(line3) > 0) {
display.setCursor(0, 32);
display.println(line3);
}
display.display();
#endif
}
/* -------------------------- 摄像头操作 -------------------------- */
bool camera_init() {
if (is_initialised) return true;
Serial.println("\n===== 摄像头初始化 =====");
camera_mutex = xSemaphoreCreateMutex();
#ifdef ENABLE_SD_CARD
// 初始化SD卡(允许失败,不影响主功能)
if (!sd_init()) {
Serial.println("SD卡初始化失败,继续启动系统");
}
#endif
gpio_uninstall_isr_service();
esp_err_t err;
int init_retry = 0;
while (init_retry < 3) {
err = esp_camera_init(&camera_config);
if (err == ESP_OK) break;
Serial.printf("摄像头初始化失败(重试%d): %s\n",
init_retry+1, esp_err_to_name(err));
#ifdef ENABLE_SD_CARD
if (sd_ready) {
sd_log((String("摄像头初始化失败: ") + esp_err_to_name(err)).c_str());
}
#endif
if (err == ESP_ERR_INVALID_STATE) {
gpio_uninstall_isr_service();
}
init_retry++;
vTaskDelay(1000 / portTICK_PERIOD_MS);
}
if (err != ESP_OK) {
Serial.println("摄像头初始化彻底失败");
#ifdef ENABLE_SD_CARD
if (sd_ready) {
sd_log("摄像头初始化彻底失败");
}
#endif
return false;
}
sensor_t* s = esp_camera_sensor_get();
if (s) {
s->set_vflip(s, 1);
s->set_hmirror(s, 1);
s->set_awb_gain(s, 1);
}
size_t model_size = MODEL_INPUT_WIDTH * MODEL_INPUT_HEIGHT * 3;
model_size = (model_size + 3) & ~3; // 4字节对齐
model_buf = (uint8_t*)aligned_alloc(4, model_size);
if (!model_buf) {
Serial.println("模型缓冲区分配失败");
camera_deinit();
return false;
}
Serial.printf("模型缓冲区: %zu bytes\n", model_size);
is_initialised = true;
Serial.println("摄像头初始化成功");
#ifdef ENABLE_SD_CARD
if (sd_ready) {
sd_log("摄像头初始化成功");
}
#endif
return true;
}
void camera_deinit() {
if (!is_initialised) return;
esp_camera_deinit();
if (model_buf) free(model_buf);
model_buf = NULL;
is_initialised = false;
}
bool camera_capture_to_sd() {
if (!is_initialised) return false;
if (xSemaphoreTake(camera_mutex, 2000 / portTICK_PERIOD_MS) != pdTRUE) {
return false;
}
bool success = false;
fb = esp_camera_fb_get();
if (fb && fb->len > 1024) {
#ifdef ENABLE_SD_CARD
if (sd_ready) {
success = sd_save_image(fb->buf, fb->len);
}
#endif
esp_camera_fb_return(fb);
fb = NULL;
}
xSemaphoreGive(camera_mutex);
return success;
}
bool process_image_from_sd(uint8_t* model_buf, size_t model_size) {
if (!sd_ready || !model_buf) return false;
File dir = SD.open("/images");
if (!dir) return false;
File latest_file;
time_t latest_time = 0;
while (File file = dir.openNextFile()) {
if (!file.isDirectory() && strstr(file.name(), ".jpg")) {
time_t t = file.getLastWrite();
if (t > latest_time) {
latest_time = t;
if (latest_file) latest_file.close();
latest_file = file;
} else {
file.close();
}
}
}
dir.close();
if (!latest_file) return false;
const char* img_path = latest_file.path();
latest_file.close();
size_t rgb_size = EI_CAMERA_COLS * EI_CAMERA_ROWS * 3;
uint8_t* rgb_buf = (uint8_t*)ps_malloc(rgb_size);
if (!rgb_buf) return false;
bool success = false;
if (convert_jpeg_to_rgb888_from_sd(img_path, rgb_buf, rgb_size)) {
success = ei::image::processing::crop_and_interpolate_rgb888(
rgb_buf, EI_CAMERA_COLS, EI_CAMERA_ROWS,
model_buf, MODEL_INPUT_WIDTH, MODEL_INPUT_HEIGHT
);
if (!success) {
success = backup_resize_rgb888(
rgb_buf, EI_CAMERA_COLS, EI_CAMERA_ROWS,
model_buf, MODEL_INPUT_WIDTH, MODEL_INPUT_HEIGHT
);
}
}
free(rgb_buf);
return success;
}
/* -------------------------- 推理核心 -------------------------- */
#ifdef ENABLE_INFERENCE
void run_inference() {
if (!is_initialised || !model_buf) {
oled_print("未就绪", "设备异常");
return;
}
Serial.println("\n===== 开始推理 =====");
oled_print("识别中...");
if (!camera_capture_to_sd()) {
oled_print("处理失败", "图像捕获失败");
#ifdef ENABLE_SD_CARD
if (sd_ready) {
sd_log("图像捕获失败");
}
#endif
return;
}
if (!process_image_from_sd(model_buf, MODEL_INPUT_WIDTH * MODEL_INPUT_HEIGHT * 3)) {
oled_print("处理失败", "图像转换失败");
#ifdef ENABLE_SD_CARD
if (sd_ready) {
sd_log("图像转换失败");
}
#endif
return;
}
ei::signal_t signal;
signal.total_length = MODEL_INPUT_WIDTH * MODEL_INPUT_HEIGHT;
signal.get_data = [](size_t offset, size_t length, float* out) {
size_t pixel_ix = offset * 3;
if (pixel_ix + 3*length > MODEL_INPUT_WIDTH * MODEL_INPUT_HEIGHT * 3) {
Serial.println("错误:模型输入数据越界");
return -1;
}
for (size_t i = 0; i < length; i++) {
uint8_t r = model_buf[pixel_ix];
uint8_t g = model_buf[pixel_ix + 1];
uint8_t b = model_buf[pixel_ix + 2];
out[i] = (r - 127.5f) / 127.5f;
out[i + length] = (g - 127.5f) / 127.5f;
out[i + length * 2] = (b - 127.5f) / 127.5f;
pixel_ix += 3;
}
return 0;
};
ei_impulse_result_t result;
memset(&result, 0, sizeof(result));
EI_IMPULSE_ERROR err = run_classifier(&signal, &result, false);
if (err != EI_IMPULSE_OK) {
Serial.printf("推理失败: %d\n", err);
oled_print("推理错误", String(err).c_str());
#ifdef ENABLE_SD_CARD
if (sd_ready) {
sd_log((String("推理失败: ") + String(err)).c_str());
}
#endif
return;
}
float max_prob = 0;
const char* max_label = "未知";
for (uint16_t i = 0; i < EI_CLASSIFIER_LABEL_COUNT; i++) {
if (result.classification[i].value > max_prob) {
max_prob = result.classification[i].value;
max_label = ei_classifier_inferencing_categories[i];
}
}
Serial.printf("识别结果: %s (%.1f%%)\n", max_label, max_prob*100);
oled_print("识别为:", max_label, (String(max_prob*100, 1) + "%").c_str());
#ifdef ENABLE_SD_CARD
if (sd_ready) {
sd_log((String("识别结果: ") + max_label + " (" + String(max_prob*100) + "%)").c_str());
}
#endif
}
#endif
/* -------------------------- HTTP服务 -------------------------- */
#ifdef ENABLE_HTTP_SERVER
void handle_client(WiFiClient& client) {
String req = client.readStringUntil('\n');
req.trim();
Serial.println("HTTP请求: " + req);
if (req.startsWith("GET /photo")) {
client.println("HTTP/1.1 200 OK");
client.println("Content-Type: image/jpeg");
client.println("Connection: close");
client.println();
#ifdef ENABLE_SD_CARD
if (sd_ready) {
File dir = SD.open("/images");
File latest_file;
while (File file = dir.openNextFile()) {
if (!file.isDirectory() && strstr(file.name(), ".jpg")) {
if (!latest_file || file.getLastWrite() > latest_file.getLastWrite()) {
if (latest_file) latest_file.close();
latest_file = file;
} else {
file.close();
}
}
}
dir.close();
if (latest_file) {
size_t fileSize = latest_file.size();
const size_t bufferSize = 1024;
uint8_t buffer[bufferSize];
while (fileSize > 0) {
size_t read = latest_file.read(buffer, min(bufferSize, fileSize));
client.write(buffer, read);
fileSize -= read;
}
latest_file.close();
} else {
client.print("无图像文件");
}
} else {
client.print("SD卡未就绪");
}
#else
client.print("SD卡功能未启用");
#endif
} else if (req.startsWith("GET /infer")) {
client.println("HTTP/1.1 200 OK");
client.println("Content-Type: text/plain");
client.println("Connection: close");
client.println();
run_inference();
client.println("推理已触发");
} else if (req.startsWith("GET /")) {
client.println("HTTP/1.1 200 OK");
client.println("Content-Type: text/html");
client.println("Connection: close");
client.println();
client.println("<html><body>");
client.println("ESP32 识别系统");
client.println("查看最新照片
");
client.println("运行识别
");
client.println("</body></html>");
} else {
client.println("HTTP/1.1 404 Not Found");
client.println("Content-Type: text/plain");
client.println("Connection: close");
client.println();
client.println("404 Not Found");
}
delay(1);
client.stop();
}
#endif
/* -------------------------- WiFi连接 -------------------------- */
void connect_wifi() {
if (wifi_connected) return;
Serial.println("\n===== WiFi连接 =====");
oled_print("连接WiFi...", ssid);
WiFi.begin(ssid, password);
int attempt = 0;
while (WiFi.status() != WL_CONNECTED && attempt < 20) {
delay(500);
Serial.print(".");
attempt++;
}
if (WiFi.status() == WL_CONNECTED) {
wifi_connected = true;
Serial.println("\nWiFi连接成功");
Serial.print("IP地址: ");
Serial.println(WiFi.localIP());
oled_print("WiFi已连接", WiFi.localIP().toString().c_str());
#ifdef ENABLE_HTTP_SERVER
server.begin();
Serial.println("HTTP服务启动");
#endif
#ifdef ENABLE_SD_CARD
if (sd_ready) {
sd_log((String("WiFi连接成功 - IP: ") + WiFi.localIP().toString()).c_str());
}
#endif
} else {
Serial.println("\nWiFi连接失败");
oled_print("WiFi连接失败", "请检查配置");
#ifdef ENABLE_SD_CARD
if (sd_ready) {
sd_log("WiFi连接失败");
}
#endif
}
}
/* -------------------------- 初始化和主循环 -------------------------- */
void setup() {
Serial.begin(115200);
Serial.println("Edge Impulse 识别系统启动中");
// 初始化OLED
#ifdef ENABLE_OLED_DISPLAY
Wire.begin(OLED_SDA, OLED_SCL);
if (!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) {
Serial.println(F("SSD1306 OLED显示屏初始化失败"));
} else {
display.clearDisplay();
display.setTextSize(1);
display.setTextColor(SSD1306_WHITE);
display.setCursor(0, 0);
display.println("Edge Impulse");
display.println("识别系统启动中");
display.display();
}
#endif
// 初始化时间
init_time();
// 初始化摄像头
if (!camera_init()) {
oled_print("错误", "摄像头初始化失败");
delay(5000);
}
// 连接WiFi
connect_wifi();
// 启动看门狗
esp_task_wdt_init(10, false);
}
void loop() {
// 喂狗
esp_task_wdt_reset();
// 检查WiFi连接
if (!wifi_connected || WiFi.status() != WL_CONNECTED) {
wifi_connected = false;
connect_wifi();
}
// 处理HTTP请求
#ifdef ENABLE_HTTP_SERVER
if (wifi_connected) {
WiFiClient client = server.available();
if (client) {
handle_client(client);
}
}
#endif
// 主循环逻辑
static unsigned long last_inference_time = 0;
if (millis() - last_inference_time > 10000) { // 每10秒运行一次推理
last_inference_time = millis();
#ifdef ENABLE_INFERENCE
run_inference();
#endif
}
delay(100);
}






#include "camera_pins.h"
#include <WiFi.h>
#include "esp_camera.h"
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
// Edge Impulse模型库(需手动添加到项目目录)
#include "shibie_inferencing.h"
#include "edge-impulse-sdk/dsp/image/image.hpp"
#include "esp_task_wdt.h"
#include "freertos/semphr.h"
#include <SD_MMC.h> // SD卡库(使用SPI或SDMMC接口)
#include <SPIFFS.h>
#include <SD.h> // SPI接口SD卡库(新增)
#include <Time.h> // 时间函数库(新增)
#include "esp_task_wdt.h"
#include "freertos/semphr.h"
// 功能开关
#define ENABLE_INFERENCE 1
#define ENABLE_HTTP_SERVER 1
#define ENABLE_OLED_DISPLAY 1
#define ENABLE_SD_CARD 1 // 启用SD卡功能
#define SUPPORT_OBJECT_DETECTION 0
// 摄像头配置
#define CAMERA_MODEL_AI_THINKER
#define XCLK_FREQ_HZ 2000000 // 降低时钟频率
#define FRAME_SIZE FRAMESIZE_QVGA // 320x240分辨率
#define JPEG_QUALITY 12
#define MAX_CAPTURE_RETRIES 3
// 图像尺寸
#define EI_CAMERA_COLS 320
#define EI_CAMERA_ROWS 240
#define MODEL_INPUT_WIDTH EI_CLASSIFIER_INPUT_WIDTH
#define MODEL_INPUT_HEIGHT EI_CLASSIFIER_INPUT_HEIGHT
// OLED配置
#define OLED_WIDTH 128
#define OLED_HEIGHT 64
#define OLED_RESET -1
#define OLED_SDA 21
#define OLED_SCL 22
Adafruit_SSD1306 display(OLED_WIDTH, OLED_HEIGHT, &Wire, OLED_RESET);
// WiFi配置
const char* ssid = "88888888";
const char* password = "11111111";
// SD卡配置(SPI接口)
#define SD_MOUNT_POINT "/sdcard"
#define SD_PIN_CS 15 // 改为常用的SPI CS引脚(原5可能冲突)
// 全局变量
WiFiServer server(80);
static bool is_initialised = false;
static bool wifi_connected = false;
static bool sd_ready = false;
uint8_t* model_buf = NULL;
camera_fb_t* fb = NULL;
SemaphoreHandle_t camera_mutex = NULL;
SemaphoreHandle_t sd_mutex = NULL;
// 摄像头配置
static camera_config_t camera_config = {
.pin_pwdn = PWDN_GPIO_NUM,
.pin_reset = RESET_GPIO_NUM,
.pin_xclk = XCLK_GPIO_NUM,
.pin_sscb_sda = SIOD_GPIO_NUM,
.pin_sscb_scl = SIOC_GPIO_NUM,
.pin_d7 = Y9_GPIO_NUM,
.pin_d6 = Y8_GPIO_NUM,
.pin_d5 = Y7_GPIO_NUM,
.pin_d4 = Y6_GPIO_NUM,
.pin_d3 = Y5_GPIO_NUM,
.pin_d2 = Y4_GPIO_NUM,
.pin_d1 = Y3_GPIO_NUM,
.pin_d0 = Y2_GPIO_NUM,
.pin_vsync = VSYNC_GPIO_NUM,
.pin_href = HREF_GPIO_NUM,
.pin_pclk = PCLK_GPIO_NUM,
.xclk_freq_hz = XCLK_FREQ_HZ,
.ledc_timer = LEDC_TIMER_0,
.ledc_channel = LEDC_CHANNEL_0,
.pixel_format = PIXFORMAT_JPEG,
.frame_size = FRAME_SIZE,
.jpeg_quality = JPEG_QUALITY,
.fb_count = 1,
.fb_location = CAMERA_FB_IN_PSRAM,
.grab_mode = CAMERA_GRAB_WHEN_EMPTY,
};
/* -------------------------- 时间初始化 -------------------------- */
// 初始化时间(使用NTP同步)
void init_time() {
configTime(8 * 3600, 0, "pool.ntp.org", "time.nist.gov"); // 北京时间(UTC+8)
Serial.println("等待时间同步...");
time_t now = time(nullptr);
int retry_count = 0;
// 等待时间同步,超时则跳过
while (now < 1609459200 && retry_count < 10) { // 等待同步到2021年之后
delay(1000);
Serial.print(".");
now = time(nullptr);
retry_count++;
}
if (now >= 1609459200) {
Serial.println("\n时间同步完成");
struct tm* tm_info = localtime(&now);
char time_str[26];
strftime(time_str, 26, "%Y-%m-%d %H:%M:%S", tm_info);
Serial.print("当前时间: ");
Serial.println(time_str);
} else {
Serial.println("\n时间同步超时,使用默认时间");
}
}
/* -------------------------- SD卡操作 -------------------------- */
#ifdef ENABLE_SD_CARD
bool sd_init() {
sd_mutex = xSemaphoreCreateMutex();
if (!sd_mutex) {
Serial.println("SD卡互斥锁创建失败");
return false;
}
Serial.println("初始化SD卡...");
// 尝试SDMMC模式(仅适用于支持SDMMC的板子)
if (!SD_MMC.begin("/sdcard", true)) { // 1线模式
Serial.println("SDMMC模式失败,尝试SPI模式...");
// SPI模式初始化(低速模式提高兼容性)
if (!SD.begin(SD_PIN_CS, SPI, 1000000)) { // 1MHz低速率
Serial.println("SPI模式初始化失败,检查:");
Serial.println("1. 引脚连接是否正确(CS=" + String(SD_PIN_CS) + ", SCK=18, MOSI=23, MISO=19)");
Serial.println("2. SD卡是否为FAT32格式");
Serial.println("3. 尝试更换SD卡");
return false;
}
}
// 验证SD卡读写功能
File testFile = SD.open("/test.txt", FILE_WRITE);
if (testFile) {
testFile.println("SD卡测试成功");
testFile.close();
// 读取测试
testFile = SD.open("/test.txt", FILE_READ);
if (testFile) {
String content = testFile.readString();
testFile.close();
SD.remove("/test.txt"); // 删除测试文件
Serial.println("SD卡读写验证成功: " + content);
}
} else {
Serial.println("SD卡写入测试失败");
return false;
}
// 创建目录
if (!SD.exists("/logs")) SD.mkdir("/logs");
if (!SD.exists("/images")) SD.mkdir("/images");
sd_ready = true;
Serial.println("SD卡初始化成功");
return true;
}
// 写入日志到SD卡
void sd_log(const char* message) {
if (!sd_ready) return;
if (xSemaphoreTake(sd_mutex, 1000 / portTICK_PERIOD_MS) != pdTRUE) return;
time_t now = time(nullptr);
struct tm* tm = localtime(&now);
char filename[32];
sprintf(filename, "/logs/%04d%02d%02d.txt",
tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
File file = SD.open(filename, FILE_APPEND);
if (file) {
char timestamp[32];
sprintf(timestamp, "[%02d:%02d:%02d] ",
tm->tm_hour, tm->tm_min, tm->tm_sec);
file.print(timestamp);
file.println(message);
file.close();
}
xSemaphoreGive(sd_mutex);
}
// 保存图像到SD卡
bool sd_save_image(const uint8_t* data, size_t size) {
if (!sd_ready || !data || size == 0) return false;
if (xSemaphoreTake(sd_mutex, 1000 / portTICK_PERIOD_MS) != pdTRUE) return false;
time_t now = time(nullptr);
struct tm* tm = localtime(&now);
char filename[64];
sprintf(filename, "/images/img_%04d%02d%02d_%02d%02d%02d.jpg",
tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday,
tm->tm_hour, tm->tm_min, tm->tm_sec);
File file = SD.open(filename, FILE_WRITE);
if (!file) {
xSemaphoreGive(sd_mutex);
return false;
}
bool success = (file.write(data, size) == size);
file.close();
xSemaphoreGive(sd_mutex);
if (success) {
Serial.printf("图像保存到: %s\n", filename);
sd_log((String("图像保存: ") + filename).c_str());
}
return success;
}
// 从SD卡读取图像
size_t sd_read_image(const char* filename, uint8_t* buffer, size_t max_size) {
if (!sd_ready || !buffer || max_size == 0) return 0;
if (xSemaphoreTake(sd_mutex, 1000 / portTICK_PERIOD_MS) != pdTRUE) return 0;
File file = SD.open(filename, FILE_READ);
if (!file) {
xSemaphoreGive(sd_mutex);
return 0;
}
size_t size = file.size();
if (size > max_size) size = max_size;
size_t read = file.read(buffer, size);
file.close();
xSemaphoreGive(sd_mutex);
return read;
}
#endif
/* -------------------------- 图像处理函数 -------------------------- */
bool convert_jpeg_to_rgb888_from_sd(const char* jpeg_path, uint8_t* rgb_data, size_t rgb_size) {
if (!sd_ready || !rgb_data) return false;
File file = SD.open(jpeg_path, FILE_READ);
if (!file) return false;
size_t jpeg_size = file.size();
if (jpeg_size == 0 || jpeg_size > 1024*200) {
file.close();
return false;
}
uint8_t* jpeg_buf = (uint8_t*)ps_malloc(jpeg_size);
if (!jpeg_buf) {
file.close();
return false;
}
size_t read = file.read(jpeg_buf, jpeg_size);
file.close();
if (read != jpeg_size) {
free(jpeg_buf);
return false;
}
bool success = fmt2rgb888(jpeg_buf, jpeg_size, PIXFORMAT_JPEG, rgb_data);
free(jpeg_buf);
return success;
}
// 备用缩放函数(当默认缩放失败时使用)
bool backup_resize_rgb888(const uint8_t* src, uint32_t src_width, uint32_t src_height,
uint8_t* dst, uint32_t dst_width, uint32_t dst_height) {
if (!src || !dst || src_width == 0 || src_height == 0 || dst_width == 0 || dst_height == 0) {
return false;
}
if (((uintptr_t)dst) % 4 != 0) {
Serial.println("错误:输出缓冲区未4字节对齐");
return false;
}
float x_ratio = (float)src_width / (float)dst_width;
float y_ratio = (float)src_height / (float)dst_height;
for (uint32_t y = 0; y < dst_height; y++) {
for (uint32_t x = 0; x < dst_width; x++) {
float src_x = x * x_ratio;
float src_y = y * y_ratio;
uint32_t x1 = (uint32_t)src_x;
uint32_t y1 = (uint32_t)src_y;
uint32_t x2 = (x1 < src_width - 1) ? x1 + 1 : x1;
uint32_t y2 = (y1 < src_height - 1) ? y1 + 1 : y1;
float fx = src_x - x1;
float fy = src_y - y1;
for (uint8_t c = 0; c < 3; c++) {
uint8_t v11 = src[(y1 * src_width + x1) * 3 + c];
uint8_t v12 = src[(y2 * src_width + x1) * 3 + c];
uint8_t v21 = src[(y1 * src_width + x2) * 3 + c];
uint8_t v22 = src[(y2 * src_width + x2) * 3 + c];
uint8_t v1 = (uint8_t)((1 - fx) * v11 + fx * v21);
uint8_t v2 = (uint8_t)((1 - fx) * v12 + fx * v22);
dst[(y * dst_width + x) * 3 + c] = (uint8_t)((1 - fy) * v1 + fy * v2);
}
}
}
return true;
}
/* -------------------------- 工具函数 -------------------------- */
void oled_print(const char* line1, const char* line2 = "", const char* line3 = "") {
#ifdef ENABLE_OLED_DISPLAY
display.clearDisplay();
display.setTextSize(1);
display.setTextColor(SSD1306_WHITE);
display.setCursor(0, 0);
display.println(line1);
if (strlen(line2) > 0) {
display.setCursor(0, 16);
display.println(line2);
}
if (strlen(line3) > 0) {
display.setCursor(0, 32);
display.println(line3);
}
display.display();
#endif
}
/* -------------------------- 摄像头操作 -------------------------- */
bool camera_init() {
if (is_initialised) return true;
Serial.println("\n===== 摄像头初始化 =====");
camera_mutex = xSemaphoreCreateMutex();
#ifdef ENABLE_SD_CARD
// 初始化SD卡(允许失败,不影响主功能)
if (!sd_init()) {
Serial.println("SD卡初始化失败,继续启动系统");
}
#endif
gpio_uninstall_isr_service();
esp_err_t err;
int init_retry = 0;
while (init_retry < 3) {
err = esp_camera_init(&camera_config);
if (err == ESP_OK) break;
Serial.printf("摄像头初始化失败(重试%d): %s\n",
init_retry+1, esp_err_to_name(err));
#ifdef ENABLE_SD_CARD
if (sd_ready) {
sd_log((String("摄像头初始化失败: ") + esp_err_to_name(err)).c_str());
}
#endif
if (err == ESP_ERR_INVALID_STATE) {
gpio_uninstall_isr_service();
}
init_retry++;
vTaskDelay(1000 / portTICK_PERIOD_MS);
}
if (err != ESP_OK) {
Serial.println("摄像头初始化彻底失败");
#ifdef ENABLE_SD_CARD
if (sd_ready) {
sd_log("摄像头初始化彻底失败");
}
#endif
return false;
}
sensor_t* s = esp_camera_sensor_get();
if (s) {
s->set_vflip(s, 1);
s->set_hmirror(s, 1);
s->set_awb_gain(s, 1);
}
size_t model_size = MODEL_INPUT_WIDTH * MODEL_INPUT_HEIGHT * 3;
model_size = (model_size + 3) & ~3; // 4字节对齐
model_buf = (uint8_t*)aligned_alloc(4, model_size);
if (!model_buf) {
Serial.println("模型缓冲区分配失败");
camera_deinit();
return false;
}
Serial.printf("模型缓冲区: %zu bytes\n", model_size);
is_initialised = true;
Serial.println("摄像头初始化成功");
#ifdef ENABLE_SD_CARD
if (sd_ready) {
sd_log("摄像头初始化成功");
}
#endif
return true;
}
void camera_deinit() {
if (!is_initialised) return;
esp_camera_deinit();
if (model_buf) free(model_buf);
model_buf = NULL;
is_initialised = false;
}
bool camera_capture_to_sd() {
if (!is_initialised) return false;
if (xSemaphoreTake(camera_mutex, 2000 / portTICK_PERIOD_MS) != pdTRUE) {
return false;
}
bool success = false;
fb = esp_camera_fb_get();
if (fb && fb->len > 1024) {
#ifdef ENABLE_SD_CARD
if (sd_ready) {
success = sd_save_image(fb->buf, fb->len);
}
#endif
esp_camera_fb_return(fb);
fb = NULL;
}
xSemaphoreGive(camera_mutex);
return success;
}
bool process_image_from_sd(uint8_t* model_buf, size_t model_size) {
if (!sd_ready || !model_buf) return false;
File dir = SD.open("/images");
if (!dir) return false;
File latest_file;
time_t latest_time = 0;
while (File file = dir.openNextFile()) {
if (!file.isDirectory() && strstr(file.name(), ".jpg")) {
time_t t = file.getLastWrite();
if (t > latest_time) {
latest_time = t;
if (latest_file) latest_file.close();
latest_file = file;
} else {
file.close();
}
}
}
dir.close();
if (!latest_file) return false;
const char* img_path = latest_file.path();
latest_file.close();
size_t rgb_size = EI_CAMERA_COLS * EI_CAMERA_ROWS * 3;
uint8_t* rgb_buf = (uint8_t*)ps_malloc(rgb_size);
if (!rgb_buf) return false;
bool success = false;
if (convert_jpeg_to_rgb888_from_sd(img_path, rgb_buf, rgb_size)) {
success = ei::image::processing::crop_and_interpolate_rgb888(
rgb_buf, EI_CAMERA_COLS, EI_CAMERA_ROWS,
model_buf, MODEL_INPUT_WIDTH, MODEL_INPUT_HEIGHT
);
if (!success) {
success = backup_resize_rgb888(
rgb_buf, EI_CAMERA_COLS, EI_CAMERA_ROWS,
model_buf, MODEL_INPUT_WIDTH, MODEL_INPUT_HEIGHT
);
}
}
free(rgb_buf);
return success;
}
/* -------------------------- 推理核心 -------------------------- */
#ifdef ENABLE_INFERENCE
void run_inference() {
if (!is_initialised || !model_buf) {
oled_print("未就绪", "设备异常");
return;
}
Serial.println("\n===== 开始推理 =====");
oled_print("识别中...");
if (!camera_capture_to_sd()) {
oled_print("处理失败", "图像捕获失败");
#ifdef ENABLE_SD_CARD
if (sd_ready) {
sd_log("图像捕获失败");
}
#endif
return;
}
if (!process_image_from_sd(model_buf, MODEL_INPUT_WIDTH * MODEL_INPUT_HEIGHT * 3)) {
oled_print("处理失败", "图像转换失败");
#ifdef ENABLE_SD_CARD
if (sd_ready) {
sd_log("图像转换失败");
}
#endif
return;
}
ei::signal_t signal;
signal.total_length = MODEL_INPUT_WIDTH * MODEL_INPUT_HEIGHT;
signal.get_data = [](size_t offset, size_t length, float* out) {
size_t pixel_ix = offset * 3;
if (pixel_ix + 3*length > MODEL_INPUT_WIDTH * MODEL_INPUT_HEIGHT * 3) {
Serial.println("错误:模型输入数据越界");
return -1;
}
for (size_t i = 0; i < length; i++) {
uint8_t r = model_buf[pixel_ix];
uint8_t g = model_buf[pixel_ix + 1];
uint8_t b = model_buf[pixel_ix + 2];
out[i] = (r - 127.5f) / 127.5f;
out[i + length] = (g - 127.5f) / 127.5f;
out[i + length * 2] = (b - 127.5f) / 127.5f;
pixel_ix += 3;
}
return 0;
};
ei_impulse_result_t result;
memset(&result, 0, sizeof(result));
EI_IMPULSE_ERROR err = run_classifier(&signal, &result, false);
if (err != EI_IMPULSE_OK) {
Serial.printf("推理失败: %d\n", err);
oled_print("推理错误", String(err).c_str());
#ifdef ENABLE_SD_CARD
if (sd_ready) {
sd_log((String("推理失败: ") + String(err)).c_str());
}
#endif
return;
}
float max_prob = 0;
const char* max_label = "未知";
for (uint16_t i = 0; i < EI_CLASSIFIER_LABEL_COUNT; i++) {
if (result.classification[i].value > max_prob) {
max_prob = result.classification[i].value;
max_label = ei_classifier_inferencing_categories[i];
}
}
Serial.printf("识别结果: %s (%.1f%%)\n", max_label, max_prob*100);
oled_print("识别为:", max_label, (String(max_prob*100, 1) + "%").c_str());
#ifdef ENABLE_SD_CARD
if (sd_ready) {
sd_log((String("识别结果: ") + max_label + " (" + String(max_prob*100) + "%)").c_str());
}
#endif
}
#endif
/* -------------------------- HTTP服务 -------------------------- */
#ifdef ENABLE_HTTP_SERVER
void handle_client(WiFiClient& client) {
String req = client.readStringUntil('\n');
req.trim();
Serial.println("HTTP请求: " + req);
if (req.startsWith("GET /photo")) {
client.println("HTTP/1.1 200 OK");
client.println("Content-Type: image/jpeg");
client.println("Connection: close");
client.println();
#ifdef ENABLE_SD_CARD
if (sd_ready) {
File dir = SD.open("/images");
File latest_file;
while (File file = dir.openNextFile()) {
if (!file.isDirectory() && strstr(file.name(), ".jpg")) {
if (!latest_file || file.getLastWrite() > latest_file.getLastWrite()) {
if (latest_file) latest_file.close();
latest_file = file;
} else {
file.close();
}
}
}
dir.close();
if (latest_file) {
size_t fileSize = latest_file.size();
const size_t bufferSize = 1024;
uint8_t buffer[bufferSize];
while (fileSize > 0) {
size_t read = latest_file.read(buffer, min(bufferSize, fileSize));
client.write(buffer, read);
fileSize -= read;
}
latest_file.close();
} else {
client.print("无图像文件");
}
} else {
client.print("SD卡未就绪");
}
#else
client.print("SD卡功能未启用");
#endif
} else if (req.startsWith("GET /infer")) {
client.println("HTTP/1.1 200 OK");
client.println("Content-Type: text/plain");
client.println("Connection: close");
client.println();
run_inference();
client.println("推理已触发");
} else if (req.startsWith("GET /")) {
client.println("HTTP/1.1 200 OK");
client.println("Content-Type: text/html");
client.println("Connection: close");
client.println();
client.println("<html><body>");
client.println("ESP32 识别系统");
client.println("查看最新照片
");
client.println("运行识别
");
client.println("</body></html>");
} else {
client.println("HTTP/1.1 404 Not Found");
client.println("Content-Type: text/plain");
client.println("Connection: close");
client.println();
client.println("404 Not Found");
}
delay(1);
client.stop();
}
#endif
/* -------------------------- WiFi连接 -------------------------- */
void connect_wifi() {
if (wifi_connected) return;
Serial.println("\n===== WiFi连接 =====");
oled_print("连接WiFi...", ssid);
WiFi.begin(ssid, password);
int attempt = 0;
while (WiFi.status() != WL_CONNECTED && attempt < 20) {
delay(500);
Serial.print(".");
attempt++;
}
if (WiFi.status() == WL_CONNECTED) {
wifi_connected = true;
Serial.println("\nWiFi连接成功");
Serial.print("IP地址: ");
Serial.println(WiFi.localIP());
oled_print("WiFi已连接", WiFi.localIP().toString().c_str());
#ifdef ENABLE_HTTP_SERVER
server.begin();
Serial.println("HTTP服务启动");
#endif
#ifdef ENABLE_SD_CARD
if (sd_ready) {
sd_log((String("WiFi连接成功 - IP: ") + WiFi.localIP().toString()).c_str());
}
#endif
} else {
Serial.println("\nWiFi连接失败");
oled_print("WiFi连接失败", "请检查配置");
#ifdef ENABLE_SD_CARD
if (sd_ready) {
sd_log("WiFi连接失败");
}
#endif
}
}
/* -------------------------- 初始化和主循环 -------------------------- */
void setup() {
Serial.begin(115200);
Serial.println("Edge Impulse 识别系统启动中");
// 初始化OLED
#ifdef ENABLE_OLED_DISPLAY
Wire.begin(OLED_SDA, OLED_SCL);
if (!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) {
Serial.println(F("SSD1306 OLED显示屏初始化失败"));
} else {
display.clearDisplay();
display.setTextSize(1);
display.setTextColor(SSD1306_WHITE);
display.setCursor(0, 0);
display.println("Edge Impulse");
display.println("识别系统启动中");
display.display();
}
#endif
// 初始化时间
init_time();
// 初始化摄像头
if (!camera_init()) {
oled_print("错误", "摄像头初始化失败");
delay(5000);
}
// 连接WiFi
connect_wifi();
// 启动看门狗
esp_task_wdt_init(10, false);
}
void loop() {
// 喂狗
esp_task_wdt_reset();
// 检查WiFi连接
if (!wifi_connected || WiFi.status() != WL_CONNECTED) {
wifi_connected = false;
connect_wifi();
}
// 处理HTTP请求
#ifdef ENABLE_HTTP_SERVER
if (wifi_connected) {
WiFiClient client = server.available();
if (client) {
handle_client(client);
}
}
#endif
// 主循环逻辑
static unsigned long last_inference_time = 0;
if (millis() - last_inference_time > 10000) { // 每10秒运行一次推理
last_inference_time = millis();
#ifdef ENABLE_INFERENCE
run_inference();
#endif
}
delay(100);
}


