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,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();
}
}