阮工的单片机编程经验集:如何做稳定单片机程序与上位机程序防卡顿,js等经验;阮丁远于20191208

阮工的单片机编程经验集V2.0:如何做稳定单片机程序 ,阮丁远于20191208  :

 

================================================================================================

20191207 :   
5V等电源输入时不要串联防反接用的二极管,否则由于二极管压降大和内阻大,从而使电源续流能力和稳定性大大下降,

从而导致各种干扰时的不稳定性!
================================================================================================


stm32+mos管驱动开关电源变压器时,是否在mos管输入极前接个变压器做信号输入耦合,否则如果stm32一死机则导致mos处于
一直导通的高电平状态而烧毁,加了变压器则stm32输出死机时则无信号给变压器次级而保护住mos管

================================================================================================

timer定时器里判断io引脚输入的状态计数来获得io状态,比用main的loop循环里读IO状态要更实时,因为main可能执行其他代码而对输入错过


================================================================================================


c#代码需要try 包住,对于c#调用c++的dll时,所有函数得加[HandleProcessCorruptedStateExceptions]来使c++出错时不崩溃,而是进入c#的try catch


================================================================================================

    x_1quan_puls=Tx_Buffer[90]+Tx_Buffer[91]*256+Tx_Buffer[92]*256*256;
  而不能为 Tx_Buffer[90]+Tx_Buffer[91]*0xff+Tx_Buffer[92]*0xff*0xff

================================================================================================

u32 iiii;
                u32 per_minnn=0;
                
                printf("head_and_wav_len1:%d",head_and_wav_len1);
                
                
                #define perwritenum 3000
                
                
                
                iiii=0;
                
                do{
                    
                    per_minnn=perwritenum;
                    
                    if(per_minnn>(head_and_wav_len1-iiii)){
                        
                        per_minnn=head_and_wav_len1-iiii;
                        
                    }
                    if(per_minnn==0){
                        
                        break;
                        
                    }
                    
                    
                        netconn_write(conn, request+iiii, per_minnn, NETCONN_COPY);
                    
                    
                                            vTaskDelay(5);
                    
                    iiii=iiii+per_minnn;
                
                            printf("netconn_write:iiii=%d,per_minnn=%d\r\n",iiii,per_minnn);
            
                    
                }
                while(iiii                 
                
                
比以下代码好维护得多:


                                
                                for(iiii=0;iiii                                     
                                    
                                    if(perwritenum                                         netconn_write(conn, request+iiii, perwritenum, NETCONN_COPY);
                                        
                                        
                                    printf(" netconn_write ok....,iiii=%d ,write=%d\r\n",iiii,perwritenum);
                                        
                                        
                                    }else{
                                        
                                        netconn_write(conn, request+iiii, head_and_wav_len1, NETCONN_COPY);
                                        break;
                                    }
                                    
                              
                                                     if((iiii+perwritenum*2)>head_and_wav_len1){ 
                                                    
                                                    
                                                                    
                                                                             netconn_write(conn, request+(iiii+perwritenum), (head_and_wav_len1-iiii), NETCONN_COPY);
                                                 
                                                                            printf(" netconn_write ok 002....(head_and_wav_len1-iiii)=%d \r\n",(head_and_wav_len1-iiii));
                                                                
                                                    
                                                         break;
                                                    
                                                     }
                                 
                                
                                }


================================================================================================


c# winform程序里,注意:如果当前显示的是子窗口且主窗口隐藏,则关闭窗口后会exe还在进程管理器里,

需要子窗口再closing事件里把主窗口的is_ruuning设置为0

================================================================================================

     SPI_FLASH_BufferRead(fft_model1, huanxingci_addr, NFREQ*Max_NFRAME*4);//
  
NFREQ*Max_NFRAME*4为100KB

,而最后一个参数类型为u16, 所以导致了读取数据异常的奇怪bug


=========================================
有时keil打印浮点数时始终为0或其他异常,改为这个:

void PrintFloat(float value)
{
    int tmp,tmp1,tmp2,tmp3,tmp4,tmp5,tmp6;
    tmp = (int)value;
    tmp1=(int)((value-tmp)*10)%10;
    tmp2=(int)((value-tmp)*100)%10;
    tmp3=(int)((value-tmp)*1000)%10;
    tmp4=(int)((value-tmp)*10000)%10;
    tmp5=(int)((value-tmp)*100000)%10;
    tmp6=(int)((value-tmp)*1000000)%10;
    printf("f-value=%d.%d%d%d%d%d%d\r\n",tmp,tmp1,tmp2,tmp3,tmp4,tmp5,tmp6);
}


=========================================


char 数组存储float变量时,

浮点(FLOAT)转换为CHAR

??float wTemp=3.3;
??
?char sBuf[4];
???char* temp;
???
memset(sBuf,0,sizeof(sBuf));
?
??temp=(char*)(&wTemp);
??
?sBuf[0] = temp[0] ;
?
??sBuf[1] = temp[1];
???
sBuf[2] = temp[2];
???
sBuf[3] = temp[3];?

CHAR转换为浮点(FLOAT)

??char sBuf[4];
??sBuf[0]=0x33;
??sBuf[1]=0x33;
??sBuf[2]=0x53;
??sBuf[3]=0x40;
??float *w=(float*)(&sBuf);
???
--------------------- 


=========================================

c#里的thread函数里如有异步事件而无阻塞,则在执行初始化后在末尾加 while(true){ Sleep(10);},防止线程退出后相关变量等被注销

=========================================
原来只是把formmark窗体 hide了,现在是close:


是否没close导致formmark窗体多个实例存在而异常??

=========================================

稳定性=稳定X稳定X....,

所以如果网线通讯可靠,宁可用3个CPU+3个DM9000aep网卡+3个网口+1个路由器 形式来扩展,

也不要用 3个CPU+3个cpu间串口通讯或485通讯+1个DM9000aep网卡+1个网口 形式来扩展,

因为网口的可靠性已确认,多几个也没事,而串口通讯或485通讯稳定性未知,比如大波特率下的可能的异常


=========================================


    
    FMC_SDRAMInitStructure.FMC_SDClockPeriod改为FMC_SDClock_Period_3后播放网络mp3时不死机了,说明要对sdram的线要手动布好,下次用等长线蛇形线!!!:


    
    
    FMC_SDRAMInitStructure.FMC_SDClockPeriod             = FMC_SDClock_Period_2; 
    //FMC_SDRAMInitStructure.FMC_SDClockPeriod             = FMC_SDClock_Period_3; 


  ,

  或者暂时补救的方法:SDRAM_GPIO_Config函数里把GPIO_Speed_100MHz改为GPIO_Speed_25MHz :


void SDRAM_GPIO_Config(void)
{        
    GPIO_InitTypeDef GPIO_InitStructure ;

    RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOC | RCC_AHB1Periph_GPIOD | RCC_AHB1Periph_GPIOE |       
    RCC_AHB1Periph_GPIOF | RCC_AHB1Periph_GPIOG | RCC_AHB1Periph_GPIOH | RCC_AHB1Periph_GPIOI,ENABLE);     

    GPIO_InitStructure.GPIO_Mode  = GPIO_Mode_AF;       
  

    //GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;   
    
  GPIO_InitStructure.GPIO_Speed = GPIO_Speed_25MHz;   

=========================================


改数组的长度或malloc的分配长度时,注意先搜索这个变量名,在文件中查找全部,看这个指针变量的最大写入长度,防止数组下标溢出而死机,可以加超出长度的判断和过滤

=========================================

01a1.程序做稳定的秘诀1:所有要申请内存的做到1个固定长度的数组里,用is_free来标记是否可用,而用malloc在多线程时申请内存太不稳定

01a2.电路里多个用电模块可以分开用几个稳压芯片单独供电,不要公用1个稳压芯片

01a3.降低fsmc总线频率或SDRAM频率,或sdio总线频率或i2c总线频率则可能解决一些不稳定性故障


01a.异步方式的判断某个io口的状态时,可以用timer计时和记高电平数量等来做异步式的软件式防抖算法!


0. 485工业总线通讯时,每次报文必须段,应IP+cmd方式,报文段则错误率小,

0b.w25q256 等存储器,一切读写前先初始化时记得重置cs脚为高电平,不能不管cs脚状态
,
    fatfs里 res=f_open(&ftemp,(const TCHAR*)main_script_outfname,FA_CREATE_NEW);没有|FA_WRITE,则只能新建文件而无法写入数据
            


0c.
stm32f407的flash写入时,注意一个写入范围跨2个扇区时的问题,由于擦除前没做缓存备份,所以写入会导致老数据丢失,
可以用

 void STMFLASH_pre_earse(uint32_t save_addr,uint32_t number)
{
uint32_t StartSector,EndSector,i, save_addr_temp;
StartSector = GetSector(save_addr);
  EndSector = GetSector(save_addr+number);

    
    FLASH_Unlock(); 
    
 FLASH_ClearFlag(FLASH_FLAG_EOP | FLASH_FLAG_OPERR | FLASH_FLAG_WRPERR | 
                  FLASH_FLAG_PGAERR | FLASH_FLAG_PGPERR|FLASH_FLAG_PGSERR);
    FLASH->ACR&=~(1<<10);            //FLASH擦除期间,必须禁止数据fetch!!!搞了我两晚上才发现这个问题!
 
    
    
    
    
    for (i = StartSector; i<=EndSector; i += 8) 
  {
      while (FLASH_EraseSector(i, VoltageRange_3) != FLASH_COMPLETE)
      {
      }
  }
    
      FLASH->ACR|=1<<10;        //FLASH擦除结束,开启数据fetch
   FLASH_Lock();
  
  
}

来提前擦除,然后统一用不带擦除的写入函数来分步写入


0.数据层和逻辑层分离,比如读写操作可以单独在1个线程里,外部需要读写数据库得可以做个消息队列传入数据层里一个个执行 ,这样就不会有数据库锁在高并发锁死的问题和卡软件的问题,还可以做读写分离,写入数据时自动热备份在多台服务器的数据库里,

0a.类似,相同的属性和函数必须抽象为1个,集中在1个函数里,不要分布在每个母函数里,否则要改一个地方就得改所有调用的地方,牵一发而动全身


0b.可靠通讯里不要用if(packet_id!=cur_packet_id){......}等机制实现可靠性,而要用比如xy轴目标坐标步数是否等于上一次目标步数的,另外上位机处理返回包的id时要判断recv_id!=last_send_id是否相等,不相等则丢包(包发送函数为单独线程,并取消息队列,一次只发一个,这样稳定点)
机制实现可靠性,控制蜂鸣器的代码也一样:if(cur_to_beep_cc!=to_beep_cc){to_beep_cc=cur_to_beep_cc;....
而电磁阀等不需要满足次数或步数等,可以直接丢包时不断重发!!

1.中断和 main函数或其子函数 里不要同时有同一外设模块信号的操作,如对74hc595的操作,对HT1621B的操作的信号线必须是有序的,而不能穿插,否则容易错乱或花屏
2.中断里标志位变化时,回main函数或其子函数后尽量再判断标记位,否则容易导致遗传上次标志位的作用,而没有及时发现中断返回后标志位已改变,而导致程序混乱
3.类似于信号量临界区的稳定性处理
4.对所有死等待如等待某标志位 ,做超时退出处理,防止卡死
5.所有数组下标尽量在访问数组前都做下标是否越界判断
5b.做除零异常判断,如果分母为零就不除
5c.注意:如果程序里有很多个定时器或多个高速外部中断在工作,而且定时频率都很高,那么如果栈空间分配很小,那么很容易发生嵌套的多层次抢占中断而在进入中断函数时发生局部变量分配储存空间时失败即堆栈溢出,而导致死机和卡死!,尽量
把功能用分支if做到1个定时器中断里,少用定时器,并尽可能减小定时器频率,并尽可能加大栈初始化空间,如stm32单片机里 .s 文件里的一些定义
6.对于一些潜在的不稳定因素(甚至1-2个月才出现一次故障),比如网络忙时包ID的先后次序发生变异,而导致if xxxx.id>old.id 永远无法再成立而卡死
7.多机通信尽量用单向通信,比如主从模式,主机发包轮询查询并从机返回结果模式,双向的全双工通信模式的稳定性比较差
8.所有按钮处理必须做按钮去抖动算法,甚至不是阻塞式延时式的去抖动算法,而是状态机时的异步流程方式的过滤掉按得过快的2次按钮事件间隔,比如200ms内再无按钮事件且按钮已没按住才算按了1次,而且最好加个104电容在按钮里并联
9.多机主从通信尽量用 目的机ID+通信协议版本号+分包的子包ID序号(如1-10)+子包总数+本子包数据长度+上次包的全局ID+crc32校验,方式来处理重复包,30ms内没收到从机反馈数据则重发包(比如重发4-6次)来处理丢包,并当上次包的全局ID大于一定值时发清零包ID的cmd包来复位ID计数,但要判断从机先收到大点的ID的包的情况
10.网口通信比usb通信稳定,usb在干扰时容易掉线
11.TF卡只适合做只读的fat32文件系统应用,如要常写入,最好配合 W25q64来固定分片方式的无fat32文件系统式的写入,因为TF卡常写入,容易写坏文件系统格式,比如意外掉电时等
12.对于一些串口接口的模块,如果波特率用得太大,则单片机容易串口中断响应不过来,而发生嵌套的异常串口中断,或丢串口数据,导致通信不稳定,波特率最好低点!
13.单片机程序的架构最好做成异步式的状态机式的虚拟多线程方式,不要用任何阻塞式的死等待和延时,延时也要做成状态机里的某个状态的delay的异步非阻塞式的累加,加到一定值才进入下1个状态码,异步方式比阻塞式的方式的稳定性要好,处理实时性也高,多级菜单的屏幕显示和按键处理也可以用状态机式的异步流程,有个状态码指示当前菜单的第1级选择项号,选择深度,第2级选择项号。。。。
14.相同的类似的属于同一功能概念下的处理过程最好集成到一个函数里做分支判断,而不要把1个功能分散到很多个函数里,这样代码比较难维护和二次修改
15.可以抽象为1个功能或1个流程体内的东西最好归纳法和抽象法抽象一下,不要做成if分支遍历和穷举所有情况方式,就像要做成矢量图,而不是像素图,抽象概括后的代码写法更有简洁性,更
有可维护性和稳定性!
15b.复杂度尽量做成可配置数组里的数据,用配置数据来展开对代码的控制,而不是把复杂度全部映射到代码if分支等分支上,就像要做成矢量图,而不是像素图,就像一个图形圆在可配置数组里(矢量图里)是 yuan_type和sin cos,而若是代码分支法,则是一堆关于圆的点阵

15c.按15点所说的抽象和归纳代码后的代码简洁化和可维护性加大化,可以使代码的条理和逻辑层次更加清晰,使更容易分析和预感到可能的bug和细节逻辑不严谨,从而预防bug和不稳定性bug,最终影响程序运行时稳定性,所以bug是由思维决定的


16.看门狗在程序完工后最好加上去,软件里的关键数据最好做RAM区多处备份,如备份10份,死机自动复位恢复后,检查RAM区里备份里的各备份,重复次数最高的那几份备份(且重复次数在6次以上)最可能为原始未被破坏的数据,并做个ram标志位检查是上电复位,还是看门狗复位,复位后可以做不清除ram数据,程序跑飞后,可以提前做些标志位判断,如跑飞到某处,可能某标志位未被置位却执行到这,则可以自动判断出是已跑飞,则自动软件复位一次!

17.
单片机芯片的最近的外围一圈上的单片机芯片vdd,gnd引脚附近必须多弄几个0603封装的贴片104电容来去干扰,pcb电源类走线的线宽必须要宽,至少0.4mm以上,甚至0.8mm以上,单片机芯片在pcb局部时尽量靠近电源芯片和供电芯片处

就单单才零点几欧电阻的导线稍微长点来传输电源,比如20cm长,就会导致高频杂波变多,所以导线末端务必加470uf以上的电解电容来去耦滤波,可以想象,在单片机引脚周围1圈上也应该最好加上几个470uf电容和104电容环绕
;大功率pcb供电线走线千万不要在单片机周围走线或走过单片机,一定要把大功率电源的输入接口做在大功率电路部分的附近,避免电源线的长距离走线!,要走也不要在单片机外周走过!,且大功率pcb供电线走线要边走线边1路隔个几段距离就加个104去耦电容

18.
如果是软件模拟spi信号接口或i2c接口,那么字节发送函数里最好先禁止总中断使能,发完后再开启总中断使能,否则容易不稳定

19.
具体待确认:  

c8051f340单片机芯片工作时如果电源电压比较低或有突然的电源脉冲浪涌干扰,容易使烧好的程序丢掉,稳定性好像不怎么好,stm32单片机芯片或GD32芯片好像就好点,就没这个问题!,推荐优先选后者

20.
对于以太网网络模块芯片应用,最好把这芯片的RST复位信号接到单片机上,并RST脚接个104电容(有时候干扰容易导致意外硬式复位,而反而不稳定),这样,网络卡死时可以强制复位网卡芯片一次来修复

21.
lwip等网络协议栈的某些版本有可能有内存泄露bug,或长时间运行后卡死,最好做定时自动软件复位单片机功能,来防止内存泄露过度

22.
芯片上如果flash够,最好做从服务器查询新版BIN固件来自动升级单片机自身的hex固件功能(固件下载完要做CRC32校对),以及usb的固件升级功能,即net bootloader和usb bootloader,来应对【做项目即使完成和验收后,几个月出现的bug要修复,而措手不及】


23.
比如 休眠后唤醒时给PB1引脚1个高电平后不明原因的死机,不一定是进入未定义中断,而是main函数之前bootloader对PB1响应:主程序进入休眠态,唤醒后 pb1为高,进入bootloader态,但bootloader里没有重新初始化stm32 cpu的时钟系统等,故卡死,虽然bootloader后跳转到的main里有重新初始化stm32 cpu的时钟系统等的代码,但是已执行不到


24.
密脚芯片单片机等ic焊接时上松香吸锡后,如果用酒精清洗,是否过几个月后会发生引脚间松香,助焊剂等物质发生变质而短路?而不稳定?

25.
故障实时监测功能很重要!(备灾1):比如某stm32版的带网口的板子做个板子是否在线监测和掉线时短信提醒功能,即每10秒钟发送1个心跳包给服务器,如果服务器50秒内没收到某板子的任何心跳包,则此板子判为已掉线,并发送报警短信,email给对应的技术员来通知处理和修复

26.
同一个安装点上弄多个冗余板子,某块故障时,另1块自动切换到上线!,或双电源,1个后备电池逆变电源,主电故障则自动切换到备电(备灾2)

27.
电源接插头不要用圆孔的那种电源插座或usb接口插座,要用专用的接线端子母座和接线端子插头(绿色方形的带2针的那种,最好还带上扣机制,插紧后自动上扣),否则电源不稳或接触不良,导致板子掉程序或其他异常,信号线也可以如此处理


28.
回流焊最后1段温度曲线的达到最高温度时如保持时间过长(推荐205度保持15秒,对于无铅锡膏)导致锡膏被烤干,或锡膏过期了,就会导致出来后焊接效果不好,比如焊盘上锡的亮度不高,无光泽,太干燥,这时容易影响产品性能和加速日后的产品性能衰变

29.
eeprom写入前不需要擦除整个扇区,比如at24c02之类的,而单片机自带的flash和w25q64等都是flash,写入前如果不擦除整个扇区,可能部分字节会写错字节,而导致异常和不稳,或者做了擦除处理,但是写某个数据时
未备份扇区里原数据而直接擦除扇区,导致数据丢失,比如要写入的数据数组跨越了2个扇区的边界的情况

30.
尽量不要用一些外置的什么is62lv256+ls373方案的扩展ram内存方式,而要直接用自带高ram大小的192K ram的单片机,比如stm32f407或stm32f103zet,stm32103re ,gd32f103cb等,因为外扩方式容易不稳定,比如受焊接质量,打板精度,环境湿度温度,电源波动等影响
而不稳


31.
递归函数尽量用while/for循环+栈或数组或链表的方式来替代,而不要做成多层次调用函数自身方式的递归,做也做成带递归深度变量的,限制最大递归深度,否则容易堆栈溢出而死机

32.
当要进行可能的后果极其危险或非常重要,不能被任何干扰误触发的控制操作时(比如房屋爆破队驱动炸药点火的装置,自动驾驶的紧急刹车制动驱动器,高压总闸开关继电器),务必要备份10份以上的
控制码,如果只有1份控制码,当这份控制码被强干扰信号改变内存数据而变为导致触发的目标控制码时,就会误动作,而如果是10份,会校验所有10份控制完全一致后再打开控制,这样只有1份或2份被干扰
后就不会误动作了,所有内存区的任何变量也都实时备份10份以上,这样被干扰后可实时恢复正确的数据(残留下的重复次数最多的那几份且重复次数在6次以上即为未破坏数据),
当10份备份中重复次数在6次以下,意外着系统也无法自己自我修复,就会自动进入崩溃状态或自动软式复位,
再进行重要控制或驱动前,也检查所有内存区的各变量的实时备份的10份数据,如果任何一个变量的10份备份中各数据间重复次数在6次以下,则意外着干扰太强了,系统已错乱,则不继续进行重要控制或进行驱动。
,进行i/o控制的代码最好在前面多加几百行陷井代码和判断是否已被干扰弄崩溃的代码,来尽可能减少意外直接跑飞到【进行i/o控制的代码】的概率,或者不用任何简单的i/o来控制重要设备或高压的重要设备,
而用i2c或spi接口的外围驱动芯片来间接控制,且i2c或spi必须为软件模拟,在i2c或spi的发字节函数里,每发1个比特就再判断一次【是否已被干扰弄崩溃即检查所有变量的10份备份是否还算完整和一致】,
这样就非常可靠和稳定,不会被强干扰弄误触发了!


33.
在存在强电流,强功率,强电压,强静电,强射频耦合传递,或可能有接触式火花的地方尽量多的加光耦隔离来隔离信号控制计算处理部分和高压信号驱动部分,如果一个系统上存在2个或以上独立供电电源,
相互之间也必须完全隔离(GND等都不要接一起),用光耦耦合来传递控制,
继电器尽量换为可关闭的晶闸管,或场效应管,或其他大功率开关管,这些控制开断时有【无火花】的优点,防止继电器通断时的火花放电间隙导致的强干扰,如果实在要用继电器,在继电器的2触点引出脚间接入RC去火花电路,
,并在继电器的2触点引出脚控制的真空电磁阀或电机等感应负载上反向并联入【1个高耐压二极管+串1个0.5欧左右的大功率电阻】,以及并联入1个体积大的104电容,来去除真空电磁阀或电机等在断电瞬间产生的
反向电动势,从而消除反向电动势在继电器触点刚断开时的微小间隙里产生火花,从而消除火花产生的强干扰来使单片机跑飞,死机或复位的后果,


,另外如果用继电器,最好每个继电器的VCC串1个10欧的电阻后再控制继电器,这样继电器吸合时对电源的脉冲干扰小点!


34.
做编程项目或电子项目,务必做一个 发现的未解决的程序bug的备忘.txt ,及时记录发现的偶尔出现的bug,和突然灵感里闪现的预感到的可能会出现的bug情况,以及要改进的地方,
比如一些操作不变鸡肋问题,稳定性问题,都要及时记录,因为如果每忘记1点点bug,都意味着可能日后会出现这个bug,即导致项目的彻底失败,毕竟【一个项目的成功是靠堵住所有一些可能的细节虫洞和鸡肋来做到的】

35.
如果是在别人的开放平台或接口上做程序,那么需要定期检查所有情况都是否正常,比如即使今天全部正常,但是他们平台也许早就换了接口版本,只是老用户还是维持老版本接口,新订购的用户用我们isv软件时却已切换到新接口,不能用,却发现不了

36.
字符串数组即二维的uchar数组,在定义时,前1个方括号索引字符串序号,后1个方括号索引当前字符串的字符下标,比如u8 wavfile1n[7][15] 代表7个最长14个字符的字符串,方括号别弄反了,容易异常


37.
板子上电源处加的电解电容在1000uf以上时,容易上电时电容瞬间电流过大而导致开关电源发生短路保护,如此循环,而不易启动


38.
EMI:pcb电源输入部分增加共轭电感器抑制中高频的共模噪声,并加共模滤波电容(2个电容串联,2端接电源正负,中间点接地)和差模滤波电容

39.
如果要抗雷击浪涌,应在电源输入处并联入动作电压大于额定电压2-3倍以上的压敏电阻,并且电源输入处pcb走线宽度要粗,并电源输入处的压敏电阻前的走线串联入玻璃保险管的座的相关元件

40.
pcb信号线的走线尽量短而且宽: pcb布线时选择合理的导线宽度 由于瞬变电流在印制线条上所产生的冲击干扰主要是由印制导线的电感成分造成的,因此应尽量减小印制导线的电感量。印制导线的电感量与其长度成正比,与其宽度成反比
晶振走线必须尽量短:           时钟线、信号线也尽可能靠近地线,并且走线不要过长,以减小回路的环面积
信号线走线拐角应采用圆弧形:   电路板上的印制线宽度不要突变,拐角应采用圆弧形,不要直角或尖角。(9)时钟线、信号线也尽可能靠近地线,并且走线不要过长,以减小回路的环面积。
保持环路面积最小,降低干扰对系统的影响,提高系统的抗干扰性能。并联的导线紧紧放在一起,使用一条粗导线进行连接,信号线紧挨地平面布线可以降低干扰。电源与地之间增加高频滤波电容
采用完整的地平面设计,采用多层板设计,铺设地层,便于干扰信号泄放。

41.
密脚芯片怀疑存在连焊时的处理技巧1:拖焊时务必放很多松香到吸锡铜丝线上,否则容易吸锡不彻底,而留下ic引脚间不稳定性的短路问题,最后用99%浓度(不能含水多)的酒精擦去残留的松香,并再烙铁顺引脚刮下来清理一遍酒精等


42.
尽量用全局变量或全局数组变量替代局部变量,因为进入函数时要不断的分配局部变量,容易影响执行效率和耗用完堆栈空间等;

43.
电源部分的正常和稳定(比如电压波纹小,高频噪声滤除率高,功率实足,抗突变的浪涌大电流能力强)是单片机电路和控制电路稳定工作的最重要的前

44.
像LM1086-3.3或LT1086-3.3,LMXXXXX....之类的贴片式三端稳压器,在贴片工艺完后并过回流焊炉后,由于焊盘上印刷锡膏时锡膏量不足或者三端稳压器引脚上翘会导致三端稳压器过回流焊炉焊接后存在虚焊或接触不良,
所以应该每次都手工补焊下来补点锡,否则容易不稳定,或在电子产品运输工程中脱焊而开路
sot23封装的贴片三级管也容易引脚上翘而导致回流焊的焊接质量不良而导致开路或虚焊(如在运输过程中振动而脱焊)

45.
所以出厂前的24h老化测试和pcb带电工作的高强度振动测试也很重要,来检测是否有虚焊的现象(如在运输过程中振动而脱焊)

46.
电源开关等不要用机械式的单刀双掷式的那种微型拨动开关,很容易损坏或接触不良,最好做成自锁式的那种按钮式电源开关或船型开关


47.
注意场效应管的前置驱动电压放大级用的三极管的发射极最好是接地形式的而场效应管G端接三极管的集电极再1个上拉电阻上拉到比如24V,否则如果NPN的发射极接G端时,当三极管导通,
发射极的电压最大也只有基极的电压的大小,将无法良好的使场效应管完全导通,比如IRF540N的Vgs需要10V左右才完全导通,另外应该在G端对地并联1个10V稳压管,因为一般Vgs也不能太大,20V左右最大了

不要用单片机IO口直接驱动场效应管,因为一般场效应管Vgs在10V左右才能完全导通和饱和,单片机io口电压不够,会驱动不良

48.
光耦存在寿命,所以尽量用非光耦的取代元件来做隔离,比如一些ic:
光耦中的发光二极管随着时间推移而老化;比如开关电源反馈环路中光耦老化,传输比下降后可能引发故障。
不能用线性光耦作为模拟量传感:通光耦产品手册中对电流传输比只给出一个大概的范围(从低到高可以差别一倍),而且没有给出温漂和老化的指标。用于开环传输模拟量,精度是得不到保证的,所以导致过压、过流,以致损坏

49.
74hc595的时钟和数据线上要加阻容滤波的  这类IC  速度太高   对毛刺干扰特别敏感,而导致误动作或屏幕错乱

49a.
做电压检测电路时,比如测220V电压或380V电压,不要用线性光耦+1M电阻来降压采样,而要用微型工频变压器(铁芯那种(非高频磁芯))来降压并整流采样,因为光耦会老化和被击穿(雷击等意外),或有温飘

50.
不要在任何中断函数里调用1个需要等待另1种中断函数来置位目标所需标志位才能结束的函数或代码段,否则可能因为中断优先级的关系而导致这另1种中断函数永远无法执行而卡死在等待里,导致死机

51.
谨慎使用keil自带的库函数之sprintf和printf,容易出问题,比如卡死或内存泄露

52.
LQFP64/LQFP48之类的密脚IC如果用拖焊法手动焊接,那么如果芯片在pcb上的每个引脚的焊盘的长度如果比较长,则很容易拖焊后在引脚的焊盘的靠芯片内部方向处产生锡的残留而导致2个引脚焊盘间的短路,所以
此法不稳定,还是用钢网刷锡膏+回流焊法比较可靠!


53.
ad采样均值滤波法时应该用:

        
            chindex=0;
            
            
            
            
            
            
              memcpy(&ch_vout1_lvbo_temp[0],&ch_vout1_lvbo[chindex][1],(junzhi_lvbo_count-1)*sizeof(float));
            
                memcpy(&ch_vout1_lvbo[chindex][0],&ch_vout1_lvbo_temp[0],(junzhi_lvbo_count-1)*sizeof(float));
            
            
            ch_vout1_lvbo[chindex][(junzhi_lvbo_count-1)]=read_adc_value();
            
            
        if(ch_caiyang_alled[chindex]==0){
        
            ch_vout1_lvbo_index1[chindex]++;
            if(ch_vout1_lvbo_index1[chindex]>=(junzhi_lvbo_count)){
                
                ch_caiyang_alled[chindex]=1;
            }
            
        }
    

这样的代码机制,而不是插满一次缓冲池后从头开始覆盖,而是不断往前移1格来不断的从缓冲池的末尾插入,而且memcpy时注意是(junzhi_lvbo_count-1)*sizeof(float)个字节,而不是(junzhi_lvbo_count-1)个


54.
数字电源或开关电源的mos管的驱动级的信号输入最好带一级最大高电平时间限制的硬件式电路,比如最大允许高电平500us,来防止卡死时一直高电平而使mos一直导通而烧坏mos或高频变压器!


55.
注意临界大小判断时容易出现的bug:
    if((ch_vout1_zhankongbi_value1+diff_v1)<=max_zhankongbi1){
     ch_vout1_zhankongbi_value1=ch_vout1_zhankongbi_value1+diff_v1;        
    }

,这里,如果diff_v1很大,但是ch_vout1_zhankongbi_value1+diff_v1刚好只比max_zhankongbi1大1,那么就会陷入死循环,永远无法达到max_zhankongbi1,而且不是只离max_zhankongbi1一点,容易使调节环在误差比较大时卡死
,所以要加else来改为:
    if((ch_vout1_zhankongbi_value1+diff_v1)<=max_zhankongbi1){
        ch_vout1_zhankongbi_value1=ch_vout1_zhankongbi_value1+diff_v1;        
         }else{
        ch_vout1_zhankongbi_value1=max_zhankongbi1;
                             
         }


56.
数字电源的每次反馈调整pwn或每次pid调整时的调整间隔周期必须在相隔500us左右以上而不是1个while死循环里不停的调整,
而要用timer定时定间隔的调(所以输出级的电解电容务必2200uF以上,来延长电压突变缓冲时间),而且每次调整的误差比例乘积因子必须要小,否则容易输出电压波纹很大!
控制输出电压波纹的关键是这500us内不断的采样adc(可多次DMA式adc)来累加并在500us结束时取累加值的平均值来数字滤波,因为一般ad采样电压的波纹比较大


57.
keil中 3.11/6.1会被当做小数来计算结果,如果是3/23,则结果为0,因为被当做整数来处理,所以必须写为(float)3.0/(float)23.0

58.
如果需要动态改变单片机定时器的定时周期,那么要注意周期定时值不能被改的太小,否则会导致不断的高速的进入定时中断而卡死,要判断和限制下


59.
【bug调试和发现bug原因,解决问题现象】的原则是【在相对系中寻找局部绝对系来作为支撑点,进而慢慢全部覆盖问题机制】,比如找几个确定点和可以差量法,对比法确定的程序现象和机理,然后再慢慢消元,减少变化量的个数,尽量避免多个量
一起在变的问题框架,因为变量1多,要测试的样本就成指数级增长!


60.
c#上位机程序防卡顿经验1:
阻塞式的任务和代码要全部在后台线程里独立运行,而不能在主界面的主线程里执行,对于后台线程里要跨线程操作主界面控件的,这段操作界面的代码要用到Invoke,相当于卡死界面一点时间,所以在
Invoke里调用的 Action actionDelegate3 = (x) =>{。。。。}之类的里的代码尽量执行时间耗时极短,比如尽量把界面控件某状态的死等待代码不要放在Invoke的执行体代码里,
比如 while ((int)(webb1.Tag) == 0 || webb1.IsBusy == true) { System.Threading.Thread.Sleep(10); }这段等待webbrowers加载网页完毕的代码不要放在Invoke的执行体代码里,而放在
线程代码里,即Invoke的执行体代码执行完后的紧接着的下一句代码再执行 while ((int)(webb1.Tag) == 0 || webb1.IsBusy == true) { System.Threading.Thread.Sleep(10); }
,而Invoke的执行体代码里如果执行webbrowers1.Navigate(aaa1.url);之类的,也会在打开网页时卡顿一段时间界面,所以尽量改为异步方式的Navigate函数,比如BeginNavigate()之类的

61.
C#里等待webbrowser是否加载网页完成的稳定可靠的检测方法为:延时式再判断累计法(注意:线程里读webbrowser的Isbusy值时要用Invoke,否则容易出错):
 
                      int kongxianed = 0;
                      while (true)
                      {

                          System.Threading.Thread.Sleep(100);
                          bool isbusy1=true;


                          Action actionDelegate3a11 = (x) =>
                         {
                             isbusy1 = webb1.IsBusy;
                         };

                          webb1.Invoke(actionDelegate3a11, "");


                          if (isbusy1 != true)
                          {
                              kongxianed++;

                          }
                          else
                          {
                              kongxianed = 0;

                          }

                          if (kongxianed > 20)//如果2秒内都无Busy状态再出现,那么极有可能网页和其所有网页子框架都已加载完成
                          {
                              break;

                          }


                      }


62.
c#里容易异常的地方和一些界面控制代码外围尽量包1层try ...catch,来防止崩溃出错

63.
c#里object 不能直接隐式转为int,而要先tostring()后再int.parse?


64.
js里的ajax里提交后尽量加&r="+Math.random()后缀来防止缓冲而不更新

65.
路径可能变动的网页里的带背景img定义的css尽量不要放在网页里,而放在一个外部css文件里,这样图片可以相对css文件来定义路径

66.
对于ajax点击链接执行函数但不跳转,建议不要用href:void(0)...+onclick(兼容性不好,有些浏览器异常打开新窗口),而用div+onclick+div加手型鼠标css
,而且onclick所在div要显式定义好height和width,否则容易兼容性不好,而有的浏览器点击无响应!
Example:CSS鼠标手型效果 CSS鼠标手型效果
Example:CSS鼠标手型效果 CSS鼠标手型效果
注:pointer也是小手鼠标,建议大家用pointer,因为它可以兼容多种浏览器。


67.
js里或帝国cms里esetcookie('last_xianhua'.$pinlun_id,$time1,time()+3600,0);的设置cookie超时时间,是按绝对值超时到期时间,而不是按相对的到期时长

68.
c# webbrowser模拟提交表单时,

                      refwin.Focus();
                      webBrowser1.Focus();
                      get_elem("input", "", "", "", "id", "J_payeeShowAccount").Focus();
                      SendKeys.SendWait(arr1[uidto_index].Trim());
                      get_elem("input", "", "", "", "id", "J_payeeShowAccount").SetAttribute("value", arr1[uidto_index].Trim());

                      try
                      {
                          get_elem("input", "", "", "", "id", "J_payeeShowAccount").RaiseEvent("onchange");
                      }
                      catch
                      {

                      }
,其中用SendKeys.SendWait发送1次模拟按键是为了触发input输入框的onchange,而最终还是强制用.SetAttribute("value", arr1[uidto_index].Trim());来正确的设置值,防止SendKeys.SendWait发送时丢字符(比如界面很卡时或手动转移输入焦点就容易丢)
,即SendKeys.SendWait+ .SetAttribute("value", 结合法

69.

 webbrowser模拟输入和点击要1个1个字符间隔着输入,且要模拟onmousemove事件:

   public void SendWait22(string ss, int per_ms)
        {
            for (int i = 0; i < ss.Length; i++)
            {
                System.Threading.Thread.Sleep(per_ms);
                SendKeys.SendWait(ss[i].ToString());
               
            }
            System.Threading.Thread.Sleep(per_ms);
        }


  Action actionDelegate3a22qq112a = (x) =>
                            {
                                for (int i = 0; i < 20; i++)
                                {
                                    try
                                    {
                                        webBrowser1.Document.Body.RaiseEvent("onmousemove");

                                    }
                                    catch { }
                                    System.Threading.Thread.Sleep(10);
                                }

                            };

                            webBrowser1.Invoke(actionDelegate3a22qq112a, "");


70.
各项都改好和文件都复制好了,还是故障依旧,那么很可能问题不在文件本身,而在文件的访问权限给对应的服务进程没赋予权限,比如:php怎么也加不入mysql组件,php.ini和库,dll扩展都复制了也一样,此时很可能是system32和外面的php.ini,库等的安全属性没赋予iis账户和network等


71.
稳定性还含过热保护稳定性:如果pcb设计上含带过热保护的大电流芯片,比如THB7128或者一些开关电源的过热保护,所以出厂前要设置最合适的THB7128工作电流来使连续工作1周或更长都不会发生过热保护而罢工

72.
对于大功率的处理器或单片机,比如DSP,ARM9-11等,发热量很大的,那么必须加风扇散热,否则cpu的高温度会导致运行速度下降和更容易程序跑飞,从而导致死机和崩溃!:比如一些路由器的死机很可能是散热不好,没加风扇,或电源部分用的老式的变压器式
的低输出电流且容易受电网电压欠压影响输出电压的电源,而不是强悍低波纹且稳压范围广的开关电源

优化散热+自动重启自身(用于解决可能的内存泄露等)可彻底稳定路由器:

大多数的路由器散热都做得不是太好,尤其是CPU;
我的一个N年前的NW618 经过改造后,连续无障碍运行了4、5年了;
首先是主要发热部件的散热,车间里找铜皮、铜板——最好是紫铜,然后做好防短路处理,手工折好形状——也是个稍微考验脑子的活,最好是高度刚好在扣上盖后能牢牢压住;
NW618可以刷固件,刷了个番茄的;

设置定时自动重启;
别管多热,这个路由器都没给我填过麻烦;
路由器、猫 绝大多数的散热都很差,所以,提醒各位注意降温;
我是拆了3个以上的无线路由器后才发现的…… ;

73.
div+css模板里div块间的分界html注释是div里的table化,有利于节约阅读情绪成本


74.
伺服电机速度响应:加速区才几ms而一般步进要100-200ms+最好用柔性联轴器(因为加速区时扭矩很大)


75.
伺服电机套装的伺服电机简易买无刷的3线接口的直流无刷电机 ,有刷的电机不耐用

76.
数码管熄灭时再做ADC,比如4位数码管显示,分5个时间隙,假如2ms一位,则前面8ms显示,然后关闭显示2ms后做ADC,避免LED电流对ADC的影响。外挂的ADC是因为LED的电影响不到它。
ADC部分没有独立的模拟地和电源的MCU,就要这样考虑。

77.
实践证明:smt中,贴片式TF卡座过回流焊后很容易虚焊(焊脚上翘现象),所以每次必须手工补焊一次

78.
实践证明:部分旋拧方式的可调电阻内部有些是开路的,坏的,必须出厂测试

79.
不用【多层if或[多个单层if+return]】实现逐层条件判断的方法:do{   if(...){    break;}   if(...){    break;}   if(...){    break;}   if(...){    break;}...}while(0);

80.
注意:  RCC_AHB3PeriphClockCmd(RCC_AHB3Periph_FSMC, ENABLE); 不小心弄成  RCC_AHBPeriphClockCmd(RCC_AHB3Periph_FSMC, ENABLE); 或RCC_APB2PeriphClockCmd(RCC_AHB3Periph_FSMC, ENABLE)的低级错误!!

81.
#define configKERNEL_INTERRUPT_PRIORITY 255 //freertos任务切换调用中断的优先级必须最低,防止实时任务嵌套中断被任务切换中断所打断而异常!!!

82.
freertos里的
#ifndef traceMALLOC
    #define traceMALLOC( pvAddress, uiSize )
#endif

#ifndef traceFREE
    #define traceFREE( pvAddress, uiSize )
#endif

可以用于内存泄露的内存分配和回收的对称性检测,lwip里应该也有类似的机制

83.
freertos里采用heap_4.c这个内存管理方案来编译进:
#define configTOTAL_HEAP_SIZE ( ( size_t ) ( 20 * 1024 ) ) //采用heap_4.c才有此选项,即不交给keil的malloc函数来分配内存,而由系统管理


84.
如果数据传输要不断对新连接分配临时内存区,那么不定长的分配的和回收而留下内存空间间隙的话,久而久之会导致太多的内存碎片,是否最后导致性能越来越慢,甚至内存分配失败而卡死?,如lwip?,所以单片机内存分配简易采用数组划分定长分配内存区的策略才稳定!
,配合configMINIMAL_STACK_SIZE设置够大来使定长分配内存,而减少内存碎片积累
85.
Heap_Size       EQU     0x00010000  ;64k作为堆大小,即用于freertos的malloc分配内存,来使http_connect_task连接不会内存分配失败,配合configMINIMAL_STACK_SIZE设置够大来使定长分配内存,而减少内存碎片积累

86.
总线频率要留有余量,比正常能驱动外设的最高频率还稍微低点,才能稳定:
    FSMC_NORSRAMTimingInitStructure.FSMC_CLKDivision =12;//2分频的话驱动dm9000a不起来,总线频率太快!!,降为12分频就可以


87.
如果非要用usb转串口方案,稳定性和兼容性比较:FT232>PL2303双芯片>ch340,如何查某种usb转串口线所采用的芯片型号?:设备管理器里查设备属性的VID和PID法

    
88.
有些以太网网络芯片要做断线检测并自动重连,否则很容易断线后彻底卡死无法收发数据,比如dm9000aep:


     int_status = dm9000_io_read(DM9000_ISR);               /* Got ISR */
    dm9000_io_write(DM9000_ISR, int_status);    /* Clear ISR status */

    
    
     
      //Check link state
      if((int_status & ISR_LNKCHG)!=0)//掉线检测并重连
      {
            int_status = dm9000_io_read(DM9000_REG_NSR);
                
                   if((int_status & NSR_LINKST)==0)//掉线检测并重连
         {
                    rt_dm9000_init();            
                        
            dm9000_io_write(DM9000_IMR, IMR_PAR | IMR_PTM | IMR_PRM);
            
            
            
                 return (0);
                     
                 }
      }


89.
注意单句编程语句写法和次序不同而导致的稳定性不同,如:

     int_status = dm9000_io_read(DM9000_ISR);               /* Got ISR */
  
    
    
     
      //Check link state
      if((int_status & ISR_LNKCHG)!=0)//掉线检测并重连
      {
                
                  dm9000_io_write(DM9000_ISR, ISR_LNKCHG);    /* Clear ISR_LNKCHG ISR status */


           ...........



     int_status = dm9000_io_read(DM9000_ISR);               /* Got ISR */

          dm9000_io_write(DM9000_ISR, int_status);    /* Clear all ISR status */
    
    
     
      //Check link state
      if((int_status & ISR_LNKCHG)!=0)//掉线检测并重连
      {
                
               ...........


要好!


90.
c#等对于一些多线程用的组件,在线程主入口函数里的末尾务必加while(true){sleep(10);}之类的死循环来卡住,否则 线程主入口函数里新建立的子线程会因为线程主入口函数返回了而没卡住,而在不久后会全部注销子线程和变量,而导致一些运行一段时间后莫名其妙的exe退出现象!

91.
if((args.data[1]&128)==128)与 if(args.data[1]&128==128)可能有区别,因为可能==优先于逻辑与&,所以  if(args.data[1]&128==128)可能会变为  if(args.data[1]&(128==128)),即 if(args.data[1]&1)

92.
按照我做开关电源的经验,八成是变压器出问题了,一个开关电源里面的所有器件的参数都比较可控,比如耐压、耐流、功率、容值、阻值等等,唯一难以把控的来料就是变压器,每批的参数都会有偏差,找找没炸鸡的电源,测试一下MOS上面的尖峰电压吧,最有可能就是变压器漏感超标导致尖峰电压过高击穿MOS,造成连串的烧毁!:换更高耐压的mos管才是根本解决办法,比如IRF540N换为IRF640N

         
93.
程序防破解的原理:【可利用的特征的消灭技术,加大破解时间成本】:运用【变中有变】原理:在原程序正常功能的变量变化链之中再套入1层变化,而这层变化是防破解用的,破解者无法识别是正常变化还是防破解用的变化,除非理解了我的程序,但是如果有理解能力,自己都能写代码,对这个矛盾加以利用即可:
,如正常代码功能里对uuu[1]的值进行判断和分支,但是我们如果识别到软件未注册,那么就可以在判断未注册后,不做简单的返回,而是对uuu[1]的值取些非正常分支判断需要的值

94.
数字电源板开关频率可以设到36khz,不要15khz,15khz容易在负载功率波动时输出电压不稳!,而64khz又容易使输出级整流管过热!

95.
不要在中断函数里做网口发包 ,usb发包,串口发包等数据传输,这些数据传输代码要全部做在main里单线程化,然后中断函数里用flag标志位来指引main里的数据传输代码开始工作,这样才稳定!

96.
以前比如加速曲线有加速到加速曲线数组上标就停止再加速的bug:改为【加速曲线数组走到后也不停止加速,而是加速到最大kHz1才开始匀速】:

           if(x_cur_ruanqidong_ed==0){

                             if(x_cur_ruanqidong_c>=ruanqidong_num){
                                 x_cur_ruanqidong_c=ruanqidong_num;
                             }else{
                            x_cur_ruanqidong_c=x_cur_ruanqidong_c+v70_jiasu_jiange;
                             }
                             
                             
                           if((x_cur_ruanqidong_c_khz>=kHz1)){//加速曲线数组走到后也不停止加速,而是加速到最大kHz1才开始匀速
                                             
                                x_cur_jiasuqu_max_val_reached=1;  //v14
    
                              x_cur_ruanqidong_ed=1;//匀速区

                             }
                            
                            
                            
         }


                               
97.
1个应用软件在操作系统里所分配到的cpu资源是有限的,所以应用软件里的线程里应该尽量避免while+sleep死循环方式的等待某事件,这样很耗cpu,导致软件很卡顿或不流畅,尽量用while+多线程的信号量机制来解决,如  _autoResetEvent_UDPsend.WaitOne(5);//must 5,因为这样万一因为某种不稳定而卡住也就只卡5ms

98.
数字开关电源板的高频变压器的次级需要比正常圈数多绕2圈,留点升压空间,来防止变压器变压比达到最大而饱和而无法继续随占空比变大而升压,进而导致的mos管在占空比飙升后烧毁!,另外,程序里设定的输出稳压值要比正常额定输出值低2-3伏,也是为了防止变压器变压比达到最大而饱和而
无法继续稳压而导致的mos管经常烧毁!


99.

float fabs_c(float f1){
    
    
    if(f1>=0){
        
        return f1;
        
    }else{
        
        
        return f1*(-1);
        
    }
    
    
    
    
    
    
}


typedef struct PID
{
//int SetPoint; //设定目标  Desired Value 
double Proportion; //比例常数  Proportional Const 
double Integral; //积分常数  Integral Const 
double Derivative; //微分常数  Derivative Const 
float LastError; //Error[ -1] 
float LastDE; //Error[ -1] 
float DE; //Error[ -1]     
} PID;

float  P_DATA=3;//3 这个要小,否则输出电压波纹大
float  I_DATA=3;//3
float  D_DATA=0.05; //0.05
//声明 PID 实体
//*****************************************************
static PID  sPID; 
static PID  *sptr = &sPID; 

//*****************************************************
//PID 参数初始化
//*****************************************************
void IncPIDInit(void) 

sptr ->DE=0;
sptr ->LastError = 0; //Error[-1] 
sptr ->LastDE = 0; //Error[-2] 
sptr ->Proportion = P_DATA; //比例常数  Proportional Const 
sptr ->Integral = I_DATA; //积分常数 Integral Const 
sptr ->Derivative = D_DATA; //微分常数  Derivative Const 

}
//*****************************************************
//增量式 PID 控制设计 
//*****************************************************
float IncPIDCalc(float NextPoint,float SetPoint ) 

    float iError, iIncpid; //当前误差
    iError = SetPoint -  NextPoint; //增量计算
  sptr ->DE=iError-sptr ->LastError;

    iIncpid = sptr ->Proportion * (sptr ->DE) 
    + sptr->Integral * iError //E[k-1]项
    + sptr->Derivative * ((sptr ->DE) - (sptr->LastDE)); //E[k-2]项
    
    
    //iIncpid = sptr ->Proportion*(iError) 
    //- sptr->Integral*(sptr ->DE) 
    //+ sptr->Derivative*((sptr ->DE) - (sptr->LastDE)); 
        
    sptr ->LastDE = sptr ->DE;    //存储误差,用于下次计算
    sptr ->LastError = iError; 
    return(iIncpid);                           // 返回增量值 
}

//*****************************************************
static PID  sPID2; 
static PID  *sptr2 = &sPID2; 

//*****************************************************
//PID 参数初始化
//*****************************************************
void IncPIDInit2(void) 

sptr2 ->DE=0;
sptr2 ->LastError = 0; //Error[-1] 
sptr2 ->LastDE = 0; //Error[-2] 
sptr2 ->Proportion = P_DATA; //比例常数  Proportional Const 
sptr2->Integral = I_DATA; //积分常数 Integral Const 
sptr2 ->Derivative = D_DATA; //微分常数  Derivative Const 

}
//*****************************************************
//增量式 PID 控制设计 
//*****************************************************
float IncPIDCalc2(float NextPoint,float SetPoint ) 

    float iError, iIncpid; //当前误差
    iError = SetPoint -  NextPoint; //增量计算
  sptr2->DE=iError-sptr2->LastError;

    iIncpid = sptr2 ->Proportion * (sptr2->DE) 
    + sptr2->Integral * iError //E[k-1]项
    + sptr2->Derivative * ((sptr2->DE) - (sptr2->LastDE)); //E[k-2]项
    
    
    //iIncpid = sptr ->Proportion*(iError) 
    //- sptr->Integral*(sptr ->DE) 
    //+ sptr->Derivative*((sptr ->DE) - (sptr->LastDE)); 
        
    sptr2->LastDE = sptr2->DE;    //存储误差,用于下次计算
    sptr2->LastError = iError; 
    return(iIncpid);                           // 返回增量值 
}


==========

    
        IncPIDInit();
        IncPIDInit2();

=========
    diffv1=IncPIDCalc(AD_Value1,Mubiao_val1);
                       
    diffv2=IncPIDCalc2(AD_Value2,Mubiao_val2);
                        

                 ch_vout1_zhankongbi_value2=ch_vout1_zhankongbi_value2+diffv2;//由PID增量式控制算法返回增量并且赋给Voltage_2
            
               
                         
            if(ch_vout1_zhankongbi_value2<2){ch_vout1_zhankongbi_value2=2;}
            if(ch_vout1_zhankongbi_value2>max_zhankongbi1){ch_vout1_zhankongbi_value2=max_zhankongbi1;}
            
                         
                  TIM_SetCompare2(TIM1,(u16)ch_vout1_zhankongbi_value2);    

==================================================================================

100.
异步非阻塞式按钮软件去抖算法+异步式等待按钮释放算法:

u8 btn_ccc;
u8 key_realsed;
    u8 is_anykey_down;
    
u8 get_btn_key(){
     
             is_anykey_down=0;
    
                     if(GPIO_ReadInputDataBit(GPIOA,GPIO_Pin_0)==0){
                         is_anykey_down=1;
                         
                        if(key_realsed==1){
                         btn_ccc++;
                         if(btn_ccc>=10){
                             
                             key_realsed=0;
                             btn_ccc=0;
                             return 1;
                         }
                      }
                         
                     }
    
                     if(GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_5)==0){
                                                 is_anykey_down=1;
                         if(key_realsed==1){             
                         btn_ccc++;
                         if(btn_ccc>=10){
                             
                             key_realsed=0;
                             btn_ccc=0;
                             return 2;
                         }
                     }
                         
                     }
    
    
                     if(GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_6)==0){
                         
                                      is_anykey_down=1;
                            if(key_realsed==1){ 
                         btn_ccc++;
                         if(btn_ccc>=10){
                             
                             key_realsed=0;
                             btn_ccc=0;
                             return 3;
                         }
                      }
                     
                     
                     }
                     
                
                     if(GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_7)==0){
                        
                    is_anykey_down=1;
                         
                        if(key_realsed==1){                         
                         btn_ccc++;
                         if(btn_ccc>=10){
                             
                             key_realsed=0;
                             btn_ccc=0;
                             return 4;
                         }
                      }
                         
                     }        

         if(is_anykey_down==0){
                         if(key_realsed==0){
                        btn_ccc++;
                            
                            if(btn_ccc>=20){
                                     key_realsed=1;
                                
                            }
                      }else{
                            
                             btn_ccc=0;    
                            
                        }
                        
                 }
                     
                     
                     
                 return 0;         
}


==================================================================================

101.
可靠传输中,整型int序列号式确认流的稳定性永远大于布尔变量式的确认法(比如收到确认包就置位)

==================================================================================
102.

u32  next_jishuo_stage1_a_zhou;
 next_jishuo_stage1_a_zhou=(u32)(((float)1000000.00)/ ((float)a_zhou_mutli_soft_timer_hz));
和                                    
u16  next_jishuo_stage1_a_zhou;
 next_jishuo_stage1_a_zhou=(u16)(((float)1000000.00)/ ((float)a_zhou_mutli_soft_timer_hz));
的区别,

                                 
以及


(u16)(((float)1000000.00)/ (float)a_zhou_mutli_soft_timer_hz);

(u16)(((float)1000000.00)/ ((float)a_zhou_mutli_soft_timer_hz));
的区别,

以及
(byte)(x_diff_in_mcu1 >> 8 & 0xff);

(byte)((x_diff_in_mcu1 >> 8) & 0xff);

的区别,

容易造成bug


==================================================================================


103.
有时重新构架(去掉各种不稳定式机制后的新构架)比在老架构里找问题要来得快

==================================================================================

104.
单片机多定时器应用代码里,要注意可能不同定时器所用的主频不同,比如stm32f407的APB1, APB2等上的不同定时器,别搞乱了


105.
服务器使用的双滚珠风扇,4040 的12V0.3A,接通电源之后噪音会比较大,但却是不想使用4010的南北桥风扇,不是滚珠的,有不转的风险。


106.
就单单才零点几欧电阻的导线稍微长点来传输电源,比如20cm长,就会导致高频杂波变多,所以导线末端务必加470uf以上的电解电容来去耦滤波,可以想象,在单片机引脚周围1圈上也应该最好加上几个470uf电容和104电容环绕


107.
tc4427这个mos管驱动芯片的驱动输出脚必须直连到mos管栅极,有电阻的话容易受串扰而周期性波动吱吱声,比如串联200R电阻就不行


108.
大功率pcb供电线走线千万不要在单片机周围走线或走过单片机,一定要把大功率电源的输入接口做在大功率电路部分的附近,避免电源线的长距离走线!,要走也不要在单片机外周走过!,且大功率pcb供电线走线要边走线边1路隔个几段距离就加个104去耦电容

109.
芯片打磨去字会产生静电;它会损毁IC的,尤其是CMOS;坏的概率很高。
我们之前的MCU都是用锉刀磨,戴上静电环,坏的概率比较小,也可以先磨再烧,这样只要能烧的通常是好的!


110.
.Net会自动完成垃圾回收,对于比较大的图片,我们可以使用using关键字在不需要的时候自动对其进行回收

111.
多html标记的夹死比单html标记的内包块的稳定性要好的原理,可以弄新安装机制来防止html代码格式不规范而导致的卸载时卸掉了不该卸的其他块内容:


            img_elements2 = doc.DocumentNode.SelectNodes("//tr[@height='1."+yy.ToString()+""+weizhihao+"']");

            if (img_elements2 != null )
            {
                    img_elements2_end_flag= doc.DocumentNode.SelectNodes("//tr[@height='1."+yy.ToString()+""+weizhihao+"2']");


                 if (img_elements2_end_flag != null )//新安装机制,可以避免[因html安装体代码不规范而导致卸载时时而卸少了,时而卸多了]的问题!
                {

  112.
C# winform里sql server连接串为string connectionStr = "server=服务器名;database=数据库名;uid=用户名;pwd=密码"; 
,而c# asp.net里sql server连接串为"Data Source=(local);User ID=sa;Password=12;Initial Catalog=#XGM_SITE_DATATB_ICENTER#;Pooling=true;Max Pool Size=29800;Min Pool Size=50;Connection Lifetime=50" providerName="System.Data.SqlClient" />
  ,不一样的,要注意区分,不然会出现连不上问题!


113.

                double new_width = 0;
                double new_height = 0;

                Image m_src_image = Image.FromStream(ms);
               
                  
                    if (m_src_image.Width > width_for_shoujiduan)
                    {  new_width = width_for_shoujiduan;
                        new_height = m_src_image.Height - (int)(((float)m_src_image.Width - (float)width_for_shoujiduan) * ((float)m_src_image.Height / (float)m_src_image.Width));
                    }

         ......


以上为经典的不稳定性bug的结构,如果 if (m_src_image.Width > width_for_shoujiduan)不成立则new_width=0,则引发接下来的bitmap的初始化异常,所以用于赋值用的if最好配else

114.
 while ((m_WebBrowser.IsBusy || m_WebBrowser.ReadyState != WebBrowserReadyState.Complete) && aaa < 20 * 20)//不加m_WebBrowser.IsBusy 的话好像容易有最底下有1大片留白;最大等待20秒,10秒有点短
        
,然后网页加载完时也等待IsBusy:


            private void WebBrowser_DocumentCompleted(object sender, WebBrowserDocumentCompletedEventArgs e)
            {

                try
                {

                    WebBrowser m_WebBrowser = (WebBrowser)sender;


                    int aaa = 0;

                    while ((m_WebBrowser.IsBusy) && aaa < 20 * 20)//最大等待20秒,10秒有点短,因为有时候描述的高度比较长
                    {
                        Application.DoEvents();//给webbrowser用的
                        aaa++;
                        System.Threading.Thread.Sleep(50);//小点来让Application.DoEvents()更多次,来辅助界面线程!
                    }


,另外用

" + prod1_html1a.Replace("", "").Replace("", "").Replace("", "").Replace("", "").Replace("", "").Replace("", "") + "

你可能感兴趣的:(原创)