初始化

This commit is contained in:
2026-05-01 11:13:45 +08:00
commit bb1d10615e
99 changed files with 48378 additions and 0 deletions
+695
View File
@@ -0,0 +1,695 @@
#include "Attendance.h"
#include "Delay.h"
#include "Debug.h"
#include "esp8266.h" // ✨ 添加ESP8266头文件
#include <string.h>
#include <stdlib.h> // ← 添加atoi函数声明
// 配置参数
#define MAX_ATTENDANCE_RECORDS 100 // 最大记录数量
#define MIN_CONFIDENCE_THRESHOLD 0.75 // 最小置信度阈值
#define MAX_TIME_SLOT_COUNT 10 // 最大时间段数量
#define MAX_CACHED_IDS 50 // 最大缓存学号数量(从服务器下载)
// 时间段配置(动态从服务器更新)
static TimeSlot time_slots[MAX_TIME_SLOT_COUNT];
static uint8_t time_slot_count = 0; // 当前有效时间段数量
// 今日已签到学号缓存(从服务器下载)
static char cached_student_ids[MAX_CACHED_IDS][20];
static uint8_t cached_id_count = 0;
// 默认时间段配置(在服务器配置下载失败时使用)
static const TimeSlot default_time_slots[] = {
{1, 8, 0, 9, 30, "Morning Check-in"}, // 上午签到 08:00-09:30
{1, 12, 0, 13, 0, "Noon Check-out"}, // 中午签退 12:00-13:00
};
#define DEFAULT_TIME_SLOT_COUNT 2
// 当前时间(由外部更新,如果有RTC则从RTC读取)
static uint8_t current_hour = 8;
static uint8_t current_minute = 30;
// 内部存储
static AttendanceRecord attendance_records[MAX_ATTENDANCE_RECORDS];
static uint16_t record_count = 0;
static uint32_t total_attendance_count = 0; // 总考勤人次
/**
* @brief 初始化考勤模块
*/
void Attendance_Init(void)
{
record_count = 0;
total_attendance_count = 0;
current_hour = 8;
current_minute = 0;
memset(attendance_records, 0, sizeof(attendance_records));
// 初始化为默认时间段配置
memcpy(time_slots, default_time_slots, sizeof(default_time_slots));
time_slot_count = DEFAULT_TIME_SLOT_COUNT;
// 清空缓存
cached_id_count = 0;
memset(cached_student_ids, 0, sizeof(cached_student_ids));
DEBUG_INFO("[Attendance] Initialized with time slot mode");
}
/**
* @brief 清空今日签到缓存
*/
void Attendance_ClearCache(void)
{
cached_id_count = 0;
memset(cached_student_ids, 0, sizeof(cached_student_ids));
DEBUG_INFO("[Attendance] Cache cleared");
}
/**
* @brief 添加学号到缓存
*/
void Attendance_AddToCache(const char* student_id)
{
if (cached_id_count >= MAX_CACHED_IDS) {
DEBUG_ERROR("[Attendance] Cache full, cannot add more IDs");
return;
}
// 检查是否已存在
for (uint8_t i = 0; i < cached_id_count; i++) {
if (strcmp(cached_student_ids[i], student_id) == 0) {
return; // 已存在,不重复添加
}
}
strncpy(cached_student_ids[cached_id_count], student_id, 19);
cached_student_ids[cached_id_count][19] = '\0';
cached_id_count++;
DEBUG_PRINT("[Attendance] Added to cache: %s (total: %d)\r\n", student_id, cached_id_count);
}
/**
* @brief 在本地缓存中查重
* @retval 1=在缓存中找到(重复), 0=未找到
*/
uint8_t Attendance_IsDuplicateInCache(const char* student_id)
{
for (uint8_t i = 0; i < cached_id_count; i++) {
if (strcmp(cached_student_ids[i], student_id) == 0) {
DEBUG_PRINT("[Attendance] Found in cache: %s\r\n", student_id);
return 1;
}
}
return 0;
}
/**
* @brief 从服务器加载今日已签到记录到缓存
* @retval 加载的记录数量
*/
uint8_t Attendance_LoadTodayRecordsFromServer(void)
{
char slot_start[8];
char slot_end[8];
char records_buffer[512];
// 获取当前时段时间
Attendance_GetCurrentSlotTime(slot_start, slot_end);
DEBUG_PRINT("[Attendance] Loading today's records for slot %s-%s\r\n", slot_start, slot_end);
// 清空旧缓存
Attendance_ClearCache();
// 从服务器下载
uint8_t count = ESP8266_DownloadTodayRecords(slot_start, slot_end, records_buffer, sizeof(records_buffer));
if (count == 0) {
DEBUG_INFO("[Attendance] No records to load or download failed");
return 0;
}
// 解析学号列表 (格式: "1","2","3")
char* ptr = records_buffer;
char student_id[20];
uint8_t loaded = 0;
while (*ptr != '\0' && loaded < MAX_CACHED_IDS) {
// 跳过引号和逗号
while (*ptr == '"' || *ptr == ',' || *ptr == ' ') ptr++;
if (*ptr == '\0') break;
// 提取学号
char* id_start = ptr;
while (*ptr != '"' && *ptr != ',' && *ptr != '\0') ptr++;
uint8_t len = ptr - id_start;
if (len > 0 && len < 20) {
strncpy(student_id, id_start, len);
student_id[len] = '\0';
Attendance_AddToCache(student_id);
loaded++;
}
}
DEBUG_PRINT("[Attendance] Loaded %d records from server\r\n", loaded);
return loaded;
}
/**
* @brief 更新时间段配置(从服务器下载后调用)
* @param slots 新的时间段配置数组
* @param count 时间段数量
*/
void Attendance_UpdateTimeSlots(TimeSlot* slots, uint8_t count)
{
if (count > MAX_TIME_SLOT_COUNT) {
DEBUG_PRINT("[Attendance] Too many time slots: %d (max: %d)\r\n", count, MAX_TIME_SLOT_COUNT);
return;
}
// 更新时间段配置
memcpy(time_slots, slots, count * sizeof(TimeSlot));
time_slot_count = count;
DEBUG_PRINT("[Attendance] Time slots updated: %d slots loaded\r\n", count);
// 调试:打印所有时间段
for (uint8_t i = 0; i < time_slot_count; i++) {
DEBUG_PRINT("[Attendance] Slot %d: %s (%02d:%02d-%02d:%02d) %s\r\n",
i, time_slots[i].name,
time_slots[i].start_hour, time_slots[i].start_minute,
time_slots[i].end_hour, time_slots[i].end_minute,
time_slots[i].enabled ? "ENABLED" : "DISABLED");
}
}
/**
* @brief 从JSON字符串解析时间段配置
* @param json_str JSON字符串(格式:{"time_slots":[{"enabled":true,"start_time":"08:00",...}]})
* @retval 成功解析的时间段数量
*/
uint8_t Attendance_ParseTimeSlotsJSON(const char* json_str)
{
TimeSlot temp_slots[MAX_TIME_SLOT_COUNT];
uint8_t slot_count = 0;
DEBUG_INFO("[Attendance] Parsing time slots JSON...");
DEBUG_PRINT("[Attendance] JSON: %s\r\n", json_str);
// 查找 "time_slots" 数组
const char* ptr = strstr(json_str, "\"time_slots\"");
if (ptr == NULL) {
DEBUG_ERROR("[Attendance] Invalid JSON: no 'time_slots' field found");
return 0;
}
// 查找数组开始 '['
ptr = strchr(ptr, '[');
if (ptr == NULL) {
DEBUG_ERROR("[Attendance] Invalid JSON: no '[' found");
return 0;
}
ptr++; // 跳过 '['
// 解析每个时间段对象
while (*ptr != '\0' && *ptr != ']' && slot_count < MAX_TIME_SLOT_COUNT)
{
// 跳过空格、换行、逗号
while (*ptr == ' ' || *ptr == ',' || *ptr == '\n' || *ptr == '\r' || *ptr == '\t') ptr++;
// 查找对象开始
if (*ptr != '{') break;
ptr++; // 跳过 '{'
// 初始化当前时间段
temp_slots[slot_count].enabled = 1;
temp_slots[slot_count].start_hour = 0;
temp_slots[slot_count].start_minute = 0;
temp_slots[slot_count].end_hour = 0;
temp_slots[slot_count].end_minute = 0;
strcpy(temp_slots[slot_count].name, "Unknown");
// 解析对象字段
while (*ptr != '\0' && *ptr != '}')
{
// 跳过空格、换行、逗号
while (*ptr == ' ' || *ptr == ',' || *ptr == '\n' || *ptr == '\r' || *ptr == '\t') ptr++;
// 跳过引号
if (*ptr == '"') ptr++;
// 解析字段名
if (strncmp(ptr, "enabled", 7) == 0) {
ptr = strchr(ptr, ':');
if (ptr) {
ptr++;
while (*ptr == ' ') ptr++;
// 处理 true/false 或 1/0
temp_slots[slot_count].enabled = (*ptr == 't' || *ptr == '1') ? 1 : 0;
}
}
else if (strncmp(ptr, "start_time", 10) == 0) {
// 格式: "start_time": "08:30"
ptr = strchr(ptr, ':');
if (ptr) {
ptr++;
while (*ptr == ' ' || *ptr == '"') ptr++;
// 解析 "HH:MM"
temp_slots[slot_count].start_hour = (ptr[0] - '0') * 10 + (ptr[1] - '0');
temp_slots[slot_count].start_minute = (ptr[3] - '0') * 10 + (ptr[4] - '0');
}
}
else if (strncmp(ptr, "end_time", 8) == 0) {
// 格式: "end_time": "09:30"
ptr = strchr(ptr, ':');
if (ptr) {
ptr++;
while (*ptr == ' ' || *ptr == '"') ptr++;
// 解析 "HH:MM"
temp_slots[slot_count].end_hour = (ptr[0] - '0') * 10 + (ptr[1] - '0');
temp_slots[slot_count].end_minute = (ptr[3] - '0') * 10 + (ptr[4] - '0');
}
}
else if (strncmp(ptr, "name", 4) == 0) {
ptr = strchr(ptr, ':');
if (ptr) {
ptr++;
while (*ptr == ' ' || *ptr == '"') ptr++;
// 提取名称(直到遇到引号)
const char* name_start = ptr;
while (*ptr != '"' && *ptr != '\0') ptr++;
int len = ptr - name_start;
if (len > 19) len = 19;
strncpy(temp_slots[slot_count].name, name_start, len);
temp_slots[slot_count].name[len] = '\0';
}
}
// 移动到下一个字段或对象结束
while (*ptr != '\0' && *ptr != ',' && *ptr != '}') ptr++;
if (*ptr == ',') ptr++;
}
if (*ptr == '}') ptr++; // 跳过 '}'
DEBUG_PRINT("[Attendance] Parsed slot %d: %s (%02d:%02d-%02d:%02d) %s\r\n",
slot_count, temp_slots[slot_count].name,
temp_slots[slot_count].start_hour, temp_slots[slot_count].start_minute,
temp_slots[slot_count].end_hour, temp_slots[slot_count].end_minute,
temp_slots[slot_count].enabled ? "ENABLED" : "DISABLED");
slot_count++;
}
DEBUG_PRINT("[Attendance] Parsed %d time slots\r\n", slot_count);
// 应用解析结果
if (slot_count > 0) {
Attendance_UpdateTimeSlots(temp_slots, slot_count);
return slot_count;
}
DEBUG_ERROR("[Attendance] Failed to parse any time slots");
return 0;
}
/**
* @brief 设置当前时间(由外部定期调用更新)
* @param hour 小时 (0-23)
* @param minute 分钟 (0-59)
*/
void Attendance_SetCurrentTime(uint8_t hour, uint8_t minute)
{
current_hour = hour;
current_minute = minute;
}
/**
* @brief 获取当前所处的考勤时段
* @retval 时段编号 (0-N),如果不在任何时段内则返回 -1
*/
int8_t Attendance_GetCurrentTimeSlot(void)
{
for (uint8_t i = 0; i < time_slot_count; i++)
{
// 跳过禁用的时间段
if (!time_slots[i].enabled) {
continue;
}
uint16_t current_minutes = current_hour * 60 + current_minute;
uint16_t start_minutes = time_slots[i].start_hour * 60 + time_slots[i].start_minute;
uint16_t end_minutes = time_slots[i].end_hour * 60 + time_slots[i].end_minute;
if (current_minutes >= start_minutes && current_minutes <= end_minutes)
{
return i; // 返回时段编号
}
}
return -1; // 不在任何考勤时段内
}
/**
* @brief 获取当前时段的开始和结束时间
* @param start_time 输出:开始时间(格式"HH:MM")
* @param end_time 输出:结束时间(格式"HH:MM")
*/
void Attendance_GetCurrentSlotTime(char* start_time, char* end_time)
{
int8_t current_slot = Attendance_GetCurrentTimeSlot();
if (current_slot >= 0 && current_slot < time_slot_count) {
sprintf(start_time, "%02d:%02d",
time_slots[current_slot].start_hour,
time_slots[current_slot].start_minute);
sprintf(end_time, "%02d:%02d",
time_slots[current_slot].end_hour,
time_slots[current_slot].end_minute);
} else {
// 不在任何时段,返回全天
strcpy(start_time, "00:00");
strcpy(end_time, "23:59");
}
}
/**
* @brief 检查学生是否在服务器端已签到(服务器端查重)
* @param student_id 学生学号
* @retval 1=服务器显示已签到(重复), 0=未签到或查询失败
*/
uint8_t Attendance_IsDuplicateOnServer(const char* student_id)
{
char slot_start[8];
char slot_end[8];
// 获取当前时段时间
Attendance_GetCurrentSlotTime(slot_start, slot_end);
DEBUG_PRINT("[Attendance] Checking server for duplicate: %s (%s-%s)\r\n",
student_id, slot_start, slot_end);
// 调用ESP8266查询服务器(这个过程可能耗时,蜂鸣器状态由调用者控制)
return ESP8266_CheckDuplicate(student_id, slot_start, slot_end);
}
/**
* @brief 检查是否在当前时段重复打卡(本地+服务器)
* @param student_id 学生学号
* @retval 1=重复, 0=不重复
*/
uint8_t Attendance_IsDuplicate(const char* student_id)
{
int8_t current_slot = Attendance_GetCurrentTimeSlot();
if (current_slot < 0) {
return 0; // 不在考勤时段,不算重复
}
// 遍历现有记录,检查是否在当前时段已打卡
for (uint16_t i = 0; i < record_count; i++)
{
// 如果找到相同学号且在相同时段
if (strcmp(attendance_records[i].student_id, student_id) == 0 &&
attendance_records[i].time_slot == current_slot)
{
return 1; // 本时段已打卡,重复
}
}
return 0; // 不重复
}
/**
* @brief 处理人脸识别结果并记录考勤
* @param result 人脸识别结果
* @retval 考勤状态
*/
AttendanceStatus Attendance_ProcessFaceResult(FaceRecognitionResult* result)
{
// 1. 检查识别是否成功
if (result->success == 0)
{
return ATTENDANCE_UNKNOWN;
}
// 2. 检查置信度
if (result->confidence < MIN_CONFIDENCE_THRESHOLD)
{
return ATTENDANCE_LOW_CONFIDENCE;
}
// 3. 检查是否在考勤时段内
int8_t current_slot = Attendance_GetCurrentTimeSlot();
if (current_slot < 0)
{
DEBUG_PRINT("[Attendance] Not in time slot (Current: %02d:%02d)\r\n",
current_hour, current_minute);
return ATTENDANCE_OUT_OF_TIME;
}
DEBUG_PRINT("[Attendance] Current slot: %s (%02d:%02d)\r\n",
time_slots[current_slot].name, current_hour, current_minute);
// 4. 检查是否在当前时段重复打卡(本地记录检查)
if (Attendance_IsDuplicate(result->student_id))
{
DEBUG_PRINT("[Attendance] Local duplicate in slot: %s\r\n", time_slots[current_slot].name);
return ATTENDANCE_DUPLICATE;
}
// 5. 检查缓存中是否已签到(从服务器下载的今日记录)
if (Attendance_IsDuplicateInCache(result->student_id))
{
DEBUG_PRINT("[Attendance] Cache duplicate in slot: %s\r\n", time_slots[current_slot].name);
return ATTENDANCE_DUPLICATE;
}
// 6. 添加新记录
if (record_count < MAX_ATTENDANCE_RECORDS)
{
// 复制学号和姓名
strncpy(attendance_records[record_count].student_id, result->student_id, 19);
attendance_records[record_count].student_id[19] = '\0';
strncpy(attendance_records[record_count].student_name, result->student_name, 31);
attendance_records[record_count].student_name[31] = '\0';
// 记录置信度
attendance_records[record_count].confidence = result->confidence;
// 记录时间戳和时段
// 简化:使用系统运行时间戳(服务器会用接收时间替换)
attendance_records[record_count].timestamp = Get_Time_ms() / 1000;
attendance_records[record_count].time_slot = current_slot;
// 初始化同步状态
attendance_records[record_count].sync_status = SYNC_PENDING;
attendance_records[record_count].retry_count = 0;
attendance_records[record_count].last_retry_time = 0;
record_count++;
total_attendance_count++;
// 添加到缓存,防止重复签到
Attendance_AddToCache(result->student_id);
DEBUG_PRINT("[Attendance] Success! Slot=%s, Total=%d, Status=PENDING\r\n",
time_slots[current_slot].name, total_attendance_count);
return ATTENDANCE_SUCCESS;
}
else
{
// 记录已满,覆盖最早的记录
memmove(&attendance_records[0], &attendance_records[1],
sizeof(AttendanceRecord) * (MAX_ATTENDANCE_RECORDS - 1));
record_count = MAX_ATTENDANCE_RECORDS - 1;
// 在最后位置添加新记录
strncpy(attendance_records[record_count].student_id, result->student_id, 19);
attendance_records[record_count].student_id[19] = '\0';
strncpy(attendance_records[record_count].student_name, result->student_name, 31);
attendance_records[record_count].student_name[31] = '\0';
attendance_records[record_count].confidence = result->confidence;
// 简化:使用系统运行时间戳(服务器会用接收时间替换)
attendance_records[record_count].timestamp = Get_Time_ms() / 1000;
attendance_records[record_count].time_slot = current_slot;
attendance_records[record_count].sync_status = SYNC_PENDING;
attendance_records[record_count].retry_count = 0;
attendance_records[record_count].last_retry_time = 0;
record_count++;
total_attendance_count++;
return ATTENDANCE_SUCCESS;
}
}
/**
* @brief 清空所有考勤记录
*/
void Attendance_ClearRecords(void)
{
record_count = 0;
memset(attendance_records, 0, sizeof(attendance_records));
// 注意:不清空 total_attendance_count,保留历史统计
}
/**
* @brief 获取累计考勤总数
*/
uint16_t Attendance_GetTotalCount(void)
{
return total_attendance_count;
}
/**
* @brief 获取当前缓存中的记录数
*/
uint16_t Attendance_GetActiveCount(void)
{
return record_count;
}
/**
* @brief 获取待同步记录的数量(PENDING或FAILED状态)
* @retval 待同步记录数
*/
uint16_t Attendance_GetPendingCount(void)
{
uint16_t count = 0;
for (uint16_t i = 0; i < record_count; i++)
{
if (attendance_records[i].sync_status == SYNC_PENDING ||
attendance_records[i].sync_status == SYNC_FAILED)
{
count++;
}
}
return count;
}
/**
* @brief 获取下一个待同步的记录(PENDING或FAILED状态)
* @param index 输出参数,返回记录的索引
* @retval 记录指针,如果没有待同步记录则返回NULL
*/
AttendanceRecord* Attendance_GetNextPendingRecord(uint16_t* index)
{
uint32_t current_time = Get_Time_ms();
// 优先处理PENDING状态的记录
for (uint16_t i = 0; i < record_count; i++)
{
if (attendance_records[i].sync_status == SYNC_PENDING)
{
if (index != NULL)
{
*index = i;
}
return &attendance_records[i];
}
}
// 然后处理FAILED状态的记录(需要等待重试间隔)
for (uint16_t i = 0; i < record_count; i++)
{
if (attendance_records[i].sync_status == SYNC_FAILED)
{
// 指数退避算法:重试间隔 = 2^retry_count * 1000ms (最大30秒)
uint32_t retry_interval = (1 << attendance_records[i].retry_count) * 1000;
if (retry_interval > 30000) retry_interval = 30000;
// 检查是否到达重试时间
if (current_time - attendance_records[i].last_retry_time >= retry_interval)
{
if (index != NULL)
{
*index = i;
}
return &attendance_records[i];
}
}
}
return NULL; // 没有待同步记录
}
/**
* @brief 更新记录的同步状态
* @param index 记录索引
* @param status 新的同步状态
*/
void Attendance_MarkSyncStatus(uint16_t index, SyncStatus status)
{
if (index < record_count)
{
attendance_records[index].sync_status = status;
if (status == SYNC_FAILED || status == SYNC_SYNCING)
{
attendance_records[index].last_retry_time = Get_Time_ms();
}
const char* status_str[] = {"PENDING", "SYNCING", "SYNCED", "FAILED"};
DEBUG_PRINT("[Attendance] Record %d status: %s (ID=%s)\r\n",
index, status_str[status], attendance_records[index].student_id);
}
}
/**
* @brief 增加记录的重试计数
* @param index 记录索引
*/
void Attendance_IncrementRetry(uint16_t index)
{
if (index < record_count)
{
attendance_records[index].retry_count++;
DEBUG_PRINT("[Attendance] Record %d retry count: %d\r\n",
index, attendance_records[index].retry_count);
}
}
/**
* @brief 将所有FAILED记录重置为PENDING(用于网络恢复后)
*/
void Attendance_ResetFailedRecords(void)
{
uint16_t reset_count = 0;
for (uint16_t i = 0; i < record_count; i++)
{
if (attendance_records[i].sync_status == SYNC_FAILED)
{
attendance_records[i].sync_status = SYNC_PENDING;
attendance_records[i].retry_count = 0;
reset_count++;
}
}
if (reset_count > 0)
{
DEBUG_PRINT("[Attendance] Reset %d failed records to pending\r\n", reset_count);
}
}
/**
* @brief 按索引获取记录
* @param index 记录索引
* @retval 记录指针,如果索引无效则返回NULL
*/
AttendanceRecord* Attendance_GetRecordByIndex(uint16_t index)
{
if (index < record_count)
{
return &attendance_records[index];
}
return NULL;
}
+79
View File
@@ -0,0 +1,79 @@
#ifndef __ATTENDANCE_H
#define __ATTENDANCE_H
#include "stm32f10x.h"
// 同步状态枚举
typedef enum {
SYNC_PENDING = 0, // 待同步(新记录)
SYNC_SYNCING, // 同步中
SYNC_SYNCED, // 已同步成功
SYNC_FAILED // 同步失败(等待重试)
} SyncStatus;
// 考勤记录结构体
typedef struct {
char student_id[20]; // 学号
char student_name[32]; // 学生姓名
float confidence; // 识别置信度
uint32_t timestamp; // 打卡时间戳(秒)
uint8_t time_slot; // 打卡时段编号
SyncStatus sync_status; // 同步状态
uint8_t retry_count; // 重试次数
uint32_t last_retry_time; // 上次重试时间(毫秒)
} AttendanceRecord;
// 人脸识别结果结构体
typedef struct {
uint8_t success; // 识别是否成功 (1=成功, 0=失败)
char student_id[20]; // 学号
char student_name[32]; // 学生姓名
float confidence; // 置信度 (0.0-1.0)
} FaceRecognitionResult;
// 考勤状态
typedef enum {
ATTENDANCE_SUCCESS = 0, // 打卡成功
ATTENDANCE_DUPLICATE, // 重复打卡(本时段已打卡)
ATTENDANCE_UNKNOWN, // 未识别到人脸
ATTENDANCE_LOW_CONFIDENCE, // 置信度过低
ATTENDANCE_OUT_OF_TIME // 不在考勤时间段内
} AttendanceStatus;
// 时间段定义结构体
typedef struct {
uint8_t enabled; // 是否启用该时间段(1=启用, 0=禁用)
uint8_t start_hour; // 开始小时 (0-23)
uint8_t start_minute; // 开始分钟 (0-59)
uint8_t end_hour; // 结束小时 (0-23)
uint8_t end_minute; // 结束分钟 (0-59)
char name[20]; // 时段名称(如"上午签到")
} TimeSlot;
// 函数声明
void Attendance_Init(void);
void Attendance_SetCurrentTime(uint8_t hour, uint8_t minute); // 设置当前时间
void Attendance_UpdateTimeSlots(TimeSlot* slots, uint8_t count); // 更新时间段配置(从服务器下载后调用)
uint8_t Attendance_ParseTimeSlotsJSON(const char* json_str); // 从JSON字符串解析时间段配置
AttendanceStatus Attendance_ProcessFaceResult(FaceRecognitionResult* result);
uint8_t Attendance_IsDuplicate(const char* student_id);
uint8_t Attendance_IsDuplicateOnServer(const char* student_id); // 服务器端查重
void Attendance_GetCurrentSlotTime(char* start_time, char* end_time); // 获取当前时段时间
uint8_t Attendance_LoadTodayRecordsFromServer(void); // 新增:从服务器加载今日签到记录
uint8_t Attendance_IsDuplicateInCache(const char* student_id); // 新增:在本地缓存中查重
void Attendance_AddToCache(const char* student_id); // 新增:添加学号到缓存
void Attendance_ClearCache(void); // 新增:清空缓存
void Attendance_ClearRecords(void);
uint16_t Attendance_GetTotalCount(void); // 获取累计考勤总数
uint16_t Attendance_GetActiveCount(void); // 获取当前缓存记录数
int8_t Attendance_GetCurrentTimeSlot(void); // 获取当前时段编号(-1表示不在考勤时段)
// 数据同步相关函数
uint16_t Attendance_GetPendingCount(void); // 获取待同步记录数量(PENDING状态)
AttendanceRecord* Attendance_GetNextPendingRecord(uint16_t* index); // 获取下一个待同步记录
void Attendance_MarkSyncStatus(uint16_t index, SyncStatus status); // 更新记录的同步状态
void Attendance_IncrementRetry(uint16_t index); // 增加重试计数
AttendanceRecord* Attendance_GetRecordByIndex(uint16_t index); // 按索引获取记录
void Attendance_ResetFailedRecords(void); // 将所有FAILED记录重置为PENDING
#endif
+29
View File
@@ -0,0 +1,29 @@
#include "Buzzer_LED.h"
void Buzzer_LED_Init(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO | RCC_APB2Periph_GPIOB | RCC_APB2Periph_GPIOC, ENABLE);
// 释放PB4(默认是JTAG引脚),保留SWD调试
GPIO_PinRemapConfig(GPIO_Remap_SWJ_JTAGDisable, ENABLE);
// 根据连接图.md: LED=PB4, 蜂鸣器=PB5
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_4 | GPIO_Pin_5;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOB, &GPIO_InitStructure);
// PC13板载状态指示LED
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_13;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOC, &GPIO_InitStructure);
// 初始状态:LED和蜂鸣器都关闭
LED_SUCCESS_OFF();
BUZZER_OFF();
// PC13状态LED:初始化时点亮(表示系统正在初始化)
LED_STATUS_ON();
}
+19
View File
@@ -0,0 +1,19 @@
#ifndef __BUZZER_LED_H
#define __BUZZER_LED_H
#include "stm32f10x.h"
// 定义GPIO控制宏 (根据连接图.md修改)
#define LED_SUCCESS_OFF() GPIO_ResetBits(GPIOB, GPIO_Pin_4) // PB4, 高电平点亮
#define LED_SUCCESS_ON() GPIO_SetBits(GPIOB, GPIO_Pin_4)
#define BUZZER_OFF() GPIO_SetBits(GPIOB, GPIO_Pin_5) // PB5, 低电平触发
#define BUZZER_ON() GPIO_ResetBits(GPIOB, GPIO_Pin_5)
// PC13板载状态指示LED (低电平点亮)
#define LED_STATUS_ON() GPIO_ResetBits(GPIOC, GPIO_Pin_13) // 初始化中:常亮
#define LED_STATUS_OFF() GPIO_SetBits(GPIOC, GPIO_Pin_13) // 就绪后:熄灭
// 声明初始化函数
void Buzzer_LED_Init(void);
#endif
+99
View File
@@ -0,0 +1,99 @@
/*
* 加密模块实现
* 使用AES-128-CBC + Base64编码
*/
#include "Crypto.h"
#include "aes.h"
#include "base64.h"
#include <string.h>
// ⚠️ 重要:密钥和IV必须与服务器端完全一致!
// AES-128密钥(16字节)
static const uint8_t AES_KEY[16] = {
0x2b, 0x7e, 0x15, 0x16, 0x28, 0xae, 0xd2, 0xa6,
0xab, 0xf7, 0x15, 0x88, 0x09, 0xcf, 0x4f, 0x3c
};
// 初始化向量(16字节)
static const uint8_t AES_IV[16] = {
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f
};
static struct AES_ctx aes_ctx;
void Crypto_Init(void)
{
// 初始化AES上下文(使用CBC模式)
AES_init_ctx_iv(&aes_ctx, AES_KEY, AES_IV);
}
/**
* @brief PKCS#7填充
* 将数据填充到16字节的倍数
*/
static uint32_t pkcs7_pad(uint8_t* data, uint32_t data_len, uint32_t buffer_size)
{
// 计算需要填充的字节数
uint32_t padding_len = 16 - (data_len % 16);
// 检查缓冲区是否足够
if (data_len + padding_len > buffer_size) {
return 0; // 缓冲区不足
}
// 填充(PKCS#7标准)
uint32_t i;
for (i = 0; i < padding_len; i++) {
data[data_len + i] = (uint8_t)padding_len;
}
return data_len + padding_len;
}
uint32_t Crypto_EncryptJSON(const char* json_data, char* encrypted_output)
{
uint32_t json_len = strlen(json_data);
uint8_t buffer[512]; // 临时缓冲区
// 1. 复制JSON数据到缓冲区
if (json_len > sizeof(buffer) - 16) {
return 0; // 数据太长
}
memcpy(buffer, json_data, json_len);
// 2. PKCS#7填充到16字节的倍数
uint32_t padded_len = pkcs7_pad(buffer, json_len, sizeof(buffer));
if (padded_len == 0) {
return 0; // 填充失败
}
// 3. 手动实现标准CBC加密(绕过aes.c的bug)
struct AES_ctx ctx;
uint8_t iv[16];
uint32_t i, j;
// 初始化密钥和IV
AES_init_ctx(&ctx, AES_KEY);
memcpy(iv, AES_IV, 16);
// CBC加密:每个块先XOR IV,再加密,然后用密文更新IV
for (i = 0; i < padded_len; i += 16) {
// XOR with IV
for (j = 0; j < 16; j++) {
buffer[i + j] ^= iv[j];
}
// AES加密单个块
AES_ECB_encrypt(&ctx, &buffer[i]);
// 更新IV为当前密文块
memcpy(iv, &buffer[i], 16);
}
// 4. Base64编码
uint32_t encoded_len = base64_encode(buffer, padded_len, encrypted_output);
return encoded_len;
}
+30
View File
@@ -0,0 +1,30 @@
#ifndef _CRYPTO_H_
#define _CRYPTO_H_
#include <stdint.h>
#include <stddef.h>
/**
* @brief 加密JSON数据
* @param json_data 原始JSON字符串
* @param encrypted_output 输出的加密Base64字符串(需要预分配足够空间)
* @return 加密后的Base64字符串长度
*
* 注意:encrypted_output缓冲区至少需要 (strlen(json_data) / 16 + 1) * 16 * 4/3 + 10 字节
*/
uint32_t Crypto_EncryptJSON(const char* json_data, char* encrypted_output);
/**
* @brief 获取加密后数据的最大长度
* @param json_length JSON字符串长度
* @return 加密后的最大长度
*/
#define CRYPTO_MAX_ENCRYPTED_SIZE(json_len) (((json_len) / 16 + 1) * 16 * 4 / 3 + 10)
/**
* @brief 初始化加密模块
* 设置密钥和IV(初始化向量)
*/
void Crypto_Init(void);
#endif // _CRYPTO_H_
+246
View File
@@ -0,0 +1,246 @@
#include "DS1302.h"
#include "Delay.h"
// 根据连接图.md: DS1302引脚配置
// CLK: PA4, DAT: PA5, RST: PA6
#define DS1302_SCLK_PIN GPIO_Pin_4 // CLK
#define DS1302_IO_PIN GPIO_Pin_5 // DAT (双向)
#define DS1302_CE_PIN GPIO_Pin_6 // RST/CE
#define DS1302_GPIO GPIOA
// GPIO控制宏
#define SCLK_H GPIO_SetBits(DS1302_GPIO, DS1302_SCLK_PIN)
#define SCLK_L GPIO_ResetBits(DS1302_GPIO, DS1302_SCLK_PIN)
#define DATA_H GPIO_SetBits(DS1302_GPIO, DS1302_IO_PIN)
#define DATA_L GPIO_ResetBits(DS1302_GPIO, DS1302_IO_PIN)
#define CE_H GPIO_SetBits(DS1302_GPIO, DS1302_CE_PIN)
#define CE_L GPIO_ResetBits(DS1302_GPIO, DS1302_CE_PIN)
#define DATA_READ GPIO_ReadInputDataBit(DS1302_GPIO, DS1302_IO_PIN)
// DS1302寄存器地址(写地址)
#define DS1302_SECOND 0x80
#define DS1302_MINUTE 0x82
#define DS1302_HOUR 0x84
#define DS1302_DATE 0x86
#define DS1302_MONTH 0x88
#define DS1302_DAY 0x8A
#define DS1302_YEAR 0x8C
#define DS1302_WP 0x8E
// BCD码转换
#define BCD_TO_DEC(bcd) (((bcd) >> 4) * 10 + ((bcd) & 0x0F))
#define DEC_TO_BCD(dec) ((((dec) / 10) << 4) | ((dec) % 10))
// 微秒延时
static void delay_us(uint32_t us)
{
volatile uint32_t i;
while (us--) {
for (i = 0; i < 12; i++); // 72MHz下约1us
}
}
/**
* @brief 配置IO口为输出
*/
static void DS1302_DATAOUT_init(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Pin = DS1302_IO_PIN;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(DS1302_GPIO, &GPIO_InitStructure);
}
/**
* @brief 配置IO口为输入
*/
static void DS1302_DATAINPUT_init(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Pin = DS1302_IO_PIN;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
GPIO_Init(DS1302_GPIO, &GPIO_InitStructure);
}
/**
* @brief DS1302初始化
*/
void DS1302_Init(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
// 使能GPIOA时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
// 配置SCLK和CE为推挽输出
GPIO_InitStructure.GPIO_Pin = DS1302_SCLK_PIN | DS1302_CE_PIN;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(DS1302_GPIO, &GPIO_InitStructure);
// 配置IO为输出(初始状态)
DS1302_DATAOUT_init();
// 初始化引脚状态
SCLK_L;
CE_L;
}
/**
* @brief 向DS1302发送一字节数据(根据demo代码)
* @param data 要发送的数据
*/
static void DS1302_write_onebyte(uint8_t data)
{
uint8_t count = 0;
DS1302_DATAOUT_init(); // 配置为输出
SCLK_L;
for (count = 0; count < 8; count++)
{
SCLK_L;
// 先准备好数据再发送
if (data & 0x01)
DATA_H;
else
DATA_L;
SCLK_H; // 拉高时钟线,发送数据
data >>= 1;
}
}
/**
* @brief 向指定寄存器地址发送数据(根据demo代码)
* @param address 寄存器地址
* @param data 数据
*/
void DS1302_WriteByte(uint8_t address, uint8_t data)
{
CE_L;
SCLK_L;
delay_us(1);
CE_H;
delay_us(2);
DS1302_write_onebyte(address);
DS1302_write_onebyte(data);
CE_L;
SCLK_L;
delay_us(2);
}
/**
* @brief 从指定地址读取一字节数据(根据demo代码)
* @param address 寄存器地址
* @retval 读取到的数据
*/
uint8_t DS1302_ReadByte(uint8_t address)
{
uint8_t count = 0;
uint8_t return_data = 0x00;
CE_L;
SCLK_L;
delay_us(3);
CE_H;
delay_us(3);
DS1302_write_onebyte(address);
DS1302_DATAINPUT_init(); // 配置I/O口为输入
delay_us(2);
for (count = 0; count < 8; count++)
{
delay_us(2); // 使电平持续一段时间
return_data >>= 1;
SCLK_H;
delay_us(4); // 使高电平持续一段时间
SCLK_L;
delay_us(14); // ⭐ 关键延时:延时14us后再去读取电压,更加准确
if (DATA_READ)
return_data = return_data | 0x80;
}
delay_us(2);
CE_L;
DATA_L;
return return_data;
}
/**
* @brief 设置DS1302时间
* @param time 时间结构体指针
*/
void DS1302_SetTime(DS1302_Time* time)
{
// 关闭写保护
DS1302_WriteByte(DS1302_WP, 0x00);
// 写入时间数据(BCD码格式)
DS1302_WriteByte(DS1302_SECOND, DEC_TO_BCD(time->second));
DS1302_WriteByte(DS1302_MINUTE, DEC_TO_BCD(time->minute));
DS1302_WriteByte(DS1302_HOUR, DEC_TO_BCD(time->hour));
DS1302_WriteByte(DS1302_DATE, DEC_TO_BCD(time->day));
DS1302_WriteByte(DS1302_MONTH, DEC_TO_BCD(time->month));
DS1302_WriteByte(DS1302_DAY, DEC_TO_BCD(time->week));
DS1302_WriteByte(DS1302_YEAR, DEC_TO_BCD(time->year % 100));
// 开启写保护
DS1302_WriteByte(DS1302_WP, 0x80);
}
/**
* @brief 读取DS1302时间
* @param time 时间结构体指针
*/
void DS1302_GetTime(DS1302_Time* time)
{
uint8_t read_time[7];
uint8_t hour_reg;
// 读取时间数据(BCD码)
read_time[0] = DS1302_ReadByte(0x81); // 读秒
read_time[1] = DS1302_ReadByte(0x83); // 读分
read_time[2] = DS1302_ReadByte(0x85); // 读时
read_time[3] = DS1302_ReadByte(0x87); // 读日
read_time[4] = DS1302_ReadByte(0x89); // 读月
read_time[5] = DS1302_ReadByte(0x8B); // 读星期
read_time[6] = DS1302_ReadByte(0x8D); // 读年
// BCD码转换为10进制(先屏蔽控制位,再转换)
time->second = BCD_TO_DEC(read_time[0] & 0x7F); // bit7=CH
time->minute = BCD_TO_DEC(read_time[1] & 0x7F);
hour_reg = read_time[2];
if (hour_reg & 0x80) {
// 12小时制:bit5=PM, bit4~0=小时(1~12)
uint8_t hour12 = BCD_TO_DEC(hour_reg & 0x1F);
uint8_t is_pm = (hour_reg & 0x20) ? 1 : 0;
if (hour12 == 12) {
time->hour = is_pm ? 12 : 0;
} else {
time->hour = is_pm ? (hour12 + 12) : hour12;
}
} else {
// 24小时制:bit5~0=小时(0~23)
time->hour = BCD_TO_DEC(hour_reg & 0x3F);
}
time->day = BCD_TO_DEC(read_time[3] & 0x3F);
time->month = BCD_TO_DEC(read_time[4] & 0x1F);
time->week = read_time[5] & 0x07;
time->year = BCD_TO_DEC(read_time[6]) + 2000;
}
+24
View File
@@ -0,0 +1,24 @@
#ifndef __DS1302_H
#define __DS1302_H
#include "stm32f10x.h"
// DS1302时间结构体
typedef struct {
uint16_t year; // 年份 (2000-2099)
uint8_t month; // 月份 (1-12)
uint8_t day; // 日期 (1-31)
uint8_t hour; // 小时 (0-23)
uint8_t minute; // 分钟 (0-59)
uint8_t second; // 秒钟 (0-59)
uint8_t week; // 星期 (1-7, 1=周一)
} DS1302_Time;
// 函数声明
void DS1302_Init(void);
void DS1302_SetTime(DS1302_Time* time);
void DS1302_GetTime(DS1302_Time* time);
uint8_t DS1302_ReadByte(uint8_t addr);
void DS1302_WriteByte(uint8_t addr, uint8_t dat);
#endif
+136
View File
@@ -0,0 +1,136 @@
#include "K210_USART.h"
#include "Debug.h" // 添加调试支持
#include <stdio.h>
#include <string.h>
// 引用来自 main.c 的全局变量
extern volatile char g_k210_rx_buffer[];
extern volatile uint16_t g_k210_rx_counter;
extern volatile uint8_t g_k210_rx_flag;
// 全局变量:存储解析后的人脸识别结果
FaceRecognitionResult K210_FaceResult;
void K210_USART_Init(void)
{
// 根据连接图.md: K210使用USART3 (PB10/PB11)
GPIO_InitTypeDef GPIO_InitStructure;
USART_InitTypeDef USART_InitStructure;
NVIC_InitTypeDef NVIC_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART3, ENABLE);
// PB10: USART3_TX
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOB, &GPIO_InitStructure);
// PB11: USART3_RX
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_11;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
GPIO_Init(GPIOB, &GPIO_InitStructure);
USART_InitStructure.USART_BaudRate = 115200;
USART_InitStructure.USART_WordLength = USART_WordLength_8b;
USART_InitStructure.USART_StopBits = USART_StopBits_1;
USART_InitStructure.USART_Parity = USART_Parity_No;
USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;
USART_Init(USART3, &USART_InitStructure);
USART_ITConfig(USART3, USART_IT_RXNE, ENABLE);
NVIC_InitStructure.NVIC_IRQChannel = USART3_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
USART_Cmd(USART3, ENABLE);
}
/**
* @brief 解析K210发送的人脸识别数据
* @param buffer 接收到的数据缓冲区
* @param result 解析结果存储结构体
* @retval 1=解析成功, 0=解析失败
*
* K210数据格式示例: "ID:2021001,NAME:Zhang_San,CONF:0.95"
* 或者: "UNKNOWN" (未识别到人脸)
*/
uint8_t K210_ParseFaceData(const char* buffer, FaceRecognitionResult* result)
{
DEBUG_PRINT("[K210] Raw data: %s\r\n", buffer);
// 清空结果结构体
memset(result, 0, sizeof(FaceRecognitionResult));
// 检查是否为未识别
if (strstr(buffer, "UNKNOWN") != NULL)
{
DEBUG_INFO("[K210] Unknown face detected");
result->success = 0;
strcpy(result->student_name, "Unknown");
return 1;
}
// 尝试解析完整数据
char id[20] = {0};
char name[32] = {0};
float conf = 0.0f;
// 解析格式: ID:2021001,NAME:Zhang_San,CONF:0.95
int parsed = sscanf(buffer, "ID:%[^,],NAME:%[^,],CONF:%f", id, name, &conf);
if (parsed == 3)
{
DEBUG_PRINT("[K210] Parsed: ID=%s, Name=%s, Conf=%.2f\r\n", id, name, conf);
result->success = 1;
strncpy(result->student_id, id, 19);
result->student_id[19] = '\0';
// 将下划线替换为空格(方便显示)
strncpy(result->student_name, name, 31);
result->student_name[31] = '\0';
for (int i = 0; result->student_name[i] != '\0'; i++)
{
if (result->student_name[i] == '_')
result->student_name[i] = ' ';
}
result->confidence = conf;
return 1;
}
DEBUG_ERROR("[K210] Parse failed!");
return 0; // 解析失败
}
uint8_t K210_GetRxFlag(void)
{
if (g_k210_rx_flag)
{
DEBUG_INFO("[K210] Data received, parsing...");
// 解析K210发送的人脸识别数据
K210_ParseFaceData((const char*)g_k210_rx_buffer, &K210_FaceResult);
g_k210_rx_flag = 0; // 清除标志位
g_k210_rx_counter = 0; // 重置计数器
// 清空缓冲区,防止数据粘连
memset((void*)g_k210_rx_buffer, 0, K210_RX_BUFFER_SIZE);
return 1;
}
return 0;
}
void K210_ClearRxFlag(void)
{
g_k210_rx_flag = 0;
g_k210_rx_counter = 0;
}
+18
View File
@@ -0,0 +1,18 @@
#ifndef __K210_USART_H
#define __K210_USART_H
#include "stm32f10x.h"
#include "Attendance.h"
#define K210_RX_BUFFER_SIZE 128
// 声明外部变量
extern FaceRecognitionResult K210_FaceResult;
// 声明公共函数
void K210_USART_Init(void);
uint8_t K210_GetRxFlag(void);
void K210_ClearRxFlag(void);
uint8_t K210_ParseFaceData(const char* buffer, FaceRecognitionResult* result);
#endif
+106
View File
@@ -0,0 +1,106 @@
#include "LightSensor.h"
// 补光灯当前状态
static uint8_t g_fill_light_on = 0;
/**
* @brief 初始化光敏电阻ADC和补光灯GPIO
*/
void LightSensor_Init(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
ADC_InitTypeDef ADC_InitStructure;
// 1. 使能时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB | RCC_APB2Periph_ADC1, ENABLE);
// 配置ADC时钟分频(ADC时钟不能超过14MHz)
RCC_ADCCLKConfig(RCC_PCLK2_Div6); // 72MHz / 6 = 12MHz
// 2. 配置PB0为模拟输入(光敏电阻)
GPIO_InitStructure.GPIO_Pin = LIGHT_SENSOR_PIN;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN; // 模拟输入
GPIO_Init(LIGHT_SENSOR_PORT, &GPIO_InitStructure);
// 3. 配置PB1为推挽输出(补光灯)
GPIO_InitStructure.GPIO_Pin = FILL_LIGHT_PIN;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(FILL_LIGHT_PORT, &GPIO_InitStructure);
// 4. 初始化ADC1
ADC_DeInit(ADC1);
ADC_InitStructure.ADC_Mode = ADC_Mode_Independent; // 独立模式
ADC_InitStructure.ADC_ScanConvMode = DISABLE; // 单通道模式
ADC_InitStructure.ADC_ContinuousConvMode = DISABLE; // 单次转换
ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None; // 软件触发
ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right; // 右对齐
ADC_InitStructure.ADC_NbrOfChannel = 1; // 1个通道
ADC_Init(ADC1, &ADC_InitStructure);
// 5. 配置ADC通道1,采样时间239.5周期(更稳定)
ADC_RegularChannelConfig(ADC1, LIGHT_SENSOR_ADC_CH, 1, ADC_SampleTime_239Cycles5);
// 6. 使能ADC1
ADC_Cmd(ADC1, ENABLE);
// 7. ADC校准
ADC_ResetCalibration(ADC1);
while (ADC_GetResetCalibrationStatus(ADC1));
ADC_StartCalibration(ADC1);
while (ADC_GetCalibrationStatus(ADC1));
// 8. 初始状态:补光灯关闭
FILL_LIGHT_OFF();
g_fill_light_on = 0;
}
/**
* @brief 获取光敏电阻ADC值
* @return ADC值 (0-4095),值越大表示光线越暗(电阻越大)
*/
uint16_t LightSensor_GetValue(void)
{
// 启动ADC转换
ADC_SoftwareStartConvCmd(ADC1, ENABLE);
// 等待转换完成
while (!ADC_GetFlagStatus(ADC1, ADC_FLAG_EOC));
// 返回转换结果
return ADC_GetConversionValue(ADC1);
}
/**
* @brief 更新补光灯状态(在主循环中定期调用)
* 使用迟滞控制避免灯光频繁开关
*/
void LightSensor_Update(void)
{
uint16_t light_value = LightSensor_GetValue();
// 迟滞控制逻辑
if (g_fill_light_on) {
// 当前灯是亮的,只有光线足够亮时才关灯
if (light_value < LIGHT_THRESHOLD_HIGH) {
FILL_LIGHT_OFF();
g_fill_light_on = 0;
}
} else {
// 当前灯是灭的,只有光线足够暗时才开灯
if (light_value > LIGHT_THRESHOLD_LOW) {
FILL_LIGHT_ON();
g_fill_light_on = 1;
}
}
}
/**
* @brief 获取补光灯当前状态
* @return 1=亮,0=灭
*/
uint8_t LightSensor_IsLightOn(void)
{
return g_fill_light_on;
}
+30
View File
@@ -0,0 +1,30 @@
#ifndef __LIGHTSENSOR_H
#define __LIGHTSENSOR_H
#include "stm32f10x.h"
// 光敏电阻引脚定义
#define LIGHT_SENSOR_PIN GPIO_Pin_0
#define LIGHT_SENSOR_PORT GPIOB
#define LIGHT_SENSOR_ADC_CH ADC_Channel_8 // PB0 对应 ADC1 通道8
// 补光灯引脚定义
#define FILL_LIGHT_PIN GPIO_Pin_1
#define FILL_LIGHT_PORT GPIOB
// 补光灯控制宏(根据实际电路调整,假设高电平点亮)
#define FILL_LIGHT_ON() GPIO_SetBits(FILL_LIGHT_PORT, FILL_LIGHT_PIN)
#define FILL_LIGHT_OFF() GPIO_ResetBits(FILL_LIGHT_PORT, FILL_LIGHT_PIN)
// 光线阈值配置(可根据实际环境调整)
#define LIGHT_THRESHOLD_LOW 1500 // 低于此值开灯(光线暗)
#define LIGHT_THRESHOLD_HIGH 2000 // 高于此值关灯(光线亮)
// 使用迟滞区间避免频繁开关
// 函数声明
void LightSensor_Init(void); // 初始化光敏电阻ADC和补光灯GPIO
uint16_t LightSensor_GetValue(void); // 获取光敏电阻ADC值
void LightSensor_Update(void); // 更新补光灯状态(主循环中调用)
uint8_t LightSensor_IsLightOn(void); // 获取补光灯当前状态
#endif
+258
View File
@@ -0,0 +1,258 @@
// ======================================================
// OLED.c (最终修正版, 支持中文)
// ======================================================
#include "stm32f10x.h"
#include "OLED_Font.h" // 包含ASCII和中文的字模
/*引脚配置*/
#define OLED_W_SCL(x) GPIO_WriteBit(GPIOB, GPIO_Pin_8, (BitAction)(x))
#define OLED_W_SDA(x) GPIO_WriteBit(GPIOB, GPIO_Pin_9, (BitAction)(x))
/* ... 从 OLED_I2C_Init() 到 OLED_ShowBinNum() 的所有函数保持不变 ... */
/* ... 请确保您文件里这部分内容是完整的 ... */
/*引脚初始化*/
void OLED_I2C_Init(void)
{
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_OD;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_8;
GPIO_Init(GPIOB, &GPIO_InitStructure);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;
GPIO_Init(GPIOB, &GPIO_InitStructure);
OLED_W_SCL(1);
OLED_W_SDA(1);
}
void OLED_I2C_Start(void)
{
OLED_W_SDA(1);
OLED_W_SCL(1);
OLED_W_SDA(0);
OLED_W_SCL(0);
}
void OLED_I2C_Stop(void)
{
OLED_W_SDA(0);
OLED_W_SCL(1);
OLED_W_SDA(1);
}
void OLED_I2C_SendByte(uint8_t Byte)
{
uint8_t i;
for (i = 0; i < 8; i++)
{
OLED_W_SDA(Byte & (0x80 >> i));
OLED_W_SCL(1);
OLED_W_SCL(0);
}
OLED_W_SCL(1);
OLED_W_SCL(0);
}
void OLED_WriteCommand(uint8_t Command)
{
OLED_I2C_Start();
OLED_I2C_SendByte(0x78);
OLED_I2C_SendByte(0x00);
OLED_I2C_SendByte(Command);
OLED_I2C_Stop();
}
void OLED_WriteData(uint8_t Data)
{
OLED_I2C_Start();
OLED_I2C_SendByte(0x78);
OLED_I2C_SendByte(0x40);
OLED_I2C_SendByte(Data);
OLED_I2C_Stop();
}
void OLED_SetCursor(uint8_t Y, uint8_t X)
{
OLED_WriteCommand(0xB0 | Y);
OLED_WriteCommand(0x10 | ((X & 0xF0) >> 4));
OLED_WriteCommand(0x00 | (X & 0x0F));
}
void OLED_Clear(void)
{
uint8_t i, j;
for (j = 0; j < 8; j++)
{
OLED_SetCursor(j, 0);
for(i = 0; i < 128; i++)
{
OLED_WriteData(0x00);
}
}
}
void OLED_ShowChar(uint8_t Line, uint8_t Column, char Char)
{
uint8_t i;
OLED_SetCursor((Line - 1) * 2, (Column - 1) * 8);
for (i = 0; i < 8; i++)
{
OLED_WriteData(OLED_F8x16[Char - ' '][i]);
}
OLED_SetCursor((Line - 1) * 2 + 1, (Column - 1) * 8);
for (i = 0; i < 8; i++)
{
OLED_WriteData(OLED_F8x16[Char - ' '][i + 8]);
}
}
void OLED_ShowString(uint8_t Line, uint8_t Column, char *String)
{
uint8_t i;
for (i = 0; String[i] != '\0'; i++)
{
OLED_ShowChar(Line, Column + i, String[i]);
}
}
uint32_t OLED_Pow(uint32_t X, uint32_t Y)
{
uint32_t Result = 1;
while (Y--)
{
Result *= X;
}
return Result;
}
void OLED_ShowNum(uint8_t Line, uint8_t Column, uint32_t Number, uint8_t Length)
{
uint8_t i;
for (i = 0; i < Length; i++)
{
OLED_ShowChar(Line, Column + i, Number / OLED_Pow(10, Length - i - 1) % 10 + '0');
}
}
void OLED_ShowSignedNum(uint8_t Line, uint8_t Column, int32_t Number, uint8_t Length)
{
uint8_t i;
uint32_t Number1;
if (Number >= 0)
{
OLED_ShowChar(Line, Column, '+');
Number1 = Number;
}
else
{
OLED_ShowChar(Line, Column, '-');
Number1 = -Number;
}
for (i = 0; i < Length; i++)
{
OLED_ShowChar(Line, Column + i + 1, Number1 / OLED_Pow(10, Length - i - 1) % 10 + '0');
}
}
void OLED_ShowHexNum(uint8_t Line, uint8_t Column, uint32_t Number, uint8_t Length)
{
uint8_t i, SingleNumber;
for (i = 0; i < Length; i++)
{
SingleNumber = Number / OLED_Pow(16, Length - i - 1) % 16;
if (SingleNumber < 10)
{
OLED_ShowChar(Line, Column + i, SingleNumber + '0');
}
else
{
OLED_ShowChar(Line, Column + i, SingleNumber - 10 + 'A');
}
}
}
void OLED_ShowBinNum(uint8_t Line, uint8_t Column, uint32_t Number, uint8_t Length)
{
uint8_t i;
for (i = 0; i < Length; i++)
{
OLED_ShowChar(Line, Column + i, Number / OLED_Pow(2, Length - i - 1) % 2 + '0');
}
}
/**
* @brief OLED显示一个16*16的汉字
* @param Line 行位置,范围:1~4 (一个汉字占用2行高度)
* @param Column 列位置,范围:1~8 (一个汉字占用2个字符宽度)
* @param num 要显示的汉字在OLED_F16x16_CN字模数组中的索引
* @retval 无
*/
void OLED_ShowChinese(uint8_t Line, uint8_t Column, uint8_t num)
{
uint8_t i;
// 计算X坐标:一个汉字宽度为16像素,等于2个8像素的字符宽度
uint8_t x = (Column - 1) * 16;
// 计算Y坐标:一个汉字高度为16像素,等于2个8像素的行(页)高度
uint8_t y = (Line - 1) * 2;
// 检查坐标是否越界
if (x > 127 || y > 7) return;
// 显示上半部分 (16*8)
OLED_SetCursor(y, x);
for (i = 0; i < 16; i++)
{
OLED_WriteData(OLED_F16x16_CN[num][i]);
}
// 显示下半部分 (16*8)
OLED_SetCursor(y + 1, x);
for (i = 0; i < 16; i++)
{
OLED_WriteData(OLED_F16x16_CN[num][i + 16]);
}
}
/* ... OLED_Init() 函数保持不变 ... */
void OLED_Init(void)
{
uint32_t i, j;
for (i = 0; i < 1000; i++)
{
for (j = 0; j < 1000; j++);
}
OLED_I2C_Init();
OLED_WriteCommand(0xAE); //关闭显示
OLED_WriteCommand(0xD5); //设置显示时钟分频比/振荡器频率
OLED_WriteCommand(0x80);
OLED_WriteCommand(0xA8); //设置多路复用率
OLED_WriteCommand(0x3F);
OLED_WriteCommand(0xD3); //设置显示偏移
OLED_WriteCommand(0x00);
OLED_WriteCommand(0x40); //设置显示开始行
OLED_WriteCommand(0xA1); //设置左右方向,0xA1正常 0xA0左右反置
OLED_WriteCommand(0xC8); //设置上下方向,0xC8正常 0xC0上下反置
OLED_WriteCommand(0xDA); //设置COM引脚硬件配置
OLED_WriteCommand(0x12);
OLED_WriteCommand(0x81); //设置对比度控制
OLED_WriteCommand(0xCF);
OLED_WriteCommand(0xD9); //设置预充电周期
OLED_WriteCommand(0xF1);
OLED_WriteCommand(0xDB); //设置VCOMH取消选择级别
OLED_WriteCommand(0x30);
OLED_WriteCommand(0xA4); //设置整个显示打开/关闭
OLED_WriteCommand(0xA6); //设置正常/倒转显示
OLED_WriteCommand(0x8D); //设置充电泵
OLED_WriteCommand(0x14);
OLED_WriteCommand(0xAF); //开启显示
OLED_Clear();
}
+21
View File
@@ -0,0 +1,21 @@
// ======================================================
// OLED.h (最终修正版)
// ======================================================
#ifndef __OLED_H
#define __OLED_H
#include "stm32f10x.h"
void OLED_Init(void);
void OLED_Clear(void);
void OLED_ShowChar(uint8_t Line, uint8_t Column, char Char);
void OLED_ShowString(uint8_t Line, uint8_t Column, char *String);
void OLED_ShowNum(uint8_t Line, uint8_t Column, uint32_t Number, uint8_t Length);
void OLED_ShowSignedNum(uint8_t Line, uint8_t Column, int32_t Number, uint8_t Length);
void OLED_ShowHexNum(uint8_t Line, uint8_t Column, uint32_t Number, uint8_t Length);
void OLED_ShowBinNum(uint8_t Line, uint8_t Column, uint32_t Number, uint8_t Length);
// 添加显示汉字的函数声明
void OLED_ShowChinese(uint8_t Line, uint8_t Column, uint8_t num);
#endif
+326
View File
@@ -0,0 +1,326 @@
#ifndef __OLED_FONT_H
#define __OLED_FONT_H
/*OLED字模库,宽8像素,高16像素*/
const uint8_t OLED_F8x16[][16]=
{
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,// 0
0x00,0x00,0x00,0xF8,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x33,0x30,0x00,0x00,0x00,//! 1
0x00,0x10,0x0C,0x06,0x10,0x0C,0x06,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,//" 2
0x40,0xC0,0x78,0x40,0xC0,0x78,0x40,0x00,
0x04,0x3F,0x04,0x04,0x3F,0x04,0x04,0x00,//# 3
0x00,0x70,0x88,0xFC,0x08,0x30,0x00,0x00,
0x00,0x18,0x20,0xFF,0x21,0x1E,0x00,0x00,//$ 4
0xF0,0x08,0xF0,0x00,0xE0,0x18,0x00,0x00,
0x00,0x21,0x1C,0x03,0x1E,0x21,0x1E,0x00,//% 5
0x00,0xF0,0x08,0x88,0x70,0x00,0x00,0x00,
0x1E,0x21,0x23,0x24,0x19,0x27,0x21,0x10,//& 6
0x10,0x16,0x0E,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,//' 7
0x00,0x00,0x00,0xE0,0x18,0x04,0x02,0x00,
0x00,0x00,0x00,0x07,0x18,0x20,0x40,0x00,//( 8
0x00,0x02,0x04,0x18,0xE0,0x00,0x00,0x00,
0x00,0x40,0x20,0x18,0x07,0x00,0x00,0x00,//) 9
0x40,0x40,0x80,0xF0,0x80,0x40,0x40,0x00,
0x02,0x02,0x01,0x0F,0x01,0x02,0x02,0x00,//* 10
0x00,0x00,0x00,0xF0,0x00,0x00,0x00,0x00,
0x01,0x01,0x01,0x1F,0x01,0x01,0x01,0x00,//+ 11
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x80,0xB0,0x70,0x00,0x00,0x00,0x00,0x00,//, 12
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x01,0x01,0x01,0x01,0x01,0x01,0x01,//- 13
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x30,0x30,0x00,0x00,0x00,0x00,0x00,//. 14
0x00,0x00,0x00,0x00,0x80,0x60,0x18,0x04,
0x00,0x60,0x18,0x06,0x01,0x00,0x00,0x00,/// 15
0x00,0xE0,0x10,0x08,0x08,0x10,0xE0,0x00,
0x00,0x0F,0x10,0x20,0x20,0x10,0x0F,0x00,//0 16
0x00,0x10,0x10,0xF8,0x00,0x00,0x00,0x00,
0x00,0x20,0x20,0x3F,0x20,0x20,0x00,0x00,//1 17
0x00,0x70,0x08,0x08,0x08,0x88,0x70,0x00,
0x00,0x30,0x28,0x24,0x22,0x21,0x30,0x00,//2 18
0x00,0x30,0x08,0x88,0x88,0x48,0x30,0x00,
0x00,0x18,0x20,0x20,0x20,0x11,0x0E,0x00,//3 19
0x00,0x00,0xC0,0x20,0x10,0xF8,0x00,0x00,
0x00,0x07,0x04,0x24,0x24,0x3F,0x24,0x00,//4 20
0x00,0xF8,0x08,0x88,0x88,0x08,0x08,0x00,
0x00,0x19,0x21,0x20,0x20,0x11,0x0E,0x00,//5 21
0x00,0xE0,0x10,0x88,0x88,0x18,0x00,0x00,
0x00,0x0F,0x11,0x20,0x20,0x11,0x0E,0x00,//6 22
0x00,0x38,0x08,0x08,0xC8,0x38,0x08,0x00,
0x00,0x00,0x00,0x3F,0x00,0x00,0x00,0x00,//7 23
0x00,0x70,0x88,0x08,0x08,0x88,0x70,0x00,
0x00,0x1C,0x22,0x21,0x21,0x22,0x1C,0x00,//8 24
0x00,0xE0,0x10,0x08,0x08,0x10,0xE0,0x00,
0x00,0x00,0x31,0x22,0x22,0x11,0x0F,0x00,//9 25
0x00,0x00,0x00,0xC0,0xC0,0x00,0x00,0x00,
0x00,0x00,0x00,0x30,0x30,0x00,0x00,0x00,//: 26
0x00,0x00,0x00,0x80,0x00,0x00,0x00,0x00,
0x00,0x00,0x80,0x60,0x00,0x00,0x00,0x00,//; 27
0x00,0x00,0x80,0x40,0x20,0x10,0x08,0x00,
0x00,0x01,0x02,0x04,0x08,0x10,0x20,0x00,//< 28
0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x00,
0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x00,//= 29
0x00,0x08,0x10,0x20,0x40,0x80,0x00,0x00,
0x00,0x20,0x10,0x08,0x04,0x02,0x01,0x00,//> 30
0x00,0x70,0x48,0x08,0x08,0x08,0xF0,0x00,
0x00,0x00,0x00,0x30,0x36,0x01,0x00,0x00,//? 31
0xC0,0x30,0xC8,0x28,0xE8,0x10,0xE0,0x00,
0x07,0x18,0x27,0x24,0x23,0x14,0x0B,0x00,//@ 32
0x00,0x00,0xC0,0x38,0xE0,0x00,0x00,0x00,
0x20,0x3C,0x23,0x02,0x02,0x27,0x38,0x20,//A 33
0x08,0xF8,0x88,0x88,0x88,0x70,0x00,0x00,
0x20,0x3F,0x20,0x20,0x20,0x11,0x0E,0x00,//B 34
0xC0,0x30,0x08,0x08,0x08,0x08,0x38,0x00,
0x07,0x18,0x20,0x20,0x20,0x10,0x08,0x00,//C 35
0x08,0xF8,0x08,0x08,0x08,0x10,0xE0,0x00,
0x20,0x3F,0x20,0x20,0x20,0x10,0x0F,0x00,//D 36
0x08,0xF8,0x88,0x88,0xE8,0x08,0x10,0x00,
0x20,0x3F,0x20,0x20,0x23,0x20,0x18,0x00,//E 37
0x08,0xF8,0x88,0x88,0xE8,0x08,0x10,0x00,
0x20,0x3F,0x20,0x00,0x03,0x00,0x00,0x00,//F 38
0xC0,0x30,0x08,0x08,0x08,0x38,0x00,0x00,
0x07,0x18,0x20,0x20,0x22,0x1E,0x02,0x00,//G 39
0x08,0xF8,0x08,0x00,0x00,0x08,0xF8,0x08,
0x20,0x3F,0x21,0x01,0x01,0x21,0x3F,0x20,//H 40
0x00,0x08,0x08,0xF8,0x08,0x08,0x00,0x00,
0x00,0x20,0x20,0x3F,0x20,0x20,0x00,0x00,//I 41
0x00,0x00,0x08,0x08,0xF8,0x08,0x08,0x00,
0xC0,0x80,0x80,0x80,0x7F,0x00,0x00,0x00,//J 42
0x08,0xF8,0x88,0xC0,0x28,0x18,0x08,0x00,
0x20,0x3F,0x20,0x01,0x26,0x38,0x20,0x00,//K 43
0x08,0xF8,0x08,0x00,0x00,0x00,0x00,0x00,
0x20,0x3F,0x20,0x20,0x20,0x20,0x30,0x00,//L 44
0x08,0xF8,0xF8,0x00,0xF8,0xF8,0x08,0x00,
0x20,0x3F,0x00,0x3F,0x00,0x3F,0x20,0x00,//M 45
0x08,0xF8,0x30,0xC0,0x00,0x08,0xF8,0x08,
0x20,0x3F,0x20,0x00,0x07,0x18,0x3F,0x00,//N 46
0xE0,0x10,0x08,0x08,0x08,0x10,0xE0,0x00,
0x0F,0x10,0x20,0x20,0x20,0x10,0x0F,0x00,//O 47
0x08,0xF8,0x08,0x08,0x08,0x08,0xF0,0x00,
0x20,0x3F,0x21,0x01,0x01,0x01,0x00,0x00,//P 48
0xE0,0x10,0x08,0x08,0x08,0x10,0xE0,0x00,
0x0F,0x18,0x24,0x24,0x38,0x50,0x4F,0x00,//Q 49
0x08,0xF8,0x88,0x88,0x88,0x88,0x70,0x00,
0x20,0x3F,0x20,0x00,0x03,0x0C,0x30,0x20,//R 50
0x00,0x70,0x88,0x08,0x08,0x08,0x38,0x00,
0x00,0x38,0x20,0x21,0x21,0x22,0x1C,0x00,//S 51
0x18,0x08,0x08,0xF8,0x08,0x08,0x18,0x00,
0x00,0x00,0x20,0x3F,0x20,0x00,0x00,0x00,//T 52
0x08,0xF8,0x08,0x00,0x00,0x08,0xF8,0x08,
0x00,0x1F,0x20,0x20,0x20,0x20,0x1F,0x00,//U 53
0x08,0x78,0x88,0x00,0x00,0xC8,0x38,0x08,
0x00,0x00,0x07,0x38,0x0E,0x01,0x00,0x00,//V 54
0xF8,0x08,0x00,0xF8,0x00,0x08,0xF8,0x00,
0x03,0x3C,0x07,0x00,0x07,0x3C,0x03,0x00,//W 55
0x08,0x18,0x68,0x80,0x80,0x68,0x18,0x08,
0x20,0x30,0x2C,0x03,0x03,0x2C,0x30,0x20,//X 56
0x08,0x38,0xC8,0x00,0xC8,0x38,0x08,0x00,
0x00,0x00,0x20,0x3F,0x20,0x00,0x00,0x00,//Y 57
0x10,0x08,0x08,0x08,0xC8,0x38,0x08,0x00,
0x20,0x38,0x26,0x21,0x20,0x20,0x18,0x00,//Z 58
0x00,0x00,0x00,0xFE,0x02,0x02,0x02,0x00,
0x00,0x00,0x00,0x7F,0x40,0x40,0x40,0x00,//[ 59
0x00,0x0C,0x30,0xC0,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x01,0x06,0x38,0xC0,0x00,//\ 60
0x00,0x02,0x02,0x02,0xFE,0x00,0x00,0x00,
0x00,0x40,0x40,0x40,0x7F,0x00,0x00,0x00,//] 61
0x00,0x00,0x04,0x02,0x02,0x02,0x04,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,//^ 62
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,//_ 63
0x00,0x02,0x02,0x04,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,//` 64
0x00,0x00,0x80,0x80,0x80,0x80,0x00,0x00,
0x00,0x19,0x24,0x22,0x22,0x22,0x3F,0x20,//a 65
0x08,0xF8,0x00,0x80,0x80,0x00,0x00,0x00,
0x00,0x3F,0x11,0x20,0x20,0x11,0x0E,0x00,//b 66
0x00,0x00,0x00,0x80,0x80,0x80,0x00,0x00,
0x00,0x0E,0x11,0x20,0x20,0x20,0x11,0x00,//c 67
0x00,0x00,0x00,0x80,0x80,0x88,0xF8,0x00,
0x00,0x0E,0x11,0x20,0x20,0x10,0x3F,0x20,//d 68
0x00,0x00,0x80,0x80,0x80,0x80,0x00,0x00,
0x00,0x1F,0x22,0x22,0x22,0x22,0x13,0x00,//e 69
0x00,0x80,0x80,0xF0,0x88,0x88,0x88,0x18,
0x00,0x20,0x20,0x3F,0x20,0x20,0x00,0x00,//f 70
0x00,0x00,0x80,0x80,0x80,0x80,0x80,0x00,
0x00,0x6B,0x94,0x94,0x94,0x93,0x60,0x00,//g 71
0x08,0xF8,0x00,0x80,0x80,0x80,0x00,0x00,
0x20,0x3F,0x21,0x00,0x00,0x20,0x3F,0x20,//h 72
0x00,0x80,0x98,0x98,0x00,0x00,0x00,0x00,
0x00,0x20,0x20,0x3F,0x20,0x20,0x00,0x00,//i 73
0x00,0x00,0x00,0x80,0x98,0x98,0x00,0x00,
0x00,0xC0,0x80,0x80,0x80,0x7F,0x00,0x00,//j 74
0x08,0xF8,0x00,0x00,0x80,0x80,0x80,0x00,
0x20,0x3F,0x24,0x02,0x2D,0x30,0x20,0x00,//k 75
0x00,0x08,0x08,0xF8,0x00,0x00,0x00,0x00,
0x00,0x20,0x20,0x3F,0x20,0x20,0x00,0x00,//l 76
0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x00,
0x20,0x3F,0x20,0x00,0x3F,0x20,0x00,0x3F,//m 77
0x80,0x80,0x00,0x80,0x80,0x80,0x00,0x00,
0x20,0x3F,0x21,0x00,0x00,0x20,0x3F,0x20,//n 78
0x00,0x00,0x80,0x80,0x80,0x80,0x00,0x00,
0x00,0x1F,0x20,0x20,0x20,0x20,0x1F,0x00,//o 79
0x80,0x80,0x00,0x80,0x80,0x00,0x00,0x00,
0x80,0xFF,0xA1,0x20,0x20,0x11,0x0E,0x00,//p 80
0x00,0x00,0x00,0x80,0x80,0x80,0x80,0x00,
0x00,0x0E,0x11,0x20,0x20,0xA0,0xFF,0x80,//q 81
0x80,0x80,0x80,0x00,0x80,0x80,0x80,0x00,
0x20,0x20,0x3F,0x21,0x20,0x00,0x01,0x00,//r 82
0x00,0x00,0x80,0x80,0x80,0x80,0x80,0x00,
0x00,0x33,0x24,0x24,0x24,0x24,0x19,0x00,//s 83
0x00,0x80,0x80,0xE0,0x80,0x80,0x00,0x00,
0x00,0x00,0x00,0x1F,0x20,0x20,0x00,0x00,//t 84
0x80,0x80,0x00,0x00,0x00,0x80,0x80,0x00,
0x00,0x1F,0x20,0x20,0x20,0x10,0x3F,0x20,//u 85
0x80,0x80,0x80,0x00,0x00,0x80,0x80,0x80,
0x00,0x01,0x0E,0x30,0x08,0x06,0x01,0x00,//v 86
0x80,0x80,0x00,0x80,0x00,0x80,0x80,0x80,
0x0F,0x30,0x0C,0x03,0x0C,0x30,0x0F,0x00,//w 87
0x00,0x80,0x80,0x00,0x80,0x80,0x80,0x00,
0x00,0x20,0x31,0x2E,0x0E,0x31,0x20,0x00,//x 88
0x80,0x80,0x80,0x00,0x00,0x80,0x80,0x80,
0x80,0x81,0x8E,0x70,0x18,0x06,0x01,0x00,//y 89
0x00,0x80,0x80,0x80,0x80,0x80,0x80,0x00,
0x00,0x21,0x30,0x2C,0x22,0x21,0x30,0x00,//z 90
0x00,0x00,0x00,0x00,0x80,0x7C,0x02,0x02,
0x00,0x00,0x00,0x00,0x00,0x3F,0x40,0x40,//{ 91
0x00,0x00,0x00,0x00,0xFF,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0xFF,0x00,0x00,0x00,//| 92
0x00,0x02,0x02,0x7C,0x80,0x00,0x00,0x00,
0x00,0x40,0x40,0x3F,0x00,0x00,0x00,0x00,//} 93
0x00,0x06,0x01,0x01,0x02,0x02,0x04,0x04,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,//~ 94
};
/*
* 添加16x16中文字模库
* 每个汉字占用32个字节
* 汉字列表: 成(0) 功(1) 识(2) 别(3) 录(4) 入(5) 失(6) 败(7)
*/
const uint8_t OLED_F16x16_CN[][32]=
{
{0x00,0x00,0x00,0x00,0x00,0x00,0xC0,0x3F,0xC0,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x80,0x40,0x20,0x10,0x0C,0x03,0x00,0x00,0x00,0x03,0x0C,0x10,0x20,0x40,0x80,0x00},/*"成",0*/
{0x00,0xFE,0x22,0x22,0xFE,0x00,0x40,0x20,0x50,0x4C,0x43,0x4C,0x50,0x20,0x40,0x00,
0x80,0x7F,0x02,0x82,0xFF,0x00,0x40,0x44,0x58,0x41,0x4E,0x60,0x58,0x47,0x40,0x00},/*"功",1*/
{0x40,0x40,0x42,0xCC,0x00,0x00,0x00,0xFC,0x04,0x04,0x04,0x04,0x04,0xFC,0x00,0x00,
0x00,0x00,0x00,0x3F,0x10,0x88,0x40,0x23,0x19,0x01,0x01,0x09,0x11,0x23,0xC0,0x00},/*"识",2*/
{0x00,0x3E,0x22,0xE2,0x22,0x22,0x22,0x3E,0x00,0x00,0xF8,0x00,0x00,0xFF,0x00,0x00,
0x81,0x41,0x31,0x0F,0x41,0x81,0x41,0x3F,0x00,0x00,0x0F,0x40,0x80,0x7F,0x00,0x00},/*"别",3*/
{0x00,0x00,0xF8,0x88,0x88,0x88,0x88,0x08,0x08,0xFF,0x08,0x09,0x0A,0xC8,0x08,0x00,
0x80,0x60,0x1F,0x00,0x10,0x20,0x1F,0x80,0x40,0x21,0x16,0x18,0x26,0x41,0xF8,0x00},/*"录",4*/
{0x08,0x08,0x08,0xF8,0x08,0x08,0x08,0x10,0x10,0xFF,0x10,0x10,0x10,0xF0,0x00,0x00,
0x10,0x30,0x10,0x1F,0x08,0x88,0x48,0x30,0x0E,0x01,0x40,0x80,0x40,0x3F,0x00,0x00},/*"入",5*/
{0x00,0x40,0x30,0x1E,0x10,0x10,0x10,0xFF,0x10,0x10,0x10,0x10,0x10,0x00,0x00,0x00,
0x81,0x81,0x41,0x21,0x11,0x0D,0x03,0x01,0x03,0x0D,0x11,0x21,0x41,0x81,0x81,0x00},/*"失",6*/
{0x00,0xFE,0x02,0xFA,0x02,0xFE,0x40,0x20,0xD8,0x17,0x10,0x10,0xF0,0x10,0x10,0x00,
0x80,0x47,0x30,0x0F,0x10,0x67,0x80,0x40,0x21,0x16,0x08,0x16,0x21,0x40,0x80,0x00},/*"败",7*/
};
#endif
+83
View File
@@ -0,0 +1,83 @@
#include "Serial.h"
#include "Delay.h" // Get_Time_ms 在这里,所以我们包含它
#include <string.h>
#define RX_BUFFER_SIZE 1024 // ← 增大到1024字节,支持完整HTTP响应
char RxBuffer[RX_BUFFER_SIZE];
uint16_t RxCounter = 0;
// 初始化ESP8266所用的串口 (根据连接图.md: USART1 PA9/PA10)
void Serial_Init(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
USART_InitTypeDef USART_InitStructure;
NVIC_InitTypeDef NVIC_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_USART1, ENABLE);
// PA9: USART1_TX
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);
// PA10: USART1_RX
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
GPIO_Init(GPIOA, &GPIO_InitStructure);
USART_InitStructure.USART_BaudRate = 115200;
USART_InitStructure.USART_WordLength = USART_WordLength_8b;
USART_InitStructure.USART_StopBits = USART_StopBits_1;
USART_InitStructure.USART_Parity = USART_Parity_No;
USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;
USART_Init(USART1, &USART_InitStructure);
USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);
NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 2; // 优先级低于K210
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 2;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
USART_Cmd(USART1, ENABLE);
}
// 发送字符串
void Serial_SendString(const char* String)
{
for (uint32_t i = 0; String[i] != '\0'; i++)
{
while (USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET);
USART_SendData(USART1, String[i]);
}
}
// 获取接收缓冲区
char* Serial_GetRxBuffer(void)
{
return RxBuffer;
}
// 清空接收缓冲区
void Serial_ClearRxBuffer(void)
{
memset(RxBuffer, 0, RX_BUFFER_SIZE);
RxCounter = 0;
}
// USART1中断服务函数 (ESP8266)
void USART1_IRQHandler(void)
{
if (USART_GetITStatus(USART1, USART_IT_RXNE) == SET)
{
uint8_t RxData = USART_ReceiveData(USART1);
if (RxCounter < RX_BUFFER_SIZE - 1)
{
RxBuffer[RxCounter++] = RxData;
}
USART_ClearITPendingBit(USART1, USART_IT_RXNE);
}
}
+17
View File
@@ -0,0 +1,17 @@
#ifndef __SERIAL_H
#define __SERIAL_H
#include <stdio.h>
#include "stm32f10x.h"
void Serial_Init(void); // 初始化串口
void Serial_SendByte(uint8_t Byte); // 发送一个字节
void Serial_SendString(const char* String); // 发送字符串
char* Serial_GetRxBuffer(void); // 获取接收缓冲区指针
void Serial_ClearRxBuffer(void); // 清空接收缓冲区
// 注意:Get_Time_ms() 函数与串口无关,应该在 `Delay.h` 中声明。
// 我们暂时将它与串口放一起,但更规范的做法是分离。
uint32_t Get_Time_ms(void);
#endif
+237
View File
@@ -0,0 +1,237 @@
/*
* 标准AES-128-CBC实现 - 与OpenSSL完全兼容
* 基于FIPS-197标准
*/
#include "aes.h"
#include <string.h>
// S-box
static const uint8_t sbox[256] = {
0x63, 0x7c, 0x77, 0x7b, 0xf2, 0x6b, 0x6f, 0xc5, 0x30, 0x01, 0x67, 0x2b, 0xfe, 0xd7, 0xab, 0x76,
0xca, 0x82, 0xc9, 0x7d, 0xfa, 0x59, 0x47, 0xf0, 0xad, 0xd4, 0xa2, 0xaf, 0x9c, 0xa4, 0x72, 0xc0,
0xb7, 0xfd, 0x93, 0x26, 0x36, 0x3f, 0xf7, 0xcc, 0x34, 0xa5, 0xe5, 0xf1, 0x71, 0xd8, 0x31, 0x15,
0x04, 0xc7, 0x23, 0xc3, 0x18, 0x96, 0x05, 0x9a, 0x07, 0x12, 0x80, 0xe2, 0xeb, 0x27, 0xb2, 0x75,
0x09, 0x83, 0x2c, 0x1a, 0x1b, 0x6e, 0x5a, 0xa0, 0x52, 0x3b, 0xd6, 0xb3, 0x29, 0xe3, 0x2f, 0x84,
0x53, 0xd1, 0x00, 0xed, 0x20, 0xfc, 0xb1, 0x5b, 0x6a, 0xcb, 0xbe, 0x39, 0x4a, 0x4c, 0x58, 0xcf,
0xd0, 0xef, 0xaa, 0xfb, 0x43, 0x4d, 0x33, 0x85, 0x45, 0xf9, 0x02, 0x7f, 0x50, 0x3c, 0x9f, 0xa8,
0x51, 0xa3, 0x40, 0x8f, 0x92, 0x9d, 0x38, 0xf5, 0xbc, 0xb6, 0xda, 0x21, 0x10, 0xff, 0xf3, 0xd2,
0xcd, 0x0c, 0x13, 0xec, 0x5f, 0x97, 0x44, 0x17, 0xc4, 0xa7, 0x7e, 0x3d, 0x64, 0x5d, 0x19, 0x73,
0x60, 0x81, 0x4f, 0xdc, 0x22, 0x2a, 0x90, 0x88, 0x46, 0xee, 0xb8, 0x14, 0xde, 0x5e, 0x0b, 0xdb,
0xe0, 0x32, 0x3a, 0x0a, 0x49, 0x06, 0x24, 0x5c, 0xc2, 0xd3, 0xac, 0x62, 0x91, 0x95, 0xe4, 0x79,
0xe7, 0xc8, 0x37, 0x6d, 0x8d, 0xd5, 0x4e, 0xa9, 0x6c, 0x56, 0xf4, 0xea, 0x65, 0x7a, 0xae, 0x08,
0xba, 0x78, 0x25, 0x2e, 0x1c, 0xa6, 0xb4, 0xc6, 0xe8, 0xdd, 0x74, 0x1f, 0x4b, 0xbd, 0x8b, 0x8a,
0x70, 0x3e, 0xb5, 0x66, 0x48, 0x03, 0xf6, 0x0e, 0x61, 0x35, 0x57, 0xb9, 0x86, 0xc1, 0x1d, 0x9e,
0xe1, 0xf8, 0x98, 0x11, 0x69, 0xd9, 0x8e, 0x94, 0x9b, 0x1e, 0x87, 0xe9, 0xce, 0x55, 0x28, 0xdf,
0x8c, 0xa1, 0x89, 0x0d, 0xbf, 0xe6, 0x42, 0x68, 0x41, 0x99, 0x2d, 0x0f, 0xb0, 0x54, 0xbb, 0x16
};
// Rcon
static const uint8_t Rcon[11] = {
0x8d, 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1b, 0x36
};
#define getSBoxValue(num) (sbox[(num)])
// 密钥扩展
static void KeyExpansion(uint8_t* RoundKey, const uint8_t* Key)
{
unsigned i, j, k;
uint8_t tempa[4];
// 前4个轮密钥直接复制
for (i = 0; i < 4; ++i) {
RoundKey[(i * 4) + 0] = Key[(i * 4) + 0];
RoundKey[(i * 4) + 1] = Key[(i * 4) + 1];
RoundKey[(i * 4) + 2] = Key[(i * 4) + 2];
RoundKey[(i * 4) + 3] = Key[(i * 4) + 3];
}
// 生成其余轮密钥
for (i = 4; i < 44; ++i) {
k = (i - 1) * 4;
tempa[0] = RoundKey[k + 0];
tempa[1] = RoundKey[k + 1];
tempa[2] = RoundKey[k + 2];
tempa[3] = RoundKey[k + 3];
if (i % 4 == 0) {
// RotWord
const uint8_t u8tmp = tempa[0];
tempa[0] = tempa[1];
tempa[1] = tempa[2];
tempa[2] = tempa[3];
tempa[3] = u8tmp;
// SubWord
tempa[0] = getSBoxValue(tempa[0]);
tempa[1] = getSBoxValue(tempa[1]);
tempa[2] = getSBoxValue(tempa[2]);
tempa[3] = getSBoxValue(tempa[3]);
tempa[0] = tempa[0] ^ Rcon[i / 4];
}
j = i * 4;
k = (i - 4) * 4;
RoundKey[j + 0] = RoundKey[k + 0] ^ tempa[0];
RoundKey[j + 1] = RoundKey[k + 1] ^ tempa[1];
RoundKey[j + 2] = RoundKey[k + 2] ^ tempa[2];
RoundKey[j + 3] = RoundKey[k + 3] ^ tempa[3];
}
}
// AddRoundKey
static void AddRoundKey(uint8_t round, uint8_t* state, const uint8_t* RoundKey)
{
uint8_t i, j;
for (i = 0; i < 4; ++i) {
for (j = 0; j < 4; ++j) {
state[i * 4 + j] ^= RoundKey[(round * 16) + (i * 4 + j)];
}
}
}
// SubBytes
static void SubBytes(uint8_t* state)
{
uint8_t i;
for (i = 0; i < 16; ++i) {
state[i] = getSBoxValue(state[i]);
}
}
// ShiftRows
static void ShiftRows(uint8_t* state)
{
uint8_t temp;
// 第二行左移1字节
temp = state[1];
state[1] = state[5];
state[5] = state[9];
state[9] = state[13];
state[13] = temp;
// 第三行左移2字节
temp = state[2];
state[2] = state[10];
state[10] = temp;
temp = state[6];
state[6] = state[14];
state[14] = temp;
// 第四行左移3字节
temp = state[15];
state[15] = state[11];
state[11] = state[7];
state[7] = state[3];
state[3] = temp;
}
// xtime (用于MixColumns)
static uint8_t xtime(uint8_t x)
{
return ((x << 1) ^ (((x >> 7) & 1) * 0x1b));
}
// MixColumns
static void MixColumns(uint8_t* state)
{
uint8_t i;
uint8_t Tmp, Tm, t;
for (i = 0; i < 4; ++i) {
t = state[i * 4 + 0];
Tmp = state[i * 4 + 0] ^ state[i * 4 + 1] ^ state[i * 4 + 2] ^ state[i * 4 + 3];
Tm = state[i * 4 + 0] ^ state[i * 4 + 1];
Tm = xtime(Tm);
state[i * 4 + 0] ^= Tm ^ Tmp;
Tm = state[i * 4 + 1] ^ state[i * 4 + 2];
Tm = xtime(Tm);
state[i * 4 + 1] ^= Tm ^ Tmp;
Tm = state[i * 4 + 2] ^ state[i * 4 + 3];
Tm = xtime(Tm);
state[i * 4 + 2] ^= Tm ^ Tmp;
Tm = state[i * 4 + 3] ^ t;
Tm = xtime(Tm);
state[i * 4 + 3] ^= Tm ^ Tmp;
}
}
// Cipher - AES加密核心
static void Cipher(uint8_t* state, const uint8_t* RoundKey)
{
uint8_t round = 0;
// 初始轮密钥加
AddRoundKey(0, state, RoundKey);
// 9轮标准变换
for (round = 1; round < 10; ++round) {
SubBytes(state);
ShiftRows(state);
MixColumns(state);
AddRoundKey(round, state, RoundKey);
}
// 最后一轮(无MixColumns)
SubBytes(state);
ShiftRows(state);
AddRoundKey(10, state, RoundKey);
}
// ========== 公开API函数 ==========
void AES_init_ctx(struct AES_ctx* ctx, const uint8_t* key)
{
KeyExpansion(ctx->RoundKey, key);
}
void AES_init_ctx_iv(struct AES_ctx* ctx, const uint8_t* key, const uint8_t* iv)
{
KeyExpansion(ctx->RoundKey, key);
memcpy(ctx->Iv, iv, AES_BLOCKLEN);
}
void AES_ECB_encrypt(const struct AES_ctx* ctx, uint8_t* buf)
{
Cipher(buf, ctx->RoundKey);
}
void AES_CBC_encrypt_buffer(struct AES_ctx* ctx, uint8_t* buf, size_t length)
{
size_t i;
uint8_t j;
for (i = 0; i < length; i += AES_BLOCKLEN) {
// XOR with IV/previous ciphertext block
for (j = 0; j < AES_BLOCKLEN; ++j) {
buf[i + j] ^= ctx->Iv[j];
}
// Encrypt block
Cipher(buf + i, ctx->RoundKey);
// Update IV to current ciphertext block
memcpy(ctx->Iv, buf + i, AES_BLOCKLEN);
}
}
// STM32端不需要解密功能
void AES_ECB_decrypt(const struct AES_ctx* ctx, uint8_t* buf)
{
(void)ctx;
(void)buf;
}
void AES_CBC_decrypt_buffer(struct AES_ctx* ctx, uint8_t* buf, size_t length)
{
(void)ctx;
(void)buf;
(void)length;
}
+65
View File
@@ -0,0 +1,65 @@
#ifndef _AES_H_
#define _AES_H_
#include <stdint.h>
#include <stddef.h>
// 使用AES-128(16字节密钥)
#define AES_BLOCKLEN 16 // Block length in bytes - AES is 128b block only
#define AES_KEYLEN 16 // Key length in bytes (AES-128)
#define AES_keyExpSize 176
struct AES_ctx
{
uint8_t RoundKey[AES_keyExpSize];
uint8_t Iv[AES_BLOCKLEN]; // 独立的IV存储(标准做法,与OpenSSL兼容)
};
/**
* @brief 初始化AES上下文
* @param ctx AES上下文结构
* @param key 16字节密钥
*/
void AES_init_ctx(struct AES_ctx* ctx, const uint8_t* key);
/**
* @brief AES-ECB加密(单个块)
* @param ctx AES上下文
* @param buf 要加密的数据(16字节块)
*/
void AES_ECB_encrypt(const struct AES_ctx* ctx, uint8_t* buf);
/**
* @brief AES-ECB解密(单个块)
* @param ctx AES上下文
* @param buf 要解密的数据(16字节块)
*/
void AES_ECB_decrypt(const struct AES_ctx* ctx, uint8_t* buf);
/**
* @brief AES-CBC加密(多个块)
* @param ctx AES上下文
* @param buf 要加密的数据
* @param length 数据长度(必须是16的倍数)
* @param iv 初始化向量(16字节)
*/
void AES_CBC_encrypt_buffer(struct AES_ctx* ctx, uint8_t* buf, size_t length);
/**
* @brief AES-CBC解密(多个块)
* @param ctx AES上下文
* @param buf 要解密的数据
* @param length 数据长度(必须是16的倍数)
* @param iv 初始化向量(16字节)
*/
void AES_CBC_decrypt_buffer(struct AES_ctx* ctx, uint8_t* buf, size_t length);
/**
* @brief 初始化AES上下文(CBC模式)
* @param ctx AES上下文结构
* @param key 16字节密钥
* @param iv 16字节初始化向量
*/
void AES_init_ctx_iv(struct AES_ctx* ctx, const uint8_t* key, const uint8_t* iv);
#endif // _AES_H_
+237
View File
@@ -0,0 +1,237 @@
/*
* 标准AES-128-CBC实现 - 与OpenSSL完全兼容
* 基于FIPS-197标准
*/
#include "aes.h"
#include <string.h>
// S-box
static const uint8_t sbox[256] = {
0x63, 0x7c, 0x77, 0x7b, 0xf2, 0x6b, 0x6f, 0xc5, 0x30, 0x01, 0x67, 0x2b, 0xfe, 0xd7, 0xab, 0x76,
0xca, 0x82, 0xc9, 0x7d, 0xfa, 0x59, 0x47, 0xf0, 0xad, 0xd4, 0xa2, 0xaf, 0x9c, 0xa4, 0x72, 0xc0,
0xb7, 0xfd, 0x93, 0x26, 0x36, 0x3f, 0xf7, 0xcc, 0x34, 0xa5, 0xe5, 0xf1, 0x71, 0xd8, 0x31, 0x15,
0x04, 0xc7, 0x23, 0xc3, 0x18, 0x96, 0x05, 0x9a, 0x07, 0x12, 0x80, 0xe2, 0xeb, 0x27, 0xb2, 0x75,
0x09, 0x83, 0x2c, 0x1a, 0x1b, 0x6e, 0x5a, 0xa0, 0x52, 0x3b, 0xd6, 0xb3, 0x29, 0xe3, 0x2f, 0x84,
0x53, 0xd1, 0x00, 0xed, 0x20, 0xfc, 0xb1, 0x5b, 0x6a, 0xcb, 0xbe, 0x39, 0x4a, 0x4c, 0x58, 0xcf,
0xd0, 0xef, 0xaa, 0xfb, 0x43, 0x4d, 0x33, 0x85, 0x45, 0xf9, 0x02, 0x7f, 0x50, 0x3c, 0x9f, 0xa8,
0x51, 0xa3, 0x40, 0x8f, 0x92, 0x9d, 0x38, 0xf5, 0xbc, 0xb6, 0xda, 0x21, 0x10, 0xff, 0xf3, 0xd2,
0xcd, 0x0c, 0x13, 0xec, 0x5f, 0x97, 0x44, 0x17, 0xc4, 0xa7, 0x7e, 0x3d, 0x64, 0x5d, 0x19, 0x73,
0x60, 0x81, 0x4f, 0xdc, 0x22, 0x2a, 0x90, 0x88, 0x46, 0xee, 0xb8, 0x14, 0xde, 0x5e, 0x0b, 0xdb,
0xe0, 0x32, 0x3a, 0x0a, 0x49, 0x06, 0x24, 0x5c, 0xc2, 0xd3, 0xac, 0x62, 0x91, 0x95, 0xe4, 0x79,
0xe7, 0xc8, 0x37, 0x6d, 0x8d, 0xd5, 0x4e, 0xa9, 0x6c, 0x56, 0xf4, 0xea, 0x65, 0x7a, 0xae, 0x08,
0xba, 0x78, 0x25, 0x2e, 0x1c, 0xa6, 0xb4, 0xc6, 0xe8, 0xdd, 0x74, 0x1f, 0x4b, 0xbd, 0x8b, 0x8a,
0x70, 0x3e, 0xb5, 0x66, 0x48, 0x03, 0xf6, 0x0e, 0x61, 0x35, 0x57, 0xb9, 0x86, 0xc1, 0x1d, 0x9e,
0xe1, 0xf8, 0x98, 0x11, 0x69, 0xd9, 0x8e, 0x94, 0x9b, 0x1e, 0x87, 0xe9, 0xce, 0x55, 0x28, 0xdf,
0x8c, 0xa1, 0x89, 0x0d, 0xbf, 0xe6, 0x42, 0x68, 0x41, 0x99, 0x2d, 0x0f, 0xb0, 0x54, 0xbb, 0x16
};
// Rcon
static const uint8_t Rcon[11] = {
0x8d, 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1b, 0x36
};
#define getSBoxValue(num) (sbox[(num)])
// 密钥扩展
static void KeyExpansion(uint8_t* RoundKey, const uint8_t* Key)
{
unsigned i, j, k;
uint8_t tempa[4];
// 前4个轮密钥直接复制
for (i = 0; i < 4; ++i) {
RoundKey[(i * 4) + 0] = Key[(i * 4) + 0];
RoundKey[(i * 4) + 1] = Key[(i * 4) + 1];
RoundKey[(i * 4) + 2] = Key[(i * 4) + 2];
RoundKey[(i * 4) + 3] = Key[(i * 4) + 3];
}
// 生成其余轮密钥
for (i = 4; i < 44; ++i) {
k = (i - 1) * 4;
tempa[0] = RoundKey[k + 0];
tempa[1] = RoundKey[k + 1];
tempa[2] = RoundKey[k + 2];
tempa[3] = RoundKey[k + 3];
if (i % 4 == 0) {
// RotWord
const uint8_t u8tmp = tempa[0];
tempa[0] = tempa[1];
tempa[1] = tempa[2];
tempa[2] = tempa[3];
tempa[3] = u8tmp;
// SubWord
tempa[0] = getSBoxValue(tempa[0]);
tempa[1] = getSBoxValue(tempa[1]);
tempa[2] = getSBoxValue(tempa[2]);
tempa[3] = getSBoxValue(tempa[3]);
tempa[0] = tempa[0] ^ Rcon[i / 4];
}
j = i * 4;
k = (i - 4) * 4;
RoundKey[j + 0] = RoundKey[k + 0] ^ tempa[0];
RoundKey[j + 1] = RoundKey[k + 1] ^ tempa[1];
RoundKey[j + 2] = RoundKey[k + 2] ^ tempa[2];
RoundKey[j + 3] = RoundKey[k + 3] ^ tempa[3];
}
}
// AddRoundKey
static void AddRoundKey(uint8_t round, uint8_t* state, const uint8_t* RoundKey)
{
uint8_t i, j;
for (i = 0; i < 4; ++i) {
for (j = 0; j < 4; ++j) {
state[i * 4 + j] ^= RoundKey[(round * 16) + (i * 4 + j)];
}
}
}
// SubBytes
static void SubBytes(uint8_t* state)
{
uint8_t i;
for (i = 0; i < 16; ++i) {
state[i] = getSBoxValue(state[i]);
}
}
// ShiftRows
static void ShiftRows(uint8_t* state)
{
uint8_t temp;
// 第二行左移1字节
temp = state[1];
state[1] = state[5];
state[5] = state[9];
state[9] = state[13];
state[13] = temp;
// 第三行左移2字节
temp = state[2];
state[2] = state[10];
state[10] = temp;
temp = state[6];
state[6] = state[14];
state[14] = temp;
// 第四行左移3字节
temp = state[15];
state[15] = state[11];
state[11] = state[7];
state[7] = state[3];
state[3] = temp;
}
// xtime (用于MixColumns)
static uint8_t xtime(uint8_t x)
{
return ((x << 1) ^ (((x >> 7) & 1) * 0x1b));
}
// MixColumns
static void MixColumns(uint8_t* state)
{
uint8_t i;
uint8_t Tmp, Tm, t;
for (i = 0; i < 4; ++i) {
t = state[i * 4 + 0];
Tmp = state[i * 4 + 0] ^ state[i * 4 + 1] ^ state[i * 4 + 2] ^ state[i * 4 + 3];
Tm = state[i * 4 + 0] ^ state[i * 4 + 1];
Tm = xtime(Tm);
state[i * 4 + 0] ^= Tm ^ Tmp;
Tm = state[i * 4 + 1] ^ state[i * 4 + 2];
Tm = xtime(Tm);
state[i * 4 + 1] ^= Tm ^ Tmp;
Tm = state[i * 4 + 2] ^ state[i * 4 + 3];
Tm = xtime(Tm);
state[i * 4 + 2] ^= Tm ^ Tmp;
Tm = state[i * 4 + 3] ^ t;
Tm = xtime(Tm);
state[i * 4 + 3] ^= Tm ^ Tmp;
}
}
// Cipher - AES加密核心
static void Cipher(uint8_t* state, const uint8_t* RoundKey)
{
uint8_t round = 0;
// 初始轮密钥加
AddRoundKey(0, state, RoundKey);
// 9轮标准变换
for (round = 1; round < 10; ++round) {
SubBytes(state);
ShiftRows(state);
MixColumns(state);
AddRoundKey(round, state, RoundKey);
}
// 最后一轮(无MixColumns)
SubBytes(state);
ShiftRows(state);
AddRoundKey(10, state, RoundKey);
}
// ========== 公开API函数 ==========
void AES_init_ctx(struct AES_ctx* ctx, const uint8_t* key)
{
KeyExpansion(ctx->RoundKey, key);
}
void AES_init_ctx_iv(struct AES_ctx* ctx, const uint8_t* key, const uint8_t* iv)
{
KeyExpansion(ctx->RoundKey, key);
memcpy(ctx->Iv, iv, AES_BLOCKLEN);
}
void AES_ECB_encrypt(const struct AES_ctx* ctx, uint8_t* buf)
{
Cipher(buf, ctx->RoundKey);
}
void AES_CBC_encrypt_buffer(struct AES_ctx* ctx, uint8_t* buf, size_t length)
{
size_t i;
uint8_t j;
for (i = 0; i < length; i += AES_BLOCKLEN) {
// XOR with IV/previous ciphertext block
for (j = 0; j < AES_BLOCKLEN; ++j) {
buf[i + j] ^= ctx->Iv[j];
}
// Encrypt block
Cipher(buf + i, ctx->RoundKey);
// Update IV to current ciphertext block
memcpy(ctx->Iv, buf + i, AES_BLOCKLEN);
}
}
// STM32端不需要解密功能
void AES_ECB_decrypt(const struct AES_ctx* ctx, uint8_t* buf)
{
(void)ctx;
(void)buf;
}
void AES_CBC_decrypt_buffer(struct AES_ctx* ctx, uint8_t* buf, size_t length)
{
(void)ctx;
(void)buf;
(void)length;
}
+63
View File
@@ -0,0 +1,63 @@
#ifndef _AES_H_
#define _AES_H_
#include <stdint.h>
#include <stddef.h>
// AES-128配置
#define AES_BLOCKLEN 16 // Block length in bytes (AES is 128-bit block)
#define AES_KEYLEN 16 // Key length in bytes (AES-128)
#define AES_keyExpSize 176 // Expanded key size (44 * 4 bytes)
struct AES_ctx
{
uint8_t RoundKey[AES_keyExpSize]; // 扩展密钥
uint8_t Iv[AES_BLOCKLEN]; // IV向量(独立存储,标准做法)
};
/**
* @brief 初始化AES上下文(仅密钥)
* @param ctx AES上下文结构
* @param key 16字节密钥
*/
void AES_init_ctx(struct AES_ctx* ctx, const uint8_t* key);
/**
* @brief 初始化AES上下文(密钥+IV)
* @param ctx AES上下文结构
* @param key 16字节密钥
* @param iv 16字节初始化向量
*/
void AES_init_ctx_iv(struct AES_ctx* ctx, const uint8_t* key, const uint8_t* iv);
/**
* @brief AES-ECB加密(单个块)
* @param ctx AES上下文
* @param buf 要加密的数据(16字节块)
*/
void AES_ECB_encrypt(const struct AES_ctx* ctx, uint8_t* buf);
/**
* @brief AES-ECB解密(单个块)
* @param ctx AES上下文
* @param buf 要解密的数据(16字节块)
*/
void AES_ECB_decrypt(const struct AES_ctx* ctx, uint8_t* buf);
/**
* @brief AES-CBC加密(多个块)
* @param ctx AES上下文
* @param buf 要加密的数据
* @param length 数据长度(必须是16的倍数)
*/
void AES_CBC_encrypt_buffer(struct AES_ctx* ctx, uint8_t* buf, size_t length);
/**
* @brief AES-CBC解密(多个块)
* @param ctx AES上下文
* @param buf 要解密的数据
* @param length 数据长度(必须是16的倍数)
*/
void AES_CBC_decrypt_buffer(struct AES_ctx* ctx, uint8_t* buf, size_t length);
#endif // _AES_H_
+81
View File
@@ -0,0 +1,81 @@
/*
* Base64 编码/解码实现
* 优化用于嵌入式系统
*/
#include "base64.h"
#include <string.h>
static const char base64_chars[] =
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
uint32_t base64_encode(const uint8_t* input, uint32_t input_length, char* output)
{
uint32_t i, j;
uint32_t output_length = 0;
for (i = 0, j = 0; i < input_length;) {
uint32_t octet_a = i < input_length ? input[i++] : 0;
uint32_t octet_b = i < input_length ? input[i++] : 0;
uint32_t octet_c = i < input_length ? input[i++] : 0;
uint32_t triple = (octet_a << 16) + (octet_b << 8) + octet_c;
output[j++] = base64_chars[(triple >> 18) & 0x3F];
output[j++] = base64_chars[(triple >> 12) & 0x3F];
output[j++] = base64_chars[(triple >> 6) & 0x3F];
output[j++] = base64_chars[triple & 0x3F];
}
output_length = j;
// 处理padding
uint32_t mod = input_length % 3;
if (mod > 0) {
for (i = 0; i < 3 - mod; i++) {
output[output_length - 1 - i] = '=';
}
}
output[output_length] = '\0';
return output_length;
}
static uint8_t base64_char_value(char c)
{
if (c >= 'A' && c <= 'Z') return c - 'A';
if (c >= 'a' && c <= 'z') return c - 'a' + 26;
if (c >= '0' && c <= '9') return c - '0' + 52;
if (c == '+') return 62;
if (c == '/') return 63;
return 0;
}
uint32_t base64_decode(const char* input, uint32_t input_length, uint8_t* output)
{
uint32_t i, j;
uint32_t output_length = 0;
if (input_length % 4 != 0) return 0;
for (i = 0, j = 0; i < input_length;) {
uint32_t sextet_a = input[i] == '=' ? 0 : base64_char_value(input[i]); i++;
uint32_t sextet_b = input[i] == '=' ? 0 : base64_char_value(input[i]); i++;
uint32_t sextet_c = input[i] == '=' ? 0 : base64_char_value(input[i]); i++;
uint32_t sextet_d = input[i] == '=' ? 0 : base64_char_value(input[i]); i++;
uint32_t triple = (sextet_a << 18) + (sextet_b << 12) + (sextet_c << 6) + sextet_d;
if (j < input_length) output[j++] = (triple >> 16) & 0xFF;
if (j < input_length) output[j++] = (triple >> 8) & 0xFF;
if (j < input_length) output[j++] = triple & 0xFF;
}
output_length = j;
// 移除padding
if (input[input_length - 1] == '=') output_length--;
if (input[input_length - 2] == '=') output_length--;
return output_length;
}
+34
View File
@@ -0,0 +1,34 @@
#ifndef _BASE64_H_
#define _BASE64_H_
#include <stdint.h>
#include <stddef.h>
/**
* @brief Base64编码
* @param input 输入数据
* @param input_length 输入数据长度
* @param output 输出缓冲区(需要预分配足够空间)
* @return 编码后的字符串长度
*
* 注意:output缓冲区大小至少需要 ((input_length + 2) / 3) * 4 + 1 字节
*/
uint32_t base64_encode(const uint8_t* input, uint32_t input_length, char* output);
/**
* @brief Base64解码
* @param input 输入Base64字符串
* @param input_length 输入字符串长度
* @param output 输出缓冲区
* @return 解码后的数据长度
*/
uint32_t base64_decode(const char* input, uint32_t input_length, uint8_t* output);
/**
* @brief 计算Base64编码后的长度
* @param input_length 输入数据长度
* @return 编码后的长度(不含结束符)
*/
#define BASE64_ENCODE_SIZE(len) (((len) + 2) / 3 * 4)
#endif // _BASE64_H_
+589
View File
@@ -0,0 +1,589 @@
/*
* ESP8266 WiFi模块驱动(支持HTTP加密传输)
* 版本:v2.0 - 加密方案C
* 修改:
* 1. 添加HTTP POST请求支持
* 2. 添加加密数据传输
* 3. 保留原有TCP兼容接口
*/
#include "esp8266.h"
#include "Serial.h"
#include "Delay.h"
#include "Debug.h"
#include <stdio.h>
#include <string.h>
#include <stdlib.h> // ← 添加atoi函数声明
// 外部配置(在main.c中定义)
extern const char* WIFI_SSID;
extern const char* WIFI_PASSWORD;
extern const char* HTTP_SERVER_IP;
extern const char* HTTP_SERVER_PORT;
extern uint8_t g_server_connected;
/* --- 私有辅助函数 --- */
static void ESP8266_SendCmd(const char* cmd) {
Serial_SendString(cmd);
Serial_SendString("\r\n");
}
static uint8_t ESP8266_WaitForAck(const char* ack, uint32_t timeout) {
uint32_t time_start = Get_Time_ms();
uint32_t time_elapsed;
Serial_ClearRxBuffer();
DEBUG_PRINT("[ESP8266] Waiting for: %s (timeout: %dms)\r\n", ack, timeout);
while(1) {
time_elapsed = Get_Time_ms() - time_start;
if (time_elapsed > timeout) {
DEBUG_PRINT("[ESP8266] Timeout after %dms\r\n", time_elapsed);
break;
}
char* response = Serial_GetRxBuffer();
if (response && strstr(response, ack) != NULL) {
DEBUG_OK("[ESP8266] Received expected response");
return ESP8266_SUCCESS;
}
Delay_ms(10);
}
DEBUG_PRINT("[ESP8266] Failed! Response: %s\r\n", Serial_GetRxBuffer());
return ESP8266_FAIL;
}
static uint8_t IsLeapYear(uint16_t year)
{
return ((year % 4 == 0 && year % 100 != 0) || (year % 400 == 0)) ? 1 : 0;
}
static uint8_t DaysInMonth(uint16_t year, uint8_t month)
{
static const uint8_t days_table[12] = {31,28,31,30,31,30,31,31,30,31,30,31};
if (month == 2 && IsLeapYear(year)) return 29;
return days_table[month - 1];
}
static void EpochToDateTimeUTC8(uint32_t epoch_seconds, ESP8266_DateTime* datetime)
{
// 转为北京时间(UTC+8)
uint32_t total = epoch_seconds + 8U * 3600U;
uint32_t days = total / 86400U;
uint32_t sec_of_day = total % 86400U;
datetime->hour = sec_of_day / 3600U;
datetime->minute = (sec_of_day % 3600U) / 60U;
datetime->second = sec_of_day % 60U;
datetime->year = 1970;
while (1) {
uint16_t ydays = IsLeapYear(datetime->year) ? 366 : 365;
if (days < ydays) break;
days -= ydays;
datetime->year++;
}
datetime->month = 1;
while (1) {
uint8_t mdays = DaysInMonth(datetime->year, datetime->month);
if (days < mdays) break;
days -= mdays;
datetime->month++;
}
datetime->day = (uint8_t)(days + 1U);
}
/* --- 公共接口函数实现 --- */
uint8_t ESP8266_Setup(void)
{
ESP8266_SendCmd("+++");
Delay_ms(500);
ESP8266_SendCmd("AT");
if (!ESP8266_WaitForAck("OK", 1000)) return ESP8266_FAIL;
ESP8266_SendCmd("AT+CWMODE=1");
if (!ESP8266_WaitForAck("OK", 2000)) return ESP8266_FAIL;
// 先关闭可能存在的连接(避免 "link is builded" 错误)
ESP8266_SendCmd("AT+CIPCLOSE");
Delay_ms(500); // 等待连接关闭
// 设置单连接模式
ESP8266_SendCmd("AT+CIPMUX=0");
ESP8266_WaitForAck("OK", 1000);
return ESP8266_SUCCESS;
}
uint8_t ESP8266_ConnectWiFi(const char* ssid, const char* password)
{
char cmd_buffer[128];
sprintf(cmd_buffer, "AT+CWJAP=\"%s\",\"%s\"", ssid, password);
ESP8266_SendCmd(cmd_buffer);
return ESP8266_WaitForAck("WIFI GOT IP", 15000);
}
/**
* @brief 连接到HTTP服务器(新增)
*/
uint8_t ESP8266_ConnectHTTPServer(const char* ip, const char* port)
{
char cmd_buffer[100];
DEBUG_PRINT("[ESP8266] Connecting to HTTP server %s:%s\r\n", ip, port);
// 先断开之前的连接
ESP8266_SendCmd("AT+CIPCLOSE");
Delay_ms(500);
// 建立TCP连接
sprintf(cmd_buffer, "AT+CIPSTART=\"TCP\",\"%s\",%s", ip, port);
ESP8266_SendCmd(cmd_buffer);
// 等待连接成功
uint32_t start_time = Get_Time_ms();
Serial_ClearRxBuffer();
while(Get_Time_ms() - start_time < 10000) {
char* response = Serial_GetRxBuffer();
if (strstr(response, "CONNECT") != NULL || strstr(response, "ALREADY") != NULL) {
DEBUG_OK("[ESP8266] HTTP server connected");
Delay_ms(500);
return ESP8266_SUCCESS;
}
if (strstr(response, "ERROR") != NULL || strstr(response, "CLOSED") != NULL) {
DEBUG_ERROR("[ESP8266] Connection failed");
break;
}
Delay_ms(100);
}
DEBUG_ERROR("[ESP8266] Connection timeout");
return ESP8266_FAIL;
}
/**
* @brief 发送加密数据(HTTP POST请求)
*/
uint8_t ESP8266_SendEncryptedData(const char* encrypted_data)
{
char http_request[1024];
char cmd_buffer[32];
uint32_t content_length = strlen(encrypted_data);
// 构建HTTP POST请求
sprintf(http_request,
"POST /api/checkin HTTP/1.1\r\n"
"Host: %s:%s\r\n"
"Content-Type: application/octet-stream\r\n"
"X-Encrypted: AES128-CBC\r\n"
"Content-Length: %u\r\n"
"Connection: keep-alive\r\n"
"\r\n"
"%s",
HTTP_SERVER_IP,
HTTP_SERVER_PORT,
content_length,
encrypted_data
);
uint32_t request_len = strlen(http_request);
DEBUG_PRINT("[ESP8266] Sending HTTP POST (%u bytes)\r\n", request_len);
// 发送数据长度命令
sprintf(cmd_buffer, "AT+CIPSEND=%u", request_len);
ESP8266_SendCmd(cmd_buffer);
// 等待 ">" 提示符
uint32_t start_time = Get_Time_ms();
Serial_ClearRxBuffer();
while(Get_Time_ms() - start_time < 2000) {
if (strstr(Serial_GetRxBuffer(), ">") != NULL) {
DEBUG_INFO("[ESP8266] Ready to send");
// 发送HTTP请求
Serial_SendString(http_request);
Delay_ms(100);
// 不等待SEND OK,直接返回成功
DEBUG_OK("[ESP8266] Data sent");
return ESP8266_SUCCESS;
}
Delay_ms(10);
}
DEBUG_ERROR("[ESP8266] No '>' prompt");
return ESP8266_FAIL;
}
uint8_t ESP8266_GetNetworkTime(ESP8266_DateTime* datetime)
{
char response_buffer[256];
char* epoch_ptr;
uint32_t epoch_seconds = 0;
uint8_t digit_count = 0;
DEBUG_INFO("[ESP8266] Getting network time...");
// 优先直接请求业务服务器时间接口
// 期望响应: {"epoch":1700000000,"server_time":"2026-02-12 12:34:56"}
if (ESP8266_HttpGet("/api/time", response_buffer, sizeof(response_buffer)) != ESP8266_SUCCESS) {
DEBUG_ERROR("[ESP8266] /api/time request failed, try reconnect...");
if (ESP8266_ConnectHTTPServer(HTTP_SERVER_IP, HTTP_SERVER_PORT) != ESP8266_SUCCESS) {
DEBUG_ERROR("[ESP8266] Reconnect failed when getting time");
return ESP8266_FAIL;
}
g_server_connected = 1;
if (ESP8266_HttpGet("/api/time", response_buffer, sizeof(response_buffer)) != ESP8266_SUCCESS) {
DEBUG_ERROR("[ESP8266] /api/time request failed after reconnect");
return ESP8266_FAIL;
}
}
epoch_ptr = strstr(response_buffer, "\"epoch\":");
if (epoch_ptr == NULL) {
DEBUG_ERROR("[ESP8266] /api/time response missing epoch");
DEBUG_PRINT("[ESP8266] Response: %s\r\n", response_buffer);
return ESP8266_FAIL;
}
epoch_ptr += 8; // 跳过 "epoch":
while (*epoch_ptr == ' ' || *epoch_ptr == '\"') epoch_ptr++;
while (*epoch_ptr >= '0' && *epoch_ptr <= '9' && digit_count < 10) {
epoch_seconds = epoch_seconds * 10U + (uint32_t)(*epoch_ptr - '0');
epoch_ptr++;
digit_count++;
}
if (digit_count < 9) {
DEBUG_ERROR("[ESP8266] Invalid epoch value in /api/time response");
DEBUG_PRINT("[ESP8266] Response: %s\r\n", response_buffer);
return ESP8266_FAIL;
}
EpochToDateTimeUTC8(epoch_seconds, datetime);
DEBUG_PRINT("[ESP8266] Time(from server): %04d-%02d-%02d %02d:%02d:%02d\r\n",
datetime->year, datetime->month, datetime->day,
datetime->hour, datetime->minute, datetime->second);
return ESP8266_SUCCESS;
}
uint8_t ESP8266_CheckConnection(void)
{
char* response;
ESP8266_SendCmd("AT+CIPSTATUS");
Delay_ms(500);
response = Serial_GetRxBuffer();
if (strstr(response, "STATUS:3") != NULL) {
DEBUG_INFO("[ESP8266] Connection OK (STATUS:3)");
return ESP8266_SUCCESS;
}
if (strstr(response, "STATUS:4") != NULL) {
DEBUG_ERROR("[ESP8266] Connection closed (STATUS:4)");
return ESP8266_FAIL;
}
if (strstr(response, "STATUS:5") != NULL) {
DEBUG_ERROR("[ESP8266] WiFi disconnected (STATUS:5)");
return ESP8266_FAIL;
}
if (strstr(response, "STATUS:2") != NULL) {
DEBUG_ERROR("[ESP8266] Not connected to server (STATUS:2)");
return ESP8266_FAIL;
}
DEBUG_ERROR("[ESP8266] Unknown connection status");
return ESP8266_FAIL;
}
/**
* @brief 发送HTTP GET请求并接收响应
*/
uint8_t ESP8266_HttpGet(const char* path, char* response_buffer, uint16_t buffer_size)
{
char http_request[256];
char cmd_buffer[32];
DEBUG_PRINT("[ESP8266] HTTP GET: %s\r\n", path);
// 构建HTTP GET请求
sprintf(http_request,
"GET %s HTTP/1.1\r\n"
"Host: %s:%s\r\n"
"Connection: keep-alive\r\n"
"\r\n",
path,
HTTP_SERVER_IP,
HTTP_SERVER_PORT
);
uint32_t request_len = strlen(http_request);
// 发送数据长度命令
sprintf(cmd_buffer, "AT+CIPSEND=%u", request_len);
ESP8266_SendCmd(cmd_buffer);
// 等待 ">" 提示符
uint32_t start_time = Get_Time_ms();
Serial_ClearRxBuffer();
while(Get_Time_ms() - start_time < 2000) {
if (strstr(Serial_GetRxBuffer(), ">") != NULL) {
DEBUG_INFO("[ESP8266] Ready to send GET request");
// 发送HTTP GET请求
Serial_SendString(http_request);
Delay_ms(100);
break;
}
Delay_ms(10);
}
// 等待接收完整响应(最多8秒)
Serial_ClearRxBuffer();
start_time = Get_Time_ms();
uint8_t found_header = 0;
uint32_t content_length = 0;
while(Get_Time_ms() - start_time < 8000) {
char* response = Serial_GetRxBuffer();
// 1. 处理+IPD前缀(ESP8266格式:+IPD,长度:数据)
char* ipd_marker = strstr(response, "+IPD,");
if (ipd_marker != NULL) {
// 跳过 "+IPD," 找到实际数据
char* data_start = strchr(ipd_marker, ':');
if (data_start != NULL) {
data_start++; // 跳过 ':'
response = data_start; // 从这里开始才是HTTP响应
}
}
// 2. 查找HTTP响应体(JSON数据在\r\n\r\n之后)
char* body_start = strstr(response, "\r\n\r\n");
if (body_start != NULL) {
body_start += 4; // 跳过 \r\n\r\n
// 3. 如果还没提取Content-Length,尝试提取
if (!found_header) {
char* cl_str = strstr(response, "Content-Length:");
if (cl_str != NULL) {
cl_str += 15; // 跳过 "Content-Length:"
while (*cl_str == ' ') cl_str++;
content_length = atoi(cl_str);
DEBUG_PRINT("[ESP8266] Content-Length: %u\r\n", content_length);
found_header = 1;
}
}
// 4. 检查是否接收完整(比较实际长度和Content-Length)
uint16_t body_len = strlen(body_start);
// 移除末尾的 "CLOSED" "OK" 等ESP8266附加信息
char* closed_marker = strstr(body_start, "\r\nCLOSED");
if (closed_marker != NULL) {
*closed_marker = '\0';
body_len = strlen(body_start);
}
// 同时处理没有\r\n的情况
closed_marker = strstr(body_start, "CLOSED");
if (closed_marker != NULL) {
*closed_marker = '\0';
body_len = strlen(body_start);
}
// 移除末尾的OK
char* ok_marker = strstr(body_start, "\r\nOK");
if (ok_marker != NULL) {
*ok_marker = '\0';
body_len = strlen(body_start);
}
// 5. 如果数据完整或超时,复制到缓冲区
if (body_len >= content_length || (Get_Time_ms() - start_time > 3000 && body_len > 10)) {
if (body_len >= buffer_size) {
body_len = buffer_size - 1;
}
strncpy(response_buffer, body_start, body_len);
response_buffer[body_len] = '\0';
DEBUG_PRINT("[ESP8266] Received response (%d bytes)\r\n", strlen(response_buffer));
DEBUG_PRINT("[ESP8266] Response: %s\r\n", response_buffer);
return ESP8266_SUCCESS;
}
}
Delay_ms(100);
}
DEBUG_ERROR("[ESP8266] No valid HTTP response or timeout");
return ESP8266_FAIL;
}
// ========== 兼容旧版本的函数 ==========
/**
* @brief 查询学生在指定时段是否已签到(服务器端查重)
* @param student_id 学号
* @param slot_start 时段开始时间(格式"HH:MM")
* @param slot_end 时段结束时间(格式"HH:MM")
* @retval 1 表示已签到(重复), 0 表示未签到
*/
uint8_t ESP8266_CheckDuplicate(const char* student_id, const char* slot_start, const char* slot_end)
{
char path[128];
char response_buffer[256];
// 构建查询路径
sprintf(path, "/api/check_duplicate?student_id=%s&slot_start=%s&slot_end=%s",
student_id, slot_start, slot_end);
DEBUG_PRINT("[ESP8266] Checking duplicate: %s\r\n", path);
// 先检查连接状态,如果断开则重连
if (ESP8266_CheckConnection() != ESP8266_SUCCESS) {
DEBUG_INFO("[ESP8266] Connection lost, reconnecting...");
extern const char* HTTP_SERVER_IP;
extern const char* HTTP_SERVER_PORT;
if (ESP8266_ConnectHTTPServer(HTTP_SERVER_IP, HTTP_SERVER_PORT) != ESP8266_SUCCESS) {
DEBUG_ERROR("[ESP8266] Reconnect failed");
return 0; // 连接失败,默认允许签到
}
Delay_ms(200); // 等待连接稳定
}
// 发送HTTP GET请求
if (ESP8266_HttpGet(path, response_buffer, sizeof(response_buffer)) == ESP8266_SUCCESS) {
// 解析响应,查找 "duplicate":true
if (strstr(response_buffer, "\"duplicate\":true") != NULL) {
DEBUG_INFO("[ESP8266] Duplicate found on server!");
return 1; // 已签到
} else {
DEBUG_INFO("[ESP8266] No duplicate found");
return 0; // 未签到
}
}
DEBUG_ERROR("[ESP8266] Failed to check duplicate, assuming not duplicate");
return 0; // 查询失败时默认为未签到,允许本地签到
}
/**
* @brief 下载今日已签到学生列表(开机时调用)
* @param slot_start 时段开始时间(格式"HH:MM")
* @param slot_end 时段结束时间(格式"HH:MM")
* @param student_ids 输出:学号数组(逗号分隔的字符串)
* @param buffer_size 缓冲区大小
* @retval 成功下载的学号数量
*/
uint8_t ESP8266_DownloadTodayRecords(const char* slot_start, const char* slot_end, char* student_ids, uint16_t buffer_size)
{
char path[128];
char response_buffer[512];
// 构建查询路径
sprintf(path, "/api/today_records?slot_start=%s&slot_end=%s", slot_start, slot_end);
DEBUG_PRINT("[ESP8266] Downloading today's records: %s\r\n", path);
// 先检查连接状态,如果断开则重连
if (ESP8266_CheckConnection() != ESP8266_SUCCESS) {
DEBUG_INFO("[ESP8266] Connection lost, reconnecting before download...");
extern const char* HTTP_SERVER_IP;
extern const char* HTTP_SERVER_PORT;
if (ESP8266_ConnectHTTPServer(HTTP_SERVER_IP, HTTP_SERVER_PORT) != ESP8266_SUCCESS) {
DEBUG_ERROR("[ESP8266] Reconnect failed");
return 0;
}
Delay_ms(300); // 等待连接稳定
}
// 发送HTTP GET请求
if (ESP8266_HttpGet(path, response_buffer, sizeof(response_buffer)) != ESP8266_SUCCESS) {
DEBUG_ERROR("[ESP8266] Failed to download today's records");
return 0;
}
// 解析响应,提取学号列表
// 格式: {"date":"2026-02-07","records":["1","2","3"]}
char* records_start = strstr(response_buffer, "\"records\":[");
if (records_start == NULL) {
DEBUG_ERROR("[ESP8266] Invalid response format");
return 0;
}
records_start += 11; // 跳过 "records":[
char* records_end = strchr(records_start, ']');
if (records_end == NULL) {
DEBUG_ERROR("[ESP8266] Invalid response format");
return 0;
}
// 复制学号列表到输出缓冲区
uint16_t len = records_end - records_start;
if (len >= buffer_size) len = buffer_size - 1;
strncpy(student_ids, records_start, len);
student_ids[len] = '\0';
// 统计学号数量(空列表返回0)
uint8_t count = 0;
if (len > 2) { // 至少有一个学号 "x"
count = 1;
for (uint16_t i = 0; i < len; i++) {
if (student_ids[i] == ',') count++;
}
}
DEBUG_PRINT("[ESP8266] Downloaded %d records: %s\r\n", count, student_ids);
return count;
}
uint8_t ESP8266_ConnectTCPServer(const char* ip, const char* port)
{
// 重定向到HTTP版本
return ESP8266_ConnectHTTPServer(ip, port);
}
uint8_t ESP8266_SendData(char* Data)
{
char cmd_buffer[32];
uint16_t data_len = strlen(Data);
DEBUG_PRINT("[ESP8266] Sending %d bytes (legacy mode)\r\n", data_len);
sprintf(cmd_buffer, "AT+CIPSEND=%d", data_len);
ESP8266_SendCmd(cmd_buffer);
uint32_t start_time = Get_Time_ms();
Serial_ClearRxBuffer();
while(Get_Time_ms() - start_time < 1000) {
if (strstr(Serial_GetRxBuffer(), ">") != NULL) {
Serial_SendString(Data);
Delay_ms(50);
return ESP8266_SUCCESS;
}
Delay_ms(10);
}
return ESP8266_FAIL;
}
+98
View File
@@ -0,0 +1,98 @@
#ifndef __ESP8266_H
#define __ESP8266_H
#include "stm32f10x.h"
// 定义布尔类型,方便返回状态
#define ESP8266_SUCCESS 1
#define ESP8266_FAIL 0
// 时间结构体
typedef struct {
uint16_t year;
uint8_t month;
uint8_t day;
uint8_t hour;
uint8_t minute;
uint8_t second;
} ESP8266_DateTime;
/*
--- 公共函数接口 ---
*/
/**
* @brief 对ESP8266模块进行基础设置
* @retval 1 表示成功, 0 表示失败
*/
uint8_t ESP8266_Setup(void);
/**
* @brief 命令ESP8266连接到指定的WiFi网络
* @param ssid 要连接的WiFi名称 (SSID)
* @param password WiFi的密码
* @retval 1 表示成功, 0 表示失败
*/
uint8_t ESP8266_ConnectWiFi(const char* ssid, const char* password);
/**
* @brief 命令ESP8266连接到HTTP服务器(新增)
* @param ip 服务器的IP地址
* @param port 服务器的端口号
* @retval 1 表示成功, 0 表示失败
*/
uint8_t ESP8266_ConnectHTTPServer(const char* ip, const char* port);
/**
* @brief 通过ESP8266发送HTTP POST请求(新增 - 加密版本)
* @param encrypted_data 加密后的Base64数据
* @retval 1 表示成功, 0 表示失败
*/
uint8_t ESP8266_SendEncryptedData(const char* encrypted_data);
/**
* @brief 检查TCP连接是否正常
* @retval 1 表示已连接, 0 表示未连接
*/
uint8_t ESP8266_CheckConnection(void);
/**
* @brief 从网络获取当前时间(NTP/SNTP)
* @param datetime 时间结构体指针,用于存储获取的时间
* @retval 1 表示成功, 0 表示失败
*/
uint8_t ESP8266_GetNetworkTime(ESP8266_DateTime* datetime);
/**
* @brief 发送HTTP GET请求并接收响应(新增)
* @param path 请求路径(如"/api/timeslots")
* @param response_buffer 接收响应数据的缓冲区
* @param buffer_size 缓冲区大小
* @retval 1 表示成功, 0 表示失败
*/
uint8_t ESP8266_HttpGet(const char* path, char* response_buffer, uint16_t buffer_size);
/**
* @brief 查询学生在指定时段是否已签到(服务器端查重)
* @param student_id 学号
* @param slot_start 时段开始时间(格式"HH:MM")
* @param slot_end 时段结束时间(格式"HH:MM")
* @retval 1 表示已签到(重复), 0 表示未签到
*/
uint8_t ESP8266_CheckDuplicate(const char* student_id, const char* slot_start, const char* slot_end);
/**
* @brief 下载今日已签到学生列表(开机时调用)
* @param slot_start 时段开始时间(格式"HH:MM")
* @param slot_end 时段结束时间(格式"HH:MM")
* @param student_ids 输出:学号数组(逗号分隔的字符串)
* @param buffer_size 缓冲区大小
* @retval 成功下载的学号数量
*/
uint8_t ESP8266_DownloadTodayRecords(const char* slot_start, const char* slot_end, char* student_ids, uint16_t buffer_size);
// 兼容旧版本(已废弃,建议使用HTTP版本)
uint8_t ESP8266_ConnectTCPServer(const char* ip, const char* port);
uint8_t ESP8266_SendData(char* Data);
#endif
+240
View File
@@ -0,0 +1,240 @@
/*
* ESP8266 WiFi模块驱动(支持HTTP加密传输)
* 新增功能:
* 1. HTTP POST请求支持
* 2. 加密数据传输
* 3. 自动重连机制
*/
#include "esp8266.h"
#include "Serial.h"
#include "Delay.h"
#include <stdio.h>
#include <string.h>
// 外部配置(在main.c中定义)
extern const char* WIFI_SSID;
extern const char* WIFI_PASSWORD;
extern const char* HTTP_SERVER_IP;
extern const char* HTTP_SERVER_PORT;
// 内部函数声明
static uint8_t ESP8266_SendCommand(const char* cmd, const char* expected, uint16_t timeout);
static void ESP8266_ClearBuffer(void);
/**
* @brief 发送AT命令并等待响应
*/
static uint8_t ESP8266_SendCommand(const char* cmd, const char* expected, uint16_t timeout)
{
Serial_SendString(cmd);
Serial_SendString("\r\n");
Delay_ms(timeout);
if (expected == NULL) {
return ESP8266_SUCCESS;
}
// 检查是否收到期望的响应
char* response = Serial_GetRxData();
if (strstr(response, expected) != NULL) {
return ESP8266_SUCCESS;
}
return ESP8266_FAIL;
}
static void ESP8266_ClearBuffer(void)
{
Serial_ClearRxBuffer();
}
uint8_t ESP8266_Setup(void)
{
ESP8266_ClearBuffer();
// 测试AT指令
if (ESP8266_SendCommand("AT", "OK", 1000) == ESP8266_FAIL) {
return ESP8266_FAIL;
}
// 设置为STA模式(客户端模式)
if (ESP8266_SendCommand("AT+CWMODE=1", "OK", 1000) == ESP8266_FAIL) {
return ESP8266_FAIL;
}
// 关闭回显
ESP8266_SendCommand("ATE0", "OK", 500);
// 设置单连接模式
if (ESP8266_SendCommand("AT+CIPMUX=0", "OK", 1000) == ESP8266_FAIL) {
return ESP8266_FAIL;
}
return ESP8266_SUCCESS;
}
uint8_t ESP8266_ConnectWiFi(const char* ssid, const char* password)
{
char cmd[128];
ESP8266_ClearBuffer();
// 断开之前的WiFi连接
ESP8266_SendCommand("AT+CWQAP", "OK", 1000);
Delay_ms(1000);
// 连接到WiFi
sprintf(cmd, "AT+CWJAP=\"%s\",\"%s\"", ssid, password);
// WiFi连接可能需要较长时间
if (ESP8266_SendCommand(cmd, "OK", 10000) == ESP8266_FAIL) {
return ESP8266_FAIL;
}
Delay_ms(2000);
return ESP8266_SUCCESS;
}
uint8_t ESP8266_ConnectHTTPServer(const char* ip, const char* port)
{
char cmd[64];
ESP8266_ClearBuffer();
// 断开之前的TCP连接
ESP8266_SendCommand("AT+CIPCLOSE", "OK", 1000);
Delay_ms(500);
// 建立TCP连接到HTTP服务器
sprintf(cmd, "AT+CIPSTART=\"TCP\",\"%s\",%s", ip, port);
if (ESP8266_SendCommand(cmd, "OK", 5000) == ESP8266_FAIL) {
return ESP8266_FAIL;
}
Delay_ms(1000);
return ESP8266_SUCCESS;
}
uint8_t ESP8266_SendEncryptedData(const char* encrypted_data)
{
char http_request[1024];
char cmd[32];
uint32_t content_length = strlen(encrypted_data);
// 构建HTTP POST请求
sprintf(http_request,
"POST /api/checkin HTTP/1.1\r\n"
"Host: %s:%s\r\n"
"Content-Type: application/octet-stream\r\n"
"X-Encrypted: AES128-CBC\r\n"
"Content-Length: %u\r\n"
"Connection: keep-alive\r\n"
"\r\n"
"%s",
HTTP_SERVER_IP,
HTTP_SERVER_PORT,
content_length,
encrypted_data
);
uint32_t request_len = strlen(http_request);
// 发送数据长度命令
sprintf(cmd, "AT+CIPSEND=%u", request_len);
ESP8266_ClearBuffer();
if (ESP8266_SendCommand(cmd, ">", 2000) == ESP8266_FAIL) {
return ESP8266_FAIL;
}
// 发送HTTP请求
Serial_SendString(http_request);
// 等待发送完成(不阻塞太久)
Delay_ms(100);
// 可选:等待服务器响应(检查是否有"200 OK")
// 但为了不阻塞,建议不等待
return ESP8266_SUCCESS;
}
uint8_t ESP8266_CheckConnection(void)
{
ESP8266_ClearBuffer();
if (ESP8266_SendCommand("AT+CIPSTATUS", "OK", 1000) == ESP8266_FAIL) {
return ESP8266_FAIL;
}
// 检查响应中是否包含"STATUS:3"(已连接)
char* response = Serial_GetRxData();
if (strstr(response, "STATUS:3") != NULL ||
strstr(response, "STATUS:4") != NULL) {
return ESP8266_SUCCESS;
}
return ESP8266_FAIL;
}
uint8_t ESP8266_GetNetworkTime(ESP8266_DateTime* datetime)
{
// 启用SNTP
if (ESP8266_SendCommand("AT+CIPSNTPCFG=1,8,\"pool.ntp.org\"", "OK", 2000) == ESP8266_FAIL) {
return ESP8266_FAIL;
}
Delay_ms(3000); // 等待时间同步
ESP8266_ClearBuffer();
// 获取时间
if (ESP8266_SendCommand("AT+CIPSNTPTIME?", "OK", 1000) == ESP8266_FAIL) {
return ESP8266_FAIL;
}
// 解析响应(格式:+CIPSNTPTIME:Thu Jan 01 08:00:00 2024)
char* response = Serial_GetRxData();
char* time_str = strstr(response, "+CIPSNTPTIME:");
if (time_str != NULL) {
// 简单解析(这里只是示例,实际需要更完善的解析)
sscanf(time_str, "+CIPSNTPTIME:%*s %*s %d %d:%d:%d %d",
&datetime->day, &datetime->hour,
&datetime->minute, &datetime->second, &datetime->year);
return ESP8266_SUCCESS;
}
return ESP8266_FAIL;
}
// ========== 兼容旧版本的函数(已废弃) ==========
uint8_t ESP8266_ConnectTCPServer(const char* ip, const char* port)
{
// 重定向到HTTP版本
return ESP8266_ConnectHTTPServer(ip, port);
}
uint8_t ESP8266_SendData(char* Data)
{
// 警告:此函数已废弃,请使用ESP8266_SendEncryptedData
// 为了兼容,这里保留基本实现
char cmd[32];
sprintf(cmd, "AT+CIPSEND=%d", strlen(Data));
if (ESP8266_SendCommand(cmd, ">", 2000) == ESP8266_FAIL) {
return ESP8266_FAIL;
}
Serial_SendString(Data);
Delay_ms(50);
return ESP8266_SUCCESS;
}
+70
View File
@@ -0,0 +1,70 @@
#ifndef __ESP8266_H
#define __ESP8266_H
#include "stm32f10x.h"
// 定义布尔类型,方便返回状态
#define ESP8266_SUCCESS 1
#define ESP8266_FAIL 0
// 时间结构体
typedef struct {
uint16_t year;
uint8_t month;
uint8_t day;
uint8_t hour;
uint8_t minute;
uint8_t second;
} ESP8266_DateTime;
/*
--- 公共函数接口 ---
*/
/**
* @brief 对ESP8266模块进行基础设置
* @retval 1 表示成功, 0 表示失败
*/
uint8_t ESP8266_Setup(void);
/**
* @brief 命令ESP8266连接到指定的WiFi网络
* @param ssid 要连接的WiFi名称 (SSID)
* @param password WiFi的密码
* @retval 1 表示成功, 0 表示失败
*/
uint8_t ESP8266_ConnectWiFi(const char* ssid, const char* password);
/**
* @brief 命令ESP8266连接到HTTP服务器(新增)
* @param ip 服务器的IP地址
* @param port 服务器的端口号
* @retval 1 表示成功, 0 表示失败
*/
uint8_t ESP8266_ConnectHTTPServer(const char* ip, const char* port);
/**
* @brief 通过ESP8266发送HTTP POST请求(新增 - 加密版本)
* @param encrypted_data 加密后的Base64数据
* @retval 1 表示成功, 0 表示失败
*/
uint8_t ESP8266_SendEncryptedData(const char* encrypted_data);
/**
* @brief 检查TCP连接是否正常
* @retval 1 表示已连接, 0 表示未连接
*/
uint8_t ESP8266_CheckConnection(void);
/**
* @brief 从网络获取当前时间(NTP/SNTP)
* @param datetime 时间结构体指针,用于存储获取的时间
* @retval 1 表示成功, 0 表示失败
*/
uint8_t ESP8266_GetNetworkTime(ESP8266_DateTime* datetime);
// 兼容旧版本(已废弃,建议使用HTTP版本)
uint8_t ESP8266_ConnectTCPServer(const char* ip, const char* port);
uint8_t ESP8266_SendData(char* Data);
#endif