目录
一、软件调试四个目标
二、裸机软件调试
三、裸机编程
五、单片机软件调试中常见的案例(杂记)
往期系列文章:
1、裸机项目开发经验分享 - 完整开发流程介绍(项目规划与执行、器件选型、资料检索、产品测试思路等)
2、裸机项目代码设计与管理 - 项目程序的组织管理、设计优化思路等经验分享
3、裸机编程与调试 - 在项目中遇到的各类问题,解决经验分享(常见新手问题)
4、在实际开发项目中总结的硬件调试经验分享 - 调试目标、方法,以及常见调试问题解决
5、基于AltiumDesigner软件的PCB,原理图设计完整介绍,项目经验分享 [硬件开发设计]
6、硬件开发设计 - 焊接电路板,介绍焊接概念,焊接步骤,常见错误,难点等
7、电池供电应用中的电源设计:干电池特性了解、LDO与DC-DC选型设计、电流检测方案要点
虽然传闻,项目程序三分编写,七分调试;但在执行项目程序之前必须有良好的思路和规划,并不是胡乱一通写完,这样只会大大增加多余调试以及返工风险;
一、软件调试四个目标
*项目的开发时间基本用在调试与及BUG的修复(维护)
1、确保程序表达书写正确(多练);
2、程序流程是否正确(中断正常跳入、触发一些条件时是否能进入相应的流程);
3、变量的值是否正确(必要时可以观看寄存器的值);
4、引脚的电平变化是否正确(示波器捉取);
二、裸机软件调试
*积累寻找软硬故障的经验,当你积累上百个问题的时候,你会很自然地处理其它问题;
1、C语言的一般错误:内存泄漏(数组越界)、堆栈溢出、内存错误、字符串问题;
2、在定义变量后不要超过变量的作用范围,如果超过变量的范围赋值,C编译器并不报错,但程序运行结果却错了,而且这样的错误很难发现。
3、程序调试与硬件调试一样,需要分模块进行调试,软件更需要细分调试[硬件层(时钟、中断)、驱动层(IIC、SPI、UART、GPIO)、应用层(逻辑控制)],程序的确定性比硬件高,不要害怕程序问题;
4、调试方法一般方法:演绎法、强制纠错法(转换语句表达)、串口打印;
5、单片机的时钟配置、看门狗时钟配置、中断设置、RAM初始化等在系统配置文件中执行,启动文件( 裸机的bootloader );
6、裸机程序调试
a、从GPIO输出输入开始检查(示波器检测高低电平波形是否正确)
b、根据软件的控制程序去查看硬件表现是否正常;
c、保证硬件连接无误再进行软硬联调;
d、有时考虑硬件的干扰也是解决问题的关键(语音IC连线太长)
e、配置顺序问题 - AD(开启时钟与上电使能顺序);
f、漏配置问题 - 时钟没启动、使能;
g、延时问题 - 时钟频率没稳定,配置没稳定;
h、时序问题;
7、调试过程中不同编译器对寄存器的操作有所不同;
a、ARM仿真器进行仿真、调试时,不进行操作内部各种寄存器与状态寄存器还会改变;
b、有的寄存器标志位在读取操作会改变;
c、复位重启的重要性:地址的重装载;一些数据的重新初始化;寄存器标志位的清除;
8、善于利用串口打印运行信息,最简单也最有效;
9、程序使调试出来的吗: http://blog.csdn.net/zhangxingping/article/details/6899293
10、查看堆栈溢出: http://qshanbao.blog.ustc.edu.cn/?p=809
11、IO 口查看: http://blog.csdn.net/googlemi/article/details/38470565
12、解决问题的能力(心态十分重要):
a、冷静分析,捕捉更多的现象和解决方案;
b、现象复现;
c、对整个项目的完整把握,才能做出有效的分析!!!!(最稳重的解决思路);
d、解决问题的清晰思路十分重要;
e、心态与技术同样重要;
13、清晰各种千奇百怪的错误的特征,积累以及解决;
三、裸机编程
1、准备工作
a、成功的软件(demo)和硬件(开发板);主要用于对比分析问题;
b、有类似的调试经验能快速上手;
2、单片机内部空间有自己操作外设的ROM(利用指针函数触发一下,即可执行特定操作,用户无法了解其实现过程);
3、先把前人的路演习一遍,然后构建自己的项目,后期不断把学习到的新东西应用在新项目上,不断优化自己的程序架构和形成风格;
4、编写程序命名 (标准):
a、函数名称:驱动层+芯片+部件+事件+操作;
b、常量名称:功能+操作;
c、变量名称:变量类型+功能+操作;
d、对齐(函数段对齐)
e、命名清晰
5、程序的模块化与函数不同:
a、模块化是指将复杂的程序功能化整为零而成的功能块,一个模块可能由多个函数组成,可能只有一个模块,可能是紧密相连的代码块;
b、函数是为了将需要多次使用的代码统一编写;
6、含中断的程序,易出BUG;用并行的想法去编写;调试时多思路去解决;
7、程序编写就是集成的过程,多看例程(积累的过程);
8、使用程序状态标志变量的基本原则:默认状态->状态1->状态2->状态3->默认状态;
9、复用代码放在h文件,必须避免代码重复;
10、尽量以库函数接口形式编写函数,减少函数的耦合性(输入参数以及引用参数);
11、形成标准以及自我风格的编写风格(命名、缩进、标注、对齐、文件架构等 参考成熟的代码文件);
12、
程序尽量选用模板去编程,因为有很多设置会导致错误;
13、配置一个模块之前要了解以下几点:
a、IC实现功能的原理以及结构简单了解;
b、通讯接口以及协议,软件必须做到无一错误;
c、一些注意事项,(无论硬件或者软件),往往是解决问题的关键;
d、硬件连接图以及原理;
e、应用场景以及选型优势;
14、单片机是一种面向流程化的开发;
a、狭义上:初始化程序的按照一定的顺序才能被正确执行;
b、广义上:必须确保每个流程都是定向的;
15、if与for函数如果只有一行代码,则可以省略符号{ };“
勘误:任何函数只有一行代码都可省略{ }”
16、GPIO的中断开关要控制好,不用的一定要习惯关上,会影响其他的中断;
17、注意编程软件自动把无用的语句自动清除(优化),如定义变量没有使用;
18、变量必须需赋初值;静态变量不赋初值默认为零;
19、*(unsigned int *)0x002200 &= ~(1<<0); 左值必须为变量,可赋值的symbol;
五、单片机软件调试中常见的案例(杂记)
1、单片机驱动最好参照官方以及成熟的例子,设计模式,不然会出很多岔子;
2、当你的程序较大时,就需要注意内存是否够用(常量与静态变量的储存);
3、当中断多时切记注意栈的大小是否够用,不够用会造成数据的覆盖,导致程序崩溃;
4、申请大内存缓冲区必须慎重,尤其函数嵌套的栈内存不可估计,单片机内存宝贵,需要慎重使用。
5、*程序的死机原因(满足一定条件才触发,最难找出问题的出处,故编程时考虑排除问题的技巧设置):
a、指针越界;
b、数组越界;
c、使用memcpy( );导致司机;
d、导致死机的根本原因是误操作内存;
e、RAM内存溢出;
f、内存泄漏!
g、读了只读内存;会导致死机;
h、指针越界会导致死机;
6、节省ROM内存:
a、换成裸机驱动;
b、节省打印信息;
c、使用外部EEPROM储存信息;
d、多使用重复代码函数封装;
e、手工代码优化: http://bbs.21ic.com/icview-734770-1-1.html ;
f、编译器优化:1)编译压缩比: https://jingyan.baidu.com/article/546ae185d915971148f28c69.html ; 2)发行版本
7、程序的压缩思路:
a、减少重复代码(利用函数、宏定义等方式将重复代码封装);
b、删减,优化程序(删除调试信息输出);
c、调整编译器压缩比;
8、压缩程序思路:
a、优化蓝牙控制引脚;
b、清除错误表达与串口调试语句;
c、换用裸机驱动;
d、多使用重复代码函数封装;
9、一些时而出现时而好现象分析:
a、
现象:AD睡眠后醒来读取不了数据;
解决:睡眠前解初始化,醒来后重新初始化;
b、
现象:IIC通讯传感器,一半通讯正确,一半则无效;原因是原理图出错,导致通讯时地址有时有效,有时没效;
解决:示波器检查通讯的正常性;
10、中断向量读取标志->导致中断不断触发,主程序无法继续;
11、低功耗调试:
a、功耗的产生的根本原因:1) 引脚之间存在电压差,电流不断流走 2) IC内部引脚的置高置低;
b、IO配置模式(IIC外接上拉:需要配置上拉输出);
c、SPI配置空闲模式,下拉输出;
d、其余配置空闲模式,下拉输出;
e、掌握功耗的一些细节:
1)mA级以上一般是模块一直在工作中;
2)uA(百位级):有如:(1)FLASH(品牌不对,导致驱动不匹配);(2)模块配置正确(模块没接上,输出/输入要对应);
3)uA(百位以内):重点考察模块配置出错;
f、LED等产生的1mA功耗;
g、中断引脚的低功耗处理;比如滑盖、按键在正常使用情况下为一直按下(接地状态):
1)GPIO配置为浮空,外部接2MΩ上拉电阻,消除高功耗;
12、仿真调试:
a、一般驱动才会用到仿真,实时仿真少用;
b、芯片深度睡眠难以仿真,采用实时调试;
13、宏定义定义的常量类型默认是int类型;
14、对地址进行强制转换类型(避免指针内存混乱):(void*) -> (uint8_t *)
15、电源问题导致MCU与WIFI模块的串口通讯出现问题;
16、软件控制逻辑:
a、当控制逻辑增加时,必然存在很多非正常操作导致的BUG,组合BUG、综合BUG的出现;
b、尽量控制单线程控制,减少组合逻辑混乱,尽管限制单逻辑响应速度不够快;
17、蓝牙通信一些健壮性手段:a、重发机制;b、校验机制;c、等待超时;
18、切勿设置过多的标志位;尽量简化整个程序;
a、busy标志位的重要和运用,占用与释放;
b、全局标量转化为局部变量;
c、全局标志位 转化为 函数的参数,这样增加了灵活性和降低复杂度;
20、AD的程序与例程的位置不一致(校准与分频要适才行);
22、一定要消息数据类型的判断,别溢出,否则出现死机情况;
a、定时器在不断地进行自增操作(unsigned char类型),所以在系统运行一段时间后就出现死机,程序跑飞;
23、while(retry_Times--);慎用,理清楚自减后的条件(先自减,再调用);
24、PL2303波特率最高支持115200!!!!
25、SPI读写FLASH
a、W25Q16/W25Q32通用;
b、同一种类型,不同牌子的芯片可能会存在操作时序的差异;
c、24C32/24C64,地址差异一个字节;驱动时注意时序操作;
26、单片机中断处理:
a、中断函数一定要轻盈化,快进快出;
b、中断不做任何处理(只做倒计时),这样可以减少很多不可预见性的问题;
27、一些局部变量(自增或自减),尽量添加volatile修饰,以防被编译器优化失效:
a、加上volatile的情况:标志位、递减值、重要寄存器;
28、框架结构灵活:是以牺牲内存空间、架构臃肿为代价;
a、兼容性的处理办法一般采用条件编译;
29、善用指针操作:
a、利用指针自加操作,可以节省运行栈内存,运行速度更快;
b、保存接收的数据,以防止被覆盖;
c、善用指针能减少大量内存;
30、串口乱码缺失问题:
a、刷新睡眠时间;
b、优先开启串口初始化以及开启中断;
31、时得时不得的现象解决思路:
a、考虑输入参数或者其他调用的参数出错,出现冲突;
b、更换参数的表达方式;
32、独立调试的时候能成功,在文件中不能正常:
a、原因,数据冲突,解决办法:使用全局变量时先清空;
33、蓝牙到APP数据出现断包现象;解决:更改串口接收条件;由超时退出变为检测+超时机制;
34、AD软件配置问题:
a、分频数太大,导致AD检测存在不稳定状态;
b、ADC初始化配置有误(一定要遵循官方的设计);
35、keil编译器没有报错,下载程序后单片机不运行,原因是单片机内存不足导致;
36、按键不稳定,误触唤醒灯问题解决:
a、将按键处理搬出中断处理;
b、更换初始化标志变量;
c、外加上拉、滤波电路;
d、增加判断机制:如若唤醒后没有用户操作,执行立即睡眠;
37、影响堆栈的可能:
a、全局变量、缓冲区、for循环、递归、函数的嵌套;
b、keil自带启动代码模板,自动将剩余的data ram空间分配给栈;
38、LPC824 GPIO口配置问题:
a、配置为消极模式后,唤醒需要增加配置GPIO复用功能,然后才能正常配置;
39、指纹模块调试:
a、指纹重复录入;返回的错误代码不兼容新版本;
b、开发过程中,模块的资料都是不断更新的,注意做适配;
c、模块的问题;(指纹模块由于内存不足,导致不同手指录入都成功);
d、指纹头反应不灵敏,供电为生电导致的;
e、电源波动导致指纹触摸失效;
f、固件出错,部分指令失效;
g、辨识度不够,无反应等问题;更新固件;
40、初始化顺序十分重要,千万不能随意更改,即使行得通,也会存在极大的隐患;
41、蓝牙锁板电路设计注意:
a、电机与电源端接近,并且增加线路的横街面积;
b、采用钽电容稳压;
42、BOD配置时睡眠时没有启动时钟,导致清除标志位失败,系统一直复位(系统唤醒后检查复位标志位,发现复位标志位有效则进行复位);
a、每一种外设都有对应的时钟开启,发现外设失效时首先考虑外设时钟是否正常;
43、触摸按键的抗干扰设计:
a、抗干扰设计(串入电阻、滤波电容接地);
b、电源RC滤波电路;
44、RFID读卡灵敏度调试:1)一、匹配电路 二、接收电路(滤波电容、匹配电阻) 三、LPCD参数调整;(唤醒幅度、接收放大);
45、注意器件的上电初始化问题,一定的延时十分重要(指纹上电需要延时200ms才能正常发送指令);
46、单片机的串口打印十分耗时;
47、IO复用问题:
a、烧录引脚可以复用;
b、复用思路:分时复用引脚;
48、keil搜索全词匹配,注意空格也算字符,搜索时有差异;
49、全局数组变量数据被踩踏,原因排查: 1)检查局部变量在某一时刻的定义是否栈溢出了;2)检查全局变量的定义是否过大;
51、PL2303波特率最高支持115200;
52、串口卡死不能打印,晶振频率不同,按照公式修改寄存器值;
53、RTC时钟问题,外部晶振配置问题;
54、MKL16看门狗问题:1)初始化必须在SystemInit(void),内完成; 2)SystemInit(void);调用无法查看;
55、按键在主循环检测,注意与taskglobal的任务冲突,操作逻辑问题;
56、单片机栈空间很小,注意嵌套的局部变量累计超过栈空间(函数返回才会释放栈内存),造成溢出;
57、strcat输入的第一个参数必须是确定大小的数组,指针会死机;
58、局部缓冲区定义过大(比如为1K),全局数组变量空间数据会被修改!
59、IC、ID卡分类:
a、频率:13.56Mhz 高频卡 卡号:4字节(一般都是这种)、7字节、10字节
b、频率:125khz 低频卡 卡号:固定为10位
c、TypeA:M1卡、银行卡、公交车卡 TypeB:身份证
60、FLASH读写速度比EEPROM更快;
61、理清字符串与数字之间的关系;ASCII与数字之间的关系;内存中储存形式一致,实质是解释不同;
62、函数传递进来的指针数据,应该尽量保存在本地,这样操作安全并且速度快;
63、选用for(;;) 比while(1)更节省空间;
64、有些情况寻找中间变量会更节省运行时间; 如a->b->c->age,引用过长,就需要引用中间变量;
65、同时声明多个变量优于单独声明变量;
66、数据域出错,全局变量覆盖;解决局部变量改全局变量;
67、一些头文件符号缺失或错误(不会出现在问题行,而是指向C文件)
68、GPIO口不能直接赋值,用setbit或resetbit
69、数值超出范围uint8_t uint16_t(integer conversion resulted in truncation)
70、stm8头文件定义在C文件或者h文件都是可行的
71、注意变量需要初始化(赋0),不赋值的话,初始值为随机值;
72、注意使用寄存器值的时候尽量用位与清零,尤其是复杂的程序时,防止不明导致寄存器值的变化,良好习惯;
73、使用memcpy();拷贝数组缓冲区不足,指针越界导致程序死机;
------------------------------End of the article--------------------------------------------