Keil相关

Keil中常见的几种警告,固然,相对于错误的,警告的程度不及错误的严重性,有时候忽略,会出现意想不到的错误。先看看常见的几种错误,分析出来现的原因。

1、Warning 280:’i’:unreferenced local variable
说明 局部变量 i 在函数中未作任何的存取操作
解决方法 消除函数中 i变量的宣告

2、Warning 206:’Music3’:missing function-prototype
说明 Music3( )函数未作宣告 或未作外部宣告 所以无法给其他函数调用
解决方法 将叙述void Music3(void)写在程序的最前端作宣告 如果是其他文件的函数则要写成 extern void Music3(void),即作外部宣告

3、Compling :C:\8051\MANN.C
Error:318:can’t open file ‘beep.h’
说明 在编译 C:\8051\MANN.C 程序过程中 由于 main.c 用了指令#include “beep.h”,但却找不到所致
解决方法 编写一个 beep.h 的包含档并存入到 c:\8051 的工作目录中

4、Compling:C:\8051\LED.C
Error 237:’LedOn’:function already has a body
说明 LedOn( )函数名称重复定义 即有两个以上一样的函数名称
解决方法 修正其中的一个函数名称 使得函数名称都是独立的

5、***WARNING 16:UNCALLED SEGMENT,IGNORED FOR OVERLAY PROCESS
SEGMENT: ?PR?_DELAYX1MS?DELAY
说明 DelayX1ms( )函数未被其它函数调用 也会占用程序记忆体空间
解决方法 去掉 DelayX1ms( )函数 或利用条件编译#if …..#endif,可保留该函数并不编译

6、***WARNING 6 :XDATA SPACE MEMORY OVERLAP
FROM : 0025H
TO: 0025H
说明 外部资料ROM 的 0025H重复定义地址
解决方法 外部资料 ROM的定义如下
Pdata unsigned char XFR_ADC _at_0x25 其中 XFR_ADC 变量的名称为0x25,请检查是否有其它的变量名称也是定义在 0x25 处并修正它

7、WARNING 206:’DelayX1ms’: missing function-prototype
C:\8051\INPUT.C
Error 267 :’DelayX1ms ‘:requires ANSI-style prototype C:\8051\INPUT.C
说明 程序中有调用DelayX1ms 函数 但该函数没定义 即未编写程序内容或函数已定义但未作宣告
解决方法 编写 DelayX1ms 的内容 编写完后也要作宣告或作外部宣告 可在 delay.h的包含档宣告成外部 以便其它函数调用

8、***WARNING 1:UNRESOLVED EXTERNAL SYMBOL
SYMBOL:MUSIC3
MODULE:C:\8051\MUSIC.OBJ(MUSIC)
***WARNING 2:REFERENCE MADE TO UNRESOLVED EXTERNAL
SYMBOL:MUSIC3
MODULE:C:\8051\MUSIC.OBJ(MUSIC)
ADDRESS:0018H
说明 程序中有调用 MUSIC 函数 但未将该函数的含扩档 C 加入到工程档Prj 作编译和连接
解决方法 设 MUSIC3 函数在 MUSIC C里 将 MUSIC C添加到工程文件中去

9、***ERROR 107:ADDESS SPACE OVERFLOW
SPACE: DATA
SEGMENT: _DATA_GOUP_
LENGTH: 0018H
***ERROR 118: REFERENCE MADE TO ERRONEOUS EXTERNAL
SYMBOL: VOLUME
MODULE: C:\8051\OSDM.OBJ(OSDM)
ADDRESS: 4036H
说明 data 存储空间的地址范围为 0~0x7f,当公用变量数目和函数里的局部变量 如果存储模式设为SMALL 则局部变量先使用工作寄存器R2~R7作暂存 当存储器不够用时则会以 data型别的空间作暂存的个数超过 0x7f 时就会出现地址不够的现象
解决方法 将以 data 型别定义的公共变量修改为 idata型别的定义

10、“*** WARNING L1: UNRESOLVED EXTERNAL SYMBOL”
*** WARNING L2: REFERENCE MADE TO UNRESOLVED EXTERNAL
如果你在用C51编译器出现上面的警告,这个只是初学者和粗心者才会犯的错误:没把C文件添加到项目中!
另外,还有可能是因为存在没有被调用的已经定义的函数,或者相关的已经定义的变量没有使用。

(直接点击图片可进入报名页面)

开发板测评图片
围观 415

一、配置问题:

实际烧片时单片机的配置字与仿真的时候是不是一样?烧写时是否配置正确?

二、资源问题:

单片机的ROM、RAM大小与仿真器的ROM、RAM大小是不一样的!

三、驱动能力问题:

通常仿真器的驱动能力比单片机要强;可以分两步检查,一是分别用仿真器与单片机时对应IO口波形进行对比,二是当仿真器与外设连接与不连接时波形的对比;
  
要进行基本要素分析:单片机供电正常?起振没有?复位电路正常?芯片损坏?

单片机上电不运行情况的分析
 
1、单片机是没有运行,还是运行不正确?
  
2、如果是没有运行,检测单片机的基本参数是否已经满足<工作电压要在芯片的引脚处测量><复位引脚的复位曲线如何><晶振是否已经起振><芯片损坏,或者部分功能损坏>。
  
3、如果是运行不正确,检测<用了看门狗,没有处理好,芯片上电后处于不停的复位状态,看起来就象没有没有工作起来><是否进入了ISP状态><对单片机写入对所有的IO口以1秒取反一次的程序进行测试>

单片机调试办法

用LED进行状态指示,用串口进行数据指示,比用其它外设指示更可靠;

加入断点,烧写时是在不同的语句后面加入WHILE(1);

分模块调试,把每个小功能先调试通过,再进行组合。
  
Keil C本身就是编译与仿真一体的,当不要外部数据时很方便,当要外部输入时(比如用定时器作计数器用)它提供几个调试输入用的窗口可用它们来摸拟输入,同时Keil C本身提供一种调试函数用来配置摸拟外部输入的功能,这我在学习时看的一本书上讲过如用定时器作外部事件计数,调试函数由自己根据需要按特定的格式来编辑再按KEIL C的要求调入即可。学习时可用这种方法,做产品时最好是用仿真器调试。下面我给出我以前试过的用定时器作外部事件计数时摸拟外部输入的调试函数,并写出Keil C调用的步骤如下: 

1、编写如下信号函数:

  single void t0_singal(void)
  {
  while(1)
  {
  PORT3|=0x10;//pull INT0(P3.4) high again
  PORT3&=~0x10;//pull INT0(P3.4) low and generate interrupt
  PORT3|=0x10;//pull INT0(P3.4) high again
  twatch(CLOCK);//wait for 1 second
  }
  }
  
2、在DEBUG状态下单击DEBUG菜单在下拉菜单中单击“Function Editor(Open Ini File)... "选项,弹出新屏幕后,将“打开”窗口关闭,这时屏上有Function Edito窗;
  
3、在Function Editor窗中输入上面的函数;
  
4、保存(注意:扩展名为.ini),编译该程序,成功后关闭Function Editor窗口;
  
5、执行用户程序(必须连续执行);
  
6、在屏幕左下脚的命令窗口中,键入t0_singl()后(必须回车)妈可向P3.4源源不断地提供脉冲信号。
 
Keil C调试某系统时积累的一些经验

1、由于Keil C对中文支持不太好,因而会出现显示的光标与光标实际所在不一致的现象,这会对修改中文注释造成影响。在Windows2000下面,我们可以把字体设置为Courier,这样就可以显示正常。
  
2、当使用有片外内存的MCU(如W77E58,它有1K片外内存)的时候,肯定要设置标志位,并且编译方式要选择大模式,否则会出错。
  
3、当使用Keil C跟踪程序运行状态的时候,要把引起Warning的语句屏蔽,否则有可能跟踪语句的时候会出错。
  
4、在调用数组的时候,Keil C是首先把数组Load进内存。如果要在C中使用长数组的时候,我们可以使用code关键字,这样就实现了汇编的DB的功能,Keil C是不会把标志code的数组Load入内存的,它会直接读取Rom。
  
5、当编程涉及到有关通信,时序是很重要的。拉高管脚的执行速度远远比检查管脚电平的要快。
  
6、在等待管脚电平变化的时候,我们需要设置好超时处理,否则程序就会因为一个没有预计的错误而死锁。
  
7、能用C语言实现的地方,尽量不要用汇编,尤其在算法的实现,用汇编是晦涩难懂。
  
8、程序的几个参数数组所占篇幅很大,其中液晶背景数组最长,有四千个Byte,因而把那些初始化数组都放在另外一个C文件,在主文件使用使用关键字extern定义,这样就不会对主文件的编写造成干扰。
  
9、所有函数之间的相关性越低越有利于以后功能的扩展。
  
10、6.20版在编译带code关键字的数组时,编译通过但是单片机运行结果是错误的,改用6.14版后正常。

来源:网络(版权归原著作者所有)

围观 547

3、标识符

a、变量的命名

方法一:采用匈牙利命名法。命名规则的主要思想是“在变量中加入前缀以增进人们对程序的理解”。

例如平时声明32位整型变量Length对应使用匈牙利命名法为unLength。现在列出经常用到的变量类型。

变量类型 示例

char cLength

unsigned char ucLength

short int sLength

unsigned short int usLength

int nLength

unsigned int unLength

char * szBuf

unsigned char * uszBuf

volatile unsigned char __ucLength

方法二:

Ø 局部变量以小写字母命名;

Ø 全局变量以首字母大写方式命名(骆驼式);

Ø 定义类型和宏定义常数以大写字母命名;

Ø 变量的作用域越大,它的名字所带有的信息就应该越多。

Ø 局部变量: int student_age;

Ø 全局变量: int StudentAge;

Ø 宏定义常数:#define STUDENT_NUM 10

Ø 类型定义: typedef INT16S int;

(我个人喜欢第二种方法)

b、 变量命名要注意缩写而且让人简单易懂,若是特别缩写要详细说明。

经常用到的缩写如:

Count 可缩写为Cnt

Message 可缩写为Msg

Packet 可缩写为Pkt

Temp 可缩写为Tmp

平时不经常用到的缩写,要注释:

SerialCommunication 可缩写为SrlComm //串口通信变量

SerialCommunicationStatus 可缩写为SrlCommStat //串口通信状态变量

c、全局变量和全局函数的命名一定要详细,不惜多用几个单词,例如函数UARTPrintfStringForLCD,

因为它们在整个项目的许多源文件中都会用到,必须让使用者明确这个变量或函数是干什么用的。局部变量和只在一个源文件中调用的内部函数的命名可以。简略一些,但不能太短,不要使用单个字母做变量名,只有一个例外:用i、j 、k 做循环变量是可以的。

d、用于编译开关的文件头,必须加上当前文件名称,防止编译时产生冲突。

例如在UARTInterface.h 头文件中,必须加上以下内容

#ifndef __UARTINTERFACE_H__

#define __UARTINTERFACE_H__

extern void UARTPrintfString(CONST INT8* str);

extern void UARTSendNBytes(UINT8 *ucSendBytes,UINT8 ucLen);

…… //其他外部声明的代码

#endif

e、禁止用汉语拼音作为标识符名称,可读性极差。呵呵。

f、 建议名称间的区别要显而易见。使用标识符名称要注意的一个相关问题是发生在名称之间只有一个字符或少数字符不同的情况,特别是名称比较长时,当名称间的区别很容易被误读时问题就比较显著,比如1(数字1)和l(L 的小写)、0 和O、2 和Z、5 和S,或者n 和h。

4、表达式和基本语句

a、不要编写太复杂的复合表达式;

例如:

i = a >= b && c < d && c + f <= g + h; //复合表达式过于复杂

b、不要有多用途的复合表达式;

例如:

d = (a = b + c) + r ; //应拆分为两个语句:

a = b + c;

d = a + r;

c、如果代码行中的运算符比较多,用括号确定表达式的操作顺序,避免使用默认的优先级。

例如:

if(a | b && a & c) //不良的风格

if((a | b) && (a & c)) //良好的风格

注意:只需记住加减运算的优先级低于乘除运算,其它地方一律加上括号。

d、 if 语句

d.a 布尔变量与零值比较

不可将布尔变量直接与TRUE、FALSE 或者1、0 进行比较。

根据布尔类型的语义,零值为“假”(记为FALSE),任何非零值都是“真”(记为TRUE)。TRUE的值究竟是什么并没有统一的标准。例如Visual C++ 将TRUE 定义为1,而Visual Basic 则将TRUE 定义为-1。

例:假设布尔变量名字为flag,它与零值比较的标准if 语句如下:

if (flag) // 表示flag为真时满足条件

if (!flag) // 表示flag为假时满足条件

其它的用法都属于不良风格,例如:

if (flag == TRUE)

if (flag == 1 )

if (flag == FALSE)

if (flag == 0)

d.b 整型变量与零值比较

应当将整型变量用“==”或“!=”直接与0比较。

例:假设整型变量为value,它与零值比较的标准if 语句如下:

if (value == 0)

if (value != 0)

不可模仿布尔变量的风格而写成

if (value) // 会让人误解 value 是布尔变量

if (!value)

小技巧:想必大家都有过将赋值操作符“=”当作比较相等操作符“==”用过,这个错误比较的隐晦,不易排查,而且编译器从不把这类事情当作是程序员犯下的错。避免的方法有两种,一种是养成良好的编程习惯,在比较数值时小心翼翼的处理;另一种方法见下面给出的代码:

if (NULL = = p)

{

……

}

是不是觉得这种书写方式很古怪?不是程序写错了?

当然不是!

有经验的程序员为了防止将 if (p = = NULL) 误写成 if (p = NULL),而有意把p 和NULL 颠倒。编译器认为 if (p = NULL) 是合法的,但是会指出 if (NULL = p)是错误的,因为NULL不能被赋值。所以,再次遇到判断整型变量是否与某个数相等时,请这样写吧:

if(2==flag)

{

……

}

d.c 浮点变量与零值比较

不可将浮点变量用“==”或“!=”与任何数字比较。

千万要留意,无论float 还是double 类型变量,都有精度限制。所以一定要避免将浮点变量用“==”或“!=”与数字比较,应该设法转化成“>=”或“<=”形式。

假设浮点变量的名字为x,应当将

if (x == 0.0) // 隐含错误的比较

转化为

if ((x>=-EPSINON) && (x<=EPSINON)) //EPSINON 是精度

5、杂项

a. 一些常量(如圆周率PI)或者常需要在调试时修改的参数最好用#define定义,但要注意宏定义只是简单的替换,因此有些括号不可少。

b. 不要轻易调用某些库函数,因为有些库函数代码很长(我是反对使用printf之类的库函数的,但是是一家之言,并不勉强各位)。

c. 对各运算符的优先级有所了解,记不得没关系,加括号就是,千万不要自作聪明说自己记得很牢。

d. 不管有没有无效分支,switch函数一定要defaut这个分支。一来让阅读者知道程序员并没有遗忘default,并且防止程序运行过程中出现的意外(健壮性)。

e. 函数的参数和返回值没有的话最好使用void。

f. 一些常数和表格之类的应该放到code中去以节省RAM。

g. 程序编完编译看有多少code多少data,注意不要使堆栈为难。

h. 减少函数本身或函数间的递归调用

i. 编写可重入函数时,若使用全局变量,则应通过关中断、信号量(即P、V操作)等手段对其加以保护。

j. 在多重循环中,应将最忙的循环放在最内层

k. 避免循环体内含判断语句,应将循环语句置于判断语句的代码块之中。

l. 系统运行之初,要初始化有关变量及运行环境,防止未经初始化的变量被引用。

m. 编写代码时要注意随时保存,并定期备份,防止由于断电、硬盘损坏等原因造成代码丢失。

文章来源: 博客园

围观 535

常把单片机系统的复位分为冷启动和热启动。所谓冷启动,也就是一般所说的上电复位,冷启动后片内外RAM的内容是随机的,通常是0x00或0xFF;单片机的热启动是通过外部电路给运行中的单片机的复位端一复位电平而实现的,也就是所说的按键复位或看门狗复位。复位后,RAM的内容都没有改变。在某些场合,必须区分出设备的重启是热重启还是冷重启。常用的方法是:确定某内存单位为标志位(如0x40003FF4~0x40003FF7 RAM单元),启动时首先读该内存单元的内容,如果它等于一个特定的值(例如为0xAA55AA55),就认为是热启动,否则就是冷启动。

根据以上的设计思路思路定义一个变量:

uint32 unStartFlag;

在程序启动时判断:

if(unStartFlag==0xAA55AA55)

{

//热启动处理

}

else

{

//冷启动处理

unStartFlag=0xAA55AA55;

}

然而实际调试中发现,无论是热启动还是冷启动,开机后所有内存单元的值都被复位为0,当然也实现不了热启动的要求。通过看keil MDK自带的启动代码Startup.s,在这个启动代码中也并没有发现将整个RAM区域清零的语句。反汇编程序,发现从启动代码执行结束到跳转到main函数过程中,编译器还执行了很多库函数,其中__scatterload_zeroinit函数将所有W/R RAM都初始化为0(默认设置下)。为了判断冷、热启动,必须人为控制某些特定RAM在复位时不被编译器初始化为0。通过查找编译器手册,在为处理器的RAM中分出一块小片RAM,设置为NoInit格式(不对其初始化为0),如下图:

然后使用__at关键字将冷、热启动标志位定位到这个NoInit区域:

uint32 unStartFlag __at (0x40003FF4);

这样,当热启动时,变量unStartFlag所在的内存区域就不会被初始化为0,也实现了冷热启动的判断。

定义铁电0xFF7~0xFF8区域存储冷启动次数

0xFF9~0xFFA区域存储热启动次数

0xFFB~0xFFC区域存储总启动次数

围观 254

最近写一个程序,需要在用户模式下关中断,但ARM 7的体系结构决定了中断必须在特权模式下才可以更改,所以想到使用ARM的软中断来实现关中断和开中断。

使用软中断,首先要有硬件指令的支持,ARM有条指令是SWI。

SWI 指令的格式为:

SWI {条件} 24 位的立即数

SWI 指令用于产生软件中断,以便用户程序能调用操作系统的系统例程。操作系统在 SWI 的异常处理程序中提供相应的系统服务,指令中 24 位的立即数指定用户程序调用系统例程的类型,相关参数通过通用寄存器传递,当指令中 24 位的立即数被忽略时,用户程序调用系统例程的类型由通用寄存器 R0 的内容决定,同时,参数通过其他通用寄存器传递。

指令示例:

SWI 0x02 ;该指令调用操作系统编号位02的系统例程。

在keil MDK中,关键字__svc可以产生硬件SWI指令,使得处理器能响应软件中断。关键字__svc、keil MDK帮助文件中这样描述:

__svc 关键字声明超级用户调用 (SVC) 函数,该函数最多使用四个类似于整数的参数,并通过 value_in_regs 结构最多返回四个结果。

__svc 是一个函数限定符。它影响函数的类型。

语法

__svc(int svc_num) return-type function-name([argument-list]);

其中:

svc_num 是在 SVC 指令中使用的立即值。

它是一个表达式,其计算结果为以下范围内的整数:

• 在 ARM 指令中为 0 到 224–1 (24 位值)

• 在 16 位 Thumb 指令中为 0-255 (8 位值)。

要在keil MDK中使用软件中断,要做好两件事:第一件,更改启动文件,编写软件中断的汇编入口。在这个汇编入口中主要根据软件中断命令号进行相应的函数跳转;第二件,编写相应命令号的C语言服务函数。下面举例怎么样用软件中断实现开中断和关中断:

第一步:更改启动代码

keil MDK自带的启动代码有类似下面的语句:

Vectors LDR PC, Reset_Addr
LDR PC, Undef_Addr
LDR PC, SWI_Addr
LDR PC, PAbt_Addr
LDR PC, DAbt_Addr

蓝色语句是程序复位后要执行的第一条指令,即复位异常入口;而红色语句就是执行一个软件中断指令后,要跳转到的软件中断异常入口。通过语句"SWI_Addr DCD SWI_Handler"进行中转,软件中断会跳转到标号为SWI_Handler的语句处,该处即处理软件中断的命令号、源代码如下:

EXPORT SWI_Handler
extern EnableIrqFunc ;使能中断函数名,用C语言实现
extern DisableIrqFunc ;禁止中断函数名,用C语言实现
SWI_Handler
STMFD SP!, {R0,R12,LR} ;入栈
LDR R0, [LR,#-4] ;取指令
BIC R0,R0,#0xFF000000 ;取软件中断命令号
CMP R0,#0 ;和0比较,因为我的使能中断用了软件中断命令0,禁止中断使用了软件中断命令1
BLEQ EnableIrqFunc ;为零调用使能中断函数
BLNE DisableIrqFunc ;不为零调用禁止中断函数
LDMFD SP!,{R0,R12,PC}^ ;出栈

第二部:编写相应命令号的C语言服务函数。

声明软件中断:

__svc(0x00) void EnableIrq(void); //使能中断
__svc(0x01) void DisableIrq(void); //禁止中断

编写服务函数:

void DisableIrqFunc(void)
{
int temp;
__asm
{
MRS temp,SPSR
ORR temp,temp,#0x80
MSR SPSR_c,temp
}
}
void EnableIrqFunc(void)
{
int temp;
__asm
{
MRS temp,SPSR
BIC temp,temp,#0x80
MSR SPSR_c,temp
}
}

到此,使能和禁止中断的软中断就结束了,下面看一下执行过程。

在程序中,如果想关中断,只需使用:DisableIrq();

MDK编译器在执行这句函数时,自动用软中断指令代替,即: SWI 0x01

ARM执行这条软件中断指令后,发生软件中断异常,程序跳转到软件中断异常服务函数处,即汇编代码标号为SWI_Handler处,在这里判断软件中断命令号是0x01,然后执行 BLNE DisableIrqFunc 语句,调用禁止中断函数,实现关中断。

文章来源:博客园

围观 472

页面

订阅 RSS - Keil相关