AVR单片机串口USART与PC通讯实例和中断程序分析.docx
《AVR单片机串口USART与PC通讯实例和中断程序分析.docx》由会员分享,可在线阅读,更多相关《AVR单片机串口USART与PC通讯实例和中断程序分析.docx(11页珍藏版)》请在冰豆网上搜索。
AVR单片机串口USART与PC通讯实例和中断程序分析
“并行”通讯:
是指8位数据同时通过并行线进行传送,这样数据传送速度大大提高,但并行传送的线路长度受到限制,因为长度增加,干扰就会增加,数据也就容易出错。
“串行”通讯:
形容一下就是一条车道,而并口就是有8个车道同一时刻能传送8位(一个字节)数据。
但是并不是并口快,由于8位通道之间的互相干扰。
传输时速度就受到了限制。
而且当传输出错时,要同时重新传8个位的数据。
串口没有干扰,传输重发一位就可以了。
所以要比并口快。
串行通讯协议较多,单片机常用的有USART,SPI,TWI,1-Wire等。
串行通讯有分为同步和异步通讯:
通俗讲 同步就是你叫我去吃饭,我听到了就和你去吃饭;如果没有听到,你就不停的叫,直到我告诉你听到了,才一起去吃饭。
异步就是你叫我,然后自己去吃饭,我得到消息后可能立即走,也可能等到下班才去吃饭。
同步通讯:
收信发信双方在使用同步时钟,在同一时刻传输线上的数据就是要传输的信息。
异步通讯:
以字符为传输单位,字符与字符之间是异步的,而字符的位是同步的
USART:
异步串行通讯,常用与单片机和单片机,单片机和PC电脑间的数据传输。
波特率:
表征通讯速度的参数,单位是位/秒(b/s),即每秒钟传输的二进制位数,如波特率9600,表示每秒钟传输9600个二进制位数据。
收发双方必须采用同样的波特率。
波特率不同将无法正常通讯。
全双工通讯:
指是的是可以同时发送和接收数据。
半双工通讯:
指的是在同一时刻只能发送或只能接收数据。
单片机与PC通讯的电平转换:
单片机的电压一般是TTL电平,电压0v-5v,PC机串口采用的是RS-232协议,它的的电压范围是-15-+15v,电平不同,无法通讯。
要实现通讯,必须进行电平和逻辑关系的转换, 一般用 MAX232集成芯片进行电平的转换。
ATmega16串口结构:
有一个全双工的串行口,有两条通讯线,TXD:
数据发送线,RXD:
数据接收线,对应的单片机外部引脚为PD1,PD0
相关寄存器:
UDR 串口数据寄存器,
UCSRA 串口控制与状态寄存器A
UCSRB 串口控制与状态寄存器B
UCSRC 串口控制与状态寄存器C
UBRRH,UBRRL波特率寄存器
发生器对波特率发生器的输出时钟进行2、8或16的分频,具体情况取决于工作模式,如下图:
如:
系统时钟频率f=8MHZ,异步正常模式(16分频),波特率9600
则:
UBRR=8000000/16*9600-1=52-1=51; 波特率寄存器赋值:
UBRRH=0; UBRRL=51;/*10进制写法*/,
或者UBRRH=0;UBRRL=0x33;/*16进制写法*/
操作步骤:
一、设置异步模式:
UCSRC|=(1< 二、设置数据帧格式:
8位数据位,一位停止位,UCSRC|=(1< 三、设置波特率寄存器:
UBRRL=51;UBRRH=0;
四、使能发送接收:
UCSRB|=(1< 五、中断总使能:
SREG=0X80;
//函数功能:
通过PC串口向单片发送数据,单片机接收数据后,送到PA口显示,再送回到PC机
#include
#include
#definefosc8000000 //晶振8MHZ
#definebaud9600 //波特率定义
/*端口初始化函数*/
void init(void)
{
PORTA=0xFF; //PA口输出高电平
DDRA =0xFF; //PA口设置为输出
PORTD=0X00; //USART的发送接收端口分别为PD0和PD1
DDRD|=(1<}
/*串口初始化函数*/
voiduart_init(void)
{
UCSRB=0x00;
UCSRA=0x00;
UCSRC|=(1<//UCSRC寄存器与UBRRH寄存器共用相同的I/O地址,写UCSRC时,URSEL应设置为1。
UBRRL=51; //设置波特率寄存器10进制的写法
UBRRH=0;
UCSRB|=(1<}
/*发送数据函数*/
voidsend(unsignedchari)
{
while(!
(UCSRA&(1<UDR=i;
}
/*以下是接收数据函数*/
unsignedchar receive(void)
{
while(!
(UCSRA&(1<returnUDR;
}
/*主函数*/
voidmain(void)
{
unsignedchartemp;
init();
uart_init();
while
(1)
{
temp=receive(); //接收数据
PORTA=~temp; //将接收的数据取反后送PA口显示
send(temp); //向PC机发送数据
}
}
1.范例描述
按下按键0,LED0亮。
直到松手,其他按键才能起作用
按下按键1,LED1亮。
其他按键随时都能起作用
按下按键2,LED0/1都熄灭。
直到松手,其他按键才能起作用
2.电路图设计:
.
4.代码设计与说明:
本程序简单的示范了如何使用ATMEGA16的外部中断
中断的设置
按键的简单延时防抖动
中断的嵌套
变量在中断中的应用---如果变量会在中断服务程序中被修改,须加volatile限定
本范例可直接使出厂状态的新M16芯片,无需对芯片的熔丝位进行配置。
出于简化程序考虑,各种数据没有对外输出,学习时建议使用JTAGICE硬件仿真器
关于外部中断作唤醒源的条件:
(将会在后面的电源管理和睡眠模式范例中应用)
而INT0和INT1的边沿触发中断只能在空闲模式起作用,即CLKI/O不停止
INT0和INT1的低电平中断,INT2在各种睡眠模式下都可以,因为这几种中断工作
于异步模式,不需要时钟驱动
官方的M16中文手册对外部中断的描叙存在多处错误,请参考英文原版。
*/
#include
#include
#include
#include
/*宏INTERRUPT的用法与SIGNAL类似,区别在于
SIGNAL执行时全局中断触发位被清除、其他中断被禁止
INTERRUPT执行时全局中断触发位被置位、其他中断可嵌套执行
中断服务程序的编写具有一定的格式,在不同编译环境下各不相同,在WINAVR(GCC)环境下有两种方式,分别是:
第一种中断服务程序的编写格式:
SIGNAL(中断向量名)
{
…//中断服务程序内容
}
第一种中断服务程序的编写格式:
ISR(中断向量名)
{
…//中断服务程序内容
}
在这两种方式中,需要分别添加头文件:
#include和#include。
另外avr-libc提供两个API函数用于置位和清零全局中断触发位,它们是经常用到的。
分别是:
voidsei(void)和voidcli(void)由interrupt.h定义*/
//注:
内部函数_delay_ms()最高延时262.144mS@1MHz
/*该函数可以实现较精确的定时,但用JTAG仿真时较麻烦---会进入机器码窗口(Disassembeler)
.注意跳开该语段。
一旦JTAG仿真进入该内部函数语句,会变得像"死机"一样(其实在运行中),可以先[break],然后
在后面的C语句设[breakpoint],[RUN]跳过*/
//for()/while()语句计算延时时间较麻烦。
//为了使_delay_ms()函数的延时正确,须在makefile中设定F_CPU为实际的系统时钟频
//本范例为1MHz内部RC振荡器即F_CPU=1000000
/*
C:
\WinAVR\avr\include\avr\目录包括所有芯片的定义和其他头文件
其中iom16.h定义ATMEGA16芯片的特性(中断向量,寄存器,位定义...)
包括下面中断服务程序的常量SIG_INTERRUPTx,PORTx,GICR.....
*/
//管脚定义
#defineEXT_INT02//PD2按键0
#defineEXT_INT13//PD3按键1
#defineEXT_INT22//PB2按键2
#defineLED00//PB0
#defineLED11//PB1
#defineLED23//PB3
//宏定义
#defineLED0_ON()PORTB|=(1<#defineLED0_OFF()PORTB&=~(1<#defineLED1_ON()PORTB|=(1<#defineLED1_OFF()PORTB&=~(1<#defineLED2_ON()PORTB|=(1<#defineLED2_OFF()PORTB&=~(1<//51系列的高电平输出能力很弱,低电平也仅能点亮LED.所以常见输出低电平才灯亮的接法。
//AVR芯片的高低驱动能力都很强,甚至能推动8字数码管的公共极,怎么接都没问题。
//全局变量
#definehas_volatile1//这里是条件编译
//可以修改has_volatile=1或0来看程序运行的效果
#ifhas_volatile
volatileunsignedcharFLAG;//全局变量,会在中断服务程序中被修改,须加volatile限定
#else
unsignedcharFLAG;//全局变量.
#endif
//仿真时在watch窗口,监控这些变量。
SIGNAL(SIG_INTERRUPT0)//INT0中断服务程序
{
//硬件自动清除INTF0标志位
_delay_ms(10);//延时
if((PIND&(1<LED0_ON();//点亮LED0
loop_until_bit_is_set(PIND,EXT_INT0);//等待按键释放(变为高电平)
_delay_ms(10);//延时按键释放时也会抖动。
//即使同时发生其它的中断事件,如果在这里把相应的中断标志位清除,那么该中断将
不能触发进入中断服务
/*注意
读端口用PINx
写端口用PORTx*/
}
INTERRUPT(SIG_INTERRUPT1)//INT1中断服务程序
{
//硬件自动清除INTF1标志位
//这里全局中断被打开,将允许其他中断嵌套执行
_delay_ms(10);
if((PIND&(1<LED1_ON();//点亮LED1
loop_until_bit_is_set(PIND,EXT_INT1);
_delay_ms(10);
}
SIGNAL(SIG_INTERRUPT2)//INT2中断服务程序
{
//硬件自动清除INTF2标志位
_delay_ms(10);
if((PINB&(1<{
LED0_OFF();//熄灭LED0
LED1_OFF();//熄灭LED1
}
loop_until_bit_is_set(PINB,EXT_INT2);
FLAG=!
FLAG;//修改全局变量
_delay_ms(100);
}
intmain(void)
{
//上电默认DDRx=0x00,PORTx=0x00输入,无上拉电阻
PORTA=0xFF;//不用的管脚使能内部上拉电阻。
PORTC=0xFF;
PORTD=0xFF;
DDRB=(1<PORTB=~((1<//外部中断INT0,1,2做按键输入,使能内部上拉,就可以不用外接电阻了
MCUCR=(1</*
ISCx1:
0=00INTx引脚为低电平时产生中断请求
ISCx1:
0=01INTx引脚上任意的逻辑电平变化都将引发中断
ISCx1:
0=10INTx引脚的下降沿产生中断请求
ISCx1:
0=11INTx引脚的上升沿产生中断请求
MCUCSR&=~(1</*ISC2=0INT2引脚的下降沿产生异步中断请求
ISC2=1INT2引脚的上升沿产生异步中断请求
GIFR=(1<GICR=(1<FLAG=0;
sei();//使能全局中断
while
(1)
{
while(FLAG==0);
LED2_ON();//如果FLAG不加volatile限定(即has_volatile=0),
//程序将永远都运行不到这里。
while(FLAG!
=0);
LED2_OFF();
}
}
程序运行效果
按下按键0,LED0亮。
直到松手,其他按键才能起作用
按下按键1,LED1亮。
其他按键随时都能起作用
按下按键2,LED0/1都熄灭。
直到松手,其他按键才能起作用
LED2是根据按键2的顺序来亮灭,松手后变换,前提是FLAG加了volatile限定
*/