所选任务:赛道三——数据采集
【入门级题目1:多通道数据记录仪】
采集4路以上模拟或数字传感器数据,采样率1kHz以上,实时显示测量结果,并记录至SD卡(或者Flash里,支持连续记录, 自行评估数据容量),支持USB导出CSV数据供上位机分析。
一、项目描述
本项目基于 MAX32650FTHR 开发板 和 ADI CodeFusion Studio 开发环境进行设计,实现了一套多通道数据记录仪功能。系统通过ADC采集 2路模拟信号,并采集2路数字传感器数据,ADC使用MAX11261芯片采样率满足 1 kHz 要求。
采集到的数据经过格式化处理后,一方面通过虚拟串口实时显示,另一方面以 CSV 文件格式写入 SD 卡,实现连续数据记录。系统支持通过 USB 将记录文件导出到 PC,供 Excel、Python 等上位机工具进一步分析。
二、开发环境及硬件介绍
本次开发是在VS Code中使用CodeFusion Studio 进行开发。CodeFusion Studio是ADI推出的新一代嵌入式开发IDE,基于Eclipse框架并深度集成LLVM/Clang工具链,提供代码编辑、编译调试、功耗分析及外设配置一体化环境;其核心优势在于支持现代C/C++标准、内置RTOS与中间件模板、可视化时钟/引脚配置工具,并与MAX32650FTHR等开发板无缝对接,显著提升物联网应用的开发效率与代码质量。

MAX32650FTHR开发板是ADI(Maxim)面向物联网与可穿戴设备推出的紧凑型评估平台,核心搭载MAX32650微控制器,采用ARM Cortex-M4F架构,主频高达120MHz并集成浮点运算单元,内置3MB Flash和1MB SRAM,可支持复杂算法、图形界面及多协议栈运行;板卡采用Feather兼容外形,配备DAPLink调试器、电源管理IC、USB接口,SD卡槽等丰富外设,同时具备多种低功耗模式与安全加密模块,非常适合电池供电的边缘计算、语音交互及传感器融合应用的快速原型开发与功耗验证。

板载的MAX11261芯片是ADI推出的一款高精度、低噪声ADC,专为工业测量、精密仪器及传感器信号调理等对模拟前端要求严苛的场景设计,具有高分辨率、优异的线性度与动态范围,能有效抑制环境干扰并精准捕捉微弱模拟信号;其内部通常集成可编程增益放大器(PGA)、基准电压源及数字滤波器等信号链模块,简化外部电路设计,可通过I2C等标准接口与MCU无缝对接,构成稳定可靠的数据采集系统,适用于温度/压力传感、医疗监测及自动化测试等应用。

测量电路使用简单的电阻分压,一个为两个10K电阻,测量VCC/2和一个光敏电阻,和DHT11数字传感器。

三、方案介绍

本系统以 MAX32650为核心,实现温度,湿度与双路电压的周期采集、掉电存储和 USB 导出,由四个部分组成:
1. 数据采集 每 2 秒采样一次。DHT11 温湿度传感器通过单总线协议GPIO 软件模拟时序,读取湿度与温度;MAX11261 高精度 ADC 通过 I2C 接口完成两路电压采集(分辨率 1mV,转换速率 1000sps)。
2. 内部 Flash 数据存储 采样数据以 28 字节定长记录(含序号、温湿度、双路电压、状态码及 CRC 校验)追加写入芯片内部 Flash 末尾预留的 512KB 区域,掉电不丢失、复位后可继续追加,写满后自动回绕覆盖最旧数据。按 2 秒周期可连续记录约 15 小时。
3. USB 数据导出 芯片 USBHS 接口实现 CDC-ACM 虚拟串口,插入电脑后枚举为 COM 口,支持 4 个单字符命令:D 以 CSV 格式导出全部记录、E 擦除日志、S 查询记录数、H 帮助。导出数据可直接用 Excel/Python 分析。
4. 调试接口 UART0 输出实时运行日志(每轮采样打印一次读数)。
四、代码说明
1.主程序流程

主循环以约 2 秒为周期:读传感器 → 写 Flash → 处理 USB 命令。
int main(void) {
/* 初始化: DHT11 / ADC / Flash日志 / USB虚拟串口 */
DHT11_Init();
adc_max11261_init();
flashlog_init(); /* 扫描Flash, 恢复历史记录数 */
usbcdc_init(); /* CDC-ACM虚拟串口, 非阻塞 */
while (1) {
LED_On(0); delay_ms(1000); /* 心跳指示 */
LED_Off(0); delay_ms(1000);
err = DHT11_Read(&data); /* 读取温湿度 */
adc_read(0, &adc_value0); /* 读取ADC通道0 */
adc_read(1, &adc_value1); /* 读取ADC通道1 */
/* 采样数据组装成记录, 追加写入内部Flash */
rec.humidity = data.humidity;
rec.temperature = data.temperature;
rec.dht_status = (uint8_t)err;
rec.adc0_mV = adc_value0.val;
rec.adc1_mV = adc_value1.val;
flashlog_append(&rec);
usbcdc_task(); /* 处理USB虚拟串口命令 */
}
}
2. ADC 采集
ADC 采集通过 I2C 总线(I2C1,器件地址 0x30)操作外置 24 位 ADC 芯片 MAX11261,初始化时注册自定义平台函数并完成芯片复位、量程(AVDD=3V/REF=2.5V)和采样率配置。其中最关键的改动是 adc_i2c_transfer():因为 SDK 的 MXC_I2C_MasterTransaction 在上一事务 STOP 未完成时会返回 E_BUSY,这里加了最多 10 次、每次延时 50µs 的重试来消除竞态。单次采集由 adc_read(ch) 完成,内部按"设通道 → 准备转换 → 启动转换 → 轮询读取结果"四步执行,返回以 mV 为单位的转换值。
static int adc_i2c_transfer(uint8_t *txbuf, uint8_t txsize,
uint8_t *rxbuf, uint8_t rxsize)
{
mxc_i2c_req_t req;
int err, retry;
req.addr = ADC_I2C_ADDR; /* 0x30 */
req.i2c = MXC_I2C1;
req.restart = 0;
req.tx_buf = txbuf; req.tx_len = txsize;
req.rx_buf = rxbuf; req.rx_len = rxsize;
for (retry = 0; retry < ADC_I2C_BUSY_RETRIES; retry++) {
err = MXC_I2C_MasterTransaction(&req);
if (err != E_BUSY) {
return err == 0 ? 0 : -EIO;
}
MXC_Delay(50); /* 等待总线完成上一事务的 STOP */
}
return -EIO;
}
3. 内部 Flash 日志
Flash 页大小 16KB 不是 28 字节的整数倍,因此记录槽位按页对齐排列(每页 585 条)。上电时按同一布局顺序扫描,magic/CRC 有效且 seq 连续的记录视为有效数据;写满回绕后残留的旧数据因 seq 不连续会被自动跳过。
for (uint32_t slot = 0; slot < FLASHLOG_TOTAL_SLOTS; slot++) {
const flashlog_record_t *rec = (const flashlog_record_t *)slot_addr(slot);
if (!record_valid(rec)) break; /* magic 或 CRC 无效 */
if (rec_count > 0 && rec->seq != next_seq) break; /* 回绕残留区 */
next_seq = rec->seq + 1;
write_slot = slot + 1; /* 恢复写入位置 */
rec_count++;
}
写入操作在关中断的临界区内完成(Flash 控制器驱动代码运行于 RAM),页首写入前先确保目标页已擦除,实现懒擦除回绕。
int flashlog_append(flashlog_record_t *rec)
{
if (write_slot % FLASHLOG_SLOTS_PER_PAGE == 0) { /* 页首槽位 */
int err = ensure_slot_erased(write_slot); /* 懒擦除 */
if (err != E_NO_ERROR) return err;
}
rec->magic = FLASHLOG_MAGIC;
rec->seq = next_seq;
rec->crc = record_crc(rec);
err = fl_write_record(slot_addr(write_slot), rec); /* 临界区内写入 */
...
write_slot = (write_slot + 1) % FLASHLOG_TOTAL_SLOTS; /* 回绕 */
return E_NO_ERROR;
}
4. USB 虚拟串口导出
USB 部分实现了一个 CDC-ACM 虚拟串口,让 PC 通过一根 USB 线即可与板子交互:初始化时注册设备/配置描述符并接入 SDK 的枚举框架(MXC_USB_Init + enum_* + acm_init),运行中采用"中断置标志、主循环轮询"的裸机结构——USB_IRQHandler 只做事件分发,主循环每周期调用 usbcdc_task() 检查 VBUS 插拔/枚举等事件并读取主机发来的字符,逐字符交给命令处理器执行 D(把 Flash 日志以 CSV 逐行导出)、E(擦除日志)、S(查询记录数)三类命令;导出过程中每行都检查 acm_present(),主机中途拔线可安全退出。
void usbcdc_task(void)
{
uint8_t buf[16];
int chars;
chars = acm_canread(); /* 查询主机有没有发来数据 */
if (chars > 0) {
if (chars > (int)sizeof(buf)) chars = sizeof(buf);
if (acm_read(buf, chars) == chars) {
for (int i = 0; i < chars; i++) {
if (buf[i] != '\r' && buf[i] != '\n' && buf[i] != ' ') {
handle_command(buf[i]); /* 逐字符派发到 D/E/S/H */
}
}
}
}
}
五、实物演示
实物连接图

通过串口(COM7)实时输出数据,包括温度,湿度,两路模拟电压(VCC/2 和光敏电阻电压)

通过USB虚拟串口(COM8)接受命令和输出CSV格式数据

使用CodeFusion Studio在VS code中进入调试模式

六、心得体会
在这次项目中,我完成了 DHT11 温湿度和 MAX11261 双通道电压的周期采集、内部 Flash 掉电日志存储,以及通过 USB 虚拟串口实现命令交互和 CSV 数据导出。调试过程中印象最深的是 DHT11 的严格时序与 SysTick 延时机制的冲突,最终改用自由运行计数器实现延时才解决;此外还通过自定义 HardFault 处理快速定位了程序跑飞的位置。这次实践让我完整走通了"传感器采集→数据处理→非易失存储→上位机交互"的流程,对裸机开发中外设时序、资源冲突和调试手段的重要性有了切身体会