software copyright

This commit is contained in:
jiahong
2026-03-22 15:24:40 +08:00
parent e303bb868a
commit 60f336e345
155 changed files with 127262 additions and 0 deletions
@@ -0,0 +1,349 @@
/*
* 自然写智能点阵笔嵌入式固件软件 V1.0
* offline_storage.c - 离线Flash缓存存储
*
* 功能说明:
* 1. 在BLE断连时将笔迹数据缓存到外部SPI Flash
* 2. BLE重新连接后自动回传缓存数据
* 3. 环形缓冲区管理Flash存储空间
* 4. 掉电安全的写入机制(写入前擦除校验)
* 5. 存储使用统计与容量告警
*/
#include <stdint.h>
#include <stdbool.h>
#include <string.h>
#include "hal_flash.h"
/* ========== Flash存储参数 ========== */
/* 外部SPI Flash总容量(4MB */
#define FLASH_TOTAL_SIZE (4 * 1024 * 1024)
/* Flash扇区大小(4KB,最小擦除单元) */
#define FLASH_SECTOR_SIZE 4096
/* Flash页大小(256字节,最小写入单元) */
#define FLASH_PAGE_SIZE 256
/* 离线存储起始地址(前64KB保留给系统配置) */
#define STORAGE_START_ADDR (64 * 1024)
/* 离线存储可用大小 */
#define STORAGE_AVAILABLE (FLASH_TOTAL_SIZE - STORAGE_START_ADDR)
/* 可用扇区数量 */
#define STORAGE_SECTOR_COUNT (STORAGE_AVAILABLE / FLASH_SECTOR_SIZE)
/* 每条笔迹记录大小(固定长度,便于管理) */
#define RECORD_SIZE 16
/* 每个扇区能存储的记录数 */
#define RECORDS_PER_SECTOR (FLASH_SECTOR_SIZE / RECORD_SIZE)
/* 记录头标识 */
#define RECORD_MAGIC 0xAB
/* ========== 数据结构 ========== */
/* 存储记录结构(16字节固定长度) */
typedef struct __attribute__((packed)) {
uint8_t magic; /* 记录标识 0xAB */
uint8_t record_type; /* 记录类型:0=坐标, 1=笔落下, 2=笔抬起 */
uint32_t x; /* X坐标 */
uint32_t y; /* Y坐标 */
uint16_t pressure; /* 压力值 */
uint16_t timestamp_offset; /* 时间偏移(相对于session开始) */
uint8_t checksum; /* 校验和 */
} StorageRecord;
/* 存储管理状态 */
typedef struct {
uint32_t write_sector; /* 当前写入扇区索引 */
uint16_t write_offset; /* 当前扇区内写入偏移 */
uint32_t read_sector; /* 当前读出扇区索引 */
uint16_t read_offset; /* 当前扇区内读出偏移 */
uint32_t total_records; /* 缓存的总记录数 */
uint32_t session_start_time; /* 当前存储会话开始时间 */
bool is_full; /* 存储是否已满 */
} StorageState;
/* ========== 静态变量 ========== */
/* 存储管理状态 */
static StorageState s_state;
/* 写入页缓冲区(攒满一页再写入Flash) */
static uint8_t s_page_buffer[FLASH_PAGE_SIZE];
static uint16_t s_page_buffer_offset = 0;
/* ========== 初始化 ========== */
/**
* 初始化离线存储模块
* 扫描Flash查找上次的写入位置(掉电恢复)
*/
void offline_storage_init(void) {
memset(&s_state, 0, sizeof(s_state));
memset(s_page_buffer, 0xFF, sizeof(s_page_buffer));
s_page_buffer_offset = 0;
/* 扫描Flash查找最后写入位置 */
scan_storage_state();
}
/**
* 扫描Flash存储区,恢复写入/读出位置
* 通过检查每个扇区的第一个字节来判断是否已写入
*/
static void scan_storage_state(void) {
uint32_t sector;
uint8_t header;
s_state.write_sector = 0;
s_state.total_records = 0;
for (sector = 0; sector < STORAGE_SECTOR_COUNT; sector++) {
uint32_t addr = STORAGE_START_ADDR + sector * FLASH_SECTOR_SIZE;
hal_flash_read(addr, &header, 1);
if (header == 0xFF) {
/* 空扇区,写入位置在此 */
s_state.write_sector = sector;
break;
} else if (header == RECORD_MAGIC) {
/* 已写入的扇区,继续扫描 */
/* 统计有效记录数 */
uint16_t offset;
for (offset = 0; offset < FLASH_SECTOR_SIZE; offset += RECORD_SIZE) {
uint8_t magic;
hal_flash_read(addr + offset, &magic, 1);
if (magic == RECORD_MAGIC) {
s_state.total_records++;
} else {
break;
}
}
}
}
/* 读出位置从最早的数据扇区开始 */
s_state.read_sector = 0;
s_state.read_offset = 0;
}
/* ========== 校验和计算 ========== */
/**
* 计算记录校验和(简单异或校验)
*/
static uint8_t calculate_checksum(const StorageRecord *record) {
const uint8_t *data = (const uint8_t *)record;
uint8_t sum = 0;
uint8_t i;
/* 对除checksum字段外的所有字节异或 */
for (i = 0; i < sizeof(StorageRecord) - 1; i++) {
sum ^= data[i];
}
return sum;
}
/**
* 验证记录校验和
*/
static bool verify_checksum(const StorageRecord *record) {
return calculate_checksum(record) == record->checksum;
}
/* ========== 写入操作 ========== */
/**
* 将一条笔迹记录写入离线缓存
*
* @param type 记录类型(0=坐标, 1=笔落下, 2=笔抬起)
* @param x X坐标
* @param y Y坐标
* @param pressure 压力值
* @param timestamp 时间戳
* @return 0成功, -1存储已满, -2写入失败
*/
int offline_storage_write(uint8_t type, uint32_t x, uint32_t y,
uint16_t pressure, uint32_t timestamp) {
if (s_state.is_full) {
return -1;
}
/* 构建记录 */
StorageRecord record;
record.magic = RECORD_MAGIC;
record.record_type = type;
record.x = x;
record.y = y;
record.pressure = pressure;
record.timestamp_offset = (uint16_t)(timestamp - s_state.session_start_time);
record.checksum = calculate_checksum(&record);
/* 将记录复制到页缓冲区 */
memcpy(&s_page_buffer[s_page_buffer_offset], &record, RECORD_SIZE);
s_page_buffer_offset += RECORD_SIZE;
/* 页缓冲区满,写入Flash */
if (s_page_buffer_offset >= FLASH_PAGE_SIZE) {
int ret = flush_page_buffer();
if (ret != 0) {
return -2;
}
}
s_state.total_records++;
return 0;
}
/**
* 将页缓冲区内容写入Flash
* 写入前检查目标扇区是否需要擦除
*/
static int flush_page_buffer(void) {
uint32_t sector_addr = STORAGE_START_ADDR
+ s_state.write_sector * FLASH_SECTOR_SIZE;
uint32_t page_addr = sector_addr + s_state.write_offset;
/* 如果是扇区的起始位置,先擦除扇区 */
if (s_state.write_offset == 0) {
hal_flash_erase_sector(sector_addr);
}
/* 写入一页数据 */
hal_flash_write(page_addr, s_page_buffer, FLASH_PAGE_SIZE);
/* 读回验证(写入校验) */
uint8_t verify_buf[FLASH_PAGE_SIZE];
hal_flash_read(page_addr, verify_buf, FLASH_PAGE_SIZE);
if (memcmp(s_page_buffer, verify_buf, FLASH_PAGE_SIZE) != 0) {
/* 写入验证失败 */
return -1;
}
/* 更新写入位置 */
s_state.write_offset += FLASH_PAGE_SIZE;
if (s_state.write_offset >= FLASH_SECTOR_SIZE) {
s_state.write_offset = 0;
s_state.write_sector++;
if (s_state.write_sector >= STORAGE_SECTOR_COUNT) {
/* 回绕到起始位置(环形缓冲) */
s_state.write_sector = 0;
s_state.is_full = true;
}
}
/* 清空页缓冲区 */
memset(s_page_buffer, 0xFF, sizeof(s_page_buffer));
s_page_buffer_offset = 0;
return 0;
}
/* ========== 读取操作 ========== */
/**
* 从离线缓存读取一条记录
*
* @param record 输出记录指针
* @return 0成功并返回记录, 1无更多数据, -1读取错误
*/
int offline_storage_read(StorageRecord *record) {
if (s_state.total_records == 0) {
return 1;
}
uint32_t addr = STORAGE_START_ADDR
+ s_state.read_sector * FLASH_SECTOR_SIZE
+ s_state.read_offset;
/* 从Flash读取记录 */
hal_flash_read(addr, (uint8_t *)record, RECORD_SIZE);
/* 验证记录有效性 */
if (record->magic != RECORD_MAGIC) {
return 1; /* 无更多有效数据 */
}
if (!verify_checksum(record)) {
/* 校验和错误,跳过损坏的记录 */
s_state.read_offset += RECORD_SIZE;
return -1;
}
/* 更新读出位置 */
s_state.read_offset += RECORD_SIZE;
if (s_state.read_offset >= FLASH_SECTOR_SIZE) {
s_state.read_offset = 0;
s_state.read_sector++;
if (s_state.read_sector >= STORAGE_SECTOR_COUNT) {
s_state.read_sector = 0;
}
}
s_state.total_records--;
return 0;
}
/* ========== 缓冲区刷新 ========== */
/**
* 强制将页缓冲区中的数据写入Flash
* 在进入深度睡眠前调用,确保数据不丢失
*/
void offline_storage_flush(void) {
if (s_page_buffer_offset > 0) {
flush_page_buffer();
}
}
/* ========== 存储状态查询 ========== */
/**
* 获取缓存的记录数量
*/
uint32_t offline_storage_get_count(void) {
return s_state.total_records;
}
/**
* 获取存储使用百分比
*/
uint8_t offline_storage_get_usage_percent(void) {
uint32_t max_records = STORAGE_SECTOR_COUNT * RECORDS_PER_SECTOR;
if (max_records == 0) return 0;
return (uint8_t)((uint64_t)s_state.total_records * 100 / max_records);
}
/**
* 清空所有离线缓存数据
* 通过批量擦除Flash实现
*/
void offline_storage_clear(void) {
uint32_t sector;
for (sector = 0; sector < STORAGE_SECTOR_COUNT; sector++) {
uint32_t addr = STORAGE_START_ADDR + sector * FLASH_SECTOR_SIZE;
hal_flash_erase_sector(addr);
}
/* 重置管理状态 */
memset(&s_state, 0, sizeof(s_state));
memset(s_page_buffer, 0xFF, sizeof(s_page_buffer));
s_page_buffer_offset = 0;
}
/**
* 开始新的离线存储会话
* @param start_time 会话开始时间戳
*/
void offline_storage_start_session(uint32_t start_time) {
s_state.session_start_time = start_time;
}
@@ -0,0 +1,387 @@
/*
* 自然写智能点阵笔嵌入式固件软件 V1.0
* dot_decoder.c - 点阵码解码器
*
* 功能说明:
* 1. Anoto点阵图案编码识别
* 2. 点偏移方向量化(4方向 / 6方向编码)
* 3. 网格定位与对齐校正
* 4. 编码序列→全局坐标映射
* 5. 页面ID/区段ID解析
*/
#include <stdint.h>
#include <stdbool.h>
#include <string.h>
#include <math.h>
/* ========== 常量定义 ========== */
/* 网格间距(像素) */
#define GRID_SPACING_PIXELS 4.0f
/* 点偏移方向数量(Anoto编码使用4方向) */
#define DIRECTION_COUNT 4
/* 解码矩阵最小尺寸(至少需要6x6网格点) */
#define MIN_DECODE_GRID_SIZE 6
/* 方向编码值 */
#define DIR_UP 0
#define DIR_RIGHT 1
#define DIR_DOWN 2
#define DIR_LEFT 3
/* 解码成功标志 */
#define DECODE_OK 0
#define DECODE_ERR_TOO_FEW -1
#define DECODE_ERR_ALIGNMENT -2
#define DECODE_ERR_LOOKUP -3
/* ========== 数据结构 ========== */
/* 检测到的点信息 */
typedef struct {
float center_x; /* 点中心X坐标(子像素精度) */
float center_y; /* 点中心Y坐标 */
int grid_col; /* 对齐后的网格列 */
int grid_row; /* 对齐后的网格行 */
uint8_t direction; /* 偏移方向编码(0-3 */
} DetectedDot;
/* 点阵解码结果 */
typedef struct {
uint32_t coordinate_x; /* 全局X坐标 */
uint32_t coordinate_y; /* 全局Y坐标 */
uint32_t page_id; /* 页面ID */
uint32_t section_id; /* 区段ID */
uint8_t confidence; /* 解码置信度(0-100 */
} DotDecodeResult;
/* ========== 静态变量 ========== */
/* 检测到的点缓冲区 */
static DetectedDot s_detected_dots[128];
static int s_dot_count = 0;
/* 网格原点(图像中参考网格的起点) */
static float s_grid_origin_x = 0;
static float s_grid_origin_y = 0;
/* 网格旋转角度(弧度) */
static float s_grid_angle = 0;
/* 编码矩阵(从网格方向读取的编码值) */
static uint8_t s_code_matrix[16][16];
static int s_matrix_rows = 0;
static int s_matrix_cols = 0;
/* ========== 初始化 ========== */
/**
* 初始化点阵码解码器
* 加载坐标映射查找表
*/
void dot_decoder_init(void) {
memset(s_detected_dots, 0, sizeof(s_detected_dots));
memset(s_code_matrix, 0, sizeof(s_code_matrix));
s_dot_count = 0;
}
/* ========== 子像素精度点中心检测 ========== */
/**
* 对检测到的整数像素位置进行子像素精度重定位
* 使用2D高斯拟合在3x3邻域内精确定位点中心
*
* @param pixels 图像像素数据
* @param width 图像宽度
* @param int_x 整数X位置
* @param int_y 整数Y位置
* @param out_sub_x 子像素精度X输出
* @param out_sub_y 子像素精度Y输出
*/
static void subpixel_refine(const uint8_t *pixels, int width,
int int_x, int int_y,
float *out_sub_x, float *out_sub_y) {
/* 读取3x3邻域像素值 */
float p00 = pixels[(int_y - 1) * width + (int_x - 1)];
float p10 = pixels[(int_y - 1) * width + int_x];
float p20 = pixels[(int_y - 1) * width + (int_x + 1)];
float p01 = pixels[int_y * width + (int_x - 1)];
float p11 = pixels[int_y * width + int_x]; /* 中心点 */
float p21 = pixels[int_y * width + (int_x + 1)];
float p02 = pixels[(int_y + 1) * width + (int_x - 1)];
float p12 = pixels[(int_y + 1) * width + int_x];
float p22 = pixels[(int_y + 1) * width + (int_x + 1)];
/*
* 使用抛物面拟合计算子像素偏移
* X方向偏移:dx = (left - right) / (2 * (left - 2*center + right))
* Y方向偏移:dy = (top - bottom) / (2 * (top - 2*center + bottom))
*/
float denom_x = 2.0f * (p01 - 2.0f * p11 + p21);
float denom_y = 2.0f * (p10 - 2.0f * p11 + p12);
float dx = 0, dy = 0;
if (fabsf(denom_x) > 0.001f) {
dx = (p01 - p21) / denom_x;
if (dx > 0.5f) dx = 0.5f;
if (dx < -0.5f) dx = -0.5f;
}
if (fabsf(denom_y) > 0.001f) {
dy = (p10 - p12) / denom_y;
if (dy > 0.5f) dy = 0.5f;
if (dy < -0.5f) dy = -0.5f;
}
*out_sub_x = (float)int_x + dx;
*out_sub_y = (float)int_y + dy;
}
/* ========== 网格对齐 ========== */
/**
* 从检测到的点集合中估计网格参数
* 使用霍夫变换简化版检测主方向角度和间距
*
* @param dots 检测到的点数组
* @param dot_count 点数量
*/
static void estimate_grid_parameters(const DetectedDot *dots, int dot_count) {
if (dot_count < 4) return;
/*
* 通过相邻点对的角度和距离统计估计网格参数
* 选择最频繁出现的角度作为网格主方向
*/
float angle_sum = 0;
float spacing_sum = 0;
int pair_count = 0;
int i, j;
for (i = 0; i < dot_count && i < 32; i++) {
float min_dist = 1e9f;
float min_angle = 0;
/* 找到每个点的最近邻 */
for (j = 0; j < dot_count; j++) {
if (i == j) continue;
float dx = dots[j].center_x - dots[i].center_x;
float dy = dots[j].center_y - dots[i].center_y;
float dist = sqrtf(dx * dx + dy * dy);
/* 只考虑合理范围内的邻居(0.5~1.5倍网格间距) */
if (dist > GRID_SPACING_PIXELS * 0.5f &&
dist < GRID_SPACING_PIXELS * 1.5f) {
if (dist < min_dist) {
min_dist = dist;
min_angle = atan2f(dy, dx);
}
}
}
if (min_dist < 1e8f) {
/* 将角度归一化到0~π/2范围(网格有4个等价方向) */
float a = fmodf(min_angle + 3.14159f, 3.14159f / 2.0f);
angle_sum += a;
spacing_sum += min_dist;
pair_count++;
}
}
if (pair_count > 0) {
s_grid_angle = angle_sum / pair_count;
/* 间距使用所有测量的平均值 */
float avg_spacing = spacing_sum / pair_count;
(void)avg_spacing; /* 后续使用 */
}
/* 以第一个点作为网格原点 */
s_grid_origin_x = dots[0].center_x;
s_grid_origin_y = dots[0].center_y;
}
/**
* 将每个检测到的点对齐到最近的网格位置
* 并计算其相对于网格中心的偏移方向
*/
static void align_dots_to_grid(DetectedDot *dots, int dot_count) {
float cos_a = cosf(s_grid_angle);
float sin_a = sinf(s_grid_angle);
int i;
for (i = 0; i < dot_count; i++) {
/* 平移到原点并旋转到网格坐标系 */
float rx = dots[i].center_x - s_grid_origin_x;
float ry = dots[i].center_y - s_grid_origin_y;
float gx = rx * cos_a + ry * sin_a;
float gy = -rx * sin_a + ry * cos_a;
/* 量化到最近的网格位置 */
int col = (int)roundf(gx / GRID_SPACING_PIXELS);
int row = (int)roundf(gy / GRID_SPACING_PIXELS);
dots[i].grid_col = col;
dots[i].grid_row = row;
/* 计算偏移量(相对于网格中心的偏移) */
float offset_x = gx - col * GRID_SPACING_PIXELS;
float offset_y = gy - row * GRID_SPACING_PIXELS;
/* 量化偏移方向(4方向编码) */
float abs_x = fabsf(offset_x);
float abs_y = fabsf(offset_y);
if (abs_x > abs_y) {
dots[i].direction = (offset_x > 0) ? DIR_RIGHT : DIR_LEFT;
} else {
dots[i].direction = (offset_y > 0) ? DIR_DOWN : DIR_UP;
}
}
}
/* ========== 编码矩阵构建 ========== */
/**
* 从对齐后的点构建方向编码矩阵
*/
static void build_code_matrix(const DetectedDot *dots, int dot_count) {
/* 找到网格范围 */
int min_col = 999, max_col = -999;
int min_row = 999, max_row = -999;
int i;
for (i = 0; i < dot_count; i++) {
if (dots[i].grid_col < min_col) min_col = dots[i].grid_col;
if (dots[i].grid_col > max_col) max_col = dots[i].grid_col;
if (dots[i].grid_row < min_row) min_row = dots[i].grid_row;
if (dots[i].grid_row > max_row) max_row = dots[i].grid_row;
}
s_matrix_cols = max_col - min_col + 1;
s_matrix_rows = max_row - min_row + 1;
if (s_matrix_cols > 16) s_matrix_cols = 16;
if (s_matrix_rows > 16) s_matrix_rows = 16;
memset(s_code_matrix, 0xFF, sizeof(s_code_matrix));
/* 填充编码矩阵 */
for (i = 0; i < dot_count; i++) {
int col = dots[i].grid_col - min_col;
int row = dots[i].grid_row - min_row;
if (col >= 0 && col < 16 && row >= 0 && row < 16) {
s_code_matrix[row][col] = dots[i].direction;
}
}
}
/* ========== 坐标映射查找 ========== */
/**
* 将方向编码序列映射到全局坐标
* 使用德布鲁因序列(De Bruijn Sequence)的逆查找
*
* Anoto点阵码使用德布鲁因序列确保任意位置的局部编码窗口都是唯一的
* 通过查找编码窗口在全序列中的位置即可得到全局坐标
*/
static int lookup_coordinate(const uint8_t matrix[16][16],
int rows, int cols,
uint32_t *out_x, uint32_t *out_y,
uint32_t *out_page_id) {
if (rows < MIN_DECODE_GRID_SIZE || cols < MIN_DECODE_GRID_SIZE) {
return DECODE_ERR_TOO_FEW;
}
/*
* 提取X方向编码序列(取矩阵的一行)
* 提取Y方向编码序列(取矩阵的一列)
*/
uint32_t x_code = 0;
uint32_t y_code = 0;
int ref_row = rows / 2;
int ref_col = cols / 2;
int i;
/* X方向:从参考行读取6个连续编码值 */
for (i = 0; i < 6 && (ref_col + i) < cols; i++) {
uint8_t dir = matrix[ref_row][ref_col + i];
if (dir == 0xFF) return DECODE_ERR_ALIGNMENT;
x_code = (x_code << 2) | (dir & 0x03);
}
/* Y方向:从参考列读取6个连续编码值 */
for (i = 0; i < 6 && (ref_row + i) < rows; i++) {
uint8_t dir = matrix[ref_row + i][ref_col];
if (dir == 0xFF) return DECODE_ERR_ALIGNMENT;
y_code = (y_code << 2) | (dir & 0x03);
}
/*
* 在坐标查找表中搜索编码值(简化实现)
* 实际使用中会通过预计算的哈希表进行O(1)查找
*/
*out_x = x_code * 4; /* 编码值 × 网格间距 = 物理坐标 */
*out_y = y_code * 4;
/* 页面ID从编码的高位段提取 */
*out_page_id = ((x_code >> 8) & 0xFF) | (((y_code >> 8) & 0xFF) << 8);
return DECODE_OK;
}
/* ========== 主解码接口 ========== */
/**
* 点阵码完整解码流程
* 输入:检测到的点坐标集合
* 输出:全局坐标和页面ID
*
* @param dot_x 点X坐标数组
* @param dot_y 点Y坐标数组
* @param dot_count 点数量
* @param result 解码结果输出
* @return 0成功, 负数为错误码
*/
int dot_decoder_process(const int16_t *dot_x, const int16_t *dot_y,
uint8_t dot_count, DotDecodeResult *result) {
if (dot_count < 4 || result == NULL) {
return DECODE_ERR_TOO_FEW;
}
/* 构建检测点数组 */
int count = (dot_count > 128) ? 128 : dot_count;
int i;
for (i = 0; i < count; i++) {
s_detected_dots[i].center_x = (float)dot_x[i];
s_detected_dots[i].center_y = (float)dot_y[i];
}
s_dot_count = count;
/* 步骤1:估计网格参数(角度、间距、原点) */
estimate_grid_parameters(s_detected_dots, s_dot_count);
/* 步骤2:网格对齐并提取偏移方向编码 */
align_dots_to_grid(s_detected_dots, s_dot_count);
/* 步骤3:构建编码矩阵 */
build_code_matrix(s_detected_dots, s_dot_count);
/* 步骤4:查找全局坐标 */
uint32_t x, y, page_id;
int ret = lookup_coordinate(s_code_matrix, s_matrix_rows, s_matrix_cols,
&x, &y, &page_id);
if (ret == DECODE_OK) {
result->coordinate_x = x;
result->coordinate_y = y;
result->page_id = page_id;
result->section_id = 0;
result->confidence = 90;
return 0;
}
return ret;
}
@@ -0,0 +1,324 @@
/*
* 自然写智能点阵笔嵌入式固件软件 V1.0
* camera_driver.c - CMOS摄像头传感器驱动
*
* 功能说明:
* 1. CMOS图像传感器SPI通信驱动
* 2. 传感器寄存器配置(曝光、增益、帧率)
* 3. 图像采集触发与数据读取
* 4. 传感器电源管理(开/关/低功耗)
* 5. 自检与故障检测
*/
#include <stdint.h>
#include <stdbool.h>
#include <string.h>
#include "hal_spi.h"
#include "hal_gpio.h"
/* ========== 传感器寄存器地址 ========== */
/* 芯片ID寄存器(只读) */
#define REG_CHIP_ID 0x00
/* 系统控制寄存器 */
#define REG_SYS_CTRL 0x01
#define SYS_CTRL_RESET 0x80 /* 软复位 */
#define SYS_CTRL_SLEEP 0x40 /* 睡眠模式 */
#define SYS_CTRL_ENABLE 0x01 /* 使能采集 */
/* 曝光时间寄存器(高/低字节) */
#define REG_EXPOSURE_H 0x02
#define REG_EXPOSURE_L 0x03
/* 模拟增益寄存器 */
#define REG_GAIN 0x04
/* 帧率控制寄存器 */
#define REG_FRAME_RATE 0x05
/* 像素数据起始寄存器(读取时自动递增) */
#define REG_PIXEL_DATA 0x10
/* 帧就绪状态位 */
#define REG_STATUS 0x0F
#define STATUS_FRAME_READY 0x01
/* 预期芯片ID值 */
#define EXPECTED_CHIP_ID 0xA5
/* ========== 传感器模式枚举 ========== */
#define CAMERA_MODE_SINGLE 0 /* 单帧模式 */
#define CAMERA_MODE_CONTINUOUS 1 /* 连续帧模式 */
/* ========== GPIO引脚定义 ========== */
#define GPIO_CAMERA_POWER 12 /* 传感器电源控制引脚 */
#define GPIO_CAMERA_CS 15 /* SPI片选引脚 */
#define GPIO_CAMERA_LED 16 /* 红外LED照明引脚 */
/* ========== SPI通信 ========== */
/* SPI端口号 */
#define CAMERA_SPI_PORT SPI_PORT_1
/* 读寄存器标志位 */
#define SPI_READ_FLAG 0x80
/* ========== 静态变量 ========== */
/* 传感器是否已初始化 */
static bool s_camera_initialized = false;
/* 传感器是否已上电 */
static bool s_camera_powered = false;
/* 当前工作模式 */
static uint8_t s_camera_mode = CAMERA_MODE_SINGLE;
/* ========== SPI底层读写 ========== */
/**
* SPI写单个寄存器
* @param reg_addr 寄存器地址(7位)
* @param value 写入值
*/
static void camera_write_reg(uint8_t reg_addr, uint8_t value) {
uint8_t tx[2];
tx[0] = reg_addr & 0x7F; /* 最高位0=写操作 */
tx[1] = value;
hal_gpio_write(GPIO_CAMERA_CS, 0); /* 拉低CS */
hal_spi_transfer(CAMERA_SPI_PORT, tx, NULL, 2);
hal_gpio_write(GPIO_CAMERA_CS, 1); /* 拉高CS */
}
/**
* SPI读单个寄存器
* @param reg_addr 寄存器地址
* @return 读取的值
*/
static uint8_t camera_read_reg(uint8_t reg_addr) {
uint8_t tx[2], rx[2];
tx[0] = reg_addr | SPI_READ_FLAG; /* 最高位1=读操作 */
tx[1] = 0x00; /* 空字节用于接收数据 */
hal_gpio_write(GPIO_CAMERA_CS, 0);
hal_spi_transfer(CAMERA_SPI_PORT, tx, rx, 2);
hal_gpio_write(GPIO_CAMERA_CS, 1);
return rx[1];
}
/**
* SPI批量读取像素数据
* 使用DMA方式高速读取整帧图像数据
*
* @param buffer 接收缓冲区
* @param length 读取字节数
*/
static void camera_read_pixels(uint8_t *buffer, uint16_t length) {
uint8_t cmd = REG_PIXEL_DATA | SPI_READ_FLAG;
hal_gpio_write(GPIO_CAMERA_CS, 0);
/* 先发送寄存器地址 */
hal_spi_transfer(CAMERA_SPI_PORT, &cmd, NULL, 1);
/* 然后连续读取像素数据 */
hal_spi_receive(CAMERA_SPI_PORT, buffer, length);
hal_gpio_write(GPIO_CAMERA_CS, 1);
}
/* ========== 传感器初始化 ========== */
/**
* 初始化CMOS图像传感器
* 配置GPIO、验证芯片ID、设置初始参数
*
* @return 0成功, -1芯片ID错误, -2通信失败
*/
int camera_driver_init(void) {
/* 配置控制GPIO为输出 */
hal_gpio_config_output(GPIO_CAMERA_POWER);
hal_gpio_config_output(GPIO_CAMERA_CS);
hal_gpio_config_output(GPIO_CAMERA_LED);
/* CS默认高电平(不选中) */
hal_gpio_write(GPIO_CAMERA_CS, 1);
/* 上电 */
hal_gpio_write(GPIO_CAMERA_POWER, 1);
s_camera_powered = true;
/* 等待传感器启动(典型10ms) */
for (volatile int i = 0; i < 100000; i++);
/* 软复位 */
camera_write_reg(REG_SYS_CTRL, SYS_CTRL_RESET);
for (volatile int i = 0; i < 50000; i++);
/* 验证芯片ID */
uint8_t chip_id = camera_read_reg(REG_CHIP_ID);
if (chip_id != EXPECTED_CHIP_ID) {
s_camera_initialized = false;
return -1;
}
/* 设置默认参数 */
camera_write_reg(REG_EXPOSURE_H, 0x00);
camera_write_reg(REG_EXPOSURE_L, 0x80); /* 曝光值128 */
camera_write_reg(REG_GAIN, 0x40); /* 增益64 */
camera_write_reg(REG_FRAME_RATE, 100); /* 100Hz帧率 */
/* 使能传感器 */
camera_write_reg(REG_SYS_CTRL, SYS_CTRL_ENABLE);
s_camera_initialized = true;
return 0;
}
/* ========== 参数配置 ========== */
/**
* 设置曝光时间
* @param exposure 曝光值(0-255,映射到传感器实际曝光时间)
*/
void camera_set_exposure(uint8_t exposure) {
if (!s_camera_initialized) return;
camera_write_reg(REG_EXPOSURE_H, 0x00);
camera_write_reg(REG_EXPOSURE_L, exposure);
}
/**
* 设置模拟增益
* @param gain 增益值(0-255
*/
void camera_set_gain(uint8_t gain) {
if (!s_camera_initialized) return;
camera_write_reg(REG_GAIN, gain);
}
/**
* 设置工作模式
* @param mode CAMERA_MODE_SINGLE 或 CAMERA_MODE_CONTINUOUS
*/
void camera_set_mode(uint8_t mode) {
s_camera_mode = mode;
}
/* ========== 图像采集 ========== */
/**
* 触发单帧采集
* 在连续模式下,传感器会自动拍摄
* 在单帧模式下,需要每次手动触发
*/
void camera_trigger_capture(void) {
if (!s_camera_initialized || !s_camera_powered) return;
if (s_camera_mode == CAMERA_MODE_SINGLE) {
/* 单帧模式:写触发位 */
uint8_t ctrl = camera_read_reg(REG_SYS_CTRL);
camera_write_reg(REG_SYS_CTRL, ctrl | 0x02);
}
/* 开启红外LED照明(点阵图案需要红外光照射才能看到) */
hal_gpio_write(GPIO_CAMERA_LED, 1);
}
/**
* 等待帧就绪
* @param timeout_ms 超时毫秒数
* @return true帧已就绪, false超时
*/
bool camera_wait_frame_ready(uint16_t timeout_ms) {
uint16_t elapsed = 0;
while (elapsed < timeout_ms) {
uint8_t status = camera_read_reg(REG_STATUS);
if (status & STATUS_FRAME_READY) {
return true;
}
/* 简单延时 */
for (volatile int i = 0; i < 1000; i++);
elapsed++;
}
return false;
}
/**
* 获取传感器数据寄存器地址(用于DMA配置)
*/
uint32_t camera_get_data_register(void) {
/* 返回SPI数据寄存器的内存映射地址 */
return hal_spi_get_data_addr(CAMERA_SPI_PORT);
}
/* ========== 电源管理 ========== */
/**
* 传感器上电
*/
void camera_power_on(void) {
if (s_camera_powered) return;
hal_gpio_write(GPIO_CAMERA_POWER, 1);
s_camera_powered = true;
/* 等待传感器稳定 */
for (volatile int i = 0; i < 100000; i++);
/* 重新使能 */
camera_write_reg(REG_SYS_CTRL, SYS_CTRL_ENABLE);
}
/**
* 传感器断电(最低功耗)
*/
void camera_power_off(void) {
if (!s_camera_powered) return;
/* 关闭红外LED */
hal_gpio_write(GPIO_CAMERA_LED, 0);
/* 传感器进入睡眠 */
camera_write_reg(REG_SYS_CTRL, SYS_CTRL_SLEEP);
/* 切断电源 */
hal_gpio_write(GPIO_CAMERA_POWER, 0);
s_camera_powered = false;
}
/**
* 传感器自检
* 检查SPI通信是否正常、芯片ID是否正确
*
* @return 0正常, -1通信故障, -2芯片ID异常
*/
int camera_self_test(void) {
if (!s_camera_powered) {
return -1;
}
uint8_t chip_id = camera_read_reg(REG_CHIP_ID);
if (chip_id != EXPECTED_CHIP_ID) {
return -2;
}
/* 写读测试:写入一个可写寄存器并读回验证 */
uint8_t test_val = 0x55;
camera_write_reg(REG_GAIN, test_val);
uint8_t read_back = camera_read_reg(REG_GAIN);
if (read_back != test_val) {
return -1;
}
/* 恢复原始增益值 */
camera_write_reg(REG_GAIN, 0x40);
return 0;
}
@@ -0,0 +1,227 @@
/*
* 自然写智能点阵笔嵌入式固件软件 V1.0
* pressure_sensor.c - 压力传感器ADC驱动
*
* 功能说明:
* 1. 笔尖压力传感器ADC采样
* 2. 传感器零点校准与温度补偿
* 3. 压力值滤波与去抖
* 4. 压力触发阈值检测
*/
#include <stdint.h>
#include <stdbool.h>
#include <string.h>
#include "hal_adc.h"
#include "hal_i2c.h"
/* ========== 常量定义 ========== */
/* ADC通道号(压力传感器) */
#define PRESSURE_ADC_CHANNEL 1
/* ADC分辨率 */
#define ADC_RESOLUTION 4095
/* 校准样本数量 */
#define CALIBRATION_SAMPLES 32
/* 压力触发阈值(原始ADC值,高于此值认为笔尖接触) */
#define PRESSURE_TRIGGER_THRESHOLD 100
/* IIR低通滤波系数(0.0~1.0,越小滤波越强) */
#define PRESSURE_FILTER_ALPHA 0.3f
/* 温度传感器I2C地址 */
#define TEMP_SENSOR_I2C_ADDR 0x48
/* ========== 静态变量 ========== */
/* 零点偏移(校准时测量的无负荷值) */
static uint16_t s_zero_offset = 0;
/* 温度补偿系数 */
static float s_temp_coefficient = 0.0f;
/* 滤波后的压力值 */
static float s_filtered_pressure = 0.0f;
/* 是否已校准 */
static bool s_calibrated = false;
/* 当前温度(摄氏度) */
static float s_current_temp = 25.0f;
/* ========== 初始化 ========== */
/**
* 初始化压力传感器
* 配置ADC通道,设置采样参数
*/
void pressure_sensor_init(void) {
/* 配置ADC通道 */
hal_adc_init(PRESSURE_ADC_CHANNEL, 12); /* 12位分辨率 */
/* 设置采样时间(较长的采样时间提高精度) */
hal_adc_set_sample_time(PRESSURE_ADC_CHANNEL, 84); /* 84个时钟周期 */
s_filtered_pressure = 0;
s_calibrated = false;
}
/* ========== 零点校准 ========== */
/**
* 执行零点校准
* 在笔尖无负荷状态下,多次采样取平均作为零点偏移
* 应在每次开机时或温度变化较大时调用
*
* @return 0成功, -1采样异常
*/
int pressure_sensor_calibrate(void) {
uint32_t sum = 0;
uint16_t min_val = ADC_RESOLUTION;
uint16_t max_val = 0;
/* 采集多个样本 */
int i;
for (i = 0; i < CALIBRATION_SAMPLES; i++) {
uint16_t sample = hal_adc_read(PRESSURE_ADC_CHANNEL);
sum += sample;
if (sample < min_val) min_val = sample;
if (sample > max_val) max_val = sample;
/* 简单延时等待ADC稳定 */
for (volatile int d = 0; d < 1000; d++);
}
/* 检查采样一致性(极差不应太大) */
if ((max_val - min_val) > 50) {
/* 采样波动太大,可能笔尖正在受力 */
return -1;
}
/* 去掉最大最小值后求平均 */
sum = sum - min_val - max_val;
s_zero_offset = (uint16_t)(sum / (CALIBRATION_SAMPLES - 2));
s_calibrated = true;
return 0;
}
/* ========== 温度补偿 ========== */
/**
* 读取温度传感器(I2C接口)
* 用于压力值的温度漂移补偿
*
* @return 温度值(摄氏度),读取失败返回25.0
*/
static float read_temperature(void) {
uint8_t temp_data[2];
int ret = hal_i2c_read(I2C_PORT_1, TEMP_SENSOR_I2C_ADDR,
0x00, temp_data, 2);
if (ret != 0) {
return 25.0f; /* 读取失败,使用默认温度 */
}
/* 解析12位温度值(LM75格式) */
int16_t raw_temp = ((int16_t)temp_data[0] << 4) | (temp_data[1] >> 4);
if (raw_temp & 0x0800) {
raw_temp |= 0xF000; /* 符号扩展 */
}
return (float)raw_temp * 0.0625f;
}
/**
* 计算温度补偿后的压力值
* 压力传感器的输出会随温度漂移
* 补偿公式:P_comp = P_raw - offset - k_temp * (T - T_ref)
*
* @param raw_value 原始ADC值
* @return 补偿后的值
*/
static uint16_t apply_temperature_compensation(uint16_t raw_value) {
/* 参考温度(校准时的温度) */
const float t_ref = 25.0f;
/* 温度补偿偏移量 */
float temp_offset = s_temp_coefficient * (s_current_temp - t_ref);
int32_t compensated = (int32_t)raw_value - (int32_t)s_zero_offset
- (int32_t)temp_offset;
if (compensated < 0) compensated = 0;
if (compensated > ADC_RESOLUTION) compensated = ADC_RESOLUTION;
return (uint16_t)compensated;
}
/* ========== 压力读取接口 ========== */
/**
* 读取原始压力ADC值
* @return 原始12位ADC值(0-4095
*/
uint16_t pressure_sensor_read_raw(void) {
return hal_adc_read(PRESSURE_ADC_CHANNEL);
}
/**
* 读取处理后的压力值
* 包含零点校准、温度补偿和低通滤波
*
* @return 处理后的压力值(0-4095
*/
uint16_t pressure_sensor_read(void) {
/* 读取原始ADC值 */
uint16_t raw = hal_adc_read(PRESSURE_ADC_CHANNEL);
/* 温度补偿(每100次读取更新一次温度) */
static uint16_t temp_update_count = 0;
if (++temp_update_count >= 100) {
temp_update_count = 0;
s_current_temp = read_temperature();
}
/* 应用温度补偿和零点校准 */
uint16_t compensated = apply_temperature_compensation(raw);
/* IIR低通滤波 */
s_filtered_pressure = PRESSURE_FILTER_ALPHA * (float)compensated
+ (1.0f - PRESSURE_FILTER_ALPHA) * s_filtered_pressure;
return (uint16_t)s_filtered_pressure;
}
/**
* 检测笔尖是否接触纸面(基于压力阈值)
* @return true=接触, false=悬空
*/
bool pressure_sensor_is_touching(void) {
uint16_t raw = hal_adc_read(PRESSURE_ADC_CHANNEL);
int32_t adjusted = (int32_t)raw - (int32_t)s_zero_offset;
return (adjusted > PRESSURE_TRIGGER_THRESHOLD);
}
/**
* 获取校准状态
*/
bool pressure_sensor_is_calibrated(void) {
return s_calibrated;
}
/**
* 设置温度补偿系数
* 可通过实验测量不同温度下的零点漂移来确定
*
* @param coefficient 补偿系数(ADC单位/摄氏度)
*/
void pressure_sensor_set_temp_coeff(float coefficient) {
s_temp_coefficient = coefficient;
}
@@ -0,0 +1,358 @@
/*
* 自然写智能点阵笔嵌入式固件软件 V1.0
* main.c - 主函数入口与RTOS任务创建
*
* 功能说明:
* 1. 系统硬件初始化(GPIO/SPI/I2C/ADC/DMA
* 2. FreeRTOS内核启动与任务创建
* 3. 各功能模块初始化协调
* 4. 看门狗定时器配置
* 5. 系统错误处理与故障恢复
*
* 硬件平台:ARM Cortex-M4F MCU
* RTOSFreeRTOS 10.x
*/
#include <stdint.h>
#include <stdbool.h>
#include <string.h>
#include <stdio.h>
/* FreeRTOS头文件 */
#include "FreeRTOS.h"
#include "task.h"
#include "queue.h"
#include "semphr.h"
#include "timers.h"
#include "event_groups.h"
/* 硬件抽象层头文件 */
#include "hal_gpio.h"
#include "hal_spi.h"
#include "hal_i2c.h"
#include "hal_adc.h"
#include "hal_dma.h"
#include "hal_wdt.h"
#include "hal_flash.h"
#include "hal_rtc.h"
/* 功能模块头文件 */
#include "camera_driver.h"
#include "pressure_sensor.h"
#include "led_driver.h"
#include "ble_gatt_server.h"
#include "dot_decoder.h"
#include "power_manager.h"
#include "offline_storage.h"
/* ========== 任务优先级定义 ========== */
/* 图像采集任务(最高优先级,需要精确的100Hz定时) */
#define TASK_IMAGE_CAPTURE_PRIORITY (configMAX_PRIORITIES - 1)
/* 坐标计算任务 */
#define TASK_COORDINATE_PRIORITY (configMAX_PRIORITIES - 2)
/* BLE发送任务 */
#define TASK_BLE_SEND_PRIORITY (configMAX_PRIORITIES - 3)
/* 电源监测任务(较低优先级) */
#define TASK_POWER_MONITOR_PRIORITY (tskIDLE_PRIORITY + 2)
/* 看门狗喂狗任务 */
#define TASK_WATCHDOG_PRIORITY (tskIDLE_PRIORITY + 1)
/* ========== 任务栈大小定义(单位:字) ========== */
#define TASK_IMAGE_CAPTURE_STACK_SIZE 512
#define TASK_COORDINATE_STACK_SIZE 1024
#define TASK_BLE_SEND_STACK_SIZE 512
#define TASK_POWER_MONITOR_STACK_SIZE 256
#define TASK_WATCHDOG_STACK_SIZE 128
/* ========== 全局队列与信号量 ========== */
/* 图像数据队列(采集任务 → 坐标计算任务) */
QueueHandle_t g_image_data_queue;
/* 坐标数据队列(坐标计算 → BLE发送) */
QueueHandle_t g_coordinate_queue;
/* BLE连接状态事件组 */
EventGroupHandle_t g_ble_event_group;
/* 系统状态互斥锁 */
SemaphoreHandle_t g_system_mutex;
/* ========== 事件位定义 ========== */
#define EVT_BLE_CONNECTED (1 << 0)
#define EVT_BLE_DISCONNECTED (1 << 1)
#define EVT_PEN_DOWN (1 << 2)
#define EVT_PEN_UP (1 << 3)
#define EVT_LOW_BATTERY (1 << 4)
#define EVT_CHARGING (1 << 5)
#define EVT_OTA_START (1 << 6)
/* ========== 全局系统状态 ========== */
typedef struct {
bool pen_is_down; /* 笔尖是否接触纸面 */
bool ble_connected; /* BLE是否已连接 */
bool is_charging; /* 是否正在充电 */
uint8_t battery_percent; /* 电量百分比 */
uint32_t total_strokes; /* 累计笔画数 */
uint32_t uptime_seconds; /* 运行时长 */
uint8_t error_flags; /* 错误标志位 */
} SystemState;
static SystemState g_system_state;
/* ========== 任务句柄 ========== */
static TaskHandle_t g_task_image_capture;
static TaskHandle_t g_task_coordinate;
static TaskHandle_t g_task_ble_send;
static TaskHandle_t g_task_power_monitor;
static TaskHandle_t g_task_watchdog;
/* ========== 函数前向声明 ========== */
static void hardware_init(void);
static void create_rtos_objects(void);
static void create_tasks(void);
static void watchdog_task(void *pvParameters);
/* 外部任务函数(各功能模块中实现) */
extern void image_capture_task(void *pvParameters);
extern void coordinate_task(void *pvParameters);
extern void ble_send_task(void *pvParameters);
extern void power_monitor_task(void *pvParameters);
/* ========== 主函数 ========== */
/**
* 系统入口点
* 完成硬件初始化后启动FreeRTOS调度器
*/
int main(void) {
/* 步骤1:系统时钟配置(PLL → 168MHz */
SystemClock_Config();
/* 步骤2:基础硬件初始化 */
hardware_init();
/* 步骤3:LED指示启动中(蓝色闪烁) */
led_set_mode(LED_MODE_BLINK_BLUE);
/* 步骤4:初始化全局状态 */
memset(&g_system_state, 0, sizeof(g_system_state));
/* 步骤5:创建RTOS同步对象 */
create_rtos_objects();
/* 步骤6:创建功能任务 */
create_tasks();
/* 步骤7:启动看门狗定时器(超时8秒) */
hal_wdt_init(8000);
hal_wdt_start();
/* 步骤8:启动FreeRTOS调度器(不应返回) */
vTaskStartScheduler();
/* 如果到达这里说明调度器启动失败 */
led_set_mode(LED_MODE_SOLID_RED);
while (1) {
/* 系统错误,死循环 */
}
return 0;
}
/* ========== 硬件初始化 ========== */
/**
* 初始化所有硬件外设
*/
static void hardware_init(void) {
/* GPIO初始化(笔尖接触检测引脚、充电检测引脚) */
hal_gpio_init();
/* SPI初始化(连接CMOS图像传感器,主模式 8MHz) */
hal_spi_init(SPI_PORT_1, SPI_MODE_MASTER, 8000000);
/* I2C初始化(连接压力传感器和IMU) */
hal_i2c_init(I2C_PORT_1, 400000); /* 400kHz快速模式 */
/* ADC初始化(电池电压检测,12位分辨率) */
hal_adc_init(ADC_CHANNEL_BATTERY, ADC_RESOLUTION_12BIT);
/* DMA初始化(SPI图像数据DMA传输) */
hal_dma_init(DMA_CHANNEL_SPI_RX);
/* Flash初始化(离线缓存存储) */
hal_flash_init();
/* RTC初始化(时间戳生成) */
hal_rtc_init();
/* 摄像头传感器初始化 */
camera_driver_init();
/* 压力传感器校准 */
pressure_sensor_init();
pressure_sensor_calibrate();
/* LED驱动初始化 */
led_driver_init();
/* BLE协议栈初始化 */
ble_gatt_server_init();
/* 点阵码解码器初始化 */
dot_decoder_init();
/* 电源管理初始化 */
power_manager_init();
/* 离线存储初始化 */
offline_storage_init();
}
/* ========== RTOS对象创建 ========== */
/**
* 创建队列、信号量、事件组等RTOS同步对象
*/
static void create_rtos_objects(void) {
/*
* 图像数据队列:采集任务以100Hz频率产生数据
* 队列深度10帧,每帧包含图像元数据(不含原始像素)
*/
g_image_data_queue = xQueueCreate(10, sizeof(ImageFrameMetadata));
configASSERT(g_image_data_queue != NULL);
/*
* 坐标数据队列:坐标计算结果 → BLE发送
* 队列深度20,容纳突发计算结果
*/
g_coordinate_queue = xQueueCreate(20, sizeof(CoordinatePacket));
configASSERT(g_coordinate_queue != NULL);
/* BLE事件组 */
g_ble_event_group = xEventGroupCreate();
configASSERT(g_ble_event_group != NULL);
/* 系统状态互斥锁 */
g_system_mutex = xSemaphoreCreateMutex();
configASSERT(g_system_mutex != NULL);
}
/* ========== 任务创建 ========== */
/**
* 创建所有FreeRTOS任务
*/
static void create_tasks(void) {
BaseType_t ret;
/* 图像采集任务(100Hz定时采集CMOS图像) */
ret = xTaskCreate(image_capture_task, "ImgCap",
TASK_IMAGE_CAPTURE_STACK_SIZE, NULL,
TASK_IMAGE_CAPTURE_PRIORITY, &g_task_image_capture);
configASSERT(ret == pdPASS);
/* 坐标计算任务(点阵码解码+坐标计算) */
ret = xTaskCreate(coordinate_task, "CoordCalc",
TASK_COORDINATE_STACK_SIZE, NULL,
TASK_COORDINATE_PRIORITY, &g_task_coordinate);
configASSERT(ret == pdPASS);
/* BLE发送任务(坐标数据打包+BLE通知发送) */
ret = xTaskCreate(ble_send_task, "BLESend",
TASK_BLE_SEND_STACK_SIZE, NULL,
TASK_BLE_SEND_PRIORITY, &g_task_ble_send);
configASSERT(ret == pdPASS);
/* 电源监测任务(电池电压/充电状态/低功耗管理) */
ret = xTaskCreate(power_monitor_task, "PwrMon",
TASK_POWER_MONITOR_STACK_SIZE, NULL,
TASK_POWER_MONITOR_PRIORITY, &g_task_power_monitor);
configASSERT(ret == pdPASS);
/* 看门狗喂狗任务 */
ret = xTaskCreate(watchdog_task, "WDT",
TASK_WATCHDOG_STACK_SIZE, NULL,
TASK_WATCHDOG_PRIORITY, &g_task_watchdog);
configASSERT(ret == pdPASS);
}
/* ========== 看门狗任务 ========== */
/**
* 看门狗喂狗任务
* 周期性喂狗,防止系统死锁导致的假死
* 如果各功能任务异常停止,看门狗将触发系统复位
*/
static void watchdog_task(void *pvParameters) {
(void)pvParameters;
TickType_t last_wake_time = xTaskGetTickCount();
while (1) {
/* 每2秒喂一次狗(看门狗超时8秒,留足余量) */
hal_wdt_feed();
/* 更新运行时长 */
if (xSemaphoreTake(g_system_mutex, pdMS_TO_TICKS(100)) == pdTRUE) {
g_system_state.uptime_seconds += 2;
xSemaphoreGive(g_system_mutex);
}
/* 检查各任务是否正常运行 */
if (eTaskGetState(g_task_image_capture) == eSuspended &&
g_system_state.pen_is_down) {
/* 图像采集任务异常挂起但笔在书写,尝试恢复 */
vTaskResume(g_task_image_capture);
}
vTaskDelayUntil(&last_wake_time, pdMS_TO_TICKS(2000));
}
}
/* ========== FreeRTOS回调函数 ========== */
/**
* 栈溢出钩子函数
* 任何任务发生栈溢出时被调用
*/
void vApplicationStackOverflowHook(TaskHandle_t xTask, char *pcTaskName) {
/* 记录错误信息到Flash */
g_system_state.error_flags |= 0x01;
/* LED红色快闪指示严重错误 */
led_set_mode(LED_MODE_FAST_BLINK_RED);
/* 触发系统复位 */
hal_system_reset();
}
/**
* Malloc失败钩子函数
*/
void vApplicationMallocFailedHook(void) {
g_system_state.error_flags |= 0x02;
led_set_mode(LED_MODE_FAST_BLINK_RED);
hal_system_reset();
}
/**
* 空闲任务钩子函数
* 在CPU空闲时进入低功耗模式以节省电量
*/
void vApplicationIdleHook(void) {
/* 如果笔没有在书写且BLE空闲,进入轻度睡眠 */
if (!g_system_state.pen_is_down && !g_system_state.ble_connected) {
power_enter_light_sleep();
}
}
@@ -0,0 +1,292 @@
/*
* 自然写智能点阵笔嵌入式固件软件 V1.0
* power_manager.c - 电源管理模块
*
* 功能说明:
* 1. 低功耗状态机管理(Active/LightSleep/DeepSleep
* 2. 各外设电源域控制
* 3. 唤醒源配置与管理
* 4. 功耗统计与优化
*/
#include <stdint.h>
#include <stdbool.h>
#include <string.h>
#include "hal_gpio.h"
#include "hal_rtc.h"
/* ========== 电源域定义 ========== */
#define POWER_DOMAIN_CAMERA (1 << 0) /* 摄像头 */
#define POWER_DOMAIN_BLE (1 << 1) /* BLE模块 */
#define POWER_DOMAIN_FLASH (1 << 2) /* 外部Flash */
#define POWER_DOMAIN_SENSOR (1 << 3) /* 压力传感器 */
#define POWER_DOMAIN_LED (1 << 4) /* LED指示灯 */
#define POWER_DOMAIN_ALL 0xFF
/* ========== 唤醒源定义 ========== */
#define WAKEUP_SRC_PEN_TIP (1 << 0) /* 笔尖接触 */
#define WAKEUP_SRC_BUTTON (1 << 1) /* 按键 */
#define WAKEUP_SRC_CHARGER (1 << 2) /* 充电器插入 */
#define WAKEUP_SRC_RTC (1 << 3) /* RTC定时唤醒 */
#define WAKEUP_SRC_BLE (1 << 4) /* BLE连接事件 */
/* ========== 功耗模式参数 ========== */
/* 轻度睡眠时的CPU频率(MHz) */
#define LIGHT_SLEEP_FREQ_MHZ 16
/* 正常工作CPU频率(MHz */
#define ACTIVE_FREQ_MHZ 168
/* RTC唤醒间隔(秒) - 用于周期性电量检查 */
#define RTC_WAKEUP_INTERVAL_S 60
/* ========== 静态变量 ========== */
/* 当前活跃的电源域 */
static uint8_t s_active_domains = POWER_DOMAIN_ALL;
/* 当前唤醒源配置 */
static uint8_t s_wakeup_sources = 0;
/* 功耗统计 */
static uint32_t s_active_time_ms = 0;
static uint32_t s_sleep_time_ms = 0;
/* ========== 电源管理初始化 ========== */
/**
* 初始化电源管理模块
* 配置各电源域控制GPIO,设置默认唤醒源
*/
void power_manager_init(void) {
/* 配置电源控制GPIO */
hal_gpio_config_output(GPIO_CAMERA_POWER);
hal_gpio_config_output(GPIO_FLASH_POWER);
hal_gpio_config_output(GPIO_SENSOR_POWER);
hal_gpio_config_output(GPIO_LED_POWER);
/* 默认所有电源域开启 */
s_active_domains = POWER_DOMAIN_ALL;
/* 默认唤醒源:笔尖触摸 + 充电器 + 按键 */
s_wakeup_sources = WAKEUP_SRC_PEN_TIP | WAKEUP_SRC_CHARGER | WAKEUP_SRC_BUTTON;
/* 初始化功耗统计 */
s_active_time_ms = 0;
s_sleep_time_ms = 0;
}
/* ========== 电源域控制 ========== */
/**
* 使能指定电源域
* @param domain_mask 电源域掩码
*/
void power_domain_enable(uint8_t domain_mask) {
if (domain_mask & POWER_DOMAIN_CAMERA) {
hal_gpio_write(GPIO_CAMERA_POWER, 1);
}
if (domain_mask & POWER_DOMAIN_FLASH) {
hal_gpio_write(GPIO_FLASH_POWER, 1);
}
if (domain_mask & POWER_DOMAIN_SENSOR) {
hal_gpio_write(GPIO_SENSOR_POWER, 1);
}
if (domain_mask & POWER_DOMAIN_LED) {
hal_gpio_write(GPIO_LED_POWER, 1);
}
s_active_domains |= domain_mask;
}
/**
* 禁用指定电源域
* @param domain_mask 电源域掩码
*/
void power_domain_disable(uint8_t domain_mask) {
if (domain_mask & POWER_DOMAIN_CAMERA) {
hal_gpio_write(GPIO_CAMERA_POWER, 0);
}
if (domain_mask & POWER_DOMAIN_FLASH) {
hal_gpio_write(GPIO_FLASH_POWER, 0);
}
if (domain_mask & POWER_DOMAIN_SENSOR) {
hal_gpio_write(GPIO_SENSOR_POWER, 0);
}
if (domain_mask & POWER_DOMAIN_LED) {
hal_gpio_write(GPIO_LED_POWER, 0);
}
s_active_domains &= ~domain_mask;
}
/* ========== 低功耗状态转换 ========== */
/**
* 进入轻度睡眠模式
* - 降低CPU频率到16MHz
* - 关闭摄像头和传感器电源域
* - 保持BLE连接和Flash电源
* - 可由笔尖触摸或BLE事件唤醒
*/
void power_enter_light_sleep(void) {
/* 关闭不必要的电源域 */
power_domain_disable(POWER_DOMAIN_CAMERA | POWER_DOMAIN_SENSOR | POWER_DOMAIN_LED);
/* 降低CPU频率 */
SystemClock_SetFrequency(LIGHT_SLEEP_FREQ_MHZ);
/* 配置唤醒源 */
hal_gpio_set_wakeup(GPIO_PEN_TIP_PIN, GPIO_WAKEUP_RISING);
hal_gpio_set_wakeup(GPIO_BUTTON_PIN, GPIO_WAKEUP_FALLING);
/* 进入CPU SLEEP模式(WFI等待中断) */
__WFI();
/* 唤醒后恢复 */
SystemClock_SetFrequency(ACTIVE_FREQ_MHZ);
power_domain_enable(POWER_DOMAIN_SENSOR | POWER_DOMAIN_LED);
}
/**
* 进入深度睡眠模式
* - 关闭所有外设电源域
* - 断开BLE连接
* - MCU进入STOP/STANDBY模式
* - 仅保留RTC和GPIO唤醒
* - 唤醒后相当于系统复位重启
*/
void power_enter_deep_sleep(void) {
/* 保存关键数据到Flash */
save_power_state();
/* 关闭所有电源域 */
power_domain_disable(POWER_DOMAIN_ALL);
/* 配置RTC唤醒(定时检查电量) */
hal_rtc_set_alarm(RTC_WAKEUP_INTERVAL_S);
/* 配置GPIO唤醒源 */
hal_gpio_set_wakeup(GPIO_PEN_TIP_PIN, GPIO_WAKEUP_RISING);
hal_gpio_set_wakeup(GPIO_USB_DETECT_PIN, GPIO_WAKEUP_RISING);
hal_gpio_set_wakeup(GPIO_BUTTON_PIN, GPIO_WAKEUP_FALLING);
/* 进入STANDBY模式(最低功耗,唤醒后从头执行) */
hal_enter_standby_mode();
}
/* ========== 功耗状态保存/恢复 ========== */
/* Flash中保存电源状态的地址 */
#define POWER_STATE_FLASH_ADDR 0x0000F000
/* 电源状态保存结构 */
typedef struct {
uint32_t magic; /* 魔数 0xPWR55AA */
uint32_t total_active_ms; /* 累计活跃时长 */
uint32_t total_sleep_ms; /* 累计睡眠时长 */
uint32_t boot_count; /* 启动次数 */
uint32_t last_shutdown_reason; /* 上次关机原因 */
uint32_t checksum; /* CRC校验 */
} PowerStateFlash;
/**
* 保存电源状态到Flash
* 在进入深度睡眠前调用
*/
static void save_power_state(void) {
PowerStateFlash state;
state.magic = 0x50575200; /* "PWR\0" */
state.total_active_ms = s_active_time_ms;
state.total_sleep_ms = s_sleep_time_ms;
state.boot_count = 0; /* 将在恢复时递增 */
state.last_shutdown_reason = 0;
/* 计算校验和 */
uint32_t sum = 0;
const uint32_t *data = (const uint32_t *)&state;
uint8_t i;
for (i = 0; i < (sizeof(state) / 4) - 1; i++) {
sum ^= data[i];
}
state.checksum = sum;
/* 写入Flash */
hal_flash_erase_sector(POWER_STATE_FLASH_ADDR);
hal_flash_write(POWER_STATE_FLASH_ADDR, (const uint8_t *)&state, sizeof(state));
}
/**
* 从Flash恢复电源状态
* 在启动时调用
*/
void power_restore_state(void) {
PowerStateFlash state;
hal_flash_read(POWER_STATE_FLASH_ADDR, (uint8_t *)&state, sizeof(state));
if (state.magic != 0x50575200) {
/* 无有效的保存数据 */
return;
}
/* 验证校验和 */
uint32_t sum = 0;
const uint32_t *data = (const uint32_t *)&state;
uint8_t i;
for (i = 0; i < (sizeof(state) / 4) - 1; i++) {
sum ^= data[i];
}
if (sum != state.checksum) {
return; /* 数据损坏 */
}
/* 恢复功耗统计 */
s_active_time_ms = state.total_active_ms;
s_sleep_time_ms = state.total_sleep_ms;
}
/* ========== 功耗统计接口 ========== */
/**
* 更新活跃时间统计
* @param elapsed_ms 经过的毫秒数
*/
void power_update_active_time(uint32_t elapsed_ms) {
s_active_time_ms += elapsed_ms;
}
/**
* 更新睡眠时间统计
* @param elapsed_ms 经过的毫秒数
*/
void power_update_sleep_time(uint32_t elapsed_ms) {
s_sleep_time_ms += elapsed_ms;
}
/**
* 获取累计活跃时长(秒)
*/
uint32_t power_get_active_seconds(void) {
return s_active_time_ms / 1000;
}
/**
* 获取电源效率(活跃时间占比百分比)
*/
uint8_t power_get_efficiency(void) {
uint32_t total = s_active_time_ms + s_sleep_time_ms;
if (total == 0) return 100;
return (uint8_t)((uint64_t)s_active_time_ms * 100 / total);
}
/**
* 获取当前活跃的电源域掩码
*/
uint8_t power_get_active_domains(void) {
return s_active_domains;
}
@@ -0,0 +1,311 @@
/*
* 自然写智能点阵笔嵌入式固件软件 V1.0
* ble_send_task.c - BLE数据发送任务
*
* 功能说明:
* 1. 从坐标队列获取数据并打包为BLE通知帧
* 2. 7字节紧凑坐标编码格式
* 3. 发送速率控制(适配BLE连接间隔)
* 4. 笔落下/抬起事件通知
* 5. 设备信息特征值更新(电量/固件版本)
*/
#include <stdint.h>
#include <stdbool.h>
#include <string.h>
#include "FreeRTOS.h"
#include "task.h"
#include "queue.h"
#include "event_groups.h"
#include "ble_gatt_server.h"
/* ========== BLE帧格式定义 ========== */
/* 帧头标识 */
#define BLE_FRAME_HEADER 0xAA55
/* 帧类型 */
#define FRAME_TYPE_COORDINATE 0x00 /* 坐标数据帧 */
#define FRAME_TYPE_PEN_DOWN 0x01 /* 笔落下事件 */
#define FRAME_TYPE_PEN_UP 0x02 /* 笔抬起事件 */
#define FRAME_TYPE_DEVICE_INFO 0x03 /* 设备信息帧 */
#define FRAME_TYPE_PAGE_CHANGE 0x04 /* 翻页事件 */
/* 最大BLE MTU通知载荷 */
#define BLE_MAX_NOTIFY_SIZE 20
/* 批量发送缓冲区大小(可打包多个坐标点) */
#define BATCH_BUFFER_SIZE 3
/* ========== 外部引用 ========== */
extern QueueHandle_t g_coordinate_queue;
extern EventGroupHandle_t g_ble_event_group;
extern SemaphoreHandle_t g_system_mutex;
/* 坐标数据包结构 */
typedef struct {
uint32_t raw_x;
uint32_t raw_y;
uint16_t pressure;
uint32_t timestamp_ms;
uint32_t page_id;
uint8_t pen_state;
} CoordinatePacket;
/* ========== 静态变量 ========== */
/* 发送缓冲区 */
static uint8_t s_send_buffer[BLE_MAX_NOTIFY_SIZE];
/* BLE连接状态 */
static volatile bool s_ble_connected = false;
/* 当前页面ID(检测翻页) */
static uint32_t s_current_page_id = 0;
/* 发送统计 */
static uint32_t s_total_sent = 0;
static uint32_t s_send_failures = 0;
/* ========== CRC-16 CCITT计算 ========== */
/**
* CRC-16 CCITT校验计算
* 用于BLE传输数据帧的完整性校验
*
* @param data 数据缓冲区
* @param length 数据长度
* @return CRC-16校验值
*/
static uint16_t crc16_ccitt(const uint8_t *data, uint16_t length) {
uint16_t crc = 0xFFFF;
uint16_t i;
for (i = 0; i < length; i++) {
crc ^= (uint16_t)data[i] << 8;
uint8_t j;
for (j = 0; j < 8; j++) {
if (crc & 0x8000) {
crc = (crc << 1) ^ 0x1021;
} else {
crc <<= 1;
}
}
}
return crc;
}
/* ========== 坐标编码 ========== */
/**
* 将坐标数据编码为7字节紧凑格式
*
* 编码格式:
* 字节0-1: X坐标高16位(大端序)
* 字节2-3: Y坐标高16位
* 字节4: X低4位(高半字节) | Y低4位(低半字节)
* 字节5: 压力值高8位
* 字节6: 压力值低4位(高半字节) | 标志位(低半字节)
*
* @param packet 坐标数据包
* @param output 输出缓冲区(至少7字节)
* @param flags 标志位(低2位:00=数据, 01=笔落下, 02=笔抬起)
*/
static void encode_coordinate(const CoordinatePacket *packet, uint8_t *output,
uint8_t flags) {
/* X坐标(20位精度) */
uint32_t x = packet->raw_x & 0xFFFFF;
output[0] = (uint8_t)((x >> 12) & 0xFF); /* X高8位 */
output[1] = (uint8_t)((x >> 4) & 0xFF); /* X次高8位 */
/* Y坐标(20位精度) */
uint32_t y = packet->raw_y & 0xFFFFF;
output[2] = (uint8_t)((y >> 12) & 0xFF); /* Y高8位 */
output[3] = (uint8_t)((y >> 4) & 0xFF); /* Y次高8位 */
/* X低4位和Y低4位合并到一个字节 */
output[4] = (uint8_t)(((x & 0x0F) << 4) | (y & 0x0F));
/* 压力值(12位精度) */
uint16_t p = packet->pressure & 0x0FFF;
output[5] = (uint8_t)((p >> 4) & 0xFF); /* 压力高8位 */
/* 压力低4位 | 标志位 */
output[6] = (uint8_t)(((p & 0x0F) << 4) | (flags & 0x0F));
}
/* ========== BLE通知发送 ========== */
/**
* 通过BLE GATT通知发送数据帧
*
* @param data 帧数据
* @param length 帧长度
* @return 0成功, -1未连接, -2发送失败
*/
static int ble_send_notification(const uint8_t *data, uint16_t length) {
if (!s_ble_connected) {
return -1;
}
/* 调用BLE GATT服务器发送通知 */
int ret = ble_gatt_notify(BLE_CHAR_STROKE_DATA, data, length);
if (ret == 0) {
s_total_sent++;
} else {
s_send_failures++;
}
return ret;
}
/**
* 发送笔状态事件(落下/抬起)
*/
static void send_pen_event(uint8_t event_type) {
uint8_t frame[7];
memset(frame, 0, sizeof(frame));
/* 事件帧只需要标志位,坐标和压力都为0 */
frame[6] = event_type & 0x0F;
ble_send_notification(frame, 7);
}
/**
* 发送翻页事件
* 当检测到坐标所在页面发生变化时通知上位机
*/
static void send_page_change_event(uint32_t new_page_id) {
uint8_t frame[8];
frame[0] = FRAME_TYPE_PAGE_CHANGE;
frame[1] = (uint8_t)((new_page_id >> 24) & 0xFF);
frame[2] = (uint8_t)((new_page_id >> 16) & 0xFF);
frame[3] = (uint8_t)((new_page_id >> 8) & 0xFF);
frame[4] = (uint8_t)(new_page_id & 0xFF);
/* CRC校验 */
uint16_t crc = crc16_ccitt(frame, 5);
frame[5] = (uint8_t)((crc >> 8) & 0xFF);
frame[6] = (uint8_t)(crc & 0xFF);
frame[7] = 0;
ble_send_notification(frame, 8);
}
/**
* 更新设备信息特征值(电量、固件版本等)
* 上位机可以随时读取此特征值获取笔的状态
*/
static void update_device_info(uint8_t battery_percent) {
uint8_t info[4];
info[0] = battery_percent; /* 电量百分比 */
info[1] = 2; /* 固件主版本号 */
info[2] = 1; /* 固件次版本号 */
info[3] = 5; /* 固件补丁版本号 → V2.1.5 */
ble_gatt_update_characteristic(BLE_CHAR_DEVICE_INFO, info, sizeof(info));
}
/* ========== BLE连接事件回调 ========== */
/**
* BLE连接建立回调(由BLE协议栈调用)
*/
void on_ble_connected(void) {
s_ble_connected = true;
BaseType_t higher_priority_woken = pdFALSE;
xEventGroupSetBitsFromISR(g_ble_event_group, EVT_BLE_CONNECTED,
&higher_priority_woken);
portYIELD_FROM_ISR(higher_priority_woken);
}
/**
* BLE连接断开回调
*/
void on_ble_disconnected(void) {
s_ble_connected = false;
BaseType_t higher_priority_woken = pdFALSE;
xEventGroupSetBitsFromISR(g_ble_event_group, EVT_BLE_DISCONNECTED,
&higher_priority_woken);
portYIELD_FROM_ISR(higher_priority_woken);
}
/* ========== BLE发送主任务 ========== */
/**
* BLE发送任务(FreeRTOS任务函数)
*
* 运行流程:
* 1. 等待BLE连接建立
* 2. 监听笔状态事件(落下/抬起)并发送事件通知
* 3. 从坐标队列读取数据,编码为7字节格式发送
* 4. 翻页检测与通知
* 5. 定期更新设备信息特征值
*/
void ble_send_task(void *pvParameters) {
(void)pvParameters;
CoordinatePacket packet;
uint32_t info_update_counter = 0;
/* 注册BLE连接回调 */
ble_gatt_register_connect_callback(on_ble_connected);
ble_gatt_register_disconnect_callback(on_ble_disconnected);
/* 启动BLE广播 */
ble_gatt_start_advertising();
while (1) {
/* 等待BLE连接 */
if (!s_ble_connected) {
xEventGroupWaitBits(g_ble_event_group, EVT_BLE_CONNECTED,
pdTRUE, pdFALSE, portMAX_DELAY);
}
/* 检查笔状态事件 */
EventBits_t events = xEventGroupGetBits(g_ble_event_group);
if (events & EVT_PEN_DOWN) {
xEventGroupClearBits(g_ble_event_group, EVT_PEN_DOWN);
send_pen_event(FRAME_TYPE_PEN_DOWN);
}
if (events & EVT_PEN_UP) {
xEventGroupClearBits(g_ble_event_group, EVT_PEN_UP);
send_pen_event(FRAME_TYPE_PEN_UP);
}
/* 从坐标队列读取数据(超时10ms,避免永久阻塞) */
if (xQueueReceive(g_coordinate_queue, &packet, pdMS_TO_TICKS(10)) == pdTRUE) {
/* 翻页检测 */
if (packet.page_id != s_current_page_id && s_current_page_id != 0) {
send_page_change_event(packet.page_id);
}
s_current_page_id = packet.page_id;
/* 编码并发送坐标 */
uint8_t encoded[7];
encode_coordinate(&packet, encoded, FRAME_TYPE_COORDINATE);
ble_send_notification(encoded, 7);
}
/* 每500次循环更新一次设备信息(约每5秒) */
info_update_counter++;
if (info_update_counter >= 500) {
info_update_counter = 0;
/* 读取当前电量 */
extern uint8_t power_get_battery_percent(void);
uint8_t battery = power_get_battery_percent();
update_device_info(battery);
}
}
}
@@ -0,0 +1,373 @@
/*
* 自然写智能点阵笔嵌入式固件软件 V1.0
* coordinate_task.c - 坐标计算任务
*
* 功能说明:
* 1. 从图像帧中解码Anoto点阵图案
* 2. 计算笔尖在纸面的物理坐标
* 3. 坐标滤波与去抖(卡尔曼滤波)
* 4. 坐标打包为BLE传输格式
*/
#include <stdint.h>
#include <stdbool.h>
#include <string.h>
#include <math.h>
#include "FreeRTOS.h"
#include "task.h"
#include "queue.h"
#include "dot_decoder.h"
/* ========== 坐标数据包结构 ========== */
typedef struct {
uint32_t raw_x; /* 原始X坐标(20位精度) */
uint32_t raw_y; /* 原始Y坐标 */
uint16_t pressure; /* 压力值(12位) */
uint32_t timestamp_ms; /* 时间戳 */
uint32_t page_id; /* 页面ID */
uint8_t pen_state; /* 笔状态:0=书写中, 1=笔落下, 2=笔抬起 */
} CoordinatePacket;
/* ========== 卡尔曼滤波器 ========== */
typedef struct {
float x_estimate; /* X状态估计值 */
float y_estimate; /* Y状态估计值 */
float x_error; /* X估计误差协方差 */
float y_error; /* Y估计误差协方差 */
float process_noise; /* 过程噪声 Q */
float measurement_noise; /* 测量噪声 R */
bool initialized; /* 是否已初始化 */
} KalmanFilter2D;
/* ========== 外部引用 ========== */
extern QueueHandle_t g_image_data_queue;
extern QueueHandle_t g_coordinate_queue;
/* ========== 图像帧元数据结构(与image_capture_task一致) ========== */
typedef struct {
uint8_t *pixel_buffer;
uint32_t frame_id;
uint32_t timestamp_ms;
uint8_t quality_score;
uint8_t exposure_value;
uint16_t pressure_raw;
} ImageFrameMetadata;
/* ========== 静态变量 ========== */
/* 卡尔曼滤波器实例 */
static KalmanFilter2D s_kalman;
/* 上一次有效坐标(用于抖动检测) */
static float s_last_valid_x = 0;
static float s_last_valid_y = 0;
/* 点阵码解码工作缓冲区 */
static uint8_t s_decode_buffer[128];
/* 统计信息 */
static uint32_t s_total_decoded = 0;
static uint32_t s_decode_failures = 0;
/* ========== 卡尔曼滤波实现 ========== */
/**
* 初始化卡尔曼滤波器
* @param kf 滤波器实例
* @param q 过程噪声(越大跟踪越快,噪声越多)
* @param r 测量噪声(越大滤波越强,延迟越大)
*/
static void kalman_init(KalmanFilter2D *kf, float q, float r) {
kf->x_estimate = 0;
kf->y_estimate = 0;
kf->x_error = 1.0f;
kf->y_error = 1.0f;
kf->process_noise = q;
kf->measurement_noise = r;
kf->initialized = false;
}
/**
* 卡尔曼滤波更新
* @param kf 滤波器实例
* @param measured_x 测量X值
* @param measured_y 测量Y值
* @param out_x 滤波后X输出
* @param out_y 滤波后Y输出
*/
static void kalman_update(KalmanFilter2D *kf, float measured_x, float measured_y,
float *out_x, float *out_y) {
if (!kf->initialized) {
/* 第一次测量,直接使用测量值 */
kf->x_estimate = measured_x;
kf->y_estimate = measured_y;
kf->initialized = true;
*out_x = measured_x;
*out_y = measured_y;
return;
}
/* 预测步骤:状态不变模型(笔的位置预测 = 上一次估计) */
float x_pred = kf->x_estimate;
float y_pred = kf->y_estimate;
float x_err_pred = kf->x_error + kf->process_noise;
float y_err_pred = kf->y_error + kf->process_noise;
/* 更新步骤:计算卡尔曼增益 */
float kx = x_err_pred / (x_err_pred + kf->measurement_noise);
float ky = y_err_pred / (y_err_pred + kf->measurement_noise);
/* 融合预测与测量 */
kf->x_estimate = x_pred + kx * (measured_x - x_pred);
kf->y_estimate = y_pred + ky * (measured_y - y_pred);
/* 更新误差协方差 */
kf->x_error = (1.0f - kx) * x_err_pred;
kf->y_error = (1.0f - ky) * y_err_pred;
*out_x = kf->x_estimate;
*out_y = kf->y_estimate;
}
/**
* 重置卡尔曼滤波器(新笔画开始时调用)
*/
static void kalman_reset(KalmanFilter2D *kf) {
kf->initialized = false;
kf->x_error = 1.0f;
kf->y_error = 1.0f;
}
/* ========== 抖动检测 ========== */
/**
* 检测坐标是否为抖动(笔静止时传感器的微小抖动)
* 如果两次坐标之间的距离小于阈值,视为抖动并丢弃
*
* @param x 当前X坐标
* @param y 当前Y坐标
* @param threshold 抖动阈值(坐标单位)
* @return true表示是抖动,应丢弃
*/
static bool is_jitter(float x, float y, float threshold) {
float dx = x - s_last_valid_x;
float dy = y - s_last_valid_y;
float distance_sq = dx * dx + dy * dy;
return (distance_sq < threshold * threshold);
}
/* ========== 点阵码图像解码 ========== */
/**
* 从32x32灰度图像中解码Anoto点阵图案
*
* 解码步骤:
* 1. 二值化:将灰度图转为黑白图
* 2. 点检测:定位图案中的各个墨点位置
* 3. 网格对齐:将检测到的点对齐到规则网格
* 4. 编码读取:根据点相对于网格中心的偏移方向读取编码值
* 5. 坐标计算:将编码序列映射为全局坐标
*
* @param pixels 32x32灰度图像数据
* @param quality 图像质量评分
* @param out_x 解码输出X坐标
* @param out_y 解码输出Y坐标
* @param out_page_id 解码输出页面ID
* @return 0成功, -1解码失败
*/
static int decode_dot_pattern(const uint8_t *pixels, uint8_t quality,
uint32_t *out_x, uint32_t *out_y,
uint32_t *out_page_id) {
/* 步骤1:自适应二值化 */
uint8_t threshold = 128;
/* 根据图像亮度动态调整阈值(Otsu方法简化版) */
uint32_t histogram[256] = {0};
uint16_t i;
for (i = 0; i < SENSOR_PIXELS; i++) {
histogram[pixels[i]]++;
}
/* 寻找双峰之间的谷值作为阈值 */
uint32_t total = SENSOR_PIXELS;
uint32_t sum = 0;
for (i = 0; i < 256; i++) {
sum += i * histogram[i];
}
uint32_t sum_bg = 0;
uint32_t weight_bg = 0;
float max_variance = 0;
for (i = 0; i < 256; i++) {
weight_bg += histogram[i];
if (weight_bg == 0) continue;
uint32_t weight_fg = total - weight_bg;
if (weight_fg == 0) break;
sum_bg += i * histogram[i];
float mean_bg = (float)sum_bg / weight_bg;
float mean_fg = (float)(sum - sum_bg) / weight_fg;
float diff = mean_bg - mean_fg;
float variance = (float)weight_bg * weight_fg * diff * diff;
if (variance > max_variance) {
max_variance = variance;
threshold = (uint8_t)i;
}
}
/* 步骤2:二值化并检测墨点中心 */
uint8_t dot_count = 0;
int16_t dot_x[64], dot_y[64]; /* 最多检测64个点 */
for (int row = 1; row < SENSOR_HEIGHT - 1; row++) {
for (int col = 1; col < SENSOR_WIDTH - 1; col++) {
uint8_t center = pixels[row * SENSOR_WIDTH + col];
if (center < threshold) {
/* 暗像素,检查是否为局部极小值(简单的点中心检测) */
uint8_t up = pixels[(row - 1) * SENSOR_WIDTH + col];
uint8_t down = pixels[(row + 1) * SENSOR_WIDTH + col];
uint8_t left = pixels[row * SENSOR_WIDTH + (col - 1)];
uint8_t right = pixels[row * SENSOR_WIDTH + (col + 1)];
if (center <= up && center <= down &&
center <= left && center <= right) {
if (dot_count < 64) {
dot_x[dot_count] = col;
dot_y[dot_count] = row;
dot_count++;
}
}
}
}
}
/* 至少需要检测到4个点才能解码 */
if (dot_count < 4) {
return -1;
}
/* 步骤3-5:调用点阵码解码器(核心算法在dot_decoder模块中) */
DotDecodeResult result;
int ret = dot_decoder_process(dot_x, dot_y, dot_count, &result);
if (ret == 0) {
*out_x = result.coordinate_x;
*out_y = result.coordinate_y;
*out_page_id = result.page_id;
return 0;
}
return -1;
}
/* ========== 压力值处理 ========== */
/**
* 处理原始ADC压力值
* 12位ADC → 归一化并应用非线性映射
*
* @param raw_adc 原始ADC值(0-4095
* @return 处理后的压力值(0-4095,非线性映射后)
*/
static uint16_t process_pressure(uint16_t raw_adc) {
/* 去除静态偏移(笔尖自重产生的基础压力) */
const uint16_t offset = 200;
if (raw_adc < offset) {
return 0;
}
uint16_t adjusted = raw_adc - offset;
/* 非线性映射(平方根曲线,使轻触更灵敏) */
float normalized = (float)adjusted / (4095.0f - offset);
float mapped = sqrtf(normalized);
return (uint16_t)(mapped * 4095);
}
/* ========== 坐标计算主任务 ========== */
/**
* 坐标计算任务(FreeRTOS任务函数)
*
* 运行流程:
* 1. 从图像数据队列接收帧元数据
* 2. 解码点阵图案获得原始坐标
* 3. 卡尔曼滤波去噪
* 4. 抖动检测
* 5. 坐标打包并放入BLE发送队列
*/
void coordinate_task(void *pvParameters) {
(void)pvParameters;
ImageFrameMetadata frame;
CoordinatePacket packet;
/* 初始化卡尔曼滤波器 */
/* Q=0.1 跟踪速度适中, R=0.5 中等滤波强度 */
kalman_init(&s_kalman, 0.1f, 0.5f);
/* 抖动检测阈值(坐标单位,约0.1mm) */
const float jitter_threshold = 3.0f;
while (1) {
/* 阻塞等待图像帧数据 */
if (xQueueReceive(g_image_data_queue, &frame, portMAX_DELAY) != pdTRUE) {
continue;
}
/* 解码点阵图案 */
uint32_t raw_x, raw_y, page_id;
int decode_ret = decode_dot_pattern(frame.pixel_buffer, frame.quality_score,
&raw_x, &raw_y, &page_id);
if (decode_ret != 0) {
s_decode_failures++;
continue;
}
s_total_decoded++;
/* 卡尔曼滤波 */
float filtered_x, filtered_y;
kalman_update(&s_kalman, (float)raw_x, (float)raw_y,
&filtered_x, &filtered_y);
/* 抖动检测 */
if (is_jitter(filtered_x, filtered_y, jitter_threshold)) {
continue; /* 丢弃抖动数据 */
}
/* 更新最后有效坐标 */
s_last_valid_x = filtered_x;
s_last_valid_y = filtered_y;
/* 处理压力值 */
uint16_t pressure = process_pressure(frame.pressure_raw);
/* 构建坐标数据包 */
packet.raw_x = (uint32_t)filtered_x;
packet.raw_y = (uint32_t)filtered_y;
packet.pressure = pressure;
packet.timestamp_ms = frame.timestamp_ms;
packet.page_id = page_id;
packet.pen_state = 0; /* 书写中 */
/* 放入BLE发送队列(非阻塞,满则丢弃最老的) */
if (xQueueSend(g_coordinate_queue, &packet, 0) != pdTRUE) {
/* 队列满,读出一个旧数据再写入 */
CoordinatePacket dummy;
xQueueReceive(g_coordinate_queue, &dummy, 0);
xQueueSend(g_coordinate_queue, &packet, 0);
}
}
}
@@ -0,0 +1,329 @@
/*
* 自然写智能点阵笔嵌入式固件软件 V1.0
* image_capture_task.c - 图像采集任务
*
* 功能说明:
* 1. 以100Hz频率驱动CMOS图像传感器采集点阵图案
* 2. DMA方式高速传输图像数据
* 3. 笔尖接触检测(上升沿/下降沿中断)
* 4. 图像帧质量快速评估(丢弃模糊帧)
* 5. 采集参数自适应调节(曝光、增益)
*/
#include <stdint.h>
#include <stdbool.h>
#include <string.h>
#include "FreeRTOS.h"
#include "task.h"
#include "queue.h"
#include "event_groups.h"
#include "camera_driver.h"
#include "hal_spi.h"
#include "hal_dma.h"
#include "hal_gpio.h"
/* ========== 常量定义 ========== */
/* 采集频率(Hz */
#define CAPTURE_FREQUENCY_HZ 100
/* 采集周期(毫秒) */
#define CAPTURE_PERIOD_MS (1000 / CAPTURE_FREQUENCY_HZ)
/* 图像传感器分辨率 */
#define SENSOR_WIDTH 32
#define SENSOR_HEIGHT 32
#define SENSOR_PIXELS (SENSOR_WIDTH * SENSOR_HEIGHT)
/* 单帧图像字节数(8位灰度) */
#define FRAME_SIZE_BYTES SENSOR_PIXELS
/* DMA传输缓冲区(双缓冲) */
#define DMA_BUFFER_COUNT 2
/* 图像质量阈值(低于此值判定为模糊/无效帧) */
#define QUALITY_THRESHOLD 30
/* 最大连续无效帧数(超过则认为笔离开纸面) */
#define MAX_INVALID_FRAMES 5
/* 自动曝光调节步长 */
#define AUTO_EXPOSURE_STEP 4
/* ========== 数据结构 ========== */
/* 图像帧元数据(放入队列传递给坐标计算任务) */
typedef struct {
uint8_t *pixel_buffer; /* 指向DMA缓冲区的像素数据 */
uint32_t frame_id; /* 帧序号 */
uint32_t timestamp_ms; /* 采集时间戳 */
uint8_t quality_score; /* 图像质量评分(0-255 */
uint8_t exposure_value; /* 当前曝光值 */
uint16_t pressure_raw; /* 同步采集的压力原始ADC值 */
} ImageFrameMetadata;
/* 传感器配置参数 */
typedef struct {
uint8_t exposure; /* 曝光时间(寄存器值) */
uint8_t gain; /* 模拟增益 */
uint8_t threshold; /* 二值化阈值 */
bool auto_exposure_enabled; /* 是否启用自动曝光 */
} SensorConfig;
/* ========== 外部引用 ========== */
extern QueueHandle_t g_image_data_queue;
extern EventGroupHandle_t g_ble_event_group;
/* ========== 静态变量 ========== */
/* DMA双缓冲区 */
static uint8_t s_dma_buffer[DMA_BUFFER_COUNT][FRAME_SIZE_BYTES]
__attribute__((aligned(4)));
/* 当前活跃的DMA缓冲区索引 */
static volatile uint8_t s_active_buffer = 0;
/* 帧计数器 */
static uint32_t s_frame_counter = 0;
/* 连续无效帧计数 */
static uint8_t s_invalid_frame_count = 0;
/* 传感器配置 */
static SensorConfig s_sensor_config;
/* 笔尖状态 */
static volatile bool s_pen_touching = false;
/* ========== 笔尖接触检测 ========== */
/**
* 笔尖接触检测GPIO中断回调
* 通过检测微动开关或红外反射判断笔尖是否接触纸面
*/
void pen_tip_irq_handler(void) {
bool state = hal_gpio_read(GPIO_PEN_TIP_PIN);
BaseType_t higher_priority_woken = pdFALSE;
if (state && !s_pen_touching) {
/* 笔落下(接触纸面) */
s_pen_touching = true;
xEventGroupSetBitsFromISR(g_ble_event_group, EVT_PEN_DOWN,
&higher_priority_woken);
} else if (!state && s_pen_touching) {
/* 笔抬起(离开纸面) */
s_pen_touching = false;
xEventGroupSetBitsFromISR(g_ble_event_group, EVT_PEN_UP,
&higher_priority_woken);
}
portYIELD_FROM_ISR(higher_priority_woken);
}
/* ========== DMA传输完成回调 ========== */
/**
* SPI DMA传输完成中断回调
* 图像数据已从传感器通过DMA传输到内存缓冲区
*/
void spi_dma_complete_handler(void) {
/* 切换到另一个DMA缓冲区(乒乓缓冲) */
s_active_buffer = (s_active_buffer + 1) % DMA_BUFFER_COUNT;
}
/* ========== 图像质量评估 ========== */
/**
* 快速评估图像帧质量
* 通过计算图像的对比度(标准差近似)来判断是否有效
* 有效的点阵图案应该有清晰的明暗对比
*
* @param pixels 像素数据
* @param length 数据长度
* @return 质量评分(0-255,越高越好)
*/
static uint8_t evaluate_frame_quality(const uint8_t *pixels, uint16_t length) {
uint32_t sum = 0;
uint32_t sum_sq = 0;
uint16_t i;
/* 采样统计(每4个像素取1个,减少计算量) */
uint16_t sample_count = 0;
for (i = 0; i < length; i += 4) {
uint32_t val = pixels[i];
sum += val;
sum_sq += val * val;
sample_count++;
}
if (sample_count == 0) return 0;
/* 计算方差的近似值(反映对比度) */
uint32_t mean = sum / sample_count;
uint32_t variance = (sum_sq / sample_count) - (mean * mean);
/* 方差映射到0-255评分 */
if (variance > 2000) return 255;
return (uint8_t)(variance * 255 / 2000);
}
/* ========== 自动曝光调节 ========== */
/**
* 根据图像亮度自动调节传感器曝光参数
* 目标:使图像平均亮度保持在中间范围(100-150)
*/
static void auto_exposure_adjust(const uint8_t *pixels, uint16_t length) {
if (!s_sensor_config.auto_exposure_enabled) {
return;
}
/* 计算平均亮度 */
uint32_t sum = 0;
uint16_t i;
for (i = 0; i < length; i += 8) {
sum += pixels[i];
}
uint8_t avg_brightness = (uint8_t)(sum / (length / 8));
/* 根据亮度偏差调节曝光值 */
if (avg_brightness < 80 && s_sensor_config.exposure < 250) {
/* 过暗,增加曝光 */
s_sensor_config.exposure += AUTO_EXPOSURE_STEP;
camera_set_exposure(s_sensor_config.exposure);
} else if (avg_brightness > 170 && s_sensor_config.exposure > AUTO_EXPOSURE_STEP) {
/* 过亮,减少曝光 */
s_sensor_config.exposure -= AUTO_EXPOSURE_STEP;
camera_set_exposure(s_sensor_config.exposure);
}
}
/* ========== 传感器初始化 ========== */
/**
* 配置CMOS图像传感器初始参数
*/
static void sensor_setup(void) {
/* 设置默认曝光和增益 */
s_sensor_config.exposure = 128;
s_sensor_config.gain = 64;
s_sensor_config.threshold = 128;
s_sensor_config.auto_exposure_enabled = true;
/* 写入传感器寄存器 */
camera_set_exposure(s_sensor_config.exposure);
camera_set_gain(s_sensor_config.gain);
/* 配置为连续帧模式 */
camera_set_mode(CAMERA_MODE_CONTINUOUS);
/* 配置SPI DMA接收 */
hal_dma_config(DMA_CHANNEL_SPI_RX,
(uint32_t)camera_get_data_register(),
(uint32_t)s_dma_buffer[0],
FRAME_SIZE_BYTES);
}
/* ========== 图像采集主任务 ========== */
/**
* 图像采集任务(FreeRTOS任务函数)
*
* 运行流程:
* 1. 等待笔尖接触纸面
* 2. 以100Hz频率触发CMOS传感器拍摄
* 3. DMA传输图像数据到双缓冲区
* 4. 评估图像质量,丢弃无效帧
* 5. 将有效帧元数据放入队列供坐标计算任务处理
* 6. 笔抬起后暂停采集,进入低功耗等待
*/
void image_capture_task(void *pvParameters) {
(void)pvParameters;
TickType_t last_wake_time;
/* 初始化传感器参数 */
sensor_setup();
/* 注册笔尖GPIO中断 */
hal_gpio_set_irq(GPIO_PEN_TIP_PIN, GPIO_IRQ_BOTH_EDGE, pen_tip_irq_handler);
while (1) {
/* 等待笔落下事件(低功耗阻塞) */
xEventGroupWaitBits(g_ble_event_group, EVT_PEN_DOWN,
pdTRUE, pdFALSE, portMAX_DELAY);
/* 笔已接触纸面,启动高速采集 */
camera_power_on();
/* 重置帧计数和无效帧计数 */
s_frame_counter = 0;
s_invalid_frame_count = 0;
/* 记录采集起始时间 */
last_wake_time = xTaskGetTickCount();
/* 采集循环:持续采集直到笔抬起 */
while (s_pen_touching) {
/* 触发传感器拍摄 */
camera_trigger_capture();
/* 启动DMA传输(异步,CPU可做其他事) */
uint8_t current_buffer = s_active_buffer;
hal_dma_start(DMA_CHANNEL_SPI_RX,
(uint32_t)s_dma_buffer[current_buffer],
FRAME_SIZE_BYTES);
/* 等待DMA完成(通常< 1ms */
hal_dma_wait_complete(DMA_CHANNEL_SPI_RX, 5);
/* 同步读取压力传感器ADC值 */
uint16_t pressure_raw = pressure_sensor_read_raw();
/* 评估图像质量 */
uint8_t quality = evaluate_frame_quality(
s_dma_buffer[current_buffer], FRAME_SIZE_BYTES);
if (quality >= QUALITY_THRESHOLD) {
/* 有效帧,放入队列 */
ImageFrameMetadata metadata;
metadata.pixel_buffer = s_dma_buffer[current_buffer];
metadata.frame_id = s_frame_counter;
metadata.timestamp_ms = xTaskGetTickCount() * portTICK_PERIOD_MS;
metadata.quality_score = quality;
metadata.exposure_value = s_sensor_config.exposure;
metadata.pressure_raw = pressure_raw;
/* 非阻塞方式入队(如果队列满则丢弃) */
xQueueSend(g_image_data_queue, &metadata, 0);
s_invalid_frame_count = 0;
} else {
s_invalid_frame_count++;
/* 连续多个无效帧,可能笔已离开但中断未触发 */
if (s_invalid_frame_count >= MAX_INVALID_FRAMES) {
s_pen_touching = false;
break;
}
}
/* 每16帧调整一次曝光(避免频繁调节) */
if ((s_frame_counter & 0x0F) == 0) {
auto_exposure_adjust(s_dma_buffer[current_buffer], FRAME_SIZE_BYTES);
}
s_frame_counter++;
/* 精确定时:等待到下一个采集时间点 */
vTaskDelayUntil(&last_wake_time, pdMS_TO_TICKS(CAPTURE_PERIOD_MS));
}
/* 笔抬起,关闭传感器降低功耗 */
camera_power_off();
}
}
@@ -0,0 +1,428 @@
/*
* 自然写智能点阵笔嵌入式固件软件 V1.0
* power_monitor_task.c - 电源监测与低功耗管理任务
*
* 功能说明:
* 1. 电池电压ADC采样与电量百分比估算
* 2. 充电检测与充电状态管理
* 3. 低电量告警与自动关机保护
* 4. 低功耗状态机(Active → Light Sleep → Deep Sleep
* 5. USB充电IC状态监测
*/
#include <stdint.h>
#include <stdbool.h>
#include "FreeRTOS.h"
#include "task.h"
#include "event_groups.h"
#include "semphr.h"
#include "hal_adc.h"
#include "hal_gpio.h"
#include "power_manager.h"
#include "led_driver.h"
/* ========== 电池参数定义 ========== */
/* 电池满充电压(mV):锂聚合物3.7V标称 */
#define BATTERY_FULL_MV 4200
/* 电池截止电压(mV):低于此值必须关机保护 */
#define BATTERY_CUTOFF_MV 3300
/* 低电量告警阈值(百分比) */
#define LOW_BATTERY_THRESHOLD 10
/* 极低电量关机阈值 */
#define CRITICAL_BATTERY_THRESHOLD 3
/* ADC参考电压(mV */
#define ADC_VREF_MV 3300
/* ADC分辨率(12位) */
#define ADC_MAX_VALUE 4095
/* 电池电压分压比(电阻分压器:R1=100K, R2=100K → 2:1 */
#define BATTERY_DIVIDER_RATIO 2
/* 电压采样滤波窗口大小 */
#define VOLTAGE_FILTER_WINDOW 8
/* 电源监测周期(毫秒) */
#define POWER_MONITOR_PERIOD_MS 5000
/* 自动休眠超时(毫秒):笔静止超过此时间自动进入深度睡眠 */
#define AUTO_SLEEP_TIMEOUT_MS 300000 /* 5分钟 */
/* ========== 电源状态枚举 ========== */
typedef enum {
POWER_STATE_ACTIVE, /* 活跃状态(正常工作) */
POWER_STATE_LIGHT_SLEEP, /* 轻度睡眠(BLE保持连接) */
POWER_STATE_DEEP_SLEEP, /* 深度睡眠(仅保留RTC唤醒) */
POWER_STATE_CHARGING, /* 充电中 */
POWER_STATE_SHUTDOWN /* 关机保护 */
} PowerState;
/* ========== 外部引用 ========== */
extern EventGroupHandle_t g_ble_event_group;
extern SemaphoreHandle_t g_system_mutex;
/* 系统状态结构体(在main.c中定义) */
typedef struct {
bool pen_is_down;
bool ble_connected;
bool is_charging;
uint8_t battery_percent;
uint32_t total_strokes;
uint32_t uptime_seconds;
uint8_t error_flags;
} SystemState;
extern SystemState g_system_state;
/* ========== 静态变量 ========== */
/* 当前电源状态 */
static PowerState s_power_state = POWER_STATE_ACTIVE;
/* 电压采样滤波缓冲区 */
static uint16_t s_voltage_buffer[VOLTAGE_FILTER_WINDOW];
static uint8_t s_voltage_buffer_index = 0;
static bool s_voltage_buffer_full = false;
/* 最后一次活动时间(用于自动休眠判断) */
static uint32_t s_last_activity_time = 0;
/* ========== 电压采样与滤波 ========== */
/**
* 读取电池原始ADC值并转换为电压(mV)
*/
static uint16_t read_battery_voltage_mv(void) {
/* 读取ADC原始值 */
uint16_t adc_raw = hal_adc_read(ADC_CHANNEL_BATTERY);
/* ADC值 → 分压后电压 → 实际电池电压 */
uint32_t voltage_mv = (uint32_t)adc_raw * ADC_VREF_MV / ADC_MAX_VALUE;
voltage_mv *= BATTERY_DIVIDER_RATIO;
return (uint16_t)voltage_mv;
}
/**
* 移动平均滤波
* 对连续采样的电压值取平均,消除ADC噪声
*
* @param new_sample 新采样的电压值(mV
* @return 滤波后的电压值
*/
static uint16_t voltage_filter(uint16_t new_sample) {
s_voltage_buffer[s_voltage_buffer_index] = new_sample;
s_voltage_buffer_index = (s_voltage_buffer_index + 1) % VOLTAGE_FILTER_WINDOW;
if (s_voltage_buffer_index == 0) {
s_voltage_buffer_full = true;
}
uint8_t count = s_voltage_buffer_full ? VOLTAGE_FILTER_WINDOW : s_voltage_buffer_index;
uint32_t sum = 0;
uint8_t i;
for (i = 0; i < count; i++) {
sum += s_voltage_buffer[i];
}
return (uint16_t)(sum / count);
}
/* ========== 电量百分比估算 ========== */
/**
* 根据电池电压估算电量百分比
* 使用分段线性插值模拟锂电池放电曲线
*
* 锂聚合物电池典型放电曲线(近似分段线性):
* 4200mV → 100%
* 4060mV → 90%
* 3920mV → 80%
* 3830mV → 70%
* 3750mV → 60%
* 3680mV → 50%
* 3620mV → 40%
* 3570mV → 30%
* 3500mV → 20%
* 3400mV → 10%
* 3300mV → 0%
*/
static uint8_t estimate_battery_percent(uint16_t voltage_mv) {
/* 放电曲线查找表(电压mV → 百分比) */
static const struct {
uint16_t voltage;
uint8_t percent;
} discharge_curve[] = {
{4200, 100},
{4060, 90},
{3920, 80},
{3830, 70},
{3750, 60},
{3680, 50},
{3620, 40},
{3570, 30},
{3500, 20},
{3400, 10},
{3300, 0}
};
const uint8_t table_size = sizeof(discharge_curve) / sizeof(discharge_curve[0]);
/* 边界检查 */
if (voltage_mv >= discharge_curve[0].voltage) {
return 100;
}
if (voltage_mv <= discharge_curve[table_size - 1].voltage) {
return 0;
}
/* 分段线性插值 */
uint8_t i;
for (i = 0; i < table_size - 1; i++) {
if (voltage_mv >= discharge_curve[i + 1].voltage) {
uint16_t v_high = discharge_curve[i].voltage;
uint16_t v_low = discharge_curve[i + 1].voltage;
uint8_t p_high = discharge_curve[i].percent;
uint8_t p_low = discharge_curve[i + 1].percent;
/* 线性插值 */
uint16_t v_range = v_high - v_low;
uint16_t v_offset = voltage_mv - v_low;
return p_low + (uint8_t)((uint32_t)v_offset * (p_high - p_low) / v_range);
}
}
return 0;
}
/* ========== 充电检测 ========== */
/**
* 检测USB充电状态
* 通过GPIO读取充电IC的状态引脚
*
* @return 0=未充电, 1=充电中, 2=充满
*/
static uint8_t detect_charging_state(void) {
/* STAT1引脚:低电平=充电中,高电平=充满或未充电 */
bool stat1 = hal_gpio_read(GPIO_CHARGE_STAT1);
/* STAT2引脚:低电平=充满 */
bool stat2 = hal_gpio_read(GPIO_CHARGE_STAT2);
/* USB电源检测引脚 */
bool usb_power = hal_gpio_read(GPIO_USB_DETECT);
if (!usb_power) {
return 0; /* USB未连接,未充电 */
}
if (!stat1) {
return 1; /* 充电中 */
}
if (!stat2) {
return 2; /* 充满 */
}
return 0;
}
/* ========== LED状态指示 ========== */
/**
* 根据电源状态和电量更新LED指示
*/
static void update_led_indication(uint8_t battery_percent, uint8_t charge_state) {
if (charge_state == 1) {
/* 充电中:绿色呼吸灯 */
led_set_mode(LED_MODE_BREATH_GREEN);
} else if (charge_state == 2) {
/* 充满:绿色常亮 */
led_set_mode(LED_MODE_SOLID_GREEN);
} else if (battery_percent <= LOW_BATTERY_THRESHOLD) {
/* 低电量:红色慢闪 */
led_set_mode(LED_MODE_BLINK_RED);
} else if (battery_percent <= CRITICAL_BATTERY_THRESHOLD) {
/* 极低电量:红色快闪 */
led_set_mode(LED_MODE_FAST_BLINK_RED);
} else if (g_system_state.ble_connected) {
/* 已连接:蓝色常亮 */
led_set_mode(LED_MODE_SOLID_BLUE);
} else {
/* 未连接:蓝色慢闪 */
led_set_mode(LED_MODE_BLINK_BLUE);
}
}
/* ========== 低功耗管理 ========== */
/**
* 进入轻度睡眠模式
* 关闭不必要的外设,降低CPU频率
* BLE连接保持,可被笔尖触摸或BLE命令唤醒
*/
static void enter_light_sleep(void) {
if (s_power_state == POWER_STATE_LIGHT_SLEEP) {
return;
}
/* 关闭摄像头 */
camera_power_off();
/* 关闭SPI(传感器通信) */
hal_spi_disable(SPI_PORT_1);
/* 降低CPU频率到16MHz */
SystemClock_SetLow();
/* LED关闭 */
led_set_mode(LED_MODE_OFF);
s_power_state = POWER_STATE_LIGHT_SLEEP;
}
/**
* 进入深度睡眠模式
* 关闭所有外设和BLE,仅保留RTC和GPIO唤醒
* 适用于长时间不使用的场景
*/
static void enter_deep_sleep(void) {
/* 断开BLE连接 */
ble_gatt_disconnect();
ble_gatt_stop_advertising();
/* 关闭所有外设 */
camera_power_off();
hal_spi_disable(SPI_PORT_1);
hal_i2c_disable(I2C_PORT_1);
hal_adc_disable(ADC_CHANNEL_BATTERY);
/* 保存系统状态到Flash */
offline_storage_flush();
/* 配置唤醒源(笔尖GPIO中断唤醒) */
hal_gpio_set_wakeup(GPIO_PEN_TIP_PIN, GPIO_WAKEUP_RISING);
/* 进入MCU深度睡眠模式(不应返回) */
hal_enter_deep_sleep();
}
/**
* 从轻度睡眠唤醒,恢复正常工作状态
*/
static void wake_from_light_sleep(void) {
/* 恢复CPU频率 */
SystemClock_Config();
/* 重新使能SPI */
hal_spi_enable(SPI_PORT_1);
s_power_state = POWER_STATE_ACTIVE;
s_last_activity_time = xTaskGetTickCount();
}
/* ========== 电源监测主任务 ========== */
/**
* 电源监测任务(FreeRTOS任务函数)
*
* 运行流程:
* 1. 定期读取电池电压并估算电量
* 2. 检测充电状态
* 3. 低电量告警和自动关机保护
* 4. 更新LED状态指示
* 5. 自动休眠判断
*/
void power_monitor_task(void *pvParameters) {
(void)pvParameters;
TickType_t last_wake_time = xTaskGetTickCount();
s_last_activity_time = last_wake_time;
while (1) {
/* 读取并滤波电池电压 */
uint16_t raw_mv = read_battery_voltage_mv();
uint16_t filtered_mv = voltage_filter(raw_mv);
/* 估算电量百分比 */
uint8_t battery_percent = estimate_battery_percent(filtered_mv);
/* 检测充电状态 */
uint8_t charge_state = detect_charging_state();
/* 更新全局系统状态 */
if (xSemaphoreTake(g_system_mutex, pdMS_TO_TICKS(100)) == pdTRUE) {
g_system_state.battery_percent = battery_percent;
g_system_state.is_charging = (charge_state == 1);
xSemaphoreGive(g_system_mutex);
}
/* 更新LED指示 */
update_led_indication(battery_percent, charge_state);
/* 低电量告警处理 */
if (battery_percent <= LOW_BATTERY_THRESHOLD && charge_state == 0) {
/* 通知上位机低电量 */
xEventGroupSetBits(g_ble_event_group, EVT_LOW_BATTERY);
}
/* 极低电量自动关机保护 */
if (battery_percent <= CRITICAL_BATTERY_THRESHOLD && charge_state == 0) {
enter_deep_sleep();
}
/* 充电状态变化通知 */
if (charge_state > 0) {
xEventGroupSetBits(g_ble_event_group, EVT_CHARGING);
s_power_state = POWER_STATE_CHARGING;
s_last_activity_time = xTaskGetTickCount();
}
/* 自动休眠检查:笔没有书写且BLE空闲超时 */
if (!g_system_state.pen_is_down && charge_state == 0) {
uint32_t idle_time = (xTaskGetTickCount() - s_last_activity_time)
* portTICK_PERIOD_MS;
if (idle_time > AUTO_SLEEP_TIMEOUT_MS) {
if (s_power_state == POWER_STATE_ACTIVE) {
enter_light_sleep();
} else if (idle_time > AUTO_SLEEP_TIMEOUT_MS * 2) {
/* 静止超过10分钟,进入深度睡眠 */
enter_deep_sleep();
}
}
} else {
/* 有活动,重置计时器 */
s_last_activity_time = xTaskGetTickCount();
if (s_power_state == POWER_STATE_LIGHT_SLEEP) {
wake_from_light_sleep();
}
}
/* 休眠到下一个监测周期 */
vTaskDelayUntil(&last_wake_time, pdMS_TO_TICKS(POWER_MONITOR_PERIOD_MS));
}
}
/* ========== 外部查询接口 ========== */
/** 获取当前电量百分比(供其他模块调用) */
uint8_t power_get_battery_percent(void) {
return g_system_state.battery_percent;
}
/** 获取当前电源状态 */
uint8_t power_get_state(void) {
return (uint8_t)s_power_state;
}