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

net2uizoo   2021-11-22 14:13 楼主

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


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


   小数转整数时未先四舍五入而导致的0.99999丢失的问题:


val=6.7,


乘以10后:

process_exped_val=66.9999980926514

(int)process_exped_val=66


        public static int V100_convert_to_int_with_math_round(double val)
        {

            return (int)Math.Round(val, MidpointRounding.AwayFromZero); // 

        }

-------------------

或者 为 (int)Math.Round(val)而未加 MidpointRounding.AwayFromZero参数,而导致四舍五入异常!,比如漏电流始终偶尔校不准!


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

加 ,id ASC:


 SELECT * FROM kb0_ui WHERE  is_use_zengliang_tongji=1 AND kb0_idstr='t_yi1xek3dhp' AND dianwei_name='dianneng_zong' AND (ADDTIME  BETWEEN '2021/01/01 00:00:00' AND '2022/01/01 00:00:00') ORDER BY diff_value1  DESC ,id ASC LIMIT 0,22

 SELECT * FROM kb0_ui WHERE  is_use_zengliang_tongji=1 AND kb0_idstr='t_yi1xek3dhp' AND dianwei_name='dianneng_zong' AND (ADDTIME  BETWEEN '2021/01/01 00:00:00' AND '2022/01/01 00:00:00') ORDER BY diff_value1  DESC LIMIT 0,22


要快!!,


或者ORDER BY round(diff_value1,5)  DESC 也快

 

 

--------------------------

 

 

 
company_id LIKE '...' 反而比company_id ='...'要快,可能改为 company_id LIKE '...' 后主键索引变为了ADDTIME ,

explain 分析 key_len从171变小为 17了 :

 SELECT SUM(diff_value1) AS zong_diff1,date_hour_index FROM kb0_ui WHERE  is_use_zengliang_tongji=1 AND dianwei_name='dianneng_zong' AND (company_id LIKE 't_uolcoya67e')  AND (ADDTIME  >= '2021-11-04 00:00:00' ) AND ADDTIME <= '2021-11-04 23:59:59' GROUP BY date_hour_index


  SELECT SUM(diff_value1) AS zong_diff1,date_hour_index FROM kb0_ui WHERE  is_use_zengliang_tongji=1 AND dianwei_name='dianneng_zong' AND (company_id = 't_uolcoya67e')  AND (ADDTIME  >= '2021-11-04 00:00:00' ) AND ADDTIME <= '2021-11-04 23:59:59' GROUP BY date_hour_index

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


1.程序里涉及清空缓存文件时且可以选择自定义导出目录时,务必不要删除文件操作,
万一用户选择的是桌面或其他系统目录,则全当为垃圾缓存而被删除且不可恢复!!!

,在线升级前如果要删除老文件,那要保证安装目录 是含子目录  的特定名比如 c:\soft001 ,以防止误删其他文件和目录,比如万一安装在硬盘根目录


删除前最好做备份

2.
检查所有线程里有无return,防止意外return

===================================================================
服务端所有时间日期格式化为 字符串时,统一格式设为 yyyy-MM-dd HH:mm:ss
,防止操作系统设置日期与区域的格式时影响这个格式,而发生意外格式
===================================================================

MySql等里面的float字段只有7位有效数字,大数值时容易省略小数位而发生不准!,可以换为

double 字段或其他专用于金钱统计的字段

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


LMxxxx5.0的12-36V的转5V的dc-dc稳压芯片,输入脚必须并40V470uf的电解电容,特别是输入脚前串了防反接二极管时,不然容易烧管,可能是反向高电压无法通过防反接二极管


===================================================================
如果串口 校验位为2,而设为1,会使奇偶校验位发生奇怪的异常,比如一个串口调试软件调试正常,而上位机收到很多 3f 3f 3f.....


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

多个线程里不要用 MessageBox之类,而要用变量标志,在其他单线程里 MessageBox,否则一出错就弹个不完消息框!!


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

所有检测步骤做成状态机式的,可以回退的,这样方便:比如电压输出没到位时,方便回退重试的,而不是死循环等待,也不方便停止按钮

===================================================================
有些不稳定性是因为器件寿命,比如程序里错误的存在了每秒写flash 1000次的操作,则flash很快损坏!!!

对于频繁读入flash的操作,可以做个ram缓存区,修改flash和开机时才更新缓存。。。
----------------------------


,另外上电时先要等待200ms至少,来上电稳定后再读取比如w25Q64的内容,不然没上电稳定就读,容易读出错码!!!

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

注意多个设备的通信协议调用的 crc16函数,有的是高字节在前,有的是低字节在前,如果公用一个 crc16函数,就会导致改掉一个,另一个就通信不通的现象


===================================================================
关闭所有采集卡输入通道,防止切换电流源时跳闸而引发测跳闸时间时异常!:

            qiehuan_caiji_boxingABC(99);//V888,关闭所有采集卡输入通道,防止切换电流源时跳闸而引发测跳闸时间时异常!
            
,另外再加异常跳闸时的重试合闸和重上电复位!!!


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

喂狗也是有讲究的,不要在可能重复产生中断的中断服务函数里面喂狗。
万一程序死翘翘了,但是中断可不会死,这时候在中断里面喂狗的话,程序就会在跑飞和中断服务函数中切换
===================================================================

AD9里新建pcb时有时距离是三层板,导致布线时有些引脚怎么也不布的问题,删掉pcb中间层即可

===================================================================
有的发生源有 2种读电压电流的接口,一种读设定值,另一种读实时值,判断电压到位没要读实时值那个接口!!!

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

对 关闭电压输出 等高风险的按钮事件,为保证100%实时响应,则用类似以下的机制,即传入第二个参数8,不然默认0,则按钮事件互斥:

            p_void p1 = () =>
            {
             
                close_all_UI();
                libcls.MessageBoxw("已降源,请等待8秒!");//提示会有安全感
            };

            setrun(p1,8);

---

            for (int i = 0; i < 9999; i++)  
           {
                ingsetrun[i] = 0;
            }


--

        public delegate void p_void();

        int[] ingsetrun = new int[9999];

        public void setrun(p_void p1,int lock_index=0)
        {

            if (ingsetrun[lock_index] == 1)
            {

                return;

            }


            ingsetrun[lock_index] = 1;
            try
            {

                System.Threading.Thread pt1 = new System.Threading.Thread(new System.Threading.ThreadStart(p1));

                pt1.Start();

                //while (pt1.ThreadState == System.Threading.ThreadState.Running || pt1.ThreadState== System.Threading.ThreadState.Unstarted)

                while (pt1.IsAlive)
                {
                    Application.DoEvents();
                    System.Threading.Thread.Sleep(5);//10的话太卡,小点,因为Sleep太大的话退出等待要卡很久!!


                }

            }
            catch
            {

                ingsetrun[lock_index] = 0;
            }
            ingsetrun[lock_index] = 0;

        }


===================================================================
delegate
         {里的return 的作用域问题:


       int reted1 = 0;
            this.Invoke(new EventHandler(delegate
         {
             Form2_Vatcmd_shoudong_set_botelv_webbrower pwwin1 = new Form2_Vatcmd_shoudong_set_botelv_webbrower();
             pwwin1.sss2 = "请手动设置波特率到115200,然后点保存参数并重启串口服务器!";
             pwwin1.refwin1 = this;
             if (pwwin1.ShowDialog() != System.Windows.Forms.DialogResult.OK)
             {
                 reted1 = 1;
                 return;


             }
         }));
            if (reted1 == 1)
            {

                return;

            }

===================================================================
用委托和事件解决2个类相互引用的问题:

    //定义一个delegate委托  
    public delegate void read_change_event_ruan_func_ptr(int index1);
    //定义事件,类型为上面定义的read_change_event_ruan_func_ptr委托  
    public event read_change_event_ruan_func_ptr Onread1;  

    public void read_change_event_ruan(int index1)
    {

        Onread1(index1);


    }

,另一个类里:

  m_processor.Onread1 += process_P_volt_to_idata_bytes;

        public void process_P_volt_to_idata_bytes(int index1)
        {
            if ((byte)index1 == (byte)SFR.P0)
            {
                process_P_volt_to_idata_bytesDo((byte)SFR.P0, "P0");

            }
            if ((byte)index1 == (byte)SFR.P1)
            {
                process_P_volt_to_idata_bytesDo((byte)SFR.P1, "P1");

            }
            if ((byte)index1 == (byte)SFR.P2)
            {
                process_P_volt_to_idata_bytesDo((byte)SFR.P2, "P2");

            }
            if ((byte)index1 == (byte)SFR.P3)
            {
                process_P_volt_to_idata_bytesDo((byte)SFR.P3, "P3");

            }
          


        }


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


如何调整WinForm界面ComboBox控件的高度

进击的路飞桑 2020-07-06 14:27:23  636  收藏
分类专栏: # C#
版权
打开ComboBox控件的属性页,调整字体即可改变其高度

===================================================================
设备急停信号不要做成连续读plc是否急停标志位,而要做成2处:急停按钮控制plc急停,还有上位机软件里写1个w区的位来急停


===================================================================
电流功率源的ict有些是发复位会强制断下电流一下,有些是如果没旁路则不会断一下,如果瞬间断一下源输出值可能异常!!


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

得做通讯使能:再上电稳定8秒后才开始上线通讯,不然没上电稳定就不断发通讯包,容易不稳定
----

plc 别接地线,否则容易烧坏plc??

--
232转485模块的铁外壳不要直接安装在设备外壳上固定,需要加绝缘板,否则容易导致TXD灯常亮等问题
,其实不是模块问题,而是485转换模块的 232输入口的1号脚和5号脚这个gnd脚接反了,但是由于只用于发送而不接收,接反也能用,
但是不稳定,偶尔txd灯常亮。。。。。。。
,所以对于只发送的通讯线路,出厂前要检查下232线序!!!

--

通信日志功能的重要性:
加了10工位的每个工位的通信日志功能后,可以看到有时清除断路器电量发包时返回心跳包的结果内容,说明心跳包和清除断路器电量发包冲突了,从而不稳定!!!!

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

NO.0000:
比如重发包超时时间为80ms,而断路器返回包的等待时间有100ms,则会在收到返回包前重发一个包,导致最终收到2个返回包,
而第2个返回包就干扰了后续发包的返回包,造成通讯纹乱:
所以:
所有包的重发包超时时间改大到1秒,原来只有120ms左右


,即加              if (isnowait == 0)//v1002:
                                {

                                    //防止重发包的返回包干扰下面的续包:
                                    if (sleep_with_rt_ok(1 * 1000, gongwei_index, comm_index) == 1)
                                    {
                                        sended = 1;
                                        break;

                                    }


                                }


        public int SendPortSP_try_more(string v20_sub_cmd_is_dl_or_68xieyi, int gongwei_index, int is_rec_senddata_str0_or_rec_senddata_len1, int comm_index, string cmd1, byte[] ombuffer, int wait_sec = -1, int isnowait = -1, int isPLC = -1, int is_stopmsg_err1 = 0, int is_nowait_for_liji_closeV = 0, int recv_mubiao_length = 0, byte headbyte = 0, string sub_cmd = "", float wait_sec_after_write=-1)
        {
    .........................


   if (wait_sec_after_write >= 0.01f)
                                {

                                    if (sleep_with_rt_ok(wait_sec_after_write * 1000, gongwei_index,comm_index) == 1)
                                    {
                                        sended = 1;
                                        break;

                                    }

                                }

                                if (isnowait == 0)//v1002:
                                {

                                    //防止重发包的返回包干扰下面的续包:
                                    if (sleep_with_rt_ok(1 * 1000, gongwei_index, comm_index) == 1)
                                    {
                                        sended = 1;
                                        break;

                                    }


                                }
===================================================================
NO1。
所有textBox和checkbox, radioButton等全部要用拼音尾缀来命名,不能用数字尾缀来命令,否则容易
弄错textBox,而导致隐藏的bug几个月

-----------------

有很多按钮的界面,可以把按钮的事件做成一个,然后switch (button.Name)一下,比分散写到各个事件函数里要方便:

        private void f1_h2004_Click(object sender, EventArgs e)
        {
            Button button = (Button) sender;
            switch (button.Name)
            {
                case "f1_h2004":
                    this.x_Omron.写[0].value_D500[0x1b] = 1;
                    this.x_Omron.写[0].cmd_D500[0x1b] = true;
                    break;

                case "f1_h2005":

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

vs2010里如果复制某个界面的控件到新工程,那么所有输入框事件和combox控件的改变事件都要手工拷贝来,不然很可能改值后保存不住,

而引发写入参数不是改动的值,从而参数始终不变而改不了,误以为写成功了,比如跳闸阀值,这样有安全风险!!!!

===================================================================
选择性序列化:

[Serializable]
public class MyObject
{
   public int n1;
   [NonSerialized] public int n2;
   public String str;
}

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

验证断路器等写入的参数是否正确写入时,缓存原始写入值的变量最好带绝对地址,否则按变量索引增量时容易出日后的兼容性问题:


        //v3.3:
        public int get_val_by_writed_d_addr(int addr11, List<v1000_val_cls> writed_d)
        {

            foreach (v1000_val_cls in1 in writed_d)
            {
                if (in1.addr == addr11)
                {

                    return in1.val1;

                }


            }
            return -1;

        }

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

            int binval = (int)(Math.Round(Val_to_jiaozhuan) * chengyi_100_or_1000);

如果Val_to_jiaozhuan为零点几,就会异常!!,要注意,所以改为int binval = (int)(Math.Round(Val_to_jiaozhuan* chengyi_100_or_1000) );


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


  启动主校准进程之类后,再次开始时,除了判断 是否校准中 变量外,还要判断这个主校准进程是否isalive和running

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

像jieti的断路器板子,校准时 增益点和偏移点的给定电压电流值必须为20%的比率关系,不然校不准!

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

像今闰的三相源读电压电流等返回1失败后,需要再重试2-4次!!!,不然不稳定


===================================================================
ict报警旁路打开,可能不是产品和夹具的问题,可能是铜排没有拧紧而接触不良,也可能上铜排和下铜排同时接触不良,都要拧紧才能解决

---

断路器计量型的产品不同系列可能脉冲常数不一样,导致脉冲误差检测老是50%误差之类

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


同一时刻同时2个电压源报警保护,很可能是源间回路有短路,比如换相开关和电流源的换相时的回路导致ab相间短路


,另外,如果电压源老过载保护或IGBT保护而断电,则可以串一个50欧到100欧左右的电阻,可以防止保护

===================================================================
机械手取料时料框里放个复杂图案,机械手上装摄像头来模板识别这个图案,获得料框的xy偏移,也可以用于料分类的识别,
料框里用放料孔夹死料,来限位料,这样不用视觉也可以准确取料,

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

小220v继电器换为大的220V交流接触器,是否灵敏度降低?,从而自动在毛刺时不跳


===================================================================
写串口等写函数不要存在2个以上的线程在同时写串口,即使是加了lock(obj)
,可以做成1个写thread,  然后这个 thread里接收其他线程的发串口命令,来一个一个发包,

这样可以防止比如心跳包的返回干扰其他包的返回,然后写thread里2次写间隔个20ms最好,
-------
最好做成执行完几步后进行一步校验等,比如执行完。。。。步后最后对比电压电流值是否校准通过,然后不通过时可以重试,这样即使哪步丢包或错误,也可以最终自动修正,
这样程序的稳定性和鲁棒性好点!!!


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

移植老项目到新项目里时,务必先烤出来一份,再复制,防止改乱老的


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

有时候断路器校准完不准,不是校准本身问题,还可能是电压电流可能是BCD码或解析异常,导致实际通讯过来的值不准!!

不要实时通讯监测产品上线否,而要再产品上电完成后等待5秒左右后再开始通讯,防止刚通电不稳定而乱写初始化sn等


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


UT1616串口服务器的串口接收超时设置 时间不能大于50ms?? :需要比 send...发包函数里的 wait返回信号量 的延时值要小!!

,不然容易一次收到2个包而导致校验不通过?

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


双倍误差原理,漏电一直校验不准:


不代入当前漏电流值: _Current_LD;,否则因为_Current_LD不是实时的,从而导致漏电纠正的双倍误差!,所以还不如用漏电流设定值DC_cp_power_Lou_A:


                    if (is_jiaozhun_UI1_or_Lou0_or_xiangwei2 == 0)
                    {


                        U_innn = 0;
                        I_innn = 0;

                        U_innn2 = 0;
                        I_innn2 = 0;

                        U_innn3 = 0;
                        I_innn3 = 0;


                        Lou_innn = DC_cp_power_Lou_A * 1000;//不代入 _Current_LD;,否则因为_Current_LD不是实时的,从而导致漏电纠正的双倍误差!

                    

-----------

不过以上也可能是 (byte)( (int)(Lou_innn)&0xff),(byte)(((int)(Lou_innn)>>8)&0xff) 里的漏电流float值转为int值,比如29.999 ma,会自动变为29ma,

而不是自动四舍五入而变为 30ma,从而漏电流精度有问题,而校不准

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

c#用委托类似函数指针,这样可以使2个类相互解耦,从而模块化,提高代码可阅读性

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

所有参数做成拼音变量名等,不要写死程数字,该抽象为函数的就新建立函数,这样:大改时可以批量搜索某个变量名来定位所有相关点而不会漏掉,从而防止bug

===================================================================
  this.FormBorderStyle = FormBorderStyle.Fixed3D;//需要关闭按钮, 不然卡死时关不了!!!


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


    internal static class Program
    {
        [STAThread]
        private static void Main()
        {
            bool flag;
            Mutex mutex = new Mutex(true, "HXV001", out flag);
            if (flag)
            {
                mutex.ReleaseMutex();
                Application.EnableVisualStyles();
                Application.SetCompatibleTextRenderingDefault(false);
                Application.Run(new Form1());
            }
            else
            {
                MessageBox.Show("只能运行一个程序!");
            }
        }
    }


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

随机性的不稳定也可能是 参数最大值为65535,而设为900000等之类,导致参数实际为30左右,这样的阀值遇到随机性的电流时时而过,时而没过,从而随机异常!

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

去掉最左边的空列和禁止自动添加新行,禁止拖拉列宽行宽,禁止选中行等:

        public void add_col(DataGridView inn,string id,string name,int width)
        {


            DataGridViewColumn column = new DataGridViewTextBoxColumn();
            column.HeaderText = name;
            column.Name = id;
            column.Width = width;
            column.SortMode = DataGridViewColumnSortMode.NotSortable;
            //column.CellTemplate = dgvcell;//设置模板
            inn.Columns.Add(column);

        }


        public void add_row(DataGridView inn,string first_name,int[] init_cols)
        {


           int index1= inn.Rows.Add();


           inn.Rows[index1].Cells[0].Value = first_name;

           foreach (int closindex in init_cols)
           {

               inn.Rows[index1].Cells[closindex+1].Style.BackColor = Color.Green;


           }

        }
        public void init_datadgirdview_do1(DataGridView inn)
        {

            inn.EnableHeadersVisualStyles = false;

            inn.RowHeadersVisible = false;//去掉最左边的空列

            inn.AllowUserToAddRows = false;


            inn.AllowUserToResizeColumns = false;//设置datagridview的列宽不可被用户手动拖拉
            inn.AllowUserToResizeRows = false;

            inn.SelectionMode = DataGridViewSelectionMode.FullRowSelect;


            //dataGridView1_tiaoza.EditMode==DataGridViewEditMode.EditOnEnter


            inn.CellBorderStyle = DataGridViewCellBorderStyle.Raised;//单元格无边框化

            inn.DefaultCellStyle.BackColor = this.dataGridView1_tiaoza.Parent.BackColor;

            inn.BorderStyle = BorderStyle.None;

            inn.BackgroundColor = this.dataGridView1_tiaoza.Parent.BackColor;

          

        }

        public void init_datadgirdview_yaoxin()
        {

            init_datadgirdview_do1(dataGridView1_tiaoza);

            add_col(dataGridView1_tiaoza, "id", "跳闸", 100);
            add_col(dataGridView1_tiaoza, "zong", "总线路", 68);

            add_col(dataGridView1_tiaoza, "a", "A相", 66);

            add_col(dataGridView1_tiaoza, "b", "B相", 66);

            add_col(dataGridView1_tiaoza, "c", "C相", 66);


            add_col(dataGridView1_tiaoza, "N", "零线", 66);


            add_row(dataGridView1_tiaoza, "微断死锁状态",new int[]{0});
            add_row(dataGridView1_tiaoza, "过流保护", new int[] { 1,2,3 });
            add_row(dataGridView1_tiaoza, "过载保护异常", new int[] { 1, 2, 3 });
            add_row(dataGridView1_tiaoza, "过压保护", new int[] { 1, 2, 3 });
            add_row(dataGridView1_tiaoza, "欠压保护", new int[] { 1, 2, 3 });
            add_row(dataGridView1_tiaoza, "线路电弧", new int[] { 1, 2, 3 });
            add_row(dataGridView1_tiaoza, "高温保护", new int[] { 1, 2, 3,4 });
            add_row(dataGridView1_tiaoza, "漏电保护", new int[] { 0 });
            add_row(dataGridView1_tiaoza, "三相不平衡", new int[] { 0 });
            add_row(dataGridView1_tiaoza, "缺相保护", new int[] { 0 });


            init_datadgirdview_do1(dataGridView_gaojing);

            add_col(dataGridView_gaojing, "id", "告警", 100);
            add_col(dataGridView_gaojing, "zong", "总线路", 68);

            add_col(dataGridView_gaojing, "a", "A相", 66);

            add_col(dataGridView_gaojing, "b", "B相", 66);

            add_col(dataGridView_gaojing, "c", "C相", 66);


            add_col(dataGridView_gaojing, "N", "零线", 66);

            add_row(dataGridView_gaojing, "过流保护", new int[] { 1, 2, 3 });
            add_row(dataGridView_gaojing, "过载保护异常", new int[] { 1, 2, 3 });
            add_row(dataGridView_gaojing, "过压保护", new int[] { 1, 2, 3 });
            add_row(dataGridView_gaojing, "欠压保护", new int[] { 1, 2, 3 });
            add_row(dataGridView_gaojing, "线路电弧", new int[] { 1, 2, 3 });
            add_row(dataGridView_gaojing, "高温保护", new int[] { 1, 2, 3,4 });
            add_row(dataGridView_gaojing, "漏电保护", new int[] { 0 });
            add_row(dataGridView_gaojing, "三相不平衡", new int[] { 0 });
            add_row(dataGridView_gaojing, "缺相保护", new int[] { 0 });


        }


        private void dataGridView1_tiaoza_SelectionChanged_1(object sender, EventArgs e)
        {
            dataGridView1_tiaoza.ClearSelection();
        }

        private void dataGridView_gaojing_SelectionChanged(object sender, EventArgs e)
        {
            dataGridView_gaojing.ClearSelection();
        }

        private void dataGridView_qita_SelectionChanged(object sender, EventArgs e)
        {
            dataGridView_qita.ClearSelection();
        }

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

上层是个循环,所以这里不能用 return;,需要  continue:


                              if (client1_To == null)
                                {

                                    showmsg(ip1 + " toid not find!");


                                    myClientSocket.Send(Encoding.ASCII.GetBytes("toid not find!"));
                                    continue;//上层是个循环,所以这里不能用 return;,需要  continue
                                }

===================================================================
有重发机制的返回包时最好不只用if ( rttobj.recv_ok1b == 1) 之类,而用当前包的cur_cmd字符串经由hashtable来分开置 recv_ok1b,这样
可以防止上一个包的确认包影响当前包的recv_ok1b而漏包
,
或者用 if(recv_buf[2]==100&&cur_cmd="xintiaobao" )机制来判断,但这容易永不成立???


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


以下里的if ( rttobj.recv_ok1b != 1)  可以删掉,防止上一个包的确认包影响当前包的recv_ok1b而漏包!!!:

                        while (isrunning == 1 && max_retrycount <= maxccc)
                        {

                            //if ( rttobj.recv_ok1b != 1)      
                            {


                                rttobj.v16_buffer.Clear();//v90
                                rttobj.v40_cur_datastep = 0;//v90

                                if (rttobj.v20_is_UT6616 == 0)
                                {

                                    rttobj.v16_data_recv_timeout_enable = 1;//old
                                    rttobj.v16_data_recv_timeout_ccc = DateTime.Now;//old

                                    rttobj.OP_s.SP.Write(ombuffer, 0, ombuffer.Length);
                                }
                                else
                                {

                                    Connect(rttobj.gongwei_index);
                                    SendData(ombuffer, rttobj.gongwei_index);

                                }

                                if (wait_sec_after_write >= 1)
                                {

                                    if (sleep_with_rt_ok(wait_sec_after_write * 1000, gongwei_index) == 1)
                                    {
                                        sended = 1;
                                        break;

                                    }

                                }


                            }


--------------------------


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

有时候返回包了但功能没执行到位,可能不是因为其本身,而且启动代码有问题,导致已经在工作的假象


===================================================================
读参数到输入框前先清空输入框,防止本次读失败,而遗传上次的输入框值!!

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

发包函数里的发包状态清零语句前最好加 System.Threading.Thread.Sleep(3);,防止下一个包影响上一个包的发包代码后的if(recv_ok1b!=1.........:


        public int SendPortSP_try_more(string v20_sub_cmd_is_dl_or_68xieyi, int gongwei_index, int is_rec_senddata_str0_or_rec_senddata_len1, int comm_index, string cmd1, byte[] ombuffer, int wait_sec = -1, int isnowait = -1, int isPLC = -1, int is_stopmsg_err1 = 0, int is_nowait_for_liji_closeV = 0, int recv_mubiao_length = 0, byte headbyte = 0, string sub_cmd = "", int wait_sec_after_write=-1)
        {

           


            lock (lock_obj1[comm_lock_index])//bug????
            {
                

                lock (lock_obj2_recv_timeout)
                {


                    rttobj.v16_buffer.Clear();//v80
                    rttobj.v40_cur_datastep = 0;//v400 !!!!!must 
                    System.Threading.Thread.Sleep(3);//v500,防止下一个包影响上一个包的发包代码后的if(recv_ok1b!=1.........

                    rttobj.recv_ok1b = 0;//v80
                }

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

Program.cs里加入容错机制,出错时不奔溃,而显示错误信息:

 static class Program
    {


        public static void errlog1(string str)
        {
          
            str = str.ToLower().Replace("system.", "");//去掉特征串,防止看出是c#的
            str = str.ToLower().Replace("windows.", "");
            str = str.ToLower().Replace("form.", "");

            MuNiuMa.lib.file_rw pw1 = new MuNiuMa.lib.file_rw();


            string cont1 = "";

            try
            {
                cont1 = pw1.read_a_file(System.Windows.Forms.Application.StartupPath + "/errlog1_G.txt");
            }
            catch
            {

            }
            if (cont1.Length > 1024 * 400)
            {

                cont1 = "";

            }
            try
            {
                pw1.write_a_file(System.Windows.Forms.Application.StartupPath + "/errlog1_G.txt", cont1 + "\r\n\r\n" + str + "\r\n" + DateTime.Now.ToString());

            }
            catch
            {

            }

        }


        /// <summary>
        /// 应用程序的主入口点。
        /// </summary>
        [STAThread]
        static void Main()
        {


            //处理未捕获的异常
            Application.SetUnhandledExceptionMode(UnhandledExceptionMode.CatchException);
            //处理UI线程异常
            Application.ThreadException += new System.Threading.ThreadExceptionEventHandler(Application_ThreadException);
            //处理非UI线程异常
            AppDomain.CurrentDomain.UnhandledException += new UnhandledExceptionEventHandler(CurrentDomain_UnhandledException);
                


            Application.EnableVisualStyles();
            Application.SetCompatibleTextRenderingDefault(false);
            Application.Run(new Form1());
        }


        static void Application_ThreadException(object sender, System.Threading.ThreadExceptionEventArgs e)
        {
            string str = "";
            string strDateInfo = "出现应用程序未处理的异常:" + DateTime.Now.ToString() + "\r\n";
            Exception error = e.Exception as Exception;
            if (error != null)
            {
                str = string.Format(strDateInfo + "异常类型:{0}\r\n异常消息:{1}\r\n异常信息:{2}\r\n",
                error.GetType().Name, error.Message, error.StackTrace);
            }
            else
            {
                str = string.Format("应用程序线程错误:{0}", e);
            }

            errlog1(str + "系统错误002");
            Form1.refwin1.libcls.MessageBoxw("系统错误002:"+str);

           


        }

        static void CurrentDomain_UnhandledException(object sender, UnhandledExceptionEventArgs e)
        {
            string str = "";
            Exception error = e.ExceptionObject as Exception;
            string strDateInfo = "出现应用程序未处理的异常:" + DateTime.Now.ToString() + "\r\n";
            if (error != null)
            {
                str = string.Format(strDateInfo + "Application UnhandledException:{0};\n\r堆栈信息:{1}", error.Message, error.StackTrace);
            }
            else
            {
                str = string.Format("Application UnhandledError:{0}", e);
            }

            errlog1(str + "系统错误003");
            Form1.refwin1.libcls.MessageBoxw("系统错误003:" + str);
        }
    }

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

                       


                        USART_SendData(USART1, send_temp[ii]); 
                    
                    
                        while(ccc1<1999999&&USART_GetFlagStatus(USART1,USART_FLAG_TC)==RESET){ccc1++;}
                    
                    
   会少一字节,而要:

                    
                        while(ccc1<1999999&&USART_GetFlagStatus(USART1,USART_FLAG_TC)==RESET){ccc1++;}
                    
                    
                        USART_SendData(USART1, send_temp[ii]); 
                    
                    
第一字节消失的解决方案:调用USART_SendData函数之前,先将‘TC’标志位清‘0’就OK,

代码如下

?

{
   

 USART_ClearFlag(USART1, USART_FLAG_TC);
  

  USART_SendData(USART1, dat);
   

 /* Loop until the end of transmission */
   

 while (USART_GetFlagStatus(USART1, USART_FLAG_TC) == RESET)
    {


}
}
?


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


电压输出函数里加【int need_panduan_stop_jisozhuan_flag】参数,如果按了软件里的急停按钮,则先调用输出0电压的函数,然后所有jinrun_output_UI
里的if (need_panduan_stop_jisozhuan_flag == 1)的部分的调用全部直接return,来防止后续再意外输出电压

        public int jinrun_output_UI(float U_out_val, float I_out_val, float UI_jiaodu,int need_panduan_jiaozhuan_ing,int need_panduan_stop_jisozhuan_flag)//UI_jiaodu=60度时功率因子为0.5
        {
            if (g_debug_is_ONE_PROD_mode == 1)
            {


                return 0;
            }

            if (need_panduan_stop_jisozhuan_flag == 1)//v400
            {


                if (stop_jiaozhuan_flag == 1 || is_jiaozhun_ing == 0)
                {

                    return 0;


                }


            }

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

在调试stm8/stm32时,发现把单片机程序串口设置为8位数据位,偶校验,上位机设置为8位数据位,偶校验数据一直不对,当把单片机程序改为9位数据位,偶校验,上位机设置为8位数据位,偶校验数据就对了,不知道为什么?是不是st单片机的一个bug


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

用 序列化类来保存参数时,各参数类务必不要含类的引用,而要用类的my_ID形式来保存引用哪个ID的类,否则序列化后类的引用丢失!!

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

删除元素时务必在for (i。。。。扫描外再套层 while (ishave == 1),不然某些条件下会漏删:


int ishave=1;//must 1


                while (ishave == 1)
                {

                    ishave = 0;


                    for (int ii = 0; ii < sim_UI_cls.all_yuanjian_links.Count; ii++)
                    {

                        if (sim_UI_cls.all_yuanjian_links[ii].yuanjian_id_form == pw_draw_shows.last_moveover_ele.yuanjian_id)
                        {

                            sim_UI_cls.all_yuanjian_links.RemoveAt(ii);

                            ii = 0; ishave = 1;
                            continue;


                        }

                        if (sim_UI_cls.all_yuanjian_links[ii].yuanjian_id_to == pw_draw_shows.last_moveover_ele.yuanjian_id)
                        {

                            sim_UI_cls.all_yuanjian_links.RemoveAt(ii);

                            ii = 0; ishave = 1;
                            continue;


                        }


                    }
                }


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

设计pcb时元件角度尽量一致,这样制作贴片文件时简单点,100个led灯以上时,led公共脚做成可选vcc或 gnd的,防止led贴反


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

1.网络等通讯时用 把整个 ini配置文件的字符串形式发出去,比用 | 号隔开每个项要好维护点!!


2.串口或网口通讯可以先用 解析协议里的长度字串方式或求crc是否通过的试探方式,比【sleep 60 ms加在串口接收中断开头】要快,
如果解析协议方式受不到包,可以再用【sleep 60 ms加在串口接收中断开头】方式再收一次,即二合一方式:

而且不要用while(..){if ret_ok==1...形式,而要用
System.Threading.AutoResetEvent recv_ok1 = new System.Threading.AutoResetEvent(false)

和recv_ok1.waitOne(120); 和  rzecv_ok1.Set形式!!!

--------------------------------------------------------

                                int is_sumok = 0;
                                int is_sumok2 = 0;
                                if (rttobj.v40_cur_is_dl1_or_68xieyi0 == 1)
                                {


                                    if (is_DL_sum_ok(rttobj.v16_buffer.Count, rttobj.v16_buffer.ToArray(), gongwei_index) == 1)
                                    {
                                       
                                        is_sumok = 1;
                                    }


                                }

                                if (rttobj.v40_cur_is_dl1_or_68xieyi0 == 0)
                                {


                                    if (is_68_xieyi_sum_ok(rttobj.v16_buffer.ToArray(), rttobj.v16_buffer.Count) == 1)
                                    {
                                      
                 
                                        is_sumok2 = 1;

                                    }

                                }

                                int is_len_oked = 0;


                                if (v40_cur_datastep_flag == 1 || v40_cur_datastep_fla2g == 1)//v40_cur_datastep_flag==1指:需要立即 经过if (rttobj.v16_buffer.Count >= rttobj.v40_cur_datalen)时
                                {

                                    if (rttobj.v16_buffer.Count >= rttobj.v40_cur_datalen)
                                    {

                                        if (g_enable_sum_ok_log == 1)
                                        {
                                            MuNiuMaLib.fvdou_append_error_to_log_txtH("DL_68_sum_ok","rttobj.v16_buffer.Count="+rttobj.v16_buffer.Count.ToString()+", rttobj.v40_cur_datalen="+ rttobj.v40_cur_datalen.ToString()+ ", ok!,rttobj.v40_cur_is_dl1_or_68xieyi0=" + rttobj.v40_cur_is_dl1_or_68xieyi0.ToString());//for解决重合闸断路器偶尔开上位机时通讯异常
                                        }

                                        is_len_oked = 1;

                                        //rttobj.v16_recvbuf_len = rttobj.v16_buffer.Count;


                                        //rttobj.v16_recvbuf = new byte[rttobj.v16_buffer.Count];
                                        //rttobj.v16_buffer.CopyTo(rttobj.v16_recvbuf, 0);

                                        //rttobj.v16_buffer.Clear();

                                        //rttobj.v40_cur_datastep = 0;

                                        //return 1;//return 1回来后TCPReadCallBack已经BeginRead


                                    }

                                }

                                if (is_len_oked==1&&(is_sumok == 1 || is_sumok2 == 1))
                                {

                                    if (g_enable_sum_ok_log == 1)
                                    {
                                        MuNiuMaLib.fvdou_append_error_to_log_txtH("DL_68_sum_ok", "is_sumok == 1  ok!,rttobj.v40_cur_is_dl1_or_68xieyi0=" + rttobj.v40_cur_is_dl1_or_68xieyi0.ToString());//for解决重合闸断路器偶尔开上位机时通讯异常
                                    }
                                    rttobj.v16_recvbuf_len = rttobj.v16_buffer.Count;


                                    rttobj.v16_recvbuf = new byte[rttobj.v16_buffer.Count];
                                    rttobj.v16_buffer.CopyTo(rttobj.v16_recvbuf, 0);

                                    rttobj.v16_buffer.Clear();

                                    rttobj.v40_cur_datastep = 0;

                                    return 1;//return 1回来后TCPReadCallBack已经BeginRead


                                }

                            }

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

参数类和状态类等最好和工位一起统一为1个类,比如:

如果重构,就把
cp_info_cls_info类和pw_addr_used_info类都写到OP_with_com_name类里!!!

,然后地址固定为 1+工位号,或yike协议的:20+工位号 , 而不要用分配地址方式,地址固定为和工位号有关是最稳定


================================================================================================
上位机要配mysql 绿色版,比 access稳定多了!!!


c#界面大字可以用textbox无边框模式,

c#表格可以用 panel 的边框模式,多个panel组装成表格

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

    多次重试初始化声卡,防止初始化失败:
    
    if (wm8978_Init()==0&&wm8978_Init()==0&&wm8978_Init()==0)//
    {
        
================================================================================================
        
多线程里每次加一的id之类做增量缓存机制,只第一次加并存到缓存,后续读缓存,防止多加了一次!!

然后产品通讯下线时才清除此缓存
================================================================================================
以下可以防止点击任务卡UI:

        public delegate void p_void();

        int ingsetrun = 0;

        public void setrun(p_void p1)
        {

            if (ingsetrun == 1)
            {

                return;

            }


            ingsetrun = 1;
            try
            {

                System.Threading.Thread pt1 = new System.Threading.Thread(new System.Threading.ThreadStart(p1));

                pt1.Start();

                //while (pt1.ThreadState == System.Threading.ThreadState.Running || pt1.ThreadState== System.Threading.ThreadState.Unstarted)

                while (pt1.IsAlive)
                {
                    Application.DoEvents();
                    System.Threading.Thread.Sleep(5);//10的话太卡,小点,因为Sleep太大的话退出等待要卡很久!!


                }

            }
            catch
            {

                ingsetrun = 0;
            }
            ingsetrun = 0;

        }

                    p_void p1 = () =>
                    {
                      ......................


                    };

                    setrun(p1);

================================================================================================
c#里:sleep 60 ms加在串口接收中断开头,或socket异步读取时的开头里的妙用,解决数据包的半包和粘包问题,加大比特率解决延时慢,

ping多线程卡串口接收,

有的设备有保存标志,置位后才保存,

modbus3区寄存器不能用4区的命令来读,

恒流源量程没设对可能导致精度异常,


线程里多个函数调用,延时大而影响其他函数的实时性,应多线程化调用各个函数。

发送函数可以加lock机制,一次只发送一个,防止并发冲突

chkintertert开电脑时可能一直失败而不稳定

欧姆龙plc监视模式下hostlink才能写变量,考虑模式设置能长时间保存不
-----------------------------------------

对于要多处来回拷exe调试的情况,可以集中一台电脑来编程,其他电脑只 mstsc远程桌面式编程,编完后考虑exe到其他电脑,这样可以防止源码最新版本的版本混乱!!


,这样也不好,可以用 seafile +服务端来同步源码版本!!!,可以设置覆盖时的历史备份数!!

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


单片机控制步进电机的代码里步进脉冲由timer发生时,加减速曲线时timer的频率的控制有  hz1=hz1+step的每步加上一个量的方式会很稳定而不丢步,

如果用hz1=hz_max*percent1的方式计算会丢步!!!,

另外,用当前左边的绝对步数来输出要运动的量比传输移动增量要稳定!!,可以消除浮点不精确性的累计误差!!!

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


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

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

74hc595等状态锁存芯片要定时main函数里刷新595芯片的状态数据,就像DRAM一样刷新,否则受干扰容易出错位

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

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<head_and_wav_len1);
                
                
                
比以下代码好维护得多:


                                
                                for(iiii=0;iiii<head_and_wav_len1;iiii=iiii+perwritenum){
                                    
                                    
                                    if(perwritenum<head_and_wav_len1){
                                        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<string> 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<string> 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鼠标手型效果 <a href="#" style="cursor:hand">CSS鼠标手型效果</a>
Example:CSS鼠标手型效果 <a href="#" style="cursor:pointer">CSS鼠标手型效果</a>
注: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<string> 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()更多次,来辅助界面线程!
                    }


,另外用<div flag='ruanflagdiv1' style='width:750px;  (即限定div容器和最小宽度:750px) :
     rwfile11.write_a_file(System.AppDomain.CurrentDomain.BaseDirectory + "\\..\\maketemp\\" + file11a, "<html><body><style>body{max-width:790px;max-height:16000px;font-size:12px;padding:0px;margin-left: 0px;margin-top: 0px;margin-right: 0px;margin-bottom: 0px;} \r\nimg{border:0px}a{ text-decoration:none;}</style> <div flag='ruanflagdiv1' style='width:750px;margin:0px;padding:0px'> " + prod1_html1a.Replace("<body>", "").Replace("<Body>", "").Replace("<BODY>", "").Replace("</BODY>", "").Replace("</Body>", "").Replace("</body>", "") + " </div> </body></html>

回复评论

暂无评论,赶紧抢沙发吧
电子工程世界版权所有 京B2-20211791 京ICP备10001474号-1 京公网安备 11010802033920号
    写回复