#include <SPI.h>
#include <SD.h>
#include <SoftwareSerial.h>
#include <TFT_eSPI.h>
#include <TJpg_Decoder.h>
#include <vector> // 需要安装ArduinoSTL库

// 自定义SPI引脚定义
#define SD_CS_PIN  16  // 片选引脚
#define SD_MOSI_PIN 35 // 主输出从输入
#define SD_MISO_PIN 37 // 主输入从输出
#define SD_SCK_PIN 36  // 时钟引脚

// 定义UART和引脚
#define RADAR_RX_PIN 20
#define RADAR_TX_PIN 21

// 屏幕和图像尺寸
#define IMG_W 240
#define IMG_H 240

// 图像信息结构
struct ImageInfo {
  String filename;
};

// 存储排序后的图像文件列表
std::vector<ImageInfo> imageFiles;

SoftwareSerial mySerial(RADAR_TX_PIN, RADAR_RX_PIN);
File dataFile;
const char* filename = "/data.txt"; // 存储数据的文件名

TFT_eSPI tft = TFT_eSPI();

// 计算帧间隔(毫秒),控制播放速度
const int FRAME_DELAY = 100; // 10 FPS
unsigned long lastFrameTime = 0;

// 双缓冲实现
#define BUFFER_COUNT 2
uint16_t* frameBuffers[BUFFER_COUNT];
bool bufferReady[BUFFER_COUNT] = {false, false};
int currentBuffer = 0;
int nextBuffer = 1;

// TJpg_Decoder 回调函数
bool tft_output(int16_t x, int16_t y, uint16_t w, uint16_t h, uint16_t* bitmap) {
  tft.pushImage(x, y, w, h, bitmap); // 将解码后的像素块推送到屏幕
  return true;                       // 返回true表示渲染成功
}

void setup() {
  Serial.begin(115200);
  Serial.println("Initializing SD card...");

  // 初始化自定义SPI引脚
  SPI.begin(SD_SCK_PIN, SD_MISO_PIN, SD_MOSI_PIN, SD_CS_PIN);

  // 初始化SD卡
  if (!SD.begin(SD_CS_PIN, SPI, 80000000)) {
    Serial.println("SD初始化失败");
    return;
  } else {
    Serial.println("SD初始化成功");
  }

  // 检查文件是否存在,如果不存在则创建并写入标题行
  if (!SD.exists(filename)) {
    dataFile = SD.open(filename, FILE_WRITE);
    if (dataFile) {
      dataFile.println("Timestamp,Data"); // 添加标题行
      dataFile.close();
      Serial.println("Created new file with headers.");
    } else {
      Serial.println("Error creating file!");
    }
  }

  tft.init();
  tft.setRotation(0);
  tft.setSwapBytes(true); // 交换RGB字节顺序
  tft.fillScreen(TFT_BLUE);
  Serial.println("屏幕初始化完成");

  digitalWrite(TFT_BL, HIGH);

    // The jpeg image can be scaled by a factor of 1, 2, 4, or 8
  TJpgDec.setJpgScale(1);

  // The decoder must be given the exact name of the rendering function above
  TJpgDec.setCallback(tft_output);

  // 初始化双缓冲区
  for (int i = 0; i < BUFFER_COUNT; i++) {
    frameBuffers[i] = (uint16_t*)malloc(IMG_W * IMG_H * sizeof(uint16_t));
    if (!frameBuffers[i]) {
      Serial.printf("缓冲区 %d 分配失败\n", i);
      while(1); // 内存分配失败,停止执行
    }
  }

  // 加载并排序图像文件
  readAndSortDirectory("/frames");

  // 预加载第一帧到缓冲区0
  if (!imageFiles.empty()) {
    loadImageToBuffer(imageFiles[0].filename, frameBuffers[0]);
    bufferReady[0] = true;
  }
}

// 将图像加载到指定缓冲区
void loadImageToBuffer(const String& filename, uint16_t* buffer) {
  // 打开文件
  File imageFile = SD.open(filename);
  if (!imageFile) {
    Serial.print("无法打开文件: ");
    Serial.println(filename);
    return;
  }

  // 检测原始图像尺寸
  size_t fileSize = imageFile.size();
  int width = IMG_W;
  int height = IMG_H;

  // 如果文件大小能被2整除(RGB565每像素2字节)
  if (fileSize % 2 == 0) {
    // 尝试确定原始高度(假设宽度为240)
    int possibleHeight = fileSize / (2 * width);
    
    // 如果高度合理(在有效范围内)
    if (possibleHeight > 10 && possibleHeight < 1000) {
      height = possibleHeight;
    }
  }

  Serial.print("加载图像: ");
  Serial.print(filename);
  Serial.print(" (原始尺寸: ");
  Serial.print(width);
  Serial.print("x");
  Serial.print(height);
  Serial.println(")");

  // 读取整个图像数据到缓冲区
  size_t imageDataSize = width * height * sizeof(uint16_t);
  size_t bytesRead = imageFile.read((uint8_t*)buffer, imageDataSize);
  imageFile.close();

  // 验证读取完整性
  if (bytesRead != imageDataSize) {
    Serial.printf("读取不完全: %u/%u 字节\n", bytesRead, imageDataSize);
  }
}

// 显示当前缓冲区的内容
void displayBuffer(const String& filename) {
  if (bufferReady[currentBuffer]) {
    tft.setAddrWindow(0, 0, IMG_W, IMG_H);
    tft.pushImage(0, 0, IMG_W, IMG_H, frameBuffers[currentBuffer]);

    bufferReady[currentBuffer] = false;
    
    // 在右下角显示图像信息
    tft.setTextDatum(BR_DATUM);
    tft.drawString(filename, tft.width() - 5, tft.height() - 5);
  }
}

// 用于排序的比较函数
bool compareImageInfo(const ImageInfo& a, const ImageInfo& b) {
  return a.filename < b.filename;
}

// 读取目录并排序的函数
void readAndSortDirectory(const char* dirPath) {
  imageFiles.clear(); // 清空之前的列表

  File dir = SD.open(dirPath);
  if (!dir) {
    Serial.print("无法打开目录: ");
    Serial.println(dirPath);
    return;
  }

  if (!dir.isDirectory()) {
    Serial.print("不是目录: ");
    Serial.println(dirPath);
    dir.close();
    return;
  }

  // 遍历目录中的文件
  while (true) {
    File entry = dir.openNextFile();
    if (!entry) break; // 没有更多文件

    if (!entry.isDirectory()) {
      // 构造完整路径
      String fullPath;
      if (String(dirPath) == "/") {
        fullPath = String("/") + entry.name(); // 根目录处理
      } else {
        fullPath = String(dirPath) + "/" + entry.name();
      }

      // 添加到vector
      ImageInfo info;
      info.filename = fullPath;
      imageFiles.push_back(info);
    }
    entry.close();
  }
  dir.close();

  // 按文件名排序
  std::sort(imageFiles.begin(), imageFiles.end(), compareImageInfo);
  
  Serial.print("找到 ");
  Serial.print(imageFiles.size());
  Serial.println(" 个文件:");
}

void loop() {
  static unsigned long lastChange = 0;
  static size_t currentIndex = 0;
  
  if (!imageFiles.empty() && millis() - lastChange >= 10) {

    uint32_t t = millis();


    TJpgDec.drawSdJpg(0, 0, imageFiles[currentIndex].filename);


    // // 显示当前缓冲区
    // displayBuffer(imageFiles[currentIndex].filename);
    
    // // 计算下一帧索引
    size_t nextIndex = (currentIndex + 1) % imageFiles.size();
    
    // // 预加载下一帧到后台缓冲区
    // if (!bufferReady[nextBuffer]) {
    //   loadImageToBuffer(imageFiles[nextIndex].filename, frameBuffers[nextBuffer]);
    //   bufferReady[nextBuffer] = true;
    // }
    
    // 切换缓冲区
    currentBuffer = nextBuffer;
    nextBuffer = (nextBuffer + 1) % BUFFER_COUNT;
    currentIndex = nextIndex;
    lastChange = millis();

   t = millis() - t;
    Serial.print("显示耗时: ");
    Serial.print(t); 
    Serial.println(" ms");


  }
}
最后修改:2025 年 06 月 04 日 06 : 09 PM
如果觉得我的文章对你有用,请随意赞赏