330 lines
10 KiB
C
330 lines
10 KiB
C
/*
|
||
* 自然写智能点阵笔嵌入式固件软件 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();
|
||
}
|
||
}
|