#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
© 允许规范转载