EMS 知识库
首页 / knowledge/代码解析/midware/meter

ct.c

源文件路径:midware/meter/ct.c 所属模块:midware/meter(CT 采样) 代码行数:1494

1. 文件职责

本文件实现 EMS 主控板上的 CT(电流互感器)交流采样通道:通过 TIM1 触发 ADC2 + DMA1_CH0 以 100Hz×100 点的方式采集电网三相电压/电流与 PV 侧电流,再用 TIM2 + EXTI10_15 的外部中断捕获市电方波下降沿来测量电网频率。采样结果在 task_sampling 任务中做均值(中值)去偏、真有效值(RMS)计算、FIR 滑动平均滤波、分段线性校准,最终写入全局表 g_t_ct(t_grid_ct_info / t_pv_ct_info),并累加电量。它处在数据流的最前端:midware/meter/meter.c 的 ct_handle() 每隔 500ms 调用本文件导出的 ct_read_*() 接口把 CT 数据搬进 g_t_meter.t_grid_info / t_pv_info,logic 层与 modbus 从机再从这里取数,因此本文件是整个 EMS 功率/频率数据的源头。同时它还负责采样异常(fault bit 18)判定与两条调试用的电网波形曲线录制(本地 g_grid_sampling_curve_t 与上报服务器的 g_server_grid_sampling_curve_t)。

2. 关键数据结构与宏

名称类型说明
DEBUG_SAMPLING宏置 1 时统计 DMA 中断次数与任务循环次数(ct.h:20)
FILTER_DEPTH_MID宏中值(DC 偏置)滤波深度,200(ct.h:22)
CALIBRATION宏置 1 使能分段线性校准(ct.h:24)
FILTER_DEPTH_VOLT / _FREQ / _GRID_CURR / _PV_CURR / _GRID_POWER / _PV_POWER宏各路滑动平均窗口长度:5 / 10 / 10 / 10 / 10 / 10(ct.h:25-ct.h:30)
CT_MAX_POWER宏有功功率限幅上限 18000(ct.h:32)
SAMPLING_CURVE_CHANNEL / _NUM / _BUF_LEN宏本地波形曲线:2 通道 × 1000 点(ct.h:34-ct.h:36)
SERVER_SAMPLING_CURVE_CHANNEL / _NUM / _BUF_LEN宏上传服务器波形曲线:2 通道 × 5000 点(ct.h:38-ct.h:40)
CT_SITE_E枚举CT 安装位置:GRID_CT=1、PV_CT、FAST_GRID_CT(高精度频率)、MAX_CT_SITE(ct.h:42)
CT_PHASE_E枚举相位选择:CT_PHASE_A/B/C/T(ct.h:50)
GRID_SAMPLING_CURVE_T结构体双缓冲波形录制结构:通道1/2 的使能标志、发送标志、缓冲区、计数(ct.h:59)
GRID_SERVER_SAMPLING_CURVE_T结构体上报服务器的波形结构:录制使能/发送标志/计数/RTC 时间戳/5000 点缓冲(ct.h:71)
CT_DATA_T结构体单路 CT 的实时数据:三相电压、三相电流、三相及总有功、无功、视在、功率因数、频率、快速频率、输入/输出电量(ct.h:80)
CT_RUN_TABLE_T结构体运行表:u8_clear_energy_flag + CT_DATA_T(ct.h:101)
CT_CALIBRATION_TABLE_T结构体单个校准点:s16_calibration_point(分界点)/s16_calibration_coef(系数,/10000)/s16_calibration_offset(偏移)(ct.h:107)
CT_CONFIG_TABLE_T结构体持久化校准表:A/B/C 相电压各 2 点、GRID/PV 三相功率各 5 点、35 字节保留、CRC(ct.h:114)
CT_ENERGY_SAVE_TABLE_T结构体持久化电量表:GRID/PV 各相及总输入/输出电量共 16 个 u32,60 字节保留,CRC(ct.h:134)
CT_TABLE_T结构体顶层聚合表:t_grid_ct_info、t_pv_ct_info、t_ct_config、t_ct_energy(ct.h:161)
STATIC_ASSERT(...)宏编译期断言 ASSERT_FLASH_SECTOR_SIZE % sizeof(...) == 0,保证结构体大小能整除 Flash 扇区(ct.h:132、ct.h:159)
g_t_ct全局变量顶层 CT 表实例(ct.c:39)
g_grid_sampling_curve_t全局变量绝对定位在 SDRAM_GRID_SAMPLING_CURVE_ADDR 的本地曲线缓冲(ct.c:41)
g_server_grid_sampling_curve_t全局变量绝对定位在 SDRAM_SEVER_GRID_SAMPLING_CURVE_ADDR 的服务器曲线缓冲(ct.c:42)
s_u8_detect_firststatic 全局变量频率采样启动标记,同时用作「并/离网已稳定」标志(ct.c:33)
s_u32_sampling_anomaly_tickstatic 全局变量采样异常计时基准,用于 fault bit 18 的 2 秒去抖(ct.c:37)

3. 函数清单

函数名作用域说明
grid_active_power_filterstatic(前向声明 ct.c:46,定义 ct.c:378)GRID 侧三相有功功率的滑动平均 + 分段校准 + 死区归零 + 限幅,写回 t_grid_ct_info
pv_active_power_filterstatic(前向声明 ct.c:47,定义 ct.c:492)PV 侧三相有功功率滤波,流程同上但死区为 5 且不限幅前归零
cal_grid_volstatic(前向声明 ct.c:48,定义 ct.c:80)GRID 线电压滑动平均 + 两点分段校准,同时写入 GRID 与 PV 两张表的电压字
pv_active_curr_filterstatic(前向声明 ct.c:49,定义 ct.c:279)PV 侧三相电流滑动平均 + 4 段分段校准,写回 t_pv_ct_info 电流
grid_active_curr_filterstatic(前向声明 ct.c:50,定义 ct.c:170)GRID 侧三相电流滑动平均 + 4 段分段校准,并按有功符号决定电流正负
grid_ct_energy_statisticsstatic(前向声明 ct.c:51,定义 ct.c:598)用 OSTimeGet() 计时,按总有功累计 GRID 侧输入/输出电量(60 秒结算一次)
pv_ct_energy_statisticsstatic(前向声明 ct.c:52,定义 ct.c:656)同上,累计 PV 侧输入/输出电量
ct_sampling_init全局(ct.c:61)初始化 ADC2、DMA1_CH0、TIM1(100Hz/100 点)、TIM2 与 EXTI10_15
grid_freq_sampling_handle全局(ct.c:719)由 TIM2 捕获中断/下降沿回调,测量周期并换算电网频率,首次下降沿时重启 ADC+DMA
adc_regular_data_recv全局(ct.c:795)ADC2 规则组 DMA 传输完成回调:标记缓冲区、切换下一个 DMA 缓冲、OSSemPost 唤醒采样任务
task_sampling全局(ct.c:844)uCOS-II 采样任务主体:等待信号量、中值去偏、RMS/功率累加、调用各滤波器、异常与频率丢失检测、驱动 ct_handle()
ct_read_voltage全局(ct.c:1194)按 site/phase 读线电压
ct_read_current全局(ct.c:1249)按 site/phase 读电流
ct_read_actpower全局(ct.c:1304)按 site/phase 读有功功率(支持 CT_PHASE_T)
ct_read_reactpower全局(ct.c:1367)读无功功率,当前实现恒返回 0(桩函数)
ct_read_factor全局(ct.c:1392)读功率因数,当前实现恒返回 0(桩函数)
ct_read_frequent全局(ct.c:1417)读频率,FAST_GRID_CT 返回 0.001Hz 分辨率的快速频率
ct_read_einput全局(ct.c:1438)读 CT 累计输入电量(取 t_ct_energy 持久化值)
ct_read_eoutput全局(ct.c:1457)读 CT 累计输出电量(取 t_ct_energy 持久化值)
ct_energy_clear全局(ct.c:1476)清零指定 CT 的输入/输出电量并投递 CT_ENERGY_PAGE 保存作业

4. 核心逻辑解析

4.1 采样链路初始化与三重中断/任务协作

ct_sampling_init()(ct.c:61)一次性配置好整条链路:

三者的配合方式是:

  1. TIM1 每 10ms 触发 ADC2 采样,DMA 按 ADC_DATA_LEN = ADC_CHANNEL * NUM_OF_SAMPLES = 4*250 = 1000 个半字搬运到 g_t_dma_data.t_dma_data[idx].s_s16_samples_value[250][4]。
  2. 搬运完成进 DMA 传输完成中断 → adc_regular_data_recv()(ct.c:795)。
  3. adc_regular_data_recv() 记录本缓冲区有效点数 s_u16_dma_data_len = (ADC_DATA_LEN - 剩余传输数)/ADC_CHANNEL,置 s_u16_dma_data_flag = TRUE,然后在 t_dma_data[DMA_BUFF_NUM=3] 中找下一个 flag == FALSE 的空缓冲重新配置 DMA 地址与传输数量并启动;若三个缓冲都满则回绕到下标 0(ct.c:809-ct.c:832,i >= DMA_BUFF_NUM 的判断被刻意放在循环最前以防数组越界)。最后 OSSemPost(g_t_ct_sample_sem) 唤醒采样任务。
  4. task_sampling() 用 OSSemPend(g_t_ct_sample_sem, 100, &err) 等待(ct.c:883),遍历 3 个 DMA 缓冲,对每个 flag == TRUE 的缓冲做计算并把 flag 清为 FALSE,形成生产者/消费者环形缓冲。

信号量定义在 user/main.c:59(OS_EVENT *g_t_ct_sample_sem),在 user/main.c:114 由 OSSemCreate(0) 创建;采样任务由 user/main.c:277 的 OSTaskCreateExt(task_sampling, ...) 静态创建。

4.2 中值(DC 偏置)计算

task_sampling() 维护 9 个累加器 l_s32_add_value_channel[9] 与中值 l_s32_mid_value_channel[9],初值 2048(对应 ADC 12bit 中点)。

注意:并离网切换时会检测 l_u8_detect_first != s_u8_detect_first(ct.c:886),一旦变化就把累加器清零、中值复位为 2048,重新收敛。这就是文件注释里说的「并离网切换中值初始化」。

第 4 个通道(s_s16_samples_value[count][3])不是电流/电压,而是 ADC2_IN7 的板温采样,通过 set_adc2_in7_data() 上报:中值收敛后用中值,否则用首个样本(ct.c:1044-ct.c:1051)。

4.3 RMS 与功率计算(关键公式)

对每个有效点 count(ct.c:972-ct.c:1042):

l_s32_temp[0] = sample[0] - mid[0]        // 电压瞬时值
l_s32_temp[1] = sample[1] - mid[1]        // GRID 侧电流瞬时值
l_s32_temp[2] = sample[2] - mid[2]        // PV   侧电流瞬时值

sum_grid_active_power_a += temp[0]*temp[1]      // 电压×GRID电流
sum_pv_active_power_a   += temp[0]*temp[2]      // 电压×PV电流

temp[0] = temp[0]*992/4096                      // 电压标度
temp[1] = temp[1]*233/4096                      // GRID 电流标度
temp[2] = temp[2]*233/4096                      // PV   电流标度
sum_grid_value_a  += temp[0]*temp[0]            // 电压平方和
sum_grid_curr_a   += temp[1]*temp[1]            // GRID 电流平方和
sum_pv_curr_a     += temp[2]*temp[2]            // PV   电流平方和

一个缓冲处理完后(ct.c:1055-ct.c:1069):

sum_grid_active_power_a = (sum_grid_active_power_a / 75) / len     // 有功均值
sum_grid_value_a  = sqrt(sum_grid_value_a  * 10000 / len)          // 电压有效值 ×100
sum_grid_curr_a   = sqrt(sum_grid_curr_a   * 10000 / len)          // GRID 电流有效值 ×100
sum_pv_curr_a     = sqrt(sum_pv_curr_a     * 10000 / len)          // PV 电流有效值 ×100

即无功真有效值算法:Xrms = sqrt( (10000/N) · Σ x_i² ),乘 10000 是为了在整数域保留两位小数。/75 是功率的标度转换(魔法数字),全文件仅此一处出现。

随后依次调用 cal_grid_vol()、pv_active_curr_filter()、grid_active_curr_filter()、grid_active_power_filter()、pv_active_power_filter() 完成滤波与落表;并在 ct.c:1091-ct.c:1092 把三相有功相加得到 s32_ct_actpower_t(总有功)。

4.4 滑动平均滤波与分段线性校准

四个 *_filter() 函数使用完全相同的一阶增删法滑动窗口(以 cal_grid_vol() 为例,ct.c:80):

好处是 O(1) 复杂度、无需每次重新求和,代价是 filter 累加器是 u32/s32,长期运行不会溢出(深度最大 200 × 值域)。

校准采用「分段线性」:把 CT_CALIBRATION_TABLE_T 数组当作多段折线,按输入值落在哪个 s16_calibration_point 区间选择 s16_calibration_coef / s16_calibration_offset:

GRID 侧有功校准由 calibration_flag(实参为 s_u8_detect_first)控制——只有在并网稳定后才做校准;PV 侧有功在并网模式下反而传 FALSE(因为此时功率是用 backup_volt * pv_current / 100 反算的,见 4.6)。

4.5 频率测量状态机

频率由 grid_freq_sampling_handle()(ct.c:719)在 EXTI10_15 中断中被调用,是一个两状态机:

状态 A:首次下降沿(s_u8_detect_first == FALSE,ct.c:751)

状态 B:后续每个下降沿(ct.c:724)

4.6 并/离网模式对采样与功率的影响

task_sampling() 中:

  pv_power_x = g_t_inv_info.t_run_info.u16_backup_volt_x * ct_current_x / 100

并强制 calibration_flag = FALSE(ct.c:1078-ct.c:1081)。

频率丢失检测(ct.c:1113):若距上次成功测频超过 100ms 且 s_u8_detect_first == TRUE,则把 s_u8_detect_first 置 FALSE,并把 GRID/PV 两张表的电压、电流、三相与总有功、无功、视在、功率因数、频率全部清零(ct.c:1119-ct.c:1147),避免在市电消失时把残值上报给上层。

采样异常检测(ct.c:1095):条件

(DMA有效点数 < 150 || 电压 < 300) 且 频率 >= 4200 (42.00Hz)
且 板型 != BOARD_EMS3_6_3_E 且 板型 != BOARD_EMS3_6_4_E

成立时,与 s_u32_sampling_anomaly_tick 比较,超过 2000ms 才置 ems_set_fault_bit(18, TRUE),否则先清;不成立时清故障并把计时基准刷新为当前 tick。这是一个带 2 秒确认的故障上报。

4.7 电量累计算法

grid_ct_energy_statistics()(ct.c:598)与 pv_ct_energy_statistics()(ct.c:656)结构一致,以 GRID 版本说明:

  s_u32_input += s_u32_input_temp / (s_u32Time_sum * 60)
  s_u32_input_temp = s_u32_input_temp % (s_u32Time_sum * 60)
  电量 += s_u32_input / 10
  s_u32_input %= 10
  s_u32Time_sum = 0

即把 W·ms 换算成电量单位(综合 /60 与 /10 两步后,1 个 LSB≈10Wh),余数保留到下一分钟,避免整数除法丢精度。写回 u32_ct_energy_input/output。

4.8 波形曲线录制(两条独立通道)

在 RMS 累加循环内(ct.c:978-ct.c:1032)并行录制两条互斥的曲线:

  1. 本地曲线 g_grid_sampling_curve_t(u8_sampling_curve_en == TRUE 时):双缓冲结构,u8_sampling_curve_buf_flag == FALSE 时填 curve1_buf,每点占 2 个 u16(电压、电流),计数满 SAMPLING_CURVE_NUM(=1000) 就置 u8_sampling_curve1_send_flag;buf_flag == TRUE 时填 curve2_buf,满则置 u8_sampling_curve2_send_flag。两个 flag 置位即切换缓冲,形成「边采边发」的双缓冲。
  2. 服务器曲线 g_server_grid_sampling_curve_t(u8_server_sampling_curve_en == TRUE 时):单缓冲,满 SERVER_SAMPLING_CURVE_NUM(=5000) 时置 u8_server_sampling_curve_send_flag = TRUE、计数清 0、关掉使能,并打上 SYSTEM_DATETIME 时间戳(ct.c:1019-ct.c:1025)。上层由 net/ems.c 的 STATUS_SEND_GRID_CT_SAMPLING_DATE 状态机负责发送。

两条曲线由外部(hmi / 服务器命令)设置 _en 标志后开始录制。

4.9 CT 数据回灌电表表(与 meter 模块的耦合)

采样任务末尾(ct.c:1154-ct.c:1164)调用 meter.c 的 ct_handle(u8 site, METER_RUN_TABLE_T *, u8 negate, u8 *hw_ver, u16 hw_ver_len):

4.10 对外读接口与桩函数

ct_read_*() 系列全部是纯查表函数,直接读 g_t_ct 并做 site/phase 分支;default 分支统一返回 0。只有 ct_read_frequent() 支持 FAST_GRID_CT(返回 0.001Hz 分辨率的 u16_fast_ct_frequent)。ct_read_reactpower()(ct.c:1367)与 ct_read_factor()(ct.c:1392)所有分支都赋 0,是未实现的桩函数——目前 CT 方案不计算无功与功率因数。

ct_energy_clear()(ct.c:1476)除清零电量外,还会去写 dsp_meter_calibration_data.u16_clear_cmd = 7 并置 u8_send_calibration_mode_flag,即顺带向 DSP 发送「林内电表数据清零」命令;然后 save_job_post(CT_ENERGY_PAGE)(CT_ENERGY_PAGE = 4081,data/system_config/system_config.h:33)投递持久化作业。

5. 对外接口

本文件导出(供外部调用)

名称类型调用方
ct_sampling_init()函数同样由本文件 task_sampling() 调用(ct.c:878)
grid_freq_sampling_handle()函数EXTI10_15 外部中断服务函数(hardware/exti/exti.c)
adc_regular_data_recv()函数DMA1_CH0 传输完成中断(hardware/dma/dma.c)
task_sampling(void *p_arg)任务函数user/main.c:277 的 OSTaskCreateExt
ct_read_voltage/current/actpower/reactpower/factor/frequent/einput/eoutput函数midware/meter/meter.c 的 ct_handle()(meter.c:4399-meter.c:4443)、midware/interface/modbus/modbus.c:4312-modbus.c:4332、logic/dispatch.c:604/dispatch.c:626
ct_energy_clear(u8 site)函数data/data.c:362-data/data.c:363
g_t_ct全局变量data/system_config/system_config.c(读写与持久化)、midware/auxiliary/system_aux.c 等
g_grid_sampling_curve_t全局变量HMI / 服务器波形命令处理(hmi/、net/)
g_server_grid_sampling_curve_t全局变量net/ems.c(STATUS_SEND_GRID_CT_SAMPLING_DATE)

本文件调用的外部函数/变量

名称来源模块
adc2_init / adc_enable / adc_disable / adc_dma_mode_enable / adc_dma_mode_disable / adc_flag_clear / adc_dma1_channel0_init / set_adc2_in7_datahardware/adc/adc.h
dma_channel_enable / dma_channel_disable / dma_memory_address_config / dma_transfer_number_config / dma_transfer_number_get / dma_interrupt_enable / dma_interrupt_disable / g_t_dma_data / DMA_CHXCTL_FTFIE / ADC_DATA_LEN / DMA_BUFF_NUMhardware/dma/dma.h
timer1_init / timer2_init / timer2_get_counter / timer2_set_counter / timer2_start_stop_controlhardware/timer/timer1.h、timer2.h
exti10_15_inithardware/exti/exti.h
OSSemPend / OSSemPost / OSTimeGet / uCOS-IIUCOSII
g_t_ct_sample_semuser/main.h:59
g_t_meter(t_grid_info、t_pv_info、t_config_data.u8_grid_meter_negate、u8_pv_meter_negate、u8_grid_merter_hw_ver、u8_pv_merter_hw_ver、u8_gridforming_en)、meter_get_meter_or_ct、CT_SAMPLING、METER_SAMPLING、GRID_METER、PV_METER、ct_handlemidware/meter/meter.h、midware/meter/meter.c
g_t_inv_info.t_run_info.u16_backup_volt_a/b/cmidware/inverter/inverter.h:1929
get_board_type / BOARD_EMS3_6_3_E / BOARD_EMS3_6_4_Esystem/board_init/board_init.h:30
ems_set_fault_bitdata/data.h:128
save_job_post / CT_ENERGY_PAGEdata/system_config/system_config.h:834 / :33
SYSTEM_DATETIMEmidware/auxiliary/system_aux.h:58
get_dma_data_debug_flagmidware/inverter/inverter.h:2251
dsp_meter_calibration_datamidware/inverter/alphaess/alphaess_comm_can.h:206
g_t_task_run_statususer/main.h(仅 DEBUG_SAMPLING==1 时使用)

6. 依赖关系

依赖(本文件 include / 使用)

ct.h、dma.h、adc.h、timer1.h、timer2.h、math.h、main.h、system_aux.h、system_config.h、board_init.h、meter.h、inverter.h、exti.h、gpio.h、data.h、alphaess_comm_can.h(ct.c:13-ct.c:28)。

被依赖(哪些文件会用到它)

7. 备注与风险点

  1. B/C 相功率恒为 0:在 task_sampling() 的累加循环里只累加了 l_s32_sum_grid_active_power_a(ct.c:1034)与 l_s32_sum_pv_active_power_a(ct.c:1035),_b/_c 三个变量虽被声明和清零(ct.c:965-ct.c:970)但从未累加,随后却被除以 75 和点数(ct.c:1056-ct.c:1060)传给滤波器。这与 DMA 只采 4 个通道(V、GRID_I、PV_I、温度)、硬件仅单相 CT 的现状相符,但对三相 CT 硬件来说这是明确的遗漏。
  2. l_s32_mid_value_channel[4..8] 是死数据:初始化、复位(ct.c:873-ct.c:876、ct.c:896-ct.c:899)都只覆盖下标 0-3,其余 5 个从未被使用,属于可清理的冗余(原调试打印 ct.c:1175-ct.c:1178 曾用到,现已注释)。
  3. abs() 作用于无符号量:cal_grid_vol() 中 abs(l_u16_grid_v_temp[i]) < l_pt_cal[i][0].s16_calibration_point(ct.c:138)以及 cv.c 的功率死区判断(ct.c:459)中 abs() 的实参来自可能已溢出为负值的 u16,函数式宏 abs 对无符号取绝对值无意义,建议改为有符号中间变量。
  4. 校准系数写入 u16/溢出风险:l_u16_grid_v_temp[i] = (... )/10000 + offset(ct.c:140、ct.c:144)右侧可为负,回写无符号变量会回绕,随后才在 ct.c:154-ct.c:156 用 < 0 判断(该判断永远为假)。
  5. 大量魔法数字:/75(功率标度,ct.c:1055)、*992/4096、*233/4096(电压/电流标度,ct.c:1036-ct.c:1038)、*10000(RMS 扩位,ct.c:1061)、28000(TIM2 计数器下限,ct.c:728)、2000000000(频率换算常数,ct.c:736、ct.c:741)、*10/23(电流校准分界点标度,ct.c:227)、300(电压异常阈值)、150/238(DMA 点数有效窗口,ct.c:910)、42000/65000(频率有效区间)、4200(频率异常阈值,ct.c:1095)、2000(异常确认窗口)、100(频率丢失窗口)。这些常量没有命名宏,改动风险高。
  6. FILTER_DEPTH_FREQ 收敛前的频率是 0:l_u32_ct_frequency_temp 只在 s_u8_freq_sampling_cnt >= 10 时才被赋值(ct.c:733-ct.c:743),前 10 个周期它一直是 0,却被无条件写到 u16_ct_frequent / u16_fast_ct_frequent(ct.c:773-ct.c:785)。上层若在此期间读频率会拿到 0。
  7. 首次下降沿分支会重配 DMA 但不清 e_dma_buff_idx:ct.c:764 只 memset 了 g_t_dma_data(会顺带清零 e_dma_buff_idx),但在 adc_regular_data_recv() 正在中断中操作 DMA 时重配同样的寄存器,存在并发风险(中断优先级关系中未见屏蔽)。
  8. 电量统计依赖任务周期:grid_ct_energy_statistics() 用两次调用之间的 OSTimeGet() 差值估算 Δt,若采样任务被长时间阻塞(例如 ct_handle() 内部的电表通讯),Δt 会被合并成一个大值一次结算,短时功率尖峰可能被平均掉。
  9. ct_read_einput/eoutput 读的是持久化副本 t_ct_energy,而累加写入的是运行表 t_ct_data.u32_ct_energy_input。两者由 data/system_config/system_config.c:1633-system_config.c:1636(上电加载)与 system_config.c:3044-system_config.c:3047(保存)同步;若保存作业延迟或失败,ct_read_einput() 的读数会与实时累加值不一致(存在最长一个保存周期的滞后)。
  10. DMA_BUFF_NUM 循环上界与越界防护:for (size_t i = 0; i <= DMA_BUFF_NUM; i++)(ct.c:809)用了 <= 而非 <,靠循环体内 i >= DMA_BUFF_NUM 提前 break 兜底;写法脆弱,若后人调整循环体顺序就会越界。
  11. DEBUG_SAMPLING == 1 时才有中断计数(ct.c:797-ct.c:799、ct.c:1181-ct.c:1183);ct.c:1166-ct.c:1179 是整块被注释的调试打印代码,属于遗留。