论坛» 嵌入式开发» MCU

AVR单片机总结

助工
2014-12-01 22:44 1楼

1、AVR单片机中断总结
AVR单片机只是ATMEL公司推出的一款基于RISC指令构架的高性能、低功耗单片机。
ATmega16单片机具有21个中断源,如下表所示。每一个中断源都有一个独立的中断向量作为中断服务程序的入口地址,而且所有的中断源都有自己独立的使能位。如果全局中断I和相应的中断位都置位,则在中断标志位置位时将执行中断服务程序。

向量号
程序地址
中断源
中断定义
1
0x0000
RESET
外部引脚电平产生复位、上电位复位、掉电检测复位,看门狗复位以及JTAG复位
2
0x0002
INT0
外部中断请求0
3
0x0004
INT1
外部中断请求1
4
0x0006
T/C2_COMP
T/C2比较匹配中断
5
0x0008
T/C2_OVF
T/C2溢出中断
6
0x000A
T/C1_CAP
T/C1捕获中断
7
0x000C
T/C1_COMPA
T/C1比较匹配中断A
8
0x000E
T/C1_COMPA
T/C1比较匹配中断B
9
0x0010
T/C1_OVF
T/C1溢出中断
10
0x0012
T/C0_OVF
T/C0溢出中断
11
0x0014
SPI_STC
SPI串行传输中断
12
0x0016
USART_RXC
USART接受结束中断
13
0x0018
USART_UDRE
USART发送数据寄存器中断
14
0x001A
USART_TXC
USART发送结束断
15
0x001C
ADC
ADC转换结束中断
16
0x001E
EE_RDY
EEPROM就绪中断
17
0x0020
ANA_COMP
模拟比较器中断
18
0x0022
TWI
TWI串行接口中断
19
0x0024
INT2
外部中断2请求
20
0x0026
T/C0_COMP
T/C1比较匹配中断
21
0x0028
SPM_RDY
保存程序存储器内容就绪中断
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的弱点


CS12 CS11 CS10 说明
0 0 0 无时钟源 (T/C 停止)
0 0 1 clkIO/1 ( 无预分频)
0 1 0 clkIO/8 ( 来自预分频器)
0 1 1 clkIO/64 ( 来自预分频器)
1 0 0 clkIO/256 ( 来自预分频器)
1 0 1 clkIO/1024 ( 来自预分频器)
1 1 0 外部T1 引脚,下降沿驱动
1 1 1 外部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 状态;
2 COM1x1:0 还控制OC1x 引脚输出的来源。
只要COM1x1:0 不全为零,波形发生器的输出比较功能就会重载OC1x 的通用I/O 口功能。
但是OC1x 引脚的方向仍旧受控于数据方向寄存器 (DDR)。
从OC1x 引脚输出有效信号之前必须通过数据方向寄存器的DDR_OC1x 将此引脚设置为输出。

波形发生器利用COM1x1:0 的方法在普通模式、CTC 模式和PWM 模式下有所区别。
对于所有的模式,设置COM1x1:0=0 表明比较匹配发生时波形发生器不会操作OC1x寄存器


访问16位寄存器
写16 位寄存器时,应先写入该寄存器的高位字节.
usigned int k;
k=0x1234;
TCNT1H=(unsignedchar)(k>>8);
TCNT1L=(unsigned char) k;
而读16 位寄存器时应先读取该寄存器的低位字节.
usigned int k;
k=TCNT1L;
k+=(unsignedint)(TCNT1H<<8);
使用“C” 语言时,编译器会自动处理16位操作.
usigned int k;
k=0x1234;
TCNT1=k;
k=TCNT1;




这里举例 如何用16位定时器T1实现高精度1秒连续定时,精准度跟所用晶振一样
T1 CTC模式,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.125uS 8192us 8毫秒
8 1uS 65536us 65毫秒
64 8uS 524ms 0.5秒
256 32uS 2097ms 3秒
1024 128uS 8388ms 超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,防止一个头文件被重复包含


#ifndef COMDEF_H


#define COMDEF_H


//头文件内容


#endif


2,重新定义一些类型,防止由于各种平台和编译器的不同,而产生的类型字节数差异,方便移植。


typedef unsigned char boolean; /* Boolean value type. */





typedef unsigned long int uint32; /* Unsigned 32 bit value */


typedef unsigned short uint16; /* Unsigned 16 bit value */


typedef unsigned char uint8; /* Unsigned 8 bit value */





typedef signed long int int32; /* Signed 32 bit value */


typedef signed short int16; /* Signed 16 bit value */


typedef signed char int8; /* Signed 8 bit value */








//下面的不建议使用


typedef unsigned char byte; /* Unsigned 8 bit value type. */


typedef unsigned short word; /* Unsinged 16 bit value type. */


typedef unsigned long dword; /* Unsigned 32 bit value type. */





typedef unsigned char uint1; /* Unsigned 8 bit value type. */


typedef unsigned short uint2; /* Unsigned 16 bit value type. */


typedef unsigned long uint4; /* Unsigned 32 bit value type. */





typedef signed char int1; /* Signed 8 bit value type. */


typedef signed short int2; /* Signed 16 bit value type. */


typedef long int int4; /* Signed 32 bit value type. */





typedef signed long sint31; /* Signed 32 bit value */


typedef signed short sint15; /* Signed 16 bit value */


typedef signed char sint7; /* Signed 8 bit value */





3,得到指定地址上的一个字节或字


#define MEM_B( x ) ( *( (byte *) (x) ) )


#define MEM_W( x ) ( *( (word *) (x) ) )


4,求最大值和最小值


#define MAX( x, y ) ( ((x) > (y)) ? (x) : (y) )


#define MIN( x, y ) ( ((x) < (y)) ? (x) : (y) )


5,得到一个field在结构体(struct)中的偏移量


#define FPOS( type, field ) \


/*lint -e545 */ ( (dword) &(( type*) 0)-> field ) /*lint +e545 */


6,得到一个结构体中field所占用的字节数


#define FSIZ( type, field ) sizeof(((type *) 0)->field )


7,按照LSB格式把两个字节转化为一个Word


#define FLIPW( ray ) ( (((word) (ray)[0]) * 256) + (ray)[1] )


8,按照LSB格式把一个Word转化为两个字节


#define FLOPW( ray, val ) \


(ray)[0] = ((val) / 256); \


(ray)[1] = ((val) & 0xFF)


9,得到一个变量的地址(word宽度)


#define B_PTR( var ) ( (byte *) (void *)&(var) )


#define W_PTR( var ) ( (word *) (void *)&(var) )


10,得到一个字的高位和低位字节


#define WORD_LO(xxx) ((byte) ((word)(xxx)& 255))


#define WORD_HI(xxx) ((byte) ((word)(xxx)>> 8))


11,返回一个比X大的最接近的8的倍数


#define RND8( x ) ((((x) + 7) / 8 ) * 8 )


12,将一个字母转换为大写


#define UPCASE( c ) ( ((c) >= 'a' && (c) <= 'z') ? ((c) - 0x20) :(c) )


13,判断字符是不是10进值的数字


#define DECCHK( c ) ((c) >= '0' && (c) <= '9')


14,判断字符是不是16进值的数字


#define HEXCHK( c ) ( ((c) >= '0' && (c) <= '9') ||\


((c) >= 'A'&& (c) <= 'F') ||\


((c) >= 'a' && (c) <= 'f'))


15,防止溢出的一个方法


#define INC_SAT( val ) (val = ((val)+1> (val)) ? (val)+1 : (val))


16,返回数组元素的个数


#define ARR_SIZE( a ) ( sizeof( (a) ) /sizeof( (a[0]) ) )


17,返回一个无符号数n尾的值MOD_BY_POWER_OF_TWO(X,n)=X%(2^n)


#define MOD_BY_POWER_OF_TWO( val, mod_by) \


( (dword)(val) &(dword)((mod_by)-1) )


18,对于IO空间映射在存储空间的结构,输入输出处理


#define inp(port) (*((volatile byte *) (port)))


#define inpw(port) (*((volatile word *) (port)))


#define inpdw(port) (*((volatile dword *)(port)))





#define outp(port, val) (*((volatile byte *) (port)) = ((byte)(val)))


#define outpw(port, val) (*((volatile word *) (port)) = ((word)(val)))


#define outpdw(port, val) (*((volatiledword *) (port)) = ((dword) (val)))


[2005-9-9添加]


19,使用一些宏跟踪调试


A N S I标准说明了五个预定义的宏名。它们是:


_ L I N E _


_ F I L E _


_ D A T E _


_ T I M E _


_ S T D C _


如果编译不是标准的,则可能仅支持以上宏名中的几个,或根本不支持。记住编译程序


也许还提供其它预定义的宏名。


_ L I N E _及_ F I L E _宏指令在有关# l i n e的部分中已讨论,这里讨论其余的宏名。


_ D AT E _宏指令含有形式为月/日/年的串,表示源文件被翻译到代码时的日期。


源代码翻译到目标代码的时间作为串包含在_ T I M E _中。串形式为时:分:秒。


如果实现是标准的,则宏_ S T D C _含有十进制常量1。如果它含有任何其它数,则实现是


非标准的。


可以定义宏,例如:


当定义了_DEBUG,输出数据信息和所在文件所在行


#ifdef _DEBUG


#define DEBUGMSG(msg,date)printf(msg);printf(“%d%d%d”,date,_LINE_,_FILE_)


#else


#define DEBUGMSG(msg,date)


#endif





20,宏定义防止使用是错误


用小括号包含。


例如:#define ADD(a,b) (a+b)


用do{}while(0)语句包含多语句防止错误


例如:#difne DO(a,b) a+b;\


a++;


应用时:if(….)


DO(a,b); //产生错误


else





解决方法: #difne DO(a,b) do{a+b;\


a++;}while(0)


宏中"//m.amcfsurvey.com/forum/thread/264766/#"和"##"的用法
一、一般用法
我们使用#把宏参数变为一个字符串,用##把两个宏参数贴合在一起.
用法:
#include
#include
using namespace std;


#define STR(s) #s
#define CONS(a,b) int(a##e##b)


int main()
{
printf(STR(vck)); // 输出字符串"vck"
printf("%d
", CONS(2,3)); // 2e3 输出:2000
return 0;
}


二、当宏参数是另一个宏的时候
需要注意的是凡宏定义里有用'//m.amcfsurvey.com/forum/thread/264766/#'或'##'的地方宏参数是不会再展开.


1, 非'//m.amcfsurvey.com/forum/thread/264766/#'和'##'的情况
#define TOW (2)
#define MUL(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, 当有'//m.amcfsurvey.com/forum/thread/264766/#'或'##'的时候
#define A (2)
#define STR(s) #s
#define CONS(a,b) int(a##e##b)


printf("int max: %s
", STR(INT_MAX)); // INT_MAX#include
这行会被展开为:
printf("int max: %s
", "INT_MAX");


printf("%s
", CONS(A, A)); // compile error
这一行则是:
printf("%s
", int(AeA));


INT_MAX和A都不会再被展开, 然而解决这个问题的方法很简单. 加多一层中间转换宏.
加这层宏的用意是把所有宏的参数在这层里全部展开, 那么在转换宏里的那一个宏(_STR)就能得到正确的宏参数.


#define A (2)
#define _STR(s) #s
#define STR(s) _STR(s) // 转换宏
#define _CONS(a,b) int(a##e##b)
#define CONS(a,b) _CONS(a,b) // 转换宏


printf("int max: %s
", STR(INT_MAX)); // INT_MAX,int型的最大值,为一个变量 #include
输出为: int max: 0x7fffffff
STR(INT_MAX) --> _STR(0x7fffffff) 然后再转换成字符串;


printf("%d
", CONS(A, A));
输出为:200
CONS(A, A) --> _CONS((2), (2)) -->int((2)e(2))


三、'//m.amcfsurvey.com/forum/thread/264766/#'和'##'的一些应用特例
1、合并匿名变量名
#define ___ANONYMOUS1(type, var, line) type var##line
#define __ANONYMOUS0(type, line) ___ANONYMOUS1(type, _anonymous, line)
#define ANONYMOUS(type) __ANONYMOUS0(type, __LINE__)
例:ANONYMOUS(static int); 即: static int _anonymous70; 70表示该行行号;
第一层:ANONYMOUS(static int); --> __ANONYMOUS0(static int,__LINE__);
第二层: --> ___ANONYMOUS1(static int,_anonymous, 70);
第三层: --> static int _anonymous70;
即每次只能解开当前层的宏,所以__LINE__在第二层才能被解开;


2、填充结构
#define FILL(a) {a, #a}


enum IDD{OPEN, CLOSE};
typedef struct MSG{
IDD id;
const char * msg;
}MSG;


MSG _msg[] = {FILL(OPEN), FILL(CLOSE)};
相当于:
MSG _msg[] = {{OPEN, "OPEN"},
{CLOSE,"CLOSE"}};


3、记录文件名
#define _GET_FILE_NAME(f) #f
#define GET_FILE_NAME(f) _GET_FILE_NAME(f)
static char FILE_NAME[] = GET_FILE_NAME(__FILE__);


4、得到一个数值类型所对应的字符串缓冲大小
#define _TYPE_BUF_SIZE(type) sizeof #type
#define TYPE_BUF_SIZE(type) _TYPE_BUF_SIZE(type)
char buf[TYPE_BUF_SIZE(INT_MAX)];
--> char buf[_TYPE_BUF_SIZE(0x7fffffff)];
--> char buf[sizeof "0x7fffffff"];
这里相当于:
char buf[11];


共1条 1/1 1 跳转至

回复

匿名不能发帖!请先 [ 登陆 注册]