一开始烧录的时候芯片识别不到,排查思路:
1.先检查硬件问题,我检查了link的供电,板子的供电,以及引脚是不是接反了,用蜂鸣器挡去检查芯片到ISP的通断,万用表内部会产生一个微弱的恒定电流,去测两点的阻值,如果是通的,那么说明电阻非常小,两点之间是一根纯铜线或者一个很小的电阻,所以这时候蜂鸣器会响。记得拔线。
如果怀疑是芯片的问题,先去用二极管特性测试芯片引脚,去测芯片引脚和地(GND)之间的压降。万用表显示 0.4V ~ 0.8V,说明引脚和芯片是通的。万用表在这个档位会输出一个大于 1V 的小电压,强行去"顶"开芯片里面的半导体(PN结)。顶开之后,万用表会显示电流经过时被"吃掉"了多少电压。
2.检查完之后发现硬件没什么问题,都能正常工作。然后检查软件问题,去查link驱动版本,找芯片型号是否正确。
3.检查代码,这里最重要的是不要把clk data的引脚复用了,复用之后烧进去后面大概率无法正常工作,需要擦除里面的程序,程序擦除一般做不到,因为clk data引脚功能被复用导致没法建立通信,所以一般可以借助外部擦除复位,比如在nrst引脚拉一根线短接gnd,在烧录的瞬间拉下来,这样在烧录的初期芯片会开始复位,然后立马拿开防止它一直进入复位模式。但是在这个八位单片机上没有外部复位引脚。为了方便直接更换芯片最好。
4.更换芯片之后发现还是没办法识别到芯片,继续检查基本软硬件,发现没问题,检查烧录方式

一直读不到flash,这里擦除flash大概率是误报,更新版本后就没有误报了。
在fmd programmer里面,有这么几种烧录模式

自动烧录:断电 → 上电 → 4~8ms 内握手 → 进 ISP → 读ID → 擦 → 写 → 校验 → 断电。
先断电后上电是为了制造出一次真正的复位,打开ISP窗口
ISP指的是In-System Programming,在系统编程,直接在板子上给芯片烧程序
在板烧录指的是,板子可以自带电源供电,不由link供电,并且在此模式下可以设置上电时间和下电时间
使用在板烧录肯定不行,使用不断电烧录也没办法成功握手,一般用自动烧录就可以。
关于初始化


关于led的检查
一开始烧了一个led的程序,但是灯不亮。先检查led的特性,利用万用表二极管挡位提供电流,测脚1脚2脚3.


根据这个来撰写代码
因为有绿灯红灯交替,要注意在亮红灯的时候把绿灯关掉,否则可能会出现其他颜色
这个板子是做过欠压测试的
整体adc的电压换算公式

先把adc测的值转化为引脚电压,*内部量程除以4095
将引脚电压*分压比,得到电网峰值
把峰值除以根号2得到有效值
最后换算单位
一开始采用单点采样,采样数据根本不够,低电压还好,在欠压状态下采样都是低电压,而在过压状态下进行单点采样的话容易采集到低电压,导致出现红绿灯交替的现象。
解决办法就是在一个工频周期内进行峰值采样,这里要注意不要在采样循环里面进行电压换算,在八位单片机里面这种操作非常拖时间。


但是这样采样很耗时,把采样放到主循环是个很耗时的操作,本身单片机用的就是LIRC的低速时钟,把adc采样这种操作放到主循环里会拖慢其他操作的时间,比如led红灯慢闪,原定1HZ实际上只有400mhz。并且ad采样在这里是阻塞的,大概阻塞了22.5ms,for循环在这里死等。
解决思路主要是把ad采样读取和led控制从主循环分离开

主循环只在这里发送转换命令,在后续的周期里通过标志位高频轮询查询结果。


ad模块这里主要有三个状态位。空闲状态,主循环在这里得知ad为空闲状态后就开始让他采样,将忙碌状态置为1,并返回。当ad没有采集完毕的时候也直接返回不死等。采集完毕后去读ad值,然后将忙碌状态清0。读到之后去跟当前峰值比较,更新峰值,将计数次数加1,直到加到48次后,去将本次采样最高峰值进行真实电压转换。将计数次数清0,峰值清0,去进行下一轮48次峰值采样。
这里的关键是及时返回不死等,用分布累加取代了之前的循环,主循环在这里转了48轮,完成一轮峰值采样,在这48轮内,process和led控制依然在进行,不影响系统速度。

在运算方面也能优化,在这八位单片机里面,没有FPU浮点运算单元,做浮点数运算和除法运算都是非常耗时的,因此尽量用乘法和右移来进行运算,移位指令是纯硬件电路,只需要 1 个时钟周期,**速度比除法快了几千倍。**进行零点校准和上限钳位。
用Q9移位法

LED工作

将led闪烁的工作交给中断来做。LED任务是一个时间敏感任务,需要一个绝对精准的后台来管控,中断拥有绝对的实时性,硬件计时,时间精度很高,并且非阻塞。
但是使用中断要注意几点,一是中断里面不能做耗时操作 ,printf、死等 while、或者做复杂的浮点/除法运算,整个 CPU 就会被卡死在这里,别的中断进不来,主循环也跑不动。二是要注意数据竞争问题, 主循环和中断去操作同一个引脚,可能会导致引脚状态混乱,可以考虑在主循环操作这个引脚之前强行把全局中断关了来保护数据。三是要考虑堆栈溢出风险 ,这颗芯片只有 8 层硬件堆栈。如果主循环里函数嵌套很深,突然来了中断,中断里又调用了几个函数,瞬间就会把 8 层堆栈撑爆,导致单片机直接死机。中断里面函数代码一定要及其扁平,中断会随时发生,当发生中断时,硬件会自动把当前 CPU 的寄存器状态压入堆栈。如果主函数已经将堆栈顶到快满了,如果中断里面调用比较深,这时候来个中断,堆栈会直接爆掉
printf是个很耗时的操作。我们传给printf的是一个带格式的字符串,printf需要解析格式,很消耗cpu,数字到字符串的转换也耗时,并且printf是重定向到串口的,单片机发送一个字节是通过写寄存器,硬件需要时间死等,这个时间由波特率决定。

主程序中,process会根据电压水平随时修改灯的模式,而灯的跳变只在中断里面去处理。
而关于恢复电压阈值的处理,一开始的想法是用迟延的思路去做,但是迟延的效果并没有那么好。
在实际应用中,在我们规定的电压边缘采样的时候,通常会产生采样不稳定的误差,比如恢复电压下限为195V,如果直接去用这个值去判断,LED现象一般是会跳变抖动,这种现象是正常的。
迟延的思想就是不用一个值,而是用两个值来进行出入。比如说恢复电压在195V,我们可以让它在200V时进入恢复期进行倒计时,在190V退出恢复期结束倒计时,在200--190这个区间,不会掉出恢复期不会结束倒计时。但是这种方法造成的结果就是,190到195V不会退出恢复期,但是这时候的电压其实并不算恢复电压,如果说把退出下限拉到195V又失去了意义。其实主要看恢复电压规定容忍多少误差,因为本身它只算恢复的一个期限而不是过欠压,主要还是看实际业务要求。而且要求195V进入恢复,如果按上述方法来200V才能进入恢复期,并不适用现在的项目。迟滞(双阈值)虽然能防抖,但会破坏精确度。
为了解决这个办法,有两个思路。一个是加权求平均值,现在是用的峰值来对比恢复电压,可以给采样到的几次峰值进行加权求平均,但它会引入一点时间上的滞后,并且要注意如果电压发生突变,比如说从220V突然跳变到140V或者300V,那采样的第一次是正常电压,加上后面几次不正常的电压进行加权平均,算的值是非常不准的,后面的周期采样会准,但是由于第一次采样不准,很影响后续的分闸合闸操作,对于过欠压的检测这个系统来说是毁灭性打击。可以考虑用瞬时值来判断过欠压,用平滑值来判断恢复电压。
第二个就是时间积分容忍,只在恢复电压使用这个逻辑。定义一个变量作为积分,定义两个积分范围,一个是出窗容忍积分,为2s,一个是积分上限为2.4s。在恢复电压阈值边界时,如果抖动超过2s那就结束倒计时,判定为出窗,如果抖动在2s以内,那么无事发生继续保持倒计时。给出上限是为了不让积分积的太多,因为电压要是回到窗口内,积分是要缓慢泄放的,如果没有上限的话泄放积分太慢了,也不能瞬间清0,这样的话积分变化太大,系统会误判。并且根据要求,这个恢复边缘允许有1V的误差,我们可以直接定义阈值为194和254.
case STATE_RECOVER_WAIT:
if (g_voltage >= OV_TRIP_275)
{
Relay_Open();
recover_out_cnt = 0;
g_state = STATE_OV_TRIP;
poweron_timer = 0;
LED_SetMode(LED_FAST_BLINK);
}
else if (g_voltage <= UV_TRIP_THRESHOLD)
{
Relay_Open();
recover_out_cnt = 0;
g_state = STATE_UV_TRIP;
poweron_timer = 0;
LED_SetMode(LED_SLOW_BLINK);
}
else
{
Recovery_LED_Update(elapsed);
if (Voltage_In_Recover_Window(g_voltage))
{
if (recover_out_cnt >= elapsed)
recover_out_cnt -= elapsed;
else
recover_out_cnt = 0;
poweron_timer += elapsed;
if (poweron_timer >= RECOVER_DELAY_TICKS)
{
Relay_Close();
g_state = STATE_NORMAL;
LED_SetMode(LED_OFF);
poweron_timer = 0;
}
}
else
{
recover_out_cnt += elapsed;
if (recover_out_cnt > RECOVER_OUT_MAX_TICKS)
recover_out_cnt = RECOVER_OUT_MAX_TICKS;
/*
* 出窗容忍只用于避免ADC短时抖动立即清空恢复过程。
* 194.0V、254.0V及窗口外的时间绝不累计恢复计时。
*/
if (recover_out_cnt >= RECOVER_OUT_TOL_TICKS)
{
poweron_timer = 0;
}
}
}
break;