#define AT __FILE__ ":" TOSTRING(__LINE__)

本文介绍了如何在C语言中通过宏定义`STRINGIFY`和`TOSTRING`来增强调试信息的详细程度。通过将当前行号转换为字符串并附加到错误消息中,开发者能够更轻松地定位代码中的错误位置。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

#define AT __FILE__ ":" TOSTRING(__LINE__)

有时候为了增加调试程序信息具体,加上:
#define STRINGIFY(x) #x
#define TOSTRING(x) STRINGIFY(x)
#define AT __FILE__ ":" TOSTRING(__LINE__)

example.c:
#include <stdio.h>
#define STRINGIFY(x) #x
#define TOSTRING(x) STRINGIFY(x)
#define AT __FILE__ ":" TOSTRING(__LINE__)
void error(const char *location, const char *msg)
{
  printf("Error at %s: %s\n", location, msg);
}
int main(int , char**)
{
  error(AT, "fake error");
  return 0;
}

运行结果:
$./example
Error at example.c:12: fake error

#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("<h1>ESP32 识别系统</h1>"); client.println("<a href=\"/photo\">查看最新照片</a><br>"); client.println("<a href=\"/infer\">运行识别</a><br>"); 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); }
07-22
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值