/* * 自然写智能点阵笔嵌入式固件软件 V1.0 * image_capture_task.c - 图像采集任务 * * 功能说明: * 1. 以100Hz频率驱动CMOS图像传感器采集点阵图案 * 2. DMA方式高速传输图像数据 * 3. 笔尖接触检测(上升沿/下降沿中断) * 4. 图像帧质量快速评估(丢弃模糊帧) * 5. 采集参数自适应调节(曝光、增益) */ #include #include #include #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(); } }