一、AVR单片机中断总结
AVR单片机只是ATMEL公司推出的一款基于RISC指令构架的高性能、低功耗单片机。
ATmega16单片机具有21个中断源,如下表所示。每一个中断源都有一个独立的中断向量作为中断服务程序的入口地址,而且所有的中断源都有自己独立的使能位。如果全局中断I和相应的中断位都置位,则在中断标志位置位时将执行中断服务程序。
AVR单片机有3个外部中断,由引脚触发。PB2(INT2),PD2(INT0)、PD3(INT1).。需要注意:如果允许外部中断的话,即使是INT0,INT1、INT2这三个引脚都设为输出方式,外部中断也会触发。
INT0、INT1外部中断可以选择的触发方式有上升沿触发、下降沿触发以及低电平触发;INT2只有跳变沿触发,没有电平触发。
使用外部中断涉及到的寄存器有:MCU控制寄存器MCUCR,MCU控制欲寄存器MCUCSR,通用中断控制寄存器GICR,通用中断寄存器GIFR。
AVR单片机的中断响应时间最少为4个时钟周期。在这4个时钟周期里,程序计数器PC的2字节自动入栈,而堆栈指针SP减2。
中断相关的寄存器:
T/C的时钟源
T/C的时钟源可以有多种选择,由CS12:0控制,分别用于高速(低分频)/长时间(高分频)/外部计数场合
一个16位定时器,在8MHz系统时钟驱动下,可以实现uS级的高速定时和长达8秒的超长定时,这可是标准51的弱点
CS12CS11CS10说明
000无时钟源(T/C停止)
001clkIO/1(无预分频)
010clkIO/8(来自预分频器)
011clkIO/64(来自预分频器)
100clkIO/256(来自预分频器)
101clkIO/1024(来自预分频器)
110外部T1引脚,下降沿驱动
111外部T1引脚,上升沿驱动
分频器复位
在高预分频应用时,通过复位预分频器来同步T/C与程序运行,可以减少误差。
但是必须注意另一个T/C是否也在使用这一预分频器,因为预分频器复位将会影响所有与其连接的T/C。
外部时钟源
由于使用了引脚同步逻辑,建议外部时钟的最高频率不要大于fclk_IO/2.5。
外部时钟源不送入预分频器
选择使用外部时钟源后,即使T1引脚被定义为输出,其T1引脚上的逻辑信号电平变化仍然会驱动T/C1计数,这个特性允许用户通过软件来控制计数。
输入捕捉单元
T/C的输入捕捉单元可用来捕获外部事件,并为其赋予时间标记以说明此时间的发生时刻。
外部事件发生的触发信号由引脚ICP1输入,也可通过模拟比较器单元来实现。
时间标记可用来计算频率、占空比及信号的其它特征,以及为事件创建日志。
输入捕捉单元可以工作在多种工作模式下
(使用ICR1定义TOP的(WGM1=12,14,10,8)波形产生模式时,ICP1与输入捕捉功能脱开,从而输入捕捉功能被禁用。)
在任何输入捕捉工作模式下都不推荐在操作过程中改变TOP值
当引脚ICP1上的逻辑电平(事件)发生了变化,或模拟比较器输出ACO电平发生了变化,并且这个电平变化为边沿检测器所证实,输入捕捉即被激发:
16位的TCNT1数据被拷贝到输入捕捉寄存器ICR1,同时输入捕捉标志位ICF1置位。
如果此时ICIE1=1,输入捕捉标志将产生输入捕捉中断。
中断执行时ICF1自动清零,或者也可通过软件在其对应的I/O位置写入逻辑"1”清零。
注意,改变触发源有可能造成一次输入捕捉。因此在改变触发源后必须对输入捕捉标志执行一次清零操作以避免出现错误的结果
除去使用ICR1定义TOP的波形产生模式外,T/C中的噪声抑制器与边沿检测器总是使能的。
(其实就是永远使能??)
使能噪声抑制器后,在边沿检测器前会加入额外的逻辑电路并引入4个系统时钟周期的延迟.
噪声抑制器使用的是系统时钟,因而不受预分频器的影响
使用输入捕捉中断时,中断程序应尽可能早的读取ICR1寄存器
如果处理器在下一次事件出现之前没有读取ICR1的数据,ICR1就会被新值覆盖,从而无法得到正确的捕捉结果。
测量外部信号的占空比时要求每次捕捉后都要改变触发沿。
因此读取ICR1后必须尽快改变敏感的信号边沿。改变边沿后,ICF1必须由软件清零(在对应的I/O位置写"1”)。
若仅需测量频率,且使用了中断发生,则不需对ICF1进行软件清零。
输出比较单元
16位比较器持续比较TCNT1与OCR1x的内容,一旦发现它们相等,比较器立即产生一个匹配信号。
然后OCF1x在下一个定时器时钟置位。
如果此时OCIE1x=1,OCF1x置位将引发输出比较中断。
(就是说输出比较可以工作在所有工作模式下,但PWM模式下更好用,功能更强)
输出比较单元A(OCR1A)的一个特质是定义T/C的TOP值(即计数器的分辨率)。
TOP值还用来定义通过波形发生器产生的波形的周期。
由于在任意模式下写TCNT1都将在下一个定时器时钟周期里阻止比较匹配,在使用输出比较时改变TCNT1就会有风险,不管T/C是否在运行
这个特性可以用来将OCR1x初始化为与TCNT1相同的数值而不触发中断。
强制输出比较(FOC)
工作于非PWM模式时,可以通过对强制输出比较位FOC1x写”1”的方式来产生比较匹配。
强制比较匹配不会置位OCF1x标志,也不会重载/清零定时器,
但是OC1x引脚将被更新,好象真的发生了比较匹配一样(COMx1:0决定OC1x是置位、清零,还是交替变化)。
比较匹配输出单元
比较匹配模式控制位COM1x1:0具有双重功能。
1波形发生器利用COM1x1:0来确定下一次比较匹配发生时的输出比较OC1x状态;
2COM1x1:0还控制OC1x引脚输出的来源。
只要COM1x1:0不全为零,波形发生器的输出比较功能就会重载OC1x的通用I/O口功能。
但是OC1x引脚的方向仍旧受控于数据方向寄存器(DDR)。
从OC1x引脚输出有效信号之前必须通过数据方向寄存器的DDR_OC1x将此引脚设置为输出。
波形发生器利用COM1x1:0的方法在普通模式、CTC模式和PWM模式下有所区别。
对于所有的模式,设置COM1x1:0=0表明比较匹配发生时波形发生器不会操作OC1x寄存器
访问16位寄存器
写16位寄存器时,应先写入该寄存器的高位字节.
usignedintk;
k=0x1234;
TCNT1H=(unsignedchar)(k>>8);
TCNT1L=(unsignedchar)k;
而读16位寄存器时应先读取该寄存器的低位字节.
usignedintk;
k=TCNT1L;
k+=(unsignedint)(TCNT1H<<8);
使用“C”语言时,编译器会自动处理16位操作.
usignedintk;
k=0x1234;
TCNT1=k;
k=TCNT1;
这里举例如何用16位定时器T1实现高精度1秒连续定时,精准度跟所用晶振一样
T1CTC模式,8MHz外部晶振,定时1秒的话,选256分频,刚好整步距,非常准确TOP=1000000/(0.125*256)-1=31249=0x7A11。
作连续定时,必用CTC/PWM模式作,没有累积误差,稳定度跟时钟是一样,手动重装受中断影响是很难达到的。
不过定时步距和最长定时间取决于时钟,分频系数和模式。
对于非整步距的定时时间要求,就会存在小于一个步距的偏差。
例如T1,CTC模式,8MHz
fOCn=fclk_IO/(2*N*(1+TOP))
定时时间T=0.125uS*N*(1+TOP)
分频系数定时步距最长定时时间
1(无分频)0.125uS8192us8毫秒
81uS65536us65毫秒
648uS524ms0.5秒
25632uS2097ms3秒
1024128uS8388ms超8秒了
定时1秒的话,选256分频,刚好整步距,非常准确,TOP=1000000/(0.125*256)-1=31249=0x7A11。
-----------当然了,时钟必须是高精度的晶振之类,不要用内部RC振荡器来瞎搞。
由于精度取决于晶振的精度,配合软件做RTC实时时钟是完全可行的。
用+/-20PPM的晶振,跑一个月误差1分钟[60*60*24*30=2592000秒*20ppm=52秒]。
作RTC用专门为32.768KHz时钟优化的T2定时器更合适,这里只是举例T1的实现方法
如果用+/-2.5PPM的DS32KHz(MAXIM的业界最准确的32.768KHz单片稳补时钟芯片TXCO)做时钟源,超准确
指标:-40~+85度全温度范围,年误差<4分钟,0~40度温度范围内+/-1PPM,年误差<1分钟
C语言宏定义技巧(常用宏定义)
写好C语言,漂亮的宏定义很重要,使用宏定义可以防止出错,提高可移植性,可读性,方便性等等。下面列举一些成熟软件中常用的宏定义:
1、防止一个头文件被重复包含
#ifndefCOMDEF_H
#defineCOMDEF_H
//头文件内容
#endif
2、重新定义一些类型,防止由于各种平台和编译器的不同,而产生的类型字节数差异,方便移植。
typedefunsignedcharboolean;/*Booleanvaluetype.*/
typedefunsignedlongintuint32;/*Unsigned32bitvalue*/
typedefunsignedshortuint16;/*Unsigned16bitvalue*/
typedefunsignedcharuint8;/*Unsigned8bitvalue*/
typedefsignedlongintint32;/*Signed32bitvalue*/
typedefsignedshortint16;/*Signed16bitvalue*/
typedefsignedcharint8;/*Signed8bitvalue*/
//下面的不建议使用
typedefunsignedcharbyte;/*Unsigned8bitvaluetype.*/
typedefunsignedshortword;/*Unsinged16bitvaluetype.*/
typedefunsignedlongdword;/*Unsigned32bitvaluetype.*/
typedefunsignedcharuint1;/*Unsigned8bitvaluetype.*/
typedefunsignedshortuint2;/*Unsigned16bitvaluetype.*/
typedefunsignedlonguint4;/*Unsigned32bitvaluetype.*/
typedefsignedcharint1;/*Signed8bitvaluetype.*/
typedefsignedshortint2;/*Signed16bitvaluetype.*/
typedeflongintint4;/*Signed32bitvaluetype.*/
typedefsignedlongsint31;/*Signed32bitvalue*/
typedefsignedshortsint15;/*Signed16bitvalue*/
typedefsignedcharsint7;/*Signed8bitvalue*/
3、得到指定地址上的一个字节或字
#defineMEM_B(x)(*((byte*)(x)))
#defineMEM_W(x)(*((word*)(x)))
4、求最大值和最小值
#defineMAX(x,y)(((x)>(y))?(x):(y))
#defineMIN(x,y)(((x)<(y))?(x):(y))
5、得到一个field在结构体(struct)中的偏移量
#defineFPOS(type,field)\
/*lint-e545*/((dword)&((type*)0)->field)/*lint+e545*/
6、得到一个结构体中field所占用的字节数
#defineFSIZ(type,field)sizeof(((type*)0)->field)
7、按照LSB格式把两个字节转化为一个Word
#defineFLIPW(ray)((((word)(ray)[0])*256)+(ray)[1])
8、按照LSB格式把一个Word转化为两个字节
#defineFLOPW(ray,val)\
(ray)[0]=((val)/256);\
(ray)[1]=((val)&0xFF)
9、得到一个变量的地址(word宽度)
#defineB_PTR(var)((byte*)(void*)&(var))
#defineW_PTR(var)((word*)(void*)&(var))
10、得到一个字的高位和低位字节
#defineWORD_LO(xxx)((byte)((word)(xxx)&255))
#defineWORD_HI(xxx)((byte)((word)(xxx)>>8))
11、返回一个比X大的最接近的8的倍数
#defineRND8(x)((((x)+7)/8)*8)
12、将一个字母转换为大写
#defineUPCASE(c)(((c)>='a'&&(c)<='z')?((c)-0x20):(c))
13、判断字符是不是10进值的数字
#defineDECCHK(c)((c)>='0'&&(c)<='9')
14、判断字符是不是16进值的数字
#defineHEXCHK(c)(((c)>='0'&&(c)<='9')||\
((c)>='A'&&(c)<='F')||\
((c)>='a'&&(c)<='f'))
15、防止溢出的一个方法
#defineINC_SAT(val)(val=((val)+1>(val))?(val)+1:(val))
16、返回数组元素的个数
#defineARR_SIZE(a)(sizeof((a))/sizeof((a[0])))
17、返回一个无符号数n尾的值MOD_BY_POWER_OF_TWO(X,n)=X%(2^n)
#defineMOD_BY_POWER_OF_TWO(val,mod_by)\
((dword)(val)&(dword)((mod_by)-1))
18、对于IO空间映射在存储空间的结构,输入输出处理
#defineinp(port)(*((volatilebyte*)(port)))
#defineinpw(port)(*((volatileword*)(port)))
#defineinpdw(port)(*((volatiledword*)(port)))
#defineoutp(port,val)(*((volatilebyte*)(port))=((byte)(val)))
#defineoutpw(port,val)(*((volatileword*)(port))=((word)(val)))
#defineoutpdw(port,val)(*((volatiledword*)(port))=((dword)(val)))
19、使用一些宏跟踪调试
ANSI标准说明了五个预定义的宏名。它们是:
_LINE_
_FILE_
_DATE_
_TIME_
_STDC_
如果编译不是标准的,则可能仅支持以上宏名中的几个,或根本不支持。记住编译程序
也许还提供其它预定义的宏名。
_LINE_及_FILE_宏指令在有关#line的部分中已讨论,这里讨论其余的宏名。
_DATE_宏指令含有形式为月/日/年的串,表示源文件被翻译到代码时的日期。
源代码翻译到目标代码的时间作为串包含在_TIME_中。串形式为时:分:秒。
如果实现是标准的,则宏_STDC_含有十进制常量1。如果它含有任何其它数,则实现是
非标准的。
可以定义宏,例如:
当定义了_DEBUG,输出数据信息和所在文件所在行
#ifdef_DEBUG
#defineDEBUGMSG(msg,date)printf(msg);printf(“%d%d%d”,date,_LINE_,_FILE_)
#else
#defineDEBUGMSG(msg,date)
#endif
20、宏定义防止使用是错误
用小括号包含。
例如:#defineADD(a,b)(a+b)
用do{}while(0)语句包含多语句防止错误
例如:#difneDO(a,b)a+b;\
a++;
应用时:if(….)
DO(a,b);//产生错误
else
解决方法:#difneDO(a,b)do{a+b;\
a++;}while(0)
宏中"#"和"##"的用法
一、一般用法
我们使用#把宏参数变为一个字符串,用##把两个宏参数贴合在一起.
用法:
#include
#include
usingnamespacestd;
#defineSTR(s)#s
#defineCONS(a,b)int(a##e##b)
intmain()
{
printf(STR(vck));//输出字符串"vck"
printf("%d
",CONS(2,3));//2e3输出:2000
return0;
}
二、当宏参数是另一个宏的时候
需要注意的是凡宏定义里有用'#'或'##'的地方宏参数是不会再展开.
1,非'#'和'##'的情况
#defineTOW(2)
#defineMUL(a,b)(a*b)
printf("%d*%d=%d
",TOW,TOW,MUL(TOW,TOW));
这行的宏会被展开为:
printf("%d*%d=%d
",(2),(2),((2)*(2)));
MUL里的参数TOW会被展开为(2).
2,当有'#'或'##'的时候
#defineA(2)
#defineSTR(s)#s
#defineCONS(a,b)int(a##e##b)
printf("intmax:%s
",STR(INT_MAX));//INT_MAX#include
这行会被展开为:
printf("intmax:%s
","INT_MAX");
printf("%s
",CONS(A,A));//compileerror
这一行则是:
printf("%s
",int(AeA));
INT_MAX和A都不会再被展开,然而解决这个问题的方法很简单.加多一层中间转换宏.
加这层宏的用意是把所有宏的参数在这层里全部展开,那么在转换宏里的那一个宏(_STR)就能得到正确的宏参数.
#defineA(2)
#define_STR(s)#s
#defineSTR(s)_STR(s)//转换宏
#define_CONS(a,b)int(a##e##b)
#defineCONS(a,b)_CONS(a,b)//转换宏
printf("intmax:%s
",STR(INT_MAX));//INT_MAX,int型的最大值,为一个变量#include
输出为:intmax:0x7fffffff
STR(INT_MAX)-->_STR(0x7fffffff)然后再转换成字符串;
printf("%d
",CONS(A,A));
输出为:200
CONS(A,A)-->_CONS((2),(2))-->int((2)e(2))
三、'#'和'##'的一些应用特例
1、合并匿名变量名
#define___ANONYMOUS1(type,var,line)typevar##line
#define__ANONYMOUS0(type,line)___ANONYMOUS1(type,_anonymous,line)
#defineANONYMOUS(type)__ANONYMOUS0(type,__LINE__)
例:ANONYMOUS(staticint);即:staticint_anonymous70;70表示该行行号;
第一层:ANONYMOUS(staticint);-->__ANONYMOUS0(staticint,__LINE__);
第二层:-->___ANONYMOUS1(staticint,_anonymous,70);
第三层:-->staticint_anonymous70;
即每次只能解开当前层的宏,所以__LINE__在第二层才能被解开;
2、填充结构
#defineFILL(a){a,#a}
enumIDD{OPEN,CLOSE};
typedefstructMSG{
IDDid;
constchar*msg;
}MSG;
MSG_msg[]={FILL(OPEN),FILL(CLOSE)};
相当于:
MSG_msg[]={{OPEN,"OPEN"},
{CLOSE,"CLOSE"}};
3、记录文件名
#define_GET_FILE_NAME(f)#f
#defineGET_FILE_NAME(f)_GET_FILE_NAME(f)
staticcharFILE_NAME[]=GET_FILE_NAME(__FILE__);
4、得到一个数值类型所对应的字符串缓冲大小
#define_TYPE_BUF_SIZE(type)sizeof#type
#defineTYPE_BUF_SIZE(type)_TYPE_BUF_SIZE(type)
charbuf[TYPE_BUF_SIZE(INT_MAX)];
-->charbuf[_TYPE_BUF_SIZE(0x7fffffff)];
-->charbuf[sizeof"0x7fffffff"];
这里相当于:
charbuf[11];