模拟PWM波的自适应取阈值算法
前言:
单片机开发中,我们常常接触到的波形就是PWM波,一般都是0~3.3V的数字PWM波,很少涉及模拟PWM波。两者有什么不同呢?模拟PWM波不只有高低电平,还有中间的一些变化过程,且波形可能也不是规整的矩形波,再者它的电压可能超过3.3v导致无法直接用单片机IO口识别,PWM无法直接捕获到上升下降沿。
如果你预算充足,开发的设备也不是非常的小型话,可以容纳额外的电路,那么你可以考虑从硬件上增加一些运放电路或整形电路,尽可能将模拟PWM变为数字PWM,如果不行就得考虑串联电阻来分压,然后代码中通过AD采样到数据后进行算法处理。
自适应算法
-
自适应取阈值算法思路:
这里的MAX,MIN,LOW电压值都是不确定的,受传输距离和干扰影响。- 具体算法:
- 求出PWM波采样值的MAX和MIN值:监测波形20ms(大于周期15.01ms),采集这段时间内的最大的20个采样值和最小的20个采样值(使用插入排序),舍弃20个数据的头尾各2个共4个数据,对十六个数据求平均值(求和后右移4位)。得出PWM的MAX和MIN值。
- 识别PWM波开始输出的第一个上升沿:差值MAXMINDiff = MAX - MIN, 监测波形20ms,寻找采样值从小于MIN + MAXMINDiff/3(避免1/3除法运算,1/3 = 5/15 约等于5/16 = 4/16 + 1/16 = 1/4 + 1/16 = >>2 + >>4)跳变到大于MAX - MAXMINDiff/3(且小于MIN + MAXMINDiff/3的持续时间需大于一个25kHz的周期,PWM输出期间半个周期就会有一个上升沿,若持续一个周期都未出现认为当前处于无PWM输出阶段)的上升沿。最大采集20ms,如果期间识别到上升沿则会提前结束,这段时间是不固定的。
- 识别PWM波的波峰值High和波谷值Low:PWM开始输出后进行1ms(理论PWM输出时间为1.28ms,监测时间需小于此)的监测,方法同步骤1,求出Low平均值, High平均值等于MAX平均值。
- 取阈值:求出差值HighLowDiff = High - Low,采样值取SampleHigh = High - HighLowDiff/3(1/3运算同步骤3), SampleLow = Low + HighLowDiff/3。
- 根据需要阈值也可选取为(SampleHigh + SampleLow)/2。
c代码实现(这段代码我在一个自动测试程序中一直在使用,暂未发现问题):
//自适应求阈值函数,用于区分模拟PWM的上升下降沿。此函数工作在32MHz晶振下 en_result_t GetThreshold_SelfAdaption(uint16_t* pu16AdcRegHighThd, uint16_t* pu16AdcRegLowThd) { uint32_t u32MaxMinSampleTime = 640000; //取出20ms(至少要大于一个周期时间)时间内SELF_ADAPTION_NUM个最小的采样值和SELF_ADAPTION_NUM个最大的采样值 uint32_t u32LowSampleTime = 32000; //取出1ms(不能大于1.28ms的PWM输出时间)时间内SELF_ADAPTION_NUM个最小的采样值,用于确定u16AvgVLow uint16_t u16Vmax[SELF_ADAPTION_NUM] = {0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, //0:小 19:大 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u}; uint16_t u16Vmin[SELF_ADAPTION_NUM] = {65535u, 65535u, 65535u, 65535u, 65535u, 65535u, 65535u, 65535u, 65535u, 65535u, //0:大 19:小 65535u, 65535u, 65535u, 65535u, 65535u, 65535u, 65535u, 65535u, 65535u, 65535u}; uint16_t u16VLow[SELF_ADAPTION_NUM] = {65535u, 65535u, 65535u, 65535u, 65535u, 65535u, 65535u, 65535u, 65535u, 65535u, //0:大 19:小 65535u, 65535u, 65535u, 65535u, 65535u, 65535u, 65535u, 65535u, 65535u, 65535u}; uint16_t u16AvgVmax = 0u; //最大采样值平均值 uint16_t u16AvgVmin = 0u; //最小采样值平均值 uint16_t u16AvgVHigh = 0u; //模拟PWM波峰采样值平均值 uint16_t u16AvgVLow = 0u; //模拟PWM波谷采样值平均值 uint32_t u32Sum = 0u; //采样值之和 uint32_t u32TIMCnt = 0u; //定时器计数值 uint16_t u16AdcResult = 0u; //AD采样值 uint8_t u8StateFlg = TRUE; //用于标识当前采样值处于波峰还是波谷,False:波谷 TRUE:波峰 uint8_t u8StatePreFlg = TRUE; //u8StateFlg的历史值 uint8_t u8MaxToMinFlg = FALSE; //检测波谷跳变到波峰的标志,TRUE:检测到,FALSE:未检测到 int8_t i = 0; int8_t j = 0; uint16_t u16DiffValue = 0u; //u16VLow和u16VHign的差值 uint16_t u16DiffValue_5_16 = 0u; //差值的5/16,接近1/3 uint32_t u32LatestMaxTime = 0u; //最新采样值处于u16AvgVmax附近的时间 uint32_t u32KeepMinTime = 0u; //采样电压值持续保持在u16AvgVmin附近的时间 uint32_t u32SpaceDiffCntJuge = 1280u; //间隔计数差值判断标准, 1/25000 * 32000000 = 1280,32M下25KHz一个周期计数值为1280,大于此间隔则认为是长间隔。 en_result_t enResult = Ok; //取一段时间内的采样值最小值和最大值(分别取最小/最大的SELF_ADAPTION_NUM个) u16AdcResult = 0u; Bt_Run(TIM1); //BT TIM1定时器开始计时 while(u32TIMCnt < u32MaxMinSampleTime) { Adc_Start(); //ADC转换开始 Adc_GetResult(&u16AdcResult); //获取ADC转换值 if(u16AdcResult > u16Vmax[0]) //大于u16Vmax中最小的则新增 { //插入排序,小到大排 for(i = SELF_ADAPTION_NUM - 1; i >= 0; i--) //从数组右往左找 { if(u16Vmax[i] < u16AdcResult) //找到第一个比采样值小的 { //往左移动,0号最小的被移出 for(j = 0; j < i; j++) { u16Vmax[j] = u16Vmax[j+1]; } //插入采样值 u16Vmax[i] = u16AdcResult; break; } } } if(u16AdcResult < u16Vmin[19]) //小于u16Vmin中最大的则新增 { //插入排序,小到大排 for(i = 0; i < SELF_ADAPTION_NUM; i++) //从数组左往右找 { if(u16Vmin[i] > u16AdcResult) //找到第一个比采样值大的 { //往右移动,19号最大的被移出 for(j = SELF_ADAPTION_NUM - 1; j > i; j--) { u16Vmin[j] = u16Vmin[j-1]; } //插入采样值 u16Vmin[i] = u16AdcResult; break; } } } u32TIMCnt = Bt_Cnt32Get(TIM1); //获取计时数 } //获取采样值最大最小平均值 //采样值最大值平均值 for(i = 2, u32Sum = 0; i < SELF_ADAPTION_NUM - 2; i++) //舍弃首位各两个,做16个的平均值,用移位避免除法 { u32Sum = u32Sum + u16Vmax[i]; } u16AvgVmax = u32Sum >> 4; //采样值最小值平均值 for(i = 2, u32Sum = 0; i < SELF_ADAPTION_NUM - 2; i++) //舍弃首位各两个,做16个的平均值,用移位避免除法 { u32Sum = u32Sum + u16Vmin[i]; } u16AvgVmin = u32Sum >> 4; if(u16AvgVmax > u16AvgVmin) //异常检查 { //求模拟PWM波峰波谷采样值平均值 //波峰采样值平均值 u16AvgVHigh = u16AvgVmax; //波谷采样值平均值 //找到从u16AvgVmin跳变到u16AvgVmax的上升沿,此后开始一段时间的最小采样值求取 Bt_Cnt32Set(TIM1, 0u); //计数值清零 u32TIMCnt = 0u; u8MaxToMinFlg = FALSE; u16AdcResult = 0u; u16DiffValue = u16AvgVmax - u16AvgVmin; u16DiffValue_5_16 = (u16DiffValue >> 2) + (u16DiffValue >> 4); //1/4 + 1/16 = 5/16 接近1/3 while(u32TIMCnt < u32MaxMinSampleTime) { Adc_Start(); //ADC转换开始 Adc_GetResult(&u16AdcResult); //获取ADC转换值 if(u16AdcResult < u16AvgVmin + u16DiffValue_5_16) //认为处于最小值范围 { u8StateFlg = FALSE; u32KeepMinTime = u32TIMCnt - u32LatestMaxTime; } else if(u16AdcResult > u16AvgVmax - u16DiffValue_5_16) //认为处于最大值范围 { u8StateFlg = TRUE; u32LatestMaxTime = u32TIMCnt; } if((u8StateFlg == TRUE) && (u8StatePreFlg == FALSE) && (u32KeepMinTime >= u32SpaceDiffCntJuge )) //由波谷变到波峰 { u8MaxToMinFlg = TRUE; //标记从波谷跳到波峰 break; } u8StatePreFlg = u8StateFlg; u32TIMCnt = Bt_Cnt32Get(TIM1); //获取计时数 } if(u8MaxToMinFlg == TRUE) //识别到跳变 { Bt_Cnt32Set(TIM1, 0u); //计数值清零 u32TIMCnt = 0u; u16AdcResult = 0u; while(u32TIMCnt < u32LowSampleTime) { Adc_Start(); //ADC转换开始 Adc_GetResult(&u16AdcResult); //获取ADC转换值 if(u16AdcResult < u16VLow[SELF_ADAPTION_NUM - 1]) //小于u16Vmin中最大的则新增 { //插入排序,小到大排 for(i = 0; i < SELF_ADAPTION_NUM; i++) //从数组左往右找 { if(u16VLow[i] > u16AdcResult) //找到第一个比采样值大的 { //往右移动,19号最大的被移出 for(j = SELF_ADAPTION_NUM - 1; j > i; j--) { u16VLow[j] = u16VLow[j-1]; } //插入采样值 u16VLow[i] = u16AdcResult; break; } } } u32TIMCnt = Bt_Cnt32Get(TIM1); //获取计时数 } //PWM波部分采样值最大值平均值 for(i = 2, u32Sum = 0; i < SELF_ADAPTION_NUM - 2; i++) //舍弃首位各两个,做16个的平均值,用移位避免除法 { u32Sum = u32Sum + u16VLow[i]; } u16AvgVLow = u32Sum >> 4; if(u16AvgVHigh > u16AvgVLow) //异常检查 { u16DiffValue = u16AvgVHigh - u16AvgVLow; u16DiffValue_5_16 = (u16DiffValue >> 2) + (u16DiffValue >> 4); //1/4 + 1/16 = 5/16 接近1/3 *pu16AdcRegHighThd = u16AvgVHigh - u16DiffValue_5_16; *pu16AdcRegLowThd = u16AvgVLow + u16DiffValue_5_16; } else { enResult = Error; } } else //检测时间内未识别到波谷到波峰的跳变 { enResult = Error; } } else { enResult = Error; } Bt_Stop(TIM1); //定时器停止 Bt_Cnt32Set(TIM1, 0u); //计数值清零 return enResult; }
- 具体算法: