上一篇文章 ←查看上一篇文章内容
上一篇文章中写了思路以及实现代码,但是我忘记了一个重要的问题,那就是如果答案太长了,就这么一小块160*80的屏幕怎么回显全部答案呢?
当然,拍摄也不是手动的,就很无厘头
所以此次增加三个按钮,拍摄 上翻页 下翻页

硬件连接:
-
ESP32 和 ST7735S 0.96英寸TFT彩屏 的连接方式如下:
- TFT_MISO -> 无需连接(SPI不需要)
- TFT_MOSI -> GPIO23
- TFT_SCLK -> GPIO18
- TFT_CS -> GPIO5
- TFT_DC -> GPIO16
- TFT_RST -> GPIO17
- 3.3V -> 3.3V
- GND -> GND
-
按钮连接:
- 上翻页按钮:一端连接到GPIO34,另一端连接到GND。
- 下翻页按钮:一端连接到GPIO35,另一端连接到GND。
- 拍摄按钮:一端连接到GPIO32,另一端连接到GND。
安装必要库:
- Adafruit_ST7735:打开Arduino IDE,进入
工具->库管理,搜索Adafruit_ST7735并安装。 - Adafruit_GFX:打开Arduino IDE,进入
工具->库管理,搜索Adafruit_GFX并安装。
代码:
#include <WiFi.h>
#include <HTTPClient.h>
#include <ArduinoJson.h>
#include <Adafruit_GFX.h>
#include <Adafruit_ST7735.h>
// WiFi
const char* ssid = "SSID";
const char* password = "PASSWORD";
// 通义千问API设置
const char* api_url = "https://dashscope.aliyuncs.com/compatible-mode/v1/chat/completions";
const char* api_key = "写自己的";
// 摄像头配置
#define PWDN_GPIO_NUM 32
#define RESET_GPIO_NUM -1
#define XCLK_GPIO_NUM 0
#define SIOD_GPIO_NUM 26
#define SIOC_GPIO_NUM 27
#define Y9_GPIO_NUM 35
#define Y8_GPIO_NUM 34
#define Y7_GPIO_NUM 39
#define Y6_GPIO_NUM 36
#define Y5_GPIO_NUM 21
#define Y4_GPIO_NUM 19
#define Y3_GPIO_NUM 18
#define Y2_GPIO_NUM 5
#define VSYNC_GPIO_NUM 25
#define HREF_GPIO_NUM 23
#define PCLK_GPIO_NUM 22
// TFT LCD配置
#define TFT_CS 5
#define TFT_DC 16
#define TFT_RST 17
#define TFT_MOSI 23
#define TFT_SCLK 18
// 按钮配置
#define UP_BUTTON_PIN 34
#define DOWN_BUTTON_PIN 35
#define SHOOT_BUTTON_PIN 32
Adafruit_ST7735 tft = Adafruit_ST7735(TFT_CS, TFT_DC, TFT_MOSI, TFT_SCLK, TFT_RST);
int currentLine = 0;
int maxLines = 10; // 每页显示的最大行数
String answer = "";
void setup() {
// 初始化串口通信
Serial.begin(115200);
// 初始化TFT LCD
tft.initR(INITR_BLACKTAB); // 初始化屏幕
tft.setRotation(1); // 设置屏幕方向
tft.fillScreen(ST7735_BLACK);
// 初始化摄像头
camera_config_t config;
config.ledc_channel = LEDC_CHANNEL_0;
config.ledc_timer = LEDC_TIMER_0;
config.pin_d0 = Y2_GPIO_NUM;
config.pin_d1 = Y3_GPIO_NUM;
config.pin_d2 = Y4_GPIO_NUM;
config.pin_d3 = Y5_GPIO_NUM;
config.pin_d4 = Y6_GPIO_NUM;
config.pin_d5 = Y7_GPIO_NUM;
config.pin_d6 = Y8_GPIO_NUM;
config.pin_d7 = Y9_GPIO_NUM;
config.pin_xclk = XCLK_GPIO_NUM;
config.pin_pclk = PCLK_GPIO_NUM;
config.pin_vsync = VSYNC_GPIO_NUM;
config.pin_href = HREF_GPIO_NUM;
config.pin_sscb_sda = SIOD_GPIO_NUM;
config.pin_sscb_scl = SIOC_GPIO_NUM;
config.pin_pwdn = PWDN_GPIO_NUM;
config.pin_reset = RESET_GPIO_NUM;
config.xclk_freq_hz = 20000000;
config.pixel_format = PIXFORMAT_JPEG;
config.frame_size = FRAMESIZE_UXGA; // 合适的分辨率
config.jpeg_quality = 12; // JPEG质量
config.fb_count = 1;
// 初始化摄像头
esp_err_t err = esp_camera_init(&config);
if (err != ESP_OK) {
Serial.printf("Camera init failed with error 0x%x", err);
return;
}
// 连接到WiFi
WiFi.begin(ssid, password);
while (WiFi.status() != WL_CONNECTED) {
delay(1000);
Serial.println("Connecting to WiFi...");
}
Serial.println("Connected to WiFi");
// 设置HTTP客户端
HTTPClient http;
http.begin(api_url);
http.addHeader("Content-Type", "application/json");
http.addHeader("Authorization", "Bearer " + String(api_key));
// 初始化按钮
pinMode(UP_BUTTON_PIN, INPUT_PULLUP);
pinMode(DOWN_BUTTON_PIN, INPUT_PULLUP);
pinMode(SHOOT_BUTTON_PIN, INPUT_PULLUP);
}
void loop() {
// 处理按钮输入
handleButtons();
}
void handleButtons() {
static unsigned long lastDebounceTimeUp = 0;
static unsigned long lastDebounceTimeDown = 0;
static unsigned long lastDebounceTimeShoot = 0;
static int lastButtonStateUp = HIGH;
static int lastButtonStateDown = HIGH;
static int lastButtonStateShoot = HIGH;
static int debounceDelay = 50;
int readingUp = digitalRead(UP_BUTTON_PIN);
int readingDown = digitalRead(DOWN_BUTTON_PIN);
int readingShoot = digitalRead(SHOOT_BUTTON_PIN);
if (readingUp != lastButtonStateUp) {
lastDebounceTimeUp = millis();
}
if (readingDown != lastButtonStateDown) {
lastDebounceTimeDown = millis();
}
if (readingShoot != lastButtonStateShoot) {
lastDebounceTimeShoot = millis();
}
if ((millis() - lastDebounceTimeUp) > debounceDelay) {
if (readingUp != lastButtonStateUp) {
if (readingUp == LOW) {
currentLine--;
if (currentLine < 0) {
currentLine = 0;
}
displayAnswer();
}
lastButtonStateUp = readingUp;
}
}
if ((millis() - lastDebounceTimeDown) > debounceDelay) {
if (readingDown != lastButtonStateDown) {
if (readingDown == LOW) {
currentLine++;
if (currentLine * maxLines >= answer.length()) {
currentLine = answer.length() / maxLines;
}
displayAnswer();
}
lastButtonStateDown = readingDown;
}
}
if ((millis() - lastDebounceTimeShoot) > debounceDelay) {
if (readingShoot != lastButtonStateShoot) {
if (readingShoot == LOW) {
takePhotoAndProcess();
}
lastButtonStateShoot = readingShoot;
}
}
}
void takePhotoAndProcess() {
// 拍摄照片
camera_fb_t * fb = esp_camera_fb_get();
if (!fb) {
Serial.println("Camera capture failed");
return;
}
// 将照片转换为Base64编码
size_t encodedLen = base64_encoded_length(fb->len);
char *encodedData = (char *)malloc(encodedLen + 1);
base64_encode(encodedData, (const uint8_t *)fb->buf, fb->len);
// 构造JSON请求体
String payload = "{\"messages\":[{\"role\":\"user\",\"content\":\" + ")\"}]}";
// 发送POST请求
HTTPClient http;
http.begin(api_url);
http.addHeader("Content-Type", "application/json");
http.addHeader("Authorization", "Bearer " + String(api_key));
int httpResponseCode = http.POST(payload);
if (httpResponseCode > 0) {
// 读取响应
String response = http.getString();
Serial.println(response);
// 解析JSON响应
DynamicJsonDocument doc(1024);
DeserializationError error = deserializeJson(doc, response);
if (!error) {
answer = doc["choices"][0]["message"]["content"].as<String>(); // 根据实际API返回的字段名称进行调整
Serial.println("Answer: " + answer);
// 初始化显示
currentLine = 0;
displayAnswer();
} else {
Serial.println("Error parsing JSON");
}
} else {
Serial.println("Error on sending POST: " + String(httpResponseCode));
}
// 释放资源
esp_camera_fb_return(fb);
free(encodedData);
}
void displayAnswer() {
tft.fillScreen(ST7735_BLACK);
tft.setTextColor(ST7735_WHITE);
tft.setTextSize(1); // 设置字体大小
int y = 0;
int lines = 0;
int startLine = currentLine * maxLines;
int endLine = min(startLine + maxLines, answer.length());
for (int i = startLine; i < endLine; i++) {
if (answer[i] == '\n') {
lines++;
y += 8; // 每行的高度
} else {
tft.drawChar(i - startLine, 0, y, answer[i], ST7735_WHITE, ST7735_BLACK, 1);
}
}
}
详细解释
-
变量声明:
lastDebounceTimeUp,lastDebounceTimeDown,lastDebounceTimeShoot:记录上次检测到按钮状态变化的时间。lastButtonStateUp,lastButtonStateDown,lastButtonStateShoot:记录上次的按钮状态。debounceDelay:去抖延迟时间,单位为ms。
-
读取按钮状态:
digitalRead(UP_BUTTON_PIN):读取上翻页按钮的状态。digitalRead(DOWN_BUTTON_PIN):读取下翻页按钮的状态。digitalRead(SHOOT_BUTTON_PIN):读取拍摄按钮的状态。
-
检测状态变化:
- 如果当前读取的按钮状态与上次不同,则更新
lastDebounceTime。 - 如果从上次状态变化到现在的时间超过了
debounceDelay,则检查按钮状态是否真的发生了变化。 - 如果按钮状态确实发生了变化且按钮被按下(即读取到LOW),则执行相应的动作。
- 如果当前读取的按钮状态与上次不同,则更新
-
上翻页按钮:
- 减少
currentLine的值,确保不会小于0。 - 调用
displayAnswer()函数更新屏幕显示。
- 减少
-
下翻页按钮:
- 增加
currentLine的值,确保不会超过答案的总行数。 - 调用
displayAnswer()函数更新屏幕显示。
- 增加
-
拍摄按钮:
调用takePhotoAndProcess()函数进行拍摄和处理。 - 显示答案:
displayAnswer()函数负责在TFT LCD上显示当前页的答案。
等我做出来完整的我再发布视频教程!!!
1234

被折叠的 条评论
为什么被折叠?



