最近在调试传感器的那块程序,这里总结一下自己的心得
调试程序的方法
方法1:led显示法,在程序中调用这一句函数led = 0;可以知道程序运行到哪里,为什么会出错,到什么地方陷入了死循环
方法二:串口打印法,串口打印法可以知道函数输出的东西是什么,程序中只需要使用串口中断就可以了,关于串口怎么样使用,我觉得等一下我需要总结一下最近编程的问题
现在这里要好好总结一下串口调试法,天祥哥在他的书上总结了串口调试的方法,开始的时候虽然开了一下,了解了他是什么情况,会用串口之外,其他的什么都不懂,到现在才真正明白串口中断的真正含义是什么,串口中断可以打断单片机的执行,让单片机在执行主函数的时候去执行别的函数
现在这个例子是我用串口调试关照强度的程序
![](https://i-blog.csdnimg.cn/blog_migrate/8f900a89c6347c561fdf2122f13be562.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/961ddebeb323a10fe0623af514929fc1.gif)
//*************************************** // BH1750FVI IIC测试程序 // 使用单片机STC89C51 // 晶振:11.0592M // 显示:LCD1602 // 编译环境 Keil uVision2 // 参考宏晶网站24c04通信程序 // 时间:2011年4月20日 //**************************************** #include <REG51.H> #include <math.h> //Keil library #include <stdio.h> //Keil library #include <INTRINS.H> #define uchar unsigned char #define uint unsigned int sbit SCL=P1^0; //IIC时钟引脚定义 sbit SDA=P1^1; //IIC数据引脚定义 #define SlaveAddress 0x46 //定义器件在IIC总线中的从地址,根据ALT ADDRESS地址引脚不同修改 //ALT ADDRESS引脚接地时地址为0xA6,接电源时地址为0x3A typedef unsigned char BYTE; typedef unsigned short WORD; BYTE BUF[8]; //接收数据缓存区 uchar table[5]; //显示变量 int dis_data; //变量 void delay_nms(unsigned int k); void Init_BH1750(void); void conversion(uint temp_data); void Single_Write_BH1750(uchar REG_Address); //单个写入数据 uchar Single_Read_BH1750(uchar REG_Address); //单个读取内部寄存器数据 void Multiple_Read_BH1750(); //连续的读取内部寄存器数据 //------------------------------------ void Delay5us(); void Delay5ms(); void BH1750_Start(); //起始信号 void BH1750_Stop(); //停止信号 void BH1750_SendACK(bit ack); //应答ACK bit BH1750_RecvACK(); //读ack void BH1750_SendByte(BYTE dat); //IIC单个字节写 BYTE BH1750_RecvByte(); //IIC单个字节读 //----------------------------------- //********************************************************* void conversion(uint temp_data) // 数据转换出 个,十,百,千,万 { table[0]=temp_data/10000+0x30 ; temp_data=temp_data%10000; //取余运算 table[1]=temp_data/1000+0x30 ; temp_data=temp_data%1000; //取余运算 table[2]=temp_data/100+0x30 ; temp_data=temp_data%100; //取余运算 table[3]=temp_data/10+0x30 ; temp_data=temp_data%10; //取余运算 table[4]=temp_data+0x30; } //毫秒延时************************** void delay_nms(unsigned int k) { unsigned int i,j; for(i=0;i<k;i++) { for(j=0;j<121;j++) {;}} } /************************************** 延时5微秒(STC90C52RC@12M) 不同的工作环境,需要调整此函数,注意时钟过快时需要修改 当改用1T的MCU时,请调整此延时函数 **************************************/ void Delay5us() { _nop_();_nop_();_nop_();_nop_(); _nop_();_nop_();_nop_();_nop_(); _nop_();_nop_();_nop_();_nop_(); _nop_();_nop_();_nop_();_nop_(); } /************************************** 延时5毫秒(STC90C52RC@12M) 不同的工作环境,需要调整此函数 当改用1T的MCU时,请调整此延时函数 **************************************/ void Delay5ms() { WORD n = 560; while (n--); } /************************************** 起始信号 **************************************/ void BH1750_Start() { SDA = 1; //拉高数据线 SCL = 1; //拉高时钟线 Delay5us(); //延时 SDA = 0; //产生下降沿 Delay5us(); //延时 SCL = 0; //拉低时钟线 } /************************************** 停止信号 **************************************/ void BH1750_Stop() { SDA = 0; //拉低数据线 SCL = 1; //拉高时钟线 Delay5us(); //延时 SDA = 1; //产生上升沿 Delay5us(); //延时 } /************************************** 发送应答信号 入口参数:ack (0:ACK 1:NAK) **************************************/ void BH1750_SendACK(bit ack) { SDA = ack; //写应答信号 SCL = 1; //拉高时钟线 Delay5us(); //延时 SCL = 0; //拉低时钟线 Delay5us(); //延时 } /************************************** 接收应答信号 **************************************/ bit BH1750_RecvACK() { SCL = 1; //拉高时钟线 Delay5us(); //延时 CY = SDA; //读应答信号 SCL = 0; //拉低时钟线 Delay5us(); //延时 return CY; } /************************************** 向IIC总线发送一个字节数据 **************************************/ void BH1750_SendByte(BYTE dat) { BYTE i; for (i=0; i<8; i++) //8位计数器 { dat <<= 1; //移出数据的最高位 SDA = CY; //送数据口 SCL = 1; //拉高时钟线 Delay5us(); //延时 SCL = 0; //拉低时钟线 Delay5us(); //延时 } BH1750_RecvACK(); } /************************************** 从IIC总线接收一个字节数据 **************************************/ BYTE BH1750_RecvByte() { BYTE i; BYTE dat = 0; SDA = 1; //使能内部上拉,准备读取数据, for (i=0; i<8; i++) //8位计数器 { dat <<= 1; SCL = 1; //拉高时钟线 Delay5us(); //延时 dat |= SDA; //读数据 SCL = 0; //拉低时钟线 Delay5us(); //延时 } return dat; } //********************************* void Single_Write_BH1750(uchar REG_Address) { BH1750_Start(); //起始信号 BH1750_SendByte(SlaveAddress); //发送设备地址+写信号 BH1750_SendByte(REG_Address); //内部寄存器地址,请参考中文pdf22页 // BH1750_SendByte(REG_data); //内部寄存器数据,请参考中文pdf22页 BH1750_Stop(); //发送停止信号 } //********单字节读取***************************************** /* uchar Single_Read_BH1750(uchar REG_Address) { uchar REG_data; BH1750_Start(); //起始信号 BH1750_SendByte(SlaveAddress); //发送设备地址+写信号 BH1750_SendByte(REG_Address); //发送存储单元地址,从0开始 BH1750_Start(); //起始信号 BH1750_SendByte(SlaveAddress+1); //发送设备地址+读信号 REG_data=BH1750_RecvByte(); //读出寄存器数据 BH1750_SendACK(1); BH1750_Stop(); //停止信号 return REG_data; } */ //********************************************************* // //连续读出BH1750内部数据 // //********************************************************* void Multiple_read_BH1750(void) { uchar i; BH1750_Start(); //起始信号 BH1750_SendByte(SlaveAddress+1); //发送设备地址+读信号 for (i=0; i<3; i++) //连续读取6个地址数据,存储中BUF { BUF[i] = BH1750_RecvByte(); //BUF[0]存储0x32地址中的数据 if (i == 3) { BH1750_SendACK(1); //最后一个数据需要回NOACK } else { BH1750_SendACK(0); //回应ACK } } BH1750_Stop(); //停止信号 Delay5ms(); } //初始化BH1750,根据需要请参考pdf进行修改**** void Init_BH1750() { Single_Write_BH1750(0x01); } //********************************************************* //主程序******** //********************************************************* void gzqd() { float temp; delay_nms(200); //延时200ms Init_BH1750(); //初始化BH1750 Single_Write_BH1750(0x01); // power on Single_Write_BH1750(0x10); // H- resolution mode delay_nms(180); //延时180ms Multiple_Read_BH1750(); //连续读出数据,存储在BUF中 dis_data=BUF[0]; dis_data=(dis_data<<8)+BUF[1];//合成数据 temp=(float)dis_data/1.2; conversion(temp); //计算数据和显示 } //#include<reg52.h> //包含头文件,一般情况不需要改动,头文件包含特殊功能寄存器的定义 /*------------------------------------------------ 函数声明 ------------------------------------------------*/ void SendStr(unsigned char *s); /*------------------------------------------------ 串口初始化 ------------------------------------------------*/ void InitUART (void) { SCON = 0x50; // SCON: 模式 1, 8-bit UART, 使能接收 TMOD |= 0x20; // TMOD: timer 1, mode 2, 8-bit 重装 TH1 = 0xFD; // TH1: 重装值 9600 波特率 晶振 11.0592MHz TR1 = 1; // TR1: timer 1 打开 EA = 1; //打开总中断 // ES = 1; //打开串口中断 } /*------------------------------------------------ 主函数 ------------------------------------------------*/ void main (void) { InitUART(); gzqd(); SendStr("UART test,技术论坛:www.doflye.net 请在发送区输入任意信息光照强度:"); SendStr(table); ES = 1; //打开串口中断 while (1) { } } /*------------------------------------------------ 发送一个字节 ------------------------------------------------*/ void SendByte(unsigned char dat) { SBUF = dat; while(!TI); TI = 0; } /*------------------------------------------------ 发送一个字符串 ------------------------------------------------*/ void SendStr(unsigned char *s) { while(*s!='\0')// \0 表示字符串结束标志,通过检测是否字符串末尾 { SendByte(*s); s++; } } /*------------------------------------------------ 串口中断程序 ------------------------------------------------*/ void UART_SER (void) interrupt 4 //串行中断服务程序 { unsigned int Temp; //定义临时变量 if(RI) //判断是接收中断产生 { RI=0; //标志位清零 Temp=SBUF; //读入缓冲区的值 if(Temp == 0x01) { gzqd(); SendStr(table); ES = 1; } SBUF=Temp; //把接收到的值再发回电脑端 } if(TI) //如果是发送标志位,清零 TI=0; }
但是我发现有个问题,那就是在串口函数中,尽量的在串口函数的时间中要少一些,这样让下一次串口中断进来的时候,不至于影响前一次的串口中断,所以我就把gzqd()这个函数用放在主函数里面,改动变成这样了
在中断函数中执行 if(Temp == 0x01) { flag_gzqd = 1;//光照强度标志位 }
在主函数中的while()循环改成这样 while (1) { if(flag_gzqd ==1) { flag_gzqd =0; gzqd(); SendStr(table); ES = 1; ES = 0; } } }
但是结果却发现,发送01指令,单片机并不能及时的传送出数据,也就是说,从串口函数中在跳转到主函数中来的效果比较差,没得把光照强度放在中断函数中来的效率高,我不知道这是51单片机内部运算速度的原因,还是程序本身结构的问题!!!
但是为什么把函数放在中断函数里面,会出现警告*** WARNING L15: MULTIPLE CALL TO SEGMENT,百度一下这个警告,会发现这个警告是值得引起注意的,这里有别个的博客的解析,比较有用
今天来说说*** WARNING L15: MULTIPLE CALL TO SEGMENT这个问题! 其实这个问题应该是引起注意的,有可能引起程序冲突,但是一般时候程序运行不会有问题,但是如果出来问题,那将会是很讨厌的问题. 分析一下 产生这一警告的一个根源是:例如在主循环里调用了一个函数,而在中断服务中,你又一次调用了同样的函数。这样当主循环运行到该函数中时, 一旦产生中断,则在中断里又再次调用该函数!而使得该子函数发生了重入,这时,经管概率很低,但是很可能出错!这样,编译器就给出了警告!告诉你*** WARNING L15: MULTIPLE CALL TO SEGMENT ,表达的意思是发生了重入!字面意思自己理解去吧~~~ 想要避免这种情况的方法 一.用reentrant使函数重入 关于reentrant的说明: 1,重入函数不能传递bit类型的参数和变量; 2,重入函数建立的是模拟堆栈区,所以不使用一般函数位于存储模式默认空间的可覆盖式堆栈,而是在同一空间从顶端另行分配一个非覆盖式的重入堆栈。 small 默认空间是 data; compact 默认空间是 pdata; largr 默认空间是 xdata; 3,由于要保存参数和局部变量,所以会消耗很大的栈空间;尽量少用这种模式; 4、 在同一程序中可以定义和使用不同存储器模式的重入函数,任意模式的重入函数不能调用不同存储器模式的重入函数,但可以调用普通函数。 5、 实际参数可以传递给间接调用的重入函数。无重入属性的间接调用函数不能包含调用参数。 二.如果空间多的话,可以定义两个同功能的函数,分别在中断和中断外调用 别的方法没研究出来,嘿嘿~~~对了 我建议用第二种方法好点,第一种有些限制,不爽~~ 三.主程序调用该函数时禁止中断。 可以在该函数被调用时用#pragma disable语句来实现禁止中断的目的。必须使用OVERLAY指令将该函数从覆盖分析中除去。 也可以用EA=1,EA=0来开关中断 最后,WARNING L15: MULTIPLE CALL TO SEGMENT 这个问题必须注意,可能引起程序冲突,假设你用于自动化领域,则可能导致信号产生尖峰。 产生这一警告的一个根源是:你在主循环 里调用了一个函数(如aaa),而在中断服务函数里,你用调用了这个函数(如aaa)。这样当主循环运行到该函数中 是,一旦产生中断,则在中断里又再次调用该函数!这时,很可能出错! 避免这种情况的方法很多:如,在进中断的时候置需调用该函数的标志,而在主循环中调用该函数
博客的地址在这里:http://www.cnblogs.com/CuriosityWzk/archive/2011/12/25/2301090.html
还有百度空间中也有一篇比较好的博客http://hi.baidu.com/tdfzq/item/2374ad3a3e727f88f4e4ad4a
,同样是这个问题:
![](https://i-blog.csdnimg.cn/blog_migrate/8f900a89c6347c561fdf2122f13be562.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/961ddebeb323a10fe0623af514929fc1.gif)
***WARNING L15: MULTIPLE CALL TO SEGMENT错误信息的处理 1.错误信息 ***WARNING L15: MULTIPLE CALL TO SEGMENT SEGMENT: ?PR?_WRITE_GMVLX1_REG?D_GMVLX1 CALLER1: ?PR?VSYNC_INTERRUPT?MAIN CALLER2: ?C_C51STARTUP 该警告表示连接器发现有一个函数可能会被主函数和一个中断服务程序(或者调用中断服务程序的函数)同时调用,或者同时被多个中断服务程序调用。 出现这种问题的原因之一是这个函数是不可重入性函数,当该函数运行时它可能会被一个中断打断,从而使得结果发生变化并可能会引起一些变量形式的冲突(即引起函数内一些数据的丢失,可重入性函数在任何时候都可以被ISR打断,一段时间后又可以 运行,但是相应数据不会丢失)。 原因之二是用于局部变量和变量(暂且这样翻译,arguments,[自变量,变元一数值,用于确定程序或子程序的值])的内存区被其他函数的内存区所覆盖,如果该函数被中断,则它的内存区就会被使用,这将导致其他函数的内存冲突。 原因之三:中断调用的函数过于复杂,层次较多。 解决方法:在在中断中加标志位,函数调用放在主循环中。 例如,第一个警告中函数WRITE_GMVLX1_REG 在D_GMVLX1.C 或者D_GMVLX1.A51被定义,它被一个中断服务程序或者一个调用了中断服务程序的函数调用了,调用它的函数是VSYNC_INTERRUPT,在MAIN.C中。 解决方法: 如果你确定两个函数决不会在同一时间执行(该函数被主程序调用并且中断被禁止),并且该函数不占用内存(假设只使用寄存器),则你可以完全忽略这种警告。 如果该函数占用了内存,则应该使用连接器(linker)OVERLAY指令将函数从覆盖分析(overlay analysis)中除去,例如: OVERLAY (?PR?_WRITE_GMVLX1_REG?D_GMVLX1 ! *) 上面的指令防止了该函数使用的内存区被其他函数覆盖。如果该函数中调用了其他函数,而这些被调用在程序中其他地方也被调用,你可能会需要也将这些函数排除在覆盖分析(overlay analysis)之外。这种OVERLAY指令能使编译器除去上述警告信息。 如果函数可以在其执行时被调用,则情况会变得更复杂一些。这时可以采用以下几种方法: 1.主程序调用该函数时禁止中断,可以在该函数被调用时用#pragma disable语句来实现禁止中断的目的。必须使用OVERLAY指令将该函数从覆盖分析中除去。 2.复制两份该函数的代码,一份到主程序中,另一份复制到中断服务程序中。 3.将该函数设为重入型。例如: void myfunc(void) reentrant { ... } 这种设置将会产生一个可重入堆栈,该堆栈被被用于存储函数值和局部变量,用这种方法时重入堆栈必须在STARTUP.A51文件中配置。 这种方法消耗更多的RAM并会降低重入函数的执行速度。
这里出现警告,但是为什么不能我把处理函数放在主函数中 进行不能得到正确的值,在纠结中~~~~,还有调程序的时候,程序写成一直按发送状态,但是串口接收一段数据之后,就不能一直发送了,也不知道是什么情况
原来发送串口数据的时候,发送成功后会申请串口中断,因此,发送时要把串口关闭,等发送数据完成之后在打开串口,确保安全的将数据发送成功
本文链接地址:http://www.cnblogs.com/fengdashen/p/3523147.html