初始化
This commit is contained in:
@@ -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;
|
||||
}
|
||||
@@ -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
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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_
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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
@@ -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();
|
||||
}
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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
@@ -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;
|
||||
}
|
||||
@@ -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_
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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_
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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_
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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
|
||||
Reference in New Issue
Block a user