本地本地下载

本地本地下载
AVR 单片机学习开发板教程
dushibiao
2007 年 11 月
AVR 单片机学习开发板教程
第一章
初始开发板
第二章
开发软件教程器
在本开发板中,大部分程序使用两种编译器,AVR—gcc 和 ICC—AVR.这里对 AVR—gcc
的使用方法详加说明。AVRGCC难学的地方的就是Makefile文件的制作,本光盘里面有
Makefile文件模板,你只要修改几行就可以了,用记事本打开Makefile文件。如图所示。
看到没,你只要把最后一行 TARGET=------后面的名字换为你 C 语言主文件的名字即可。
Programmer Notepad 的配置与 AVRGCC 入门(一)
单片机 AVR 的编程工具很多,有 C,ASM,PASCAL,BASIC 等等。除 ASM 由 ATMEL 公司免费提供外,其它大
多数的工具都是需要收费的。而 C 编译器更是其中收费最高的编译器。但也有例外,那就是 GCC——它不
但免费而且功能也几乎是“最强”的。所以我作为入门者,就开始学习它了!
一、 单片机编程
1、 单片机与电脑的编程有些许不同,它除一些必要地算法外,更重要的是对端口的操作。如开关量
采集、开关量控制输出、模拟量的输入、通讯的操作、显示器与键的控制等等都需要操作端口。
1
AVR 单片机学习开发板教程
dushibiao
2007 年 11 月
2、 电脑存储永久数据一般都在硬盘等介质中。而单片机则存放在 EPROM、EEPROM、FLASH 等存储器中。
3、 通讯接口的编程对单片机来说是至关重要的,特别在工控、网络等的应用中犹为重要。
4、 单片机的编程与电脑编程相比有诸多限制。这就要求编程者对单片机硬件有一定的了解。
二、 AVR 单片机编程
1、 AVR 单片的硬件:我们以 ATMega 16 为例为说明一下 AVR 单片机吧!
这是 ATMEGA 16 的引脚及其功能图(来自其 DataSheet)
A、 从图中我们可以看出有 4 个 8 位端口共 32 个引脚,大多有双重功能。它们分别命名为 PORTA、PORTB、
PORTC、PORTD。
B、 PORTA 具备普通 IO 口功能外还有 AD 转换功能,其精度可以达到 10 位,即采集到的 ADC 的值最大不超
过 1023(0-1023),对应外面实际电压值的精度需要一定的简单换算(主要看其参考电压)。如参考电压
为 2.5V,则有 2.5V 为 1023,则其精度为 2.5V/1023 就是 0.00244V。如参考电压为 5V,则有 5V/1023 也就
是 0.0049V。
C、 PORTB 口除了基本的 IO 功能外,特别要提的是 PB4、5、6、7 的功能,它是 SPI(同步串行接口),更
重要的是,它可以用来下载程序。
D、 其它的暂且不说,必竟不是 AVR 单片机的介绍文章嘛。
2
AVR 单片机学习开发板教程
dushibiao
2007 年 11 月
2、 AVR 单片机的软件:
软件当然是用来控控制这些接口的运作及其代表的含义的。这也是个非常大的问题,如果你一点都不懂软
件,请也找本计算机编程的书看看吧。
三、 开始 AVRGCC 编程吧
好了,下面我们来看一个简单的程序吧。
在 WinAVR 的 Programmer NotePad 2 中的图:
图2
执行 Make All 后,生成 HEX 文件(本例中的文件名 2.HEX)。以下是它的全过程。
3
AVR 单片机学习开发板教程
dushibiao
2007 年 11 月
> "make.exe" all
-------- begin -------avr-gcc (GCC) 3.4.1 //告知 avr-gcc 的版本号
Copyright (C)2004 Free Software Foundation,Inc. //编译器所属于公司及其版权信息
This is free software; see the source for copying conditions. There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
//这是 ELF 文件的一些信息,ELF 文件用于调试。
Size before: //转换前的 ELF 文件信息
2.elf :
section size addr
.text 208 0
.data 0 8388704
.bss 0 8388704
.noinit 0 8388704
.eeprom 0 8454144
.stab 780 0
.stabstr 1469 0
Total 2457
Converting to AVR Extended COFF: 2.cof//将 ELF 文件转换成 AVR Studio 能够接受的格式
avr-objcopy --debugging --change-section-address .data-0x800000
--change-section-address .bss-0x800000 --change-section-address .noinit-0x800000
--change-section-address .eeprom-0x810000 -O coff-ext-avr 2.elf 2.cof
Discarding local symbol outside any compilation unit: .do_copy_data_start
Discarding local symbol outside any compilation unit: .do_copy_data_loop
Discarding local symbol outside any compilation unit: .do_clear_bss_start
Discarding local symbol outside any compilation unit: .do_clear_bss_loop
Size after: //转换后的文件信息。
2.elf :
section size addr
.text 208 0
.data 0 8388704
.bss 0 8388704
.noinit 0 8388704
.eeprom 0 8454144
.stab 780 0
.stabstr 1469 0
Total 2457
Errors: none //编译过程中产生的错误数
-------- end -------- //编译结束
4
AVR 单片机学习开发板教程
dushibiao
2007 年 11 月
> Process Exit Code: 0 //avr-gcc 结束信息,0 表示正常结束
接下来用 AVRISP 程序将 2.hex 文件下载后 AVR 单片机中,观看效果。
下面我们来看看 avr-gcc 的 IDE 吧。
主窗口(图 3)
这是非常标准的 Windows Style 窗口。当然它由于不是专为 avr-gcc 设计,所以对它进行设置是必不可少
的!下面我就来设置它,以使它成为我们好用的工具吧。(呵呵!可千万别小看它哦)
在设置它之前让我们来看看,我们希望是一个怎样的工具吧。参照其它软件的 IDE。
首先,我们得有个工程(项目)管理器,这点 PN 已有,不需我们去设它。
5
AVR 单片机学习开发板教程
dushibiao
2007 年 11 月
其次,得方便我们编辑源代码,最好是有关键字(代码)高度显示,以方便我们识别它们。如果能再给我
们实时的一些提示就更好了!这方便 PN 做得非常很好(有人说 SI 很好,不过我觉得每个人有每个人的习
惯,不必强求)。
再次,我们得在不离开 IDE 的情况下,编辑 C 语言的 makefile 文件。生成我“目标代码”(计算机中术语
的话叫可执行文件),并且,下载到我们单片机中。
好了!来看看我们对它的设置吧!
1、代码高亮设置将它设置成你习惯的模式。步骤如下:打开 PN 菜单 Tools->Options,在 Options 对话框
中选择 Style->Schemes,你说看到了如图 4、5、6 所示的内容了。现在开始你的设置吧。
代码高亮设置 1-预编译(图 4)
6
AVR 单片机学习开发板教程
dushibiao
2007 年 11 月
代码高亮设置 2-操作符(如+-*/括号等)(图 5)
7
AVR 单片机学习开发板教程
dushibiao
2007 年 11 月
代码高亮设置 3-数字(图 6)
别小看这点小功能啊,它能帮你找到不少编程错误哦(图 7)
8
AVR 单片机学习开发板教程
dushibiao
2007 年 11 月
想将代码打印出来吗,有帮助的!(图 8)
其它高亮设置同上请大家自已动手吧!一定要按自己的习惯哦。
3、 设置 PN 中的菜单“新建”和工具栏图标 ,点击它新建文件时的文件缺省类型。我们当然希望是 C 类
型文件啦,如下图:
9
AVR 单片机学习开发板教程
dushibiao
2007 年 11 月
定义新建缺省的文件类型(图 9)
4、 下面开始设置的我们的 avr-gcc 工具菜单吧!
10
AVR 单片机学习开发板教程
dushibiao
2007 年 11 月
C 语文的 Make 工具设定(图 10): 精心的设置可以让你的 PN,不必其它工具差哦。
5、 C 语言 Makefile 对于初学者来说太难了,根本不知道它是干嘛的,怎么工作(说的有点夸张)及怎么
编写。幸好 WinAVR 提供给了我们一个非常好用的工具 mfile。下面就将它集成到我们的 PN 中来吧!
11
AVR 单片机学习开发板教程
dushibiao
2007 年 11 月
Makefile 工具在 PN 中的设定(图 11)(注意本工具设置为特殊设置)
Makefile 的设置是将 C:\WinAVR\bin 下的 wish84.exe、tcl84.dll、tk84.dll 三个文件复制到
C:\WinAVR\mfile 目录下。并用 PN 打开 C:\WinAVR\mfile\mfile.tcl。修改成下图所示
12
AVR 单片机学习开发板教程
dushibiao
2007 年 11 月
修改 mfile.tcl 为图中选中的部分并保存(图 12)
6、 到现在,我想你对 PN 有了一些了解了吧。看看下面几个图吧!
13
AVR 单片机学习开发板教程
dushibiao
2007 年 11 月
几个快捷键及其对应工具的设置(图 15)它们用着实在是方便啊
14
AVR 单片机学习开发板教程
dushibiao
2007 年 11 月
Makefile 执行后的图(图 16)
15
AVR 单片机学习开发板教程
dushibiao
2007 年 11 月
这是 PN 对大项目、大工程管理的超强部分了(图 17)
到此,Programmer NotePad 设置完成。
四、 Avr-gcc 简易入门
1、 对端口的操作:
A、 如果我想将 PORTB 端口设置为输出口(8 位),则在 gcc 中用如下方式
DDRB=0xFF; 注意:0xFF=0B1111 1111 表示全为 1,代表了输出。如果你改上式为:
DDRB=0x01; //即 0B0000 0001,则表示,你将 PORTB 的第 0 位(PB0)设置为输出,其它 PB1-7 为输入。
DDRB 为 AVR 的端口设置寄存器。
B、 从端口 PORTB 中读入状态,用如下方式:
Unsigned Char a=PINB; //读入端口 PORTB 的状态。若端口 PORTB 的状态如下:
PB0 为高电平 1
PB1 为低电平 0
16
AVR 单片机学习开发板教程
dushibiao
2007 年 11 月
PB2 为高电平 1
PB3 为高电平 1
PB4 为高电平 1
PB5 为低电平 0
PB6 为低电平 0
PB7 为高电平 1
则有 a = 0b1001 1101 = 0x9D
C、 向端口 PORTB 写状态(设置状态):若要装 PORTB 第 0 位和第 2 位置 1(高电平)。
DDRB=0;//PORTB 全部为输出。
PORTB=0x03; //0b0000 0101
D、 而更多的情况,我们是要将端口的某一位改变状态,而不是对整个端口操作。或只想知道端口的某一
位的状态如何的?那么如何来做呢?
例如:
将 PB4 置 1,PORTB=PORTB | 0x10; //0x10=0b0001 0000
将 PB4 置 0,PORTB=PORTB & 0xEF; //0xEF=0b1110 1111
将 PB4 置翻转,PORTB=PORTB^0x10; //0x10=0001 0000
检验 PB4 的状态,char a=PINB & 0x10; //如果 PB4 为 1,是 a>0,否则 a=0
当然,上面写法对 C 语言来说,简直就是垃圾代码了。C 语言有它自己的方式,
例如:
PORTB |= 0x10;
PORTB &= 0xEF;
PORTB ^=0x10;
If (PINB & 0x10){ 你的语句; }
是否感觉到比较简洁啊?!
E、 当然 avr-gcc 也提供了两个函数对操作位,如:sbi (PORTB,4); cbi (PORTB,4);分别将 PB4 置 1 和清
零。
2、 变量的类型
char
unsigned char
short
unsigned short
int
unsigned int
long
unsigned long
long long
17
AVR 单片机学习开发板教程
dushibiao
2007 年 11 月
unsigned long long
float
double
void
等等等等,这就请读者自己看资料了。
3、 程序控制语句,C 语言提供了非常丰富的程序流程控制语句。
i. 循环语句
A、 For(;;)语句,如:
for(i=0;i<8;i++){
循环体;
} //本例循环 8 次。
注意!for 语句是先比较后加减的。
B、 While(exp)语句,如:
i=0; While(i<8){
循环体;
i++;
} //本例循环 8 次。也是先比较后执行循环体的。
C、 Do while 语句,如:
i=0;
do{
i++;
循环体;
}while(x<8);
//本例循环 7 次,因为它是先执行后比较的语句。因为 i++在第一次比较时 i 已经是 1 了。
ii. 分支语句。
A、 if 语句,大名鼎鼎语句了,几乎所有编程软件都有它的身影。没什么好说的。
B、 switch 语句。等
好了,这些就不说下去了,因为如果你连这些都不清楚。你该做的是找本 C 教程吧。
4、 中断服务控制(SIGNAL)
18
AVR 单片机学习开发板教程
dushibiao
2007 年 11 月
void UART_Init(void){ //中断初始化函数//
UART_Ready = 1;
UART_ReceivedChar = 0;
pUART_Buffer = 0;
outp(BV(RXCIE)|BV(RXEN),UCR); // 允许串行接收中断 //
outp( (u08)UART_BAUD_SELECT, UBRR); // 设置 UART 波特率 //
sei(); // 打开全局中断 //
}
SIGNAL(SIG_UART_RECV){ //串口接收完成中断服务进程(子程序)//
UART_ReceivedChar = 1; // 指示已经接收到一个字符 //
UART_RxChar = inp(UDR); // 将收到的字符存储起来 //
}
其中 SIGNAL 标示了下面的语句由中断来调用。
五、 实例设计与编程
试验电路图(图 18)
19
AVR 单片机学习开发板教程
dushibiao
2007 年 11 月
好了,写了这么多,让我们也看个实例先吧,硬件电路图如图 18。
软件编程如下:
软件在 PN 中编写的结果如上图。
其中第 1、2 行为预编译语句,它们告诉编译器一些重要的信息。如单片机内的寄存器名称对应的向量
等.DDRB、PORTB 就在 io.h 内定义的(其实它在本例中是在 iom16.h 中定义的,io.h 是所有 AVR 单片机公共
定义,它从 makefile 中提取单片机类型,来从 include\avr 下取出对应的实际 io*.h 文件)。
第 4 行为 C 语言的主函数,特别要注意的是,avr-gcc 的主函数类型必须为 int 类型。否则出现警告错误
warning: return type of 'main' is not `int'。
第 5 行定义了三个无符号字符型变量:i,j,k。
第 6 行定义了端口 PORTB 全部为输出。
第 7 行在端口 PORTB 中输出高电平。
20
AVR 单片机学习开发板教程
dushibiao
2007 年 11 月
第 8 行到第 18 行为一个无限循环语句。
第 9 行开始到第 17 行也是一个循环。用它的目的是将端口 PORTB 的某一位置低,让 LED 点亮。
第 10 行是向端口的某一位(由变量 k 指定),其中十分重要的是 ~(1<<k)部分,前面的” ~ “是取反。
如(0b0000 0010,执行~后变为 0b1111 1101),而其中的 1<<k 在 C 语言中的意思是将 1 左移变量 k 指
定的位数。如 1=0b0000 0001,k=2,执行 1<<k 后变为 0b0000 0100。看懂这条语句了吧。:)见笑。
第 11 行到第 15 行由两个 for 循环构成的延时部分,它可以使 LED 移动的速度放慢,好让我们的肉眼能看
到。
好了。先写这些吧。
第三章
基础实验教程
实验一
流水灯实验
实验目的:学习 AVR 单片机的 IO 口操作
实验准备:请把 AVR 单片机的 IO 操作方法详细了解一下
实验要求:请把 LED 跳线打开左侧
AVR—GCC 版本程序如下
/*
Title:
AVR-GCC ledtest
Author: dushibiao
Date:
2007 10 18
Purpose: control the IO port
Frequency: internal 8M
needed
Software: AVR-GCC to compile
needed
Hardware: AVR mega16 BOARD
Connect: [email protected]
*/
#include <avr/io.h>
#include <avr/delay.h>
//注: 内部函数_delay_ms() 最高延时 [email protected][email protected]
//
该函数可以实现较精确的定时 for()/while()指令很难计算延时时间
//
为了使 _delay_ms()函数的延时正确,须在 makefile 中设定 F_CPU 为实际的系统时钟
频
//
本范例为 1MHz 内部 RC 振荡器 即 F_CPU=8000000
int main(void)
{
unsigned char i,j,k; //定义变量
PORTA=0xFF;
DDRA=0xFF;
//PA 口设为输出高电平,灯灭
21
AVR 单片机学习开发板教程
dushibiao
2007 年 11 月
while(1)
{
i=1;
for (j=0;j<8;j++) //循环 8 次,即 PA0~~PA7 轮流闪亮
{
PORTA=~i;
//反相输出,低电平有效
for (k=0;k<100;k++)
_delay_ms(20);
//延时 20*100=2 秒,可自行调节
i=i<<1;
//左移一位
// 0b00000001 PA0
// 0b00000010 PA1
// 0b00000100 PA2
// 0b00001000 PA3
// 0b00010000 PA4
// 0b00100000 PA5
// 0b01000000 PA6
// 0b10000000 PA7
}
}
}
ICC--AVR 版本程序如下
/*
Title:
icc-avr ledtest
Author: dushibiao
Date:
2007 10 18
Purpose: control the IO port
Frequency: internal 8M
needed
Software: icc-avr to compile
needed
Hardware: AVR mega16 BOARD
Connect: [email protected]
*/
#include <iom16v.h>
#include <macros.h>
//macros.h 中有 BIT(x) (1 << (x))定义,
比如 BIT(5)相当于将第 5 位置 1
/*-----------------------------------------------------------*/
/*----------------------------------------------------------------------延时函数
系统时钟:8M
-----------------------------------------------------------------------*/
22
AVR 单片机学习开发板教程
void delay_1us(void)
{
asm("nop");
}
void delay_nus(unsigned int n)
{
unsigned int i=0;
for (i=0;i<n;i++)
delay_1us();
}
void delay_1ms(void)
{
unsigned int i;
for (i=0;i<1140;i++);
}
dushibiao
2007 年 11 月
//1us 延时函数
//N us 延时函数
//1ms 延时函数
void delay_nms(unsigned int n)
//N ms 延时函数
{
unsigned int i=0;
for (i=0;i<n;i++)
delay_1ms();
}
/*-----------------------------------------------------------*/
void main()
{
DDRA=0XFF;
PORTA=0XFF;
while(1)
{
unsigned char i,j,k;
i=1;
for (j=0;j<8;j++) //循环 8 次,即 PA0~~PA7 轮流闪亮
{
PORTA=~i;
//反相输出,低电平有效
for (k=0;k<100;k++)
delay_nms(20);
//延时 20*100=2 秒,可自行调节
i=i<<1;
//左移一位
// 0b00000001 PA0
// 0b00000010 PA1
// 0b00000100 PA2
23
AVR 单片机学习开发板教程
dushibiao
2007 年 11 月
// 0b00001000 PA3
// 0b00010000 PA4
// 0b00100000 PA5
// 0b01000000 PA6
// 0b10000000 PA7
}
}
}
实验二
静态数码练习
实验目的:学习 AVR 单片机的 IO 口操作及数码的使用方法
实验准备:请把 AVR 单片机的 IO 操作方法详细了解一下
实验要求:请把 84 数码管与液晶选择跳线打到 84 一侧
AVR—GCC 版本程序如下
/*
Title:
AVR-GCC static shumaguan
Author: dushibiao
Date:
2007 10 18
Purpose: control the IO port
Frequency: internal 8M
needed
Software: AVR-GCC to compile
needed
Hardware: AVR mega16 BOARD
Connect: [email protected]
*/
#include <avr/io.h>
#include <avr/delay.h>
const unsigned char discode[]={0xc0,0xf9,0xa4,0xb0,0x99,0x92,
0x82,0xf8,0x80,0x90,0xff}; //共阳数码管段码
/*-----------------------------------------------------------*/
void display(void);
void main()
{
DDRB=0XFF;
PORTB=0XFF;
DDRA=0XFF;
PORTA=0XFF;
while(1)
//out
//
//out
24
AVR 单片机学习开发板教程
dushibiao
2007 年 11 月
{
display();
}
}
void display(void)
{
PORTB &=~(1<<PORTB0);
PORTA=discode[1];
_delay_ms(5);
PORTB |=(1<<PORTB0);
//light the first bit of the shumaguan
//PORTB0 was defined in iom16v.h
//display the thousand bit
//turn off the first bit of the shumaguan
PORTB &=~(1<<PORTB1);
PORTA=discode[2];
_delay_ms(5);
PORTB |=(1<<PORTB1);
PORTB &=~(1<<PORTB2);
PORTA=discode[3];
_delay_ms(5);
PORTB |=(1<<PORTB2);
PORTB &=~(1<<PORTB3);
PORTA=discode[4];
_delay_ms(5);
PORTB |=(1<<PORTB3);
}
ICC--AVR 版本程序如下
/*
Title:
icc-avr shumaguantest
Author: dushibiao
Date:
2007 10 18
Purpose: control the IO port
Frequency: internal 8M
needed
Software: icc-avr to compile
needed
Hardware: AVR mega16 BOARD
25
AVR 单片机学习开发板教程
dushibiao
2007 年 11 月
Connect: [email protected]
*/
#include<iom16v.h>
#include<macros.h>
//macros.h 中有 BIT(x) (1 << (x))定义,
比如 BIT(5)相当于将第 5 位置 1
const unsigned char discode[]={0xc0,0xf9,0xa4,0xb0,0x99,0x92,
0x82,0xf8,0x80,0x90,0xff}; //共阳数码管段码
/*-----------------------------------------------------------*/
/*----------------------------------------------------------------------延时函数
系统时钟:8M
-----------------------------------------------------------------------*/
void delay_1us(void)
//1us 延时函数
{
asm("nop");
}
void delay_nus(unsigned int n)
{
unsigned int i=0;
for (i=0;i<n;i++)
delay_1us();
}
void delay_1ms(void)
{
unsigned int i;
for (i=0;i<1140;i++);
}
//N us 延时函数
//1ms 延时函数
void delay_nms(unsigned int n)
//N ms 延时函数
{
unsigned int i=0;
for (i=0;i<n;i++)
delay_1ms();
}
/*-----------------------------------------------------------*/
26
AVR 单片机学习开发板教程
dushibiao
2007 年 11 月
void display(void);
void main()
{
DDRB=0XFF;
PORTB=0XFF;
DDRA=0XFF;
PORTA=0XFF;
while(1)
{
display();
}
}
//out
//
//out
void display(void)
{
PORTB &=~BIT(PORTB0);
PORTA=discode[1];
delay_nms(5);
PORTB |=BIT(PORTB0);
//light the first bit of the shumaguan
//PORTB0 was defined in iom16v.h
//display the thousand bit
//turn off the first bit of the shumaguan
PORTB &=~BIT(PORTB1);
PORTA=discode[2];
delay_nms(5);
PORTB |=BIT(PORTB1);
PORTB &=~BIT(PORTB2);
PORTA=discode[3];
delay_nms(5);
PORTB |=BIT(PORTB2);
PORTB &=~BIT(PORTB3);
PORTA=discode[4];
delay_nms(5);
PORTB |=BIT(PORTB3);
}
实验三
计数器实验
27
AVR 单片机学习开发板教程
dushibiao
2007 年 11 月
实验目的:学习 AVR 单片机的 IO 口操作及数码的使用方法
实验准备:请把 AVR 单片机的 IO 操作方法详细了解一下
实验要求:请把 84 数码管与液晶选择跳线打到 84 一侧
AVR—GCC 版本程序如下
/*
Title:
AVR-GCC ledtest
Author: dushibiao
Date:
2007 10 18
Purpose: display the four leds
Frequency: internal 8M
needed
Software: AVR-GCC to compile
needed
Hardware: AVR mega16 BOARD
Connect: [email protected]
*/
#include <avr/io.h>
#include <avr/delay.h>
//注: 内部函数_delay_ms() 最高延时 [email protected][email protected]
//
该函数可以实现较精确的定时 for()/while()指令很难计算延时时间
//
为了使 _delay_ms()函数的延时正确,须在 makefile 中设定 F_CPU 为实际的系统时钟
频
//
本范例为 8MHz 外部时钟, 即 F_CPU=8000000
const unsigned char discode[]={0xc0,0xf9,0xa4,0xb0,0x99,0x92,
0x82,0xf8,0x80,0x90,0xff}; //共阳数码管段码
void display(unsigned int number);
int main(void)
{
DDRB=0XFF;
PORTB=0XFF;
DDRA=0XFF;
PORTA=0XFF;
while(1)
{
unsigned int i=0,j;
for(i=0;i<10000;i++)
{
for(j=0;j<100;j++)
display(i);
//out
//
//out
28
AVR 单片机学习开发板教程
dushibiao
2007 年 11 月
}
}
}
/*--------------------------------------------------------*/
//para number: the number to be displayed
//return : none
/*--------------------------------------------------------*/
void display(unsigned int number)
{
PORTB &=~1;
//light the first bit of the shumaguan
//PORTB0 was defined in iom16v.h
PORTA=discode[number/1000];
//display the thousand bit
_delay_ms(5);
PORTB |=1;
//turn off the first bit of the shumaguan
PORTB &=~2;
PORTA=discode[number/100%10];
_delay_ms(5);
PORTB |=2;
PORTB &=~4;
PORTA=discode[number%100/10];
_delay_ms(5);
PORTB |=4;
PORTB &=~8;
PORTA=discode[number%10];
_delay_ms(5);
PORTB |=8;
}
ICC--AVR 版本程序如下
/*
Title:
icc-avr ledtest
Author: dushibiao
Date:
2007 10 18
Purpose: display the 4 bits leds as an counter
Frequency: internal 8M
needed
Software: icc-avr to compile
29
AVR 单片机学习开发板教程
dushibiao
2007 年 11 月
needed
Hardware: AVR mega16 BOARD
Connect: [email protected]
*/
#include<iom16v.h>
#include<macros.h>
//macros.h 中有 BIT(x) (1 << (x))定义,
比如 BIT(5)相当于将第 5 位置 1
const unsigned char discode[]={0xc0,0xf9,0xa4,0xb0,0x99,0x92,
0x82,0xf8,0x80,0x90,0xff}; //共阳数码管段码
/*-----------------------------------------------------------*/
/*----------------------------------------------------------------------延时函数
系统时钟:8M
-----------------------------------------------------------------------*/
void delay_1us(void)
//1us 延时函数
{
asm("nop");
}
void delay_nus(unsigned int n)
{
unsigned int i=0;
for (i=0;i<n;i++)
delay_1us();
}
void delay_1ms(void)
{
unsigned int i;
for (i=0;i<1140;i++);
}
void delay_nms(unsigned int n)
{
unsigned int i=0;
for (i=0;i<n;i++)
delay_1ms();
}
//N us 延时函数
//1ms 延时函数
//N ms 延时函数
30
AVR 单片机学习开发板教程
dushibiao
2007 年 11 月
/*-----------------------------------------------------------*/
void display(unsigned int number);
void main()
{
DDRB=0XFF;
//out
PORTB=0XFF;
//
DDRA=0XFF;
//out
PORTA=0XFF;
while(1)
{
unsigned int i=0,j;
for(i=0;i<10000;i++)
{
for(j=0;j<100;j++)
display(i);
}
}
}
/*--------------------------------------------------------*/
//para number: the number to be displayed
/*--------------------------------------------------------*/
void display(unsigned int number)
{
PORTB &=~BIT(PORTB0);
PORTA=discode[number/1000];
delay_nms(5);
PORTB |=BIT(PORTB0);
//light the first bit of the shumaguan
//PORTB0 was defined in iom16v.h
//display the thousand bit
//turn off the first bit of the shumaguan
PORTB &=~BIT(PORTB1);
PORTA=discode[number/100%10];
delay_nms(5);
PORTB |=BIT(PORTB1);
PORTB &=~BIT(PORTB2);
PORTA=discode[number%100/10];
delay_nms(5);
PORTB |=BIT(PORTB2);
31
AVR 单片机学习开发板教程
dushibiao
2007 年 11 月
PORTB &=~BIT(PORTB3);
PORTA=discode[number%10];
delay_nms(5);
PORTB |=BIT(PORTB3);
}
实验四
1602 液晶使用例子
实验目的:学习 AVR 单片机的 IO 口操作及数码的使用方法
实验准备:请把 AVR 单片机的 IO 操作方法详细了解一下,把 1602 液晶的使用方法
了解一下
实验要求:请把 84 数码管与液晶选择跳线打到 lcd 一侧
注:本开发板 1602 液晶使用 6 线接法
AVR—GCC 版本程序如下
1602test.c 主文件
/*
Title:
AVR-GCC ledtest
Author: dushibiao
Date:
2007 10 18
Purpose: test the lcd1602
Frequency: Ext 8M
needed
Software: AVR-GCC to compile
needed
Hardware: AVR mega16 BOARD
Connect: [email protected]
*/
#include <avr/io.h>
#include <avr/delay.h>
#include "1602.h"
//注: 内部函数_delay_ms() 最高延时 [email protected][email protected]
//
该函数可以实现较精确的定时 for()/while()指令很难计算延时时间
//
为了使 _delay_ms()函数的延时正确,须在 makefile 中设定 F_CPU 为实际的系统时钟
频
//
本范例为 8MHz 外部时钟, 即 F_CPU=8000000
int main(void)
{
LCD_init();
while(1)
{
clear();
//initialize lcd1602
//clear lcd1602
32
AVR 单片机学习开发板教程
dushibiao
2007 年 11 月
_delay_ms(10);
LCD_write_string(0,0," ATmega 16 BOARD");
//write the string on the first
//line at the first address
LCD_write_string(2,1,"dushibiao");
int i;
//you can define a variable in any where
//gcc support c++ format
for(i=0;i<255;i++)
_delay_ms(20);
clear();
_delay_ms(10);
LCD_write_string(0,0,"Stduy follow me");
LCD_write_string(0,1,"You will secceed");
for(i=0;i<255;i++)
_delay_ms(20);
}
}
1602.h 头文件
#include <avr/io.h>
#include <avr/delay.h>
#define LCD_EN_PORT
PORTA
#define LCD_EN_DDR
DDRA
#define LCD_RS_PORT
PORTA
#define LCD_RS_DDR
DDRA
#define LCD_DATA_PORT PORTA
#define LCD_DATA_DDR
DDRA
#define LCD_DATA_PIN
PINA
#define LCD_EN
0x08 //porta3
#define LCD_RS
0x04 //porta2
#define LCD_DATA
0xf0 //porta4/5/6/7
out
out
out
/*-------------------------------------------------------------------------------------------------Public function prototypes
--------------------------------------------------------------------------------------------------*/
void LCD_init
(void);
void LCD_en_write
(void);
void clear(void);
void LCD_write_char
(unsigned command,unsigned data);
void LCD_set_xy
(unsigned char x, unsigned char y);
void LCD_write_string (unsigned char X,unsigned char Y,unsigned char *s);
/*-----------------------------------------------------------function: initial the related port of 1602, set the work mode of 1602
-------------------------------------------------------------*/
33
AVR 单片机学习开发板教程
dushibiao
2007 年 11 月
void LCD_init(void)
//液晶初始化
{
LCD_DATA_DDR|=LCD_DATA;
LCD_EN_DDR|=LCD_EN;
LCD_RS_DDR|=LCD_RS;
_delay_ms(15);
LCD_write_char(0x28,0); //4 位显示
_delay_ms(15);
LCD_write_char(0x0c,0); //显示开
_delay_ms(15);
LCD_write_char(0x01,0); //清屏
}
/*-------------------------------------------------------------------fuction: write string to lcd1602
para: x--the address of a line
y--the display line
s--a pointer to a string
*--------------------------------------------------------------------*/
void LCD_write_string(unsigned char X,unsigned char Y,unsigned char *s)
{
LCD_set_xy( X, Y ); //写地址
while (*s) // 写显示字符
{
LCD_write_char( 0, *s );
s ++;
}
}
/*-----------------------------------------------------------function: set the display address
para: x---the display address of a line limit: 0---15
y---the display line
limit 0 or 1
--------------------------------------------------------------*/
void LCD_set_xy( unsigned char x, unsigned char y )
{
unsigned char address;
if (y == 0) address = 0x80 + x;
else
address = 0xc0 + x;
LCD_write_char( address, 0 );
}
/*-------------------------------------------------------------
//写地址函数
//the first line
//the second line
34
AVR 单片机学习开发板教程
dushibiao
2007 年 11 月
function : enable or disable the rs line of the lcd1602
*--------------------------------------------------------------*/
void LCD_en_write(void) //液晶使能
{
LCD_EN_PORT|=LCD_EN;
_delay_us(20);
LCD_EN_PORT&=~LCD_EN;
}
/*-----------------------------------------------------------function: write command or data to lcd1602
prar:
command: 0---write data, 1-----write command
data: commad or data which you want to write
*-------------------------------------------------------------*/
void LCD_write_char(unsigned command,unsigned data)
{
unsigned command_temp,data_temp;
command_temp=command;
data_temp=data;
_delay_us(50);
if(command==0)
{
LCD_RS_PORT|=LCD_RS; //RS=1
LCD_DATA_PORT&=0X0f;
LCD_DATA_PORT|=data_temp&0xf0; //写高四位
LCD_en_write();
data_temp=data_temp<<4;
LCD_DATA_PORT&=0X0f;
LCD_DATA_PORT|=data_temp&0xf0; //写低四位
LCD_en_write();
}
else
{
LCD_RS_PORT&=~LCD_RS; //RS=0
LCD_DATA_PORT&=0X0f;
LCD_DATA_PORT|=command_temp&0xf0; //写高四位
LCD_en_write();
command_temp=command_temp<<4;
LCD_DATA_PORT&=0x0f;
LCD_DATA_PORT|=command_temp&0xf0; //写低四位
LCD_en_write();
}
}
/*-------------------------------------------------------------
35
AVR 单片机学习开发板教程
dushibiao
2007 年 11 月
function: clear the lcd1602
---------------------------------------------------------------*/
void clear(void)
{
unsigned char i;
LCD_set_xy(0,0);
//write blank to the first line
for(i=0;i<16;i++)
LCD_write_char(0,' ');
LCD_set_xy(0,1);
//write blank to the second line
for(i=0;i<16;i++)
LCD_write_char(0,' ');
}
ICC--AVR 版本程序如下
1602test.c 主文件
#include <iom16v.h>
#include <macros.h>
#include "1602.h"
void main(void)
{
LCD_init();
//initialize lcd1602
while(1)
{
clear();
//clear lcd1602
delay_nms(10);
LCD_write_string(0,0," ATmega 16 BOARD");
//write the string on the first
//line at the first address
LCD_write_string(1,1," dushibiao ");
delay_nms(5000);
clear();
delay_nms(10);
LCD_write_string(0,0,"Stduy follow me");
LCD_write_string(0,1,"You will secceed");
delay_nms(5000);
}
}
1602.h 液晶头文件
#include <iom16v.h>
#include <macros.h>
#define LCD_EN_PORT
#define LCD_EN_DDR
PORTA
DDRA
36
AVR 单片机学习开发板教程
#define LCD_RS_PORT
PORTA
#define LCD_RS_DDR
DDRA
#define LCD_DATA_PORT PORTA
#define LCD_DATA_DDR
DDRA
#define LCD_DATA_PIN
PINA
#define LCD_EN
0x08 //porta3
#define LCD_RS
0x04 //porta2
#define LCD_DATA
0xf0 //porta4/5/6/7
dushibiao
2007 年 11 月
out
out
out
/*-------------------------------------------------------------------------------------------------Public function prototypes
--------------------------------------------------------------------------------------------------*/
void LCD_init
(void);
void LCD_en_write
(void);
void clear(void);
void LCD_write_char
(unsigned command,unsigned data);
void LCD_set_xy
(unsigned char x, unsigned char y);
void LCD_write_string (unsigned char X,unsigned char Y,unsigned char *s);
extern delay_nus
(unsigned int n);
extern delay_nms
(unsigned int n);
/*-----------------------------------------------------------function: initial the related port of 1602, set the work mode of 1602
-------------------------------------------------------------*/
void LCD_init(void)
//液晶初始化
{
LCD_DATA_DDR|=LCD_DATA;
LCD_EN_DDR|=LCD_EN;
LCD_RS_DDR|=LCD_RS;
delay_nms(15);
LCD_write_char(0x28,0); //4 位显示
delay_nms(15);
LCD_write_char(0x0c,0); //显示开
delay_nms(15);
LCD_write_char(0x01,0); //清屏
}
/*-------------------------------------------------------------------fuction: write string to lcd1602
para: x--the address of a line
y--the display line
s--a pointer to a string
*--------------------------------------------------------------------*/
void LCD_write_string(unsigned char X,unsigned char Y,unsigned char *s)
37
AVR 单片机学习开发板教程
dushibiao
2007 年 11 月
{
LCD_set_xy( X, Y ); //写地址
while (*s) // 写显示字符
{
LCD_write_char( 0, *s );
s ++;
}
}
/*-----------------------------------------------------------function: set the display address
para: x---the display address of a line limit: 0---15
y---the display line
limit 0 or 1
--------------------------------------------------------------*/
void LCD_set_xy( unsigned char x, unsigned char y ) //写地址函数
{
unsigned char address;
if (y == 0) address = 0x80 + x;
//the first line
else
address = 0xc0 + x;
//the second line
LCD_write_char( address, 0 );
}
/*------------------------------------------------------------function : enable or disable the rs line of the lcd1602
*--------------------------------------------------------------*/
void LCD_en_write(void) //液晶使能
{
LCD_EN_PORT|=LCD_EN;
delay_nus(5);
LCD_EN_PORT&=~LCD_EN;
}
/*-----------------------------------------------------------function: write command or data to lcd1602
prar:
command: 0---write data, 1-----write command
data: commad or data which you want to write
*-------------------------------------------------------------*/
void LCD_write_char(unsigned command,unsigned data)
{
unsigned command_temp,data_temp;
command_temp=command;
data_temp=data;
delay_nus(25);
38
AVR 单片机学习开发板教程
dushibiao
2007 年 11 月
if(command==0)
{
LCD_RS_PORT|=LCD_RS; //RS=1
LCD_DATA_PORT&=0X0f;
LCD_DATA_PORT|=data_temp&0xf0; //写高四位
LCD_en_write();
data_temp=data_temp<<4;
LCD_DATA_PORT&=0X0f;
LCD_DATA_PORT|=data_temp&0xf0; //写低四位
LCD_en_write();
}
else
{
LCD_RS_PORT&=~LCD_RS; //RS=0
LCD_DATA_PORT&=0X0f;
LCD_DATA_PORT|=command_temp&0xf0; //写高四位
LCD_en_write();
command_temp=command_temp<<4;
LCD_DATA_PORT&=0x0f;
LCD_DATA_PORT|=command_temp&0xf0; //写低四位
LCD_en_write();
}
}
/*------------------------------------------------------------function: clear the lcd1602
---------------------------------------------------------------*/
void clear(void)
{
unsigned char i;
LCD_set_xy(0,0);
for(i=0;i<16;i++)
LCD_write_char(0,' ');
LCD_set_xy(0,1);
for(i=0;i<16;i++)
LCD_write_char(0,' ');
}
第四章
实验六
内部功能模块实验教程
串口通信练习
实验目的:学习 AVR 单片机的串口通信使用方法
实验准备:请把 AVR 单片机的串口通信一节了解一下
39
AVR 单片机学习开发板教程
dushibiao
2007 年 11 月
实验要求:请把 LED 选择跳线打到左侧
AVR—GCC 版本程序如下
/*
Title:
AVR-GCC uarttest
Author: dushibiao
Date:
2007 10 19
Purpose: learn how to use uart
Frequency: internal 8M
needed
Software: AVR-GCC to compile
needed
Hardware: AVR mega16 BOARD
Connect: [email protected]
*/
/*
本程序简单的示范了如何使用 ATMEGA16 的 USART
USART 的设置
波特率的计算
发送采用查询方式
接收采用中断方式
除非有特殊格式要求,否则不建议使用 printf 函数库,该函数会耗用 3~6KB 程序空间
这里的应用比较简单,所以自己编写了 put_c/put_s 函数。
出于简化程序考虑,各种数据没有对外输出,学习时建议使用 JTAG ICE 硬件仿真器
*/
#include <avr/io.h>
#include <avr/delay.h>
#include <avr/signal.h>
#include <avr/interrupt.h>
/*
注: 内部函数_delay_ms() 最高延时 [email protected]
为了使 _delay_ms()函数的延时正确,须在 makefile 中设定 F_CPU 为实际的系统时钟频
本范例为 8MHz 外部石英晶体振荡器 即 F_CPU=8000000
注意 波特率误差不要超过 +/-1%.
做 USART 通讯时,除非你掌握了校准技术,否则请不要使用内部/外部 RC 振荡器
*/
40
AVR 单片机学习开发板教程
//管脚定义
#define PIN_RXD
#define PIN_TXD
0
1
//常量定义
#define BAUDRATE
//#define F_CPU
9600
800000
dushibiao
//PD0
//PD1
2007 年 11 月
RXD
TXD
//波特率
//这个已经在 makefile 里面定义了
//全局变量
//如果变量会在中断服务程序中被修改,须加 volatile 限定
volatile unsigned char FLAG;
//按键标志
volatile unsigned char PC_COMMAND;
//PC 发出的当前命令
volatile unsigned char RX_BUFFER[16]; //存放接收数据的数组
volatile unsigned char RX_index;
//存放接收数据的个数
//仿真时在 watch 窗口,监控这些变量。
void put_c(unsigned char c) //发送采用查询方式
{
while( !(UCSRA & (1<<UDRE)) );
//wait until the uart is empty
UDR=c;
//write data to uart
}
void put_s(unsigned char *ptr)
{
while (*ptr)
{
put_c(*ptr++);
}
put_c(0x0D);
put_c(0x0A); //结尾发送回车换行
}
SIGNAL(SIG_USART_RECV) //串口接收中断服务程序
{
PC_COMMAND=UDR;
switch(PC_COMMAND)
{
case '1': //0x30 ASCII '0'
PORTA^=(1<<0);
put_s("用户输入 1#指令");
break;
//LED1 取反
41
AVR 单片机学习开发板教程
case '2':
PORTA^=(1<<1);
put_s("用户输入 2#指令");
break;
case '3': //0x30 ASCII '0'
PORTA^=(1<<2);
put_s("用户输入 3#指令");
break;
case '4': //0x30 ASCII '0'
PORTA^=(1<<3);
put_s("用户输入 4#指令");
break;
case '5': //0x30 ASCII '0'
PORTA^=(1<<4);
put_s("用户输入 5#指令");
break;
case '6': //0x30 ASCII '0'
PORTA^=(1<<5);
put_s("用户输入 6#指令");
break;
case '7': //0x30 ASCII '0'
PORTA^=(1<<6);
put_s("用户输入 7#指令");
break;
case '8': //0x30 ASCII '0'
PORTA^=(1<<7);
put_s("用户输入 8#指令");
break;
dushibiao
2007 年 11 月
//LED2 取反
//LED3 取反
//LED4 取反
//LED5 取反
//LED6 取反
//LED7 取反
//LED8 取反
default:
put_s("你输入了");
put_c(PC_COMMAND);
put_s("键");
break;
}
/*
注意,使用 put_s 函数发送数据需要一定的时间,如果输入数据的速度过高将会导致数
据丢失
所以,一般建议中断服务程序的处理时间尽量的短,只做采集数据和设标志位,命令的
处理交由主程序来完成
这里只是示范简单的命令处理
*/
FLAG=0X01;
RX_BUFFER[RX_index]=PC_COMMAND;
//保存数据到数组里面
42
AVR 单片机学习开发板教程
RX_index++;
if (RX_index>=16) RX_index=0;
dushibiao
2007 年 11 月
//防止数组溢出
}
void init_USART(void)//USART 初始化
{
//USART 9600 8, n,1 PC 上位机软件(超级终端等)也要设成同样的设置才能通讯
UCSRC = (1<<URSEL) | 0x06;
//异步,8 位数据,无奇偶校验,一个停止位,无倍速
/*
UBRRH 与 UCSRC 共用 I/O 地址。因此访问该地址时需注意以下问题。
写访问
当在该地址执行写访问时, USART 寄存器选择位(URSEL)控制被写入的寄存器。
若 URSEL 为 0,对 UBRRH 值更新;若 URSEL 为 1,对 UCSRC 设置更新
读访问
对 UBRRH 或 UCSRC 寄存器的读访问则较为复杂。但在大多数应用中,基本不需要
读这些寄存器
没有 UBRR 这个 16 位寄存器,因为 UBRRL(0x09)/UBRRH(0x20)的地址不连续,而且
UBRRH 跟 UCSRC 共用地址
*/
//U2X=0 时的公式计算
UBRRL= (F_CPU/BAUDRATE/16-1)%256;
UBRRH= (F_CPU/BAUDRATE/16-1)/256;
//U2X=1 时的公式计算
//UBRRL= (F_CPU/BAUDRATE/8-1)%256;
//UBRRH= (F_CPU/BAUDRATE/8-1)/256;
//也可根据数据手册的[波特率设置的例子]查得
//UBRRL = 0x2F; //set baud rate lo
//UBRRH = 0x00; //set baud rate hi
UCSRA = 0x00;
//无倍速
UCSRB = (1<<RXCIE)|(1<<RXEN)|(1<<TXEN);
//使能接收中断,使能接收,使能发送
}
void pro_coammand(void) //多字节命令的处理程序
{
unsigned char i;
if (RX_index>=10)
43
AVR 单片机学习开发板教程
dushibiao
2007 年 11 月
{
UCSRB&= ~(1<<RXCIE); //关断 USART 接收中断
put_c(0x0D);
put_c(0x0A); //发送回车换行
put_s("Hello! 你之前输入的命令列表是:");
for (i=0;i<RX_index;i++) put_c(RX_BUFFER[i]);
put_c(0x0D);
put_c(0x0A);
put_c(0x0D);
put_c(0x0A); //发送回车换行
RX_index=0;
//清零
UCSRB|= (1<<RXCIE); //打开 USART 接收中断
}
}
int main(void)
{
//上电默认 DDRx=0x00,PORTx=0x00 输入,无上拉电阻
DDRA =0xFF;
PORTA=0XFF;
PORTB =0xFF;
部上拉电阻。
PORTC=0XFF;
DDRD =(1<<PIN_TXD);
PORTD =0xFF;
//输出
//高电平,灯灭
//不用的管脚使能内
//TXD 为输出
FLAG=0;
init_USART();
put_s("你好!");
put_s("这是一个简单的串口实验程序");
put_s("你可以在电脑上的超级终端程按下 1---8 号键,模拟用户板上的按键操作");
sei();
//使能全局中断
while (1)
{
while (FLAG==1)
pro_coammand();
//如果 FLAG 不加 volatile 限定(即 has_volatile=0),
程序将永远都运行不到这里。
}
}
/*
44
AVR 单片机学习开发板教程
dushibiao
2007 年 11 月
程序运行效果
PC 使用超级终端或 SSCOM32 串口调试程序,发送 ASCII 码的简单方法就是直接按下
对应的按键
例如 字符'0',即 0x30 ,按下键盘上的[0]即可
按下按键[0],LED0 亮,再次按下[0]键 LED0 灭,其它类同
*/
本程序使用了串口调试,本实验使用 windows 自带的超级终端。请选择
程序Æ附件Æ通讯Æ超级终端
如果你是第一次使用超级终端,将会出现下图
开始Æ
在区号一栏里填入你所在的区号
将会出现下图
45
AVR 单片机学习开发板教程
dushibiao
2007 年 11 月
在名称一栏中输入你的名字,点确定会出现下图
如图选择你所用的串口,一般为 COM1,点确定,出现下图
46
AVR 单片机学习开发板教程
dushibiao
2007 年 11 月
如图,选择波待率 9600,数据位 8 位,奇偶校验选无,停止位选 1 位,数据流控制为无,
点确定,将会出现以下
当你输入 1---8 号键是对应的 LED 会点亮,比如你输入了 2,会出现以下现象
47
AVR 单片机学习开发板教程
dushibiao
2007 年 11 月
并且对应的 LED 点亮
当你输入其它字母时,LED 没有什么变化,仅在超级终端中出现以下提示,比如你输入了 d
ICC--AVR 版本程序如下
/*
Title:
Author:
Date:
icc-avr uarttest
dushibiao
2007 10 18
48
AVR 单片机学习开发板教程
dushibiao
2007 年 11 月
Purpose: study how use uart
Frequency: internal 8M
needed
Software: icc-avr to compile
needed
Hardware: AVR mega16 BOARD
Connect: [email protected]
*/
/*
本程序简单的示范了如何使用 ATMEGA16 的 USART
USART 的设置
波特率的计算
发送采用查询方式
接收采用中断方式
除非有特殊格式要求,否则不建议使用 printf 函数库,该函数会耗用 3~6KB 程序空间
这里的应用比较简单,所以自己编写了 put_c/put_s 函数。
出于简化程序考虑,各种数据没有对外输出,学习时建议使用 JTAG ICE 硬件仿真器
*/
#include <iom16v.h>
#include <macros.h>
/*
注: 内部函数_delay_ms() 最高延时 [email protected]
为了使 _delay_ms()函数的延时正确,须在 makefile 中设定 F_CPU 为实际的系统时钟频
本范例为 8MHz 外部石英晶体振荡器 即 F_CPU=8000000
注意 波特率误差不要超过 +/-1%.
做 USART 通讯时,除非你掌握了校准技术,否则请不要使用内部/外部 RC 振荡器
*/
//管脚定义
#define PIN_RXD
#define PIN_TXD
0
1
//常量定义
#define BAUDRATE
#define F_CPU
9600 //baudrate
8000000 //the frequency of the global clock
//PD0
//PD1
RXD
TXD
//宏定义
49
AVR 单片机学习开发板教程
2007 年 11 月
dushibiao
//全局变量
//如果变量会在中断服务程序中被修改,须加 volatile 限定
volatile unsigned char FLAG;
//按键标志
volatile unsigned char PC_COMMAND;
//PC 发出的当前命令
volatile unsigned char RX_BUFFER[16]; //存放接收数据的数组
volatile unsigned char RX_index;
//存放接收数据的个数
//仿真时在 watch 窗口,监控这些变量。
void put_c(unsigned char c) //发送采用查询方式
{
while( !(UCSRA & (1<<UDRE)) );
//wait until the uart is empty
UDR=c;
//write data to uart
}
void put_s(unsigned char *ptr)
{
while (*ptr)
{
put_c(*ptr++);
}
put_c(0x0D);
put_c(0x0A); //结尾发送回车换行
}
#pragma interrupt_handler rxreceive: iv_USART_RX
//串口接收中断服务程序
void rxreceive(void)
{
PC_COMMAND=UDR;
switch(PC_COMMAND)
{
case '1': //0x30 ASCII '0'
PORTA^=(1<<0);
put_s("用户输入 1#指令");
break;
case '2':
PORTA^=(1<<1);
put_s("用户输入 2#指令");
break;
case '3': //0x30 ASCII '0'
PORTA^=(1<<2);
//LED1 取反
//LED2 取反
//LED3 取反
50
AVR 单片机学习开发板教程
dushibiao
put_s("用户输入 3#指令");
break;
case '4': //0x30 ASCII '0'
PORTA^=(1<<3);
put_s("用户输入 4#指令");
break;
case '5': //0x30 ASCII '0'
PORTA^=(1<<4);
put_s("用户输入 5#指令");
break;
case '6': //0x30 ASCII '0'
PORTA^=(1<<5);
put_s("用户输入 6#指令");
break;
case '7': //0x30 ASCII '0'
PORTA^=(1<<6);
put_s("用户输入 7#指令");
break;
case '8': //0x30 ASCII '0'
PORTA^=(1<<7);
put_s("用户输入 8#指令");
break;
2007 年 11 月
//LED4 取反
//LED5 取反
//LED6 取反
//LED7 取反
//LED8 取反
default:
printf("你输入了%c 键\n",PC_COMMAND);
break;
}
/*
注意,使用 put_s 函数发送数据需要一定的时间,如果输入数据的速度过高将会导致数
据丢失
所以,一般建议中断服务程序的处理时间尽量的短,只做采集数据和设标志位,命令的
处理交由主程序来完成
这里只是示范简单的命令处理
*/
FLAG=0X01;
RX_BUFFER[RX_index]=PC_COMMAND;
//保存数据到数组里面
RX_index++;
if (RX_index>=16) RX_index=0;
//防止数组溢出
}
void init_USART(void)//USART 初始化
{
51
AVR 单片机学习开发板教程
dushibiao
2007 年 11 月
//USART 9600 8, n,1 PC 上位机软件(超级终端等)也要设成同样的设置才能通讯
UCSRC = (1<<URSEL) | 0x06;
//异步,8 位数据,无奇偶校验,一个停止位,无倍速
/*
UBRRH 与 UCSRC 共用 I/O 地址。因此访问该地址时需注意以下问题。
写访问
当在该地址执行写访问时, USART 寄存器选择位(URSEL)控制被写入的寄存器。
若 URSEL 为 0,对 UBRRH 值更新;若 URSEL 为 1,对 UCSRC 设置更新
读访问
对 UBRRH 或 UCSRC 寄存器的读访问则较为复杂。但在大多数应用中,基本不需要
读这些寄存器
没有 UBRR 这个 16 位寄存器,因为 UBRRL(0x09)/UBRRH(0x20)的地址不连续,而且
UBRRH 跟 UCSRC 共用地址
*/
//U2X=0 时的公式计算
UBRRL= (F_CPU/BAUDRATE/16-1)%256;
UBRRH= (F_CPU/BAUDRATE/16-1)/256;
//U2X=1 时的公式计算
//UBRRL= (F_CPU/BAUDRATE/8-1)%256;
//UBRRH= (F_CPU/BAUDRATE/8-1)/256;
//也可根据数据手册的[波特率设置的例子]查得
//UBRRL = 0x2F; //set baud rate lo
//UBRRH = 0x00; //set baud rate hi
UCSRA = 0x00;
//无倍速
UCSRB = (1<<RXCIE)|(1<<RXEN)|(1<<TXEN);
//使能接收中断,使能接收,使能发送
}
void pro_coammand(void) //多字节命令的处理程序
{
unsigned char i;
if (RX_index>=10)
{
UCSRB&= ~(1<<RXCIE); //关断 USART 接收中断
put_c(0x0D);
put_c(0x0A); //发送回车换行
put_s("Hello! 你之前输入的命令列表是:");
for (i=0;i<RX_index;i++) put_c(RX_BUFFER[i]);
put_c(0x0D);
put_c(0x0A);
52
AVR 单片机学习开发板教程
dushibiao
2007 年 11 月
put_c(0x0D);
put_c(0x0A); //发送回车换行
RX_index=0;
//清零
UCSRB|= (1<<RXCIE); //打开 USART 接收中断
}
}
int main(void)
{
//上电默认 DDRx=0x00,PORTx=0x00 输入,无上拉电阻
DDRA =0xFF;
PORTA=0XFF;
PORTB =0xFF;
部上拉电阻。
PORTC=0XFF;
DDRD =(1<<PIN_TXD);
PORTD =0xFF;
//输出
//高电平,灯灭
//不用的管脚使能内
//TXD 为输出
FLAG=0;
init_USART();
put_s("你好!");
put_s("这是一个简单的串口实验程序");
put_s("你可以在电脑上的超级终端程按下 1---8 号键,模拟用户板上的按键操作");
SEI();
//使能全局中断
while (1)
{
while (FLAG==1)
pro_coammand();
}
}
/*
程序运行效果
PC 使用超级终端或 SSCOM32 串口调试程序,发送 ASCII 码的简单方法就是直接按下
对应的按键
例如 字符'0',即 0x30 ,按下键盘上的[0]即可
按下按键[0],LED0 亮,再次按下[0]键 LED0 灭,其它类同
*/
53
AVR 单片机学习开发板教程
dushibiao
2007 年 11 月
实验七
外部中断练习
实验目的:学习 AVR 单片机的外部中断使用方法
实验准备:请把 AVR 单片机的外部中断一节了解一下
实验要求:请把 84 数码管与液晶选择跳线打到 84 一侧
实验现象:当按下 K1 时数码管加 1,按下 K2 时减 1
AVR—GCC 版本程序如下
/*
Title:
AVR-GCC exttest
Author: dushibiao
Date:
2007 10 19
Purpose: learn how to use ext intterrupt
Frequency: internal 8M
needed
Software: AVR-GCC to compile
needed
Hardware: AVR mega16 BOARD
Connect: [email protected]
*/
/*
本程序简单的示范了如何使用 ATMEGA16 的外部中断
中断的设置
按键的简单延时防抖动
中断的嵌套
变量在中断中的应用---如果变量会在中断服务程序中被修改,须加 volatile 限定
本范例可直接使出厂状态的新 M16 芯片,无需对芯片的熔丝位进行配置。
出于简化程序考虑,各种数据没有对外输出,学习时建议使用 JTAG ICE 硬件仿真器
关于外部中断作唤醒源的条件:(将会在后面的电源管理和睡眠模式范例中应用)
而 INT0 和 INT1 的边沿触发中断只能在 空闲模式起作用,即 CLKI/O 不停止
INT0 和 INT1 的低电平中断,INT2 在各种睡眠模式下都可以,因为这几种中断工作于
异步模式,不需要时钟驱动
官方的 M16 中文手册对外部中断的描叙存在多处错误,请参考英文原版。
*/
#include <avr/io.h>
#include <avr/delay.h>
#include <avr/signal.h>
#include <avr/interrupt.h>
#include "shumaguan.h"
/*宏 INTERRUPT 的用法与 SIGNAL 类似,区别在于
54
AVR 单片机学习开发板教程
dushibiao
2007 年 11 月
SIGNAL 执行时全局中断触发位被清除、其他中断被禁止
INTERRUPT 执行时全局中断触发位被置位、其他中断可嵌套执行
另外 avr-libc 提供两个 API 函数用于置位和清零全局中断触发位,它们是经常用到的。
分别是:void sei(void) 和 void cli(void) 由 interrupt.h 定义 */
//注: 内部函数_delay_ms() 最高延时 [email protected]
/*
该函数可以实现较精确的定时,但用 JTAG 仿真时较麻烦---会进入机器码窗口
(Disassembeler).注意跳开该语段。
一旦 JTAG 仿真进入该内部函数语句,会变得像"死机"一样(其实在运行中),可以先
[break],然后在后面的 C 语句设[breakpoint],[RUN]
跳过*/
// for()/while()语句计算延时时间较麻烦。
//
为了使 _delay_ms()函数的延时正确,须在 makefile 中设定 F_CPU 为实际的系统时钟
频
//
本范例为 8MHz 内部 RC 振荡器 即 F_CPU=8000000
/*
C:\WinAVR\avr\include\avr\目录包括所有芯片的定义和其他头文件
其中 iom16.h 定义 ATMEGA16 芯片的特性(中断向量,寄存器,位定义...)
包括下面中断服务程序的常量 SIG_INTERRUPTx ,PORTx,GICR.....
*/
//管脚定义
#define EXT_INT0
#define EXT_INT1
2
3
//PD2
//PD3
按键 0
按键 1
//全局变量
#define has_volatile
1
//这里是条件编译
//可以修改 has_volatile=1 或 0 来看程序运
行的效果
#if has_volatile
volatile unsigned int count;
//全局变量,会在中断服务程序中被修改,须加
volatile 限定
#else
unsigned int count;
//全局变量.
#endif
//仿真时在 watch 窗口,监控这些变量。
55
AVR 单片机学习开发板教程
dushibiao
2007 年 11 月
SIGNAL(SIG_INTERRUPT0) //INT0 中断服务程序
{
//硬件自动清除 INTF0 标志位
_delay_ms(10);
//延时
if ((PIND&(1<<EXT_INT0))==0)
//重复检测,防抖动
{
while(!(PIND&(1<<EXT_INT0)));
//等待按键释放(变为高电平)
_delay_ms(10);
//延时 按键释放时也会抖动。
// 即使同时发生其它的中断事件,如果在这里把相应的中断标志位清除,那么该中断
将不能触发进入中断服务
/*
注意
读端口用
写端口用
PINx
PORTx
*/
if(count>=9999)
count=0;
else
count++;
}
}
INTERRUPT(SIG_INTERRUPT1) //INT1 中断服务程序
{
//硬件自动清除 INTF1 标志位
//这里全局中断被打开,将允许其他中断嵌套执行
_delay_ms(10);
if ((PIND&(1<<EXT_INT1))==0)
{
while(!(PIND&(1<<EXT_INT1)));
_delay_ms(10);
if(count==0)
count=9999;
else
count--;
}
}
int main(void)
{
//上电默认 DDRx=0x00,PORTx=0x00 输入,无上拉电阻
56
AVR 单片机学习开发板教程
dushibiao
2007 年 11 月
DDRA=0XFF;
PORTA =0xFF;
//不用的管脚使能内部上拉电阻。
PORTC =0xFF;
PORTD =0xFF;
DDRB = 0x0f;
//低四位输出
PORTB =0xff;
//低四位输出高电平,数码不亮,高四位输入上拉
//外部中断 INT0,1 做按键输入,使能内部上拉,就可以不用外接电阻了
MCUCR=(1<<ISC11)|(0<<ISC10)|(1<<ISC01)|(0<<ISC00);
// 注意该寄 存器有 多个 功
能
/*
ISCx1:0=00 INTx 引脚为低电平时产生中断请求
ISCx1:0=01 INTx 引脚上任意的逻辑电平变化都将引发中断
ISCx1:0=10 INTx 引脚的下降沿产生中断请求
ISCx1:0=11 INTx 引脚的上升沿产生中断请求
*/
GIFR=(1<<INTF1)|(1<<INTF0);//写 1 清除标志位,在使能中断前最好先把对应的标志位
清除,以免误触发
GICR=(1<<INT1)|(1<<INT0);
//使能三个外部中断
sei();
while (1)
{
display(count);
}
//使能全局中断
}
/*
程序运行效果
press down key1 the four bits of led increse 1
press down key2 the four bits of led decrese 1
*/
数码管头文件省略
ICC--AVR 版本程序如下
/*
Title:
icc-avr extint test
Author: dushibiao
Date:
2007 10 18
Purpose: study how use ext int
Frequency: internal 8M
Software: icc-avr
57
AVR 单片机学习开发板教程
dushibiao
2007 年 11 月
Hardware: AVR mega16 BOARD
Connect: [email protected]
*/
#include<iom16v.h>
#include <macros.h>
#include "shumaguan.h"
#define uchar unsigned char
#define uint unsigned int
volatile unsigned int count;
//predefine
//predefine
//this is the value to be displayed
//extern void delay_nms(unsigned int n);
//this function was define in delay.c
void intinitial(void);
void portinitial(void); //initialize ports
void
{
main(void)
portinitial();
intinitial();
SEI();
//enable interrupt ,this was predefined in MACROS.h
while(1)
{
display(count);
}
}
/*********************************************************************
fuction: ports initialize
**********************************************************************/
void portinitial(void)
{
PORTA=0XFF;
DDRA=0XFF;
//OUT
PORTB=0XFF;
DDRB=0X0F;
//high fout bit in , low four bits out
PORTC=0XFF;
//pull up
PORTD=0XFF;
//pull up
}
/*********************************************************************
58
AVR 单片机学习开发板教程
dushibiao
2007 年 11 月
fuction: ext interrupt initialize
**********************************************************************/
void intinitial(void)
//int1 ,int0 下降沿中断
{
MCUCR=(1<<ISC10)|(1<<ISC00);
//falling edge interrup
GICR |=(1<<INT1)|(1<<INT0);
//enable ext0,ext1 interrupt
}
/*********************************************************************
fuction: ext0 interrupt service routine
**********************************************************************/
#pragma interrupt_handler int0pro: iv_INT0
void int0pro(void)
{
delay_nms(10);
//
if(!(PIND&(1<<PD2)))
{
while(!(PIND&(1<<PD2)));
//delay until to you pull key1
if(count>=9999)
count=0;
else
count++;
}
}
/*********************************************************************
fuction: ext1 interrupt service routine
**********************************************************************/
#pragma interrupt_handler int1pro: iv_INT1
void int1pro(void)
{
delay_nms(10);
if(!(PIND&(1<<PD3)))
{
while(!(PIND&(1<<PD3)));
//delay until to you pull up the key2
if(count==0)
count=9999;
else
count--;
}
}
实验八
内部 eeprom 练习—记录开机次数
实验目的:学习 AVR 单片机的内部 eeprom 使用方法
59
AVR 单片机学习开发板教程
dushibiao
2007 年 11 月
实验准备:请把 AVR 单片机的内部 eeprom 一节了解一下
实验要求:请把 84 数码管与液晶选择跳线打到 84 一侧
实验现象:每当你按一次复位键时数码管加 1
AVR—GCC 版本程序如下
/*
Title:
AVR-GCC eeprom record power-up times
Author: dushibiao
Date:
2007 10 19
Purpose: learn how to use read and write internal eeprom
Frequency: internal 8M
Software: AVR-GCC to compile
Hardware: AVR mega16 BOARD
Connect: [email protected]
*/
/*
本程序简单的示范了如何使用 ATMEGA16 的 EERPOM
EEPROM 的简介
EEPROM 的写操作
EEPROM 的读操作
出于简化程序考虑,各种数据没有对外输出,学习时建议使用 JTAG ICE 硬件仿真器
在打开调试文件到 JTAG 后,
打开 Debug -> JTAG ICE Options 菜单,
然后在 JTAG ICE Properties 中点击 Dbug 页面,将 preserve eeprom 选项选中。
在每次仿真调试时候,就保护 EEPROM 内容了。
否则,会按照默认设置擦除 EEPROM 的内容。
由于定义了 EEPROM 变量,JTAG 调试时会询问是否初始化 EEPROM,请选择[否]
EEPROM 的数据也可以在 view->memory,选 Eeprom 窗口下察看
*/
#include <avr/io.h>
#include <avr/eeprom.h>
#include "shumaguan.h"
////时钟定为内部 1MHz,F_CPU=8000000 时钟频率对程序的运行没什么影响
/*
GCCAVR(avr-libc)里面自带了 EEPROM 的读写函数。
下面列举部分常用函数(原型)
#define eeprom_is_ready() bit_is_clear(EECR, EEWE)
检测 EEPROM 是否准备好。OK 返回 1(返回 EEWE 位)
60
AVR 单片机学习开发板教程
dushibiao
2007 年 11 月
#define eeprom_busy_wait() do {} while (!eeprom_is_ready())
等待 EEPROM 操作完成
extern uint8_t eeprom_read_byte (const uint8_t *addr);
读取指定地址的一个字节 8bit 的 EEPROM 数据
extern uint16_t eeprom_read_word (const uint16_t *addr);
读取指定地址的一个字 16bit 的 EEPROM 数据
extern void eeprom_read_block (void *buf, const void *addr, size_t n);
读取由指定地址开始的指定长度的 EEPROM 数据
extern void eeprom_write_byte (uint8_t *addr, uint8_t val);
向指定地址写入一个字节 8bit 的 EEPROM 数据
extern void eeprom_write_word (uint16_t *addr, uint16_t val);
向指定地址写入一个字 16bit 的 EEPROM 数据
extern void eeprom_write_block (const void *buf, void *addr, size_t n);
由指定地址开始写入指定长度的 EEPROM 数据
但不支持部分 AVR,原文如下:
\note This library will \e not work with the following devices since these
devices have the EEPROM IO ports at different locations:
- AT90CAN128
- ATmega48
- ATmega88
- ATmega165
- ATmega168
- ATmega169
- ATmega325
- ATmega3250
- ATmega645
- ATmega6450
*/
/*
在程序中对 EEPROM 操作有两种方式
方式一:直接指定 EERPOM 地址
即读写函数的地址有自己指定,用于需要特定数据排列格式的应用中
方式二:先定义 EEPROM 区变量法
在这种方式下变量在 EEPROM 存储器内的具体地址由编译器自动分配。
相对方式一,数据在 EEPROM 中的具体位置是不透明的。
61
AVR 单片机学习开发板教程
dushibiao
2007 年 11 月
为 EEPROM 变量赋的初始值,编译时被分配到.eeprom 段中,
可用 avr-objcopy 工具从.elf 文件中提取并产生 ihex 或 binary 等格式的文件,
从而可以使用编程器或下载线将其写入到器件的 EEPROM 中。
实际上 WINAVR 中 MFILE 生成的 MAKEFILE 已经为我们做了这一切。
它会自动生成以 “.eep” 为后缀的文件,通常它是 iHex 格式
( 这 次 测 试 发 现 分 配 地 址 是 从 0x0000 开 始 的 , 故 增 加 了 一 个 EEPROM 变 量
Evalvoid[16])
如果同时使用方式 1 和 2,请注意防止地址重叠,自己指定的地址应该选在后面。
*/
//全局变量
unsigned int counttime;
//仿真时在 watch 窗口,监控这些全局变量。
int main(void)
{
DDRA=0XFF;
PORTA=0XFF;
DDRB=0X0F;
PORTB=0XFF;
PORTC=0XFF;
PORTD=0XFF;
counttime=eeprom_read_word (0);
counttime++;
if(counttime>=10000)
counttime=0;
eeprom_write_word (0,counttime);
//out
//low four bits out, high four bits in
//PUULL-UP
//pull-up
//读出
//向 EEPROM 的 0x40 地址写入数据 0xA5
while (1)
{
display(counttime);
}
}
/*
ATmega16 包含 512 字节的 EEPROM 数据存储器。
它是作为一个独立的数据空间而存在的,可以按字节读写。
EEPROM 的寿命至少为 100,000 次擦除周期。
EEPROM 的访问由地址寄存器 EEAR、数据寄存器 EEDR 和控制寄存器 EECR 决定。
62
AVR 单片机学习开发板教程
dushibiao
2007 年 11 月
也可以通过 ISP 和 JTAG 及并行电缆来固化 EEPROM 数据
EEPROM 数据的读取:
当 EEPROM 地址设置好之后,需置位 EERE 以便将数据读入 EEDR。
EEPROM 数据的读取需要一条指令,且无需等待。
读取 EEPROM 后 CPU 要停止 4 个时钟周期才可以执行下一条指令。
注意:用户在读取 EEPROM 时应该检测 EEWE。如果一个写操作正在进行,就无法读取
EEPROM,也无法改变寄存器 EEAR。
EEPROM 数据的写入:
1 EEPROM 的写访问时间(自定时时间,编程时间)
自定时功能可以让用户软件监测何时可以开始写下一字节。(可以采用中断方式)
经过校准的 1MHz 片内振荡器用于 EEPROM 定时,不倚赖 CKSEL 熔丝位的设置。
改变 OSCCAL 寄存器的值会影响内部 RC 振荡器的频率因而影响写 EEPROM 的时间。
EEPROM 自定时时间约为 8.5 ms 即 1MHz 片内振荡器的 8448 个周期
注意:这个时间是硬件定时的,数值比较保险,其实真正的写入时间根本就用不了 8.5mS
那么长,而且跟电压有关,但芯片没有提供其他的检测
编程完成的方法
这个问题表现在旧版的 AT90S 系列上面,由于没有自定时,数值定得太短,ATMEL
给人投诉到头都爆,呵呵!
参考: 《用 ATmega8535 替换 AT90S8535》文档里面的
写 EEPROM 定时的改进
在 AT90S8535 中写 EEPROM 的时间取决于供电电压,通常为 [email protected]=5V,
[email protected]=2.7V。
ATmega8535 中写 EEPROM 的时间为 8448 个校准过的 RC 振荡器周期 (与系统时钟的
时钟源和频率无关)。
假定校准过的 RC 振荡器为 1.0MHz,则写时间的典型值为 8.4ms,与 VCC 无关。
2 为了防止无意识的 EEPROM 写操作,需要执行一个特定的写时序
(如果使用编译器的自带函数,无须自己操心)
写时序如下( 第 3 步和第 4 步的次序并不重要):
1. 等待 EEWE 位变为零
2. 等待 SPMCSR 中的 SPMEN 位变为零
3. 将新的 EEPROM 地址写入 EEAR( 可选)
4. 将新的 EEPROM 数据写入 EEDR( 可选)
5. 对 EECR 寄存器的 EEMWE 写"1",同时清零 EEWE
6. 在置位 EEMWE 的 4 个周期内,置位 EEWE
经过写访问时间之后,EEWE 硬件清零。
用户可以凭借这一位判断写时序是否已经完成。
EEWE 置位后,CPU 要停止两个时钟周期才会运行下一条指令。
注意:
63
AVR 单片机学习开发板教程
dushibiao
2007 年 11 月
1:在 CPU 写 Flash 存储器的时候不能对 EEPROM 进行编程。
在启动 EEPROM 写操作之前软件必须检查 Flash 写操作是否已经完成
步骤(2) 仅在软件包含引导程序并允许 CPU 对 Flash 进行编程时才有用。
如果 CPU 永远都不会写 Flash,步骤(2) 可省略。
2:如果在步骤 5 和 6 之间发生了中断,写操作将失败。
因为此时 EEPROM 写使能操作将超时。
如果一个操作 EEPROM 的中断打断了另一个 EEPROM 操作,EEAR 或 EEDR 寄存
器可能被修改,引起 EEPROM 操作失败。
建议此时关闭全局中断标志 I。
经过写访问时间之后,EEWE 硬件清零。用户可以凭借这一位判断写时序是否已经
完成。
EEWE 置位后,CPU 要停止两个时钟周期才会运行下一条指令。
在掉电休眠模式下的 EEPROM 写操作
若程序执行掉电指令时 EEPROM 的写操作正在进行, EEPROM 的写操作将继续,并
在指定的写访问时间之前完成。
但写操作结束后,振荡器还将继续运行,单片机并非处于完全的掉电模式。因此在执行
掉电指令之前应结束 EEPROM 的写操作。
防止 EEPROM 数据丢失
若电源电压过低,CPU 和 EEPROM 有可能工作不正常,造成 EEPROM 数据的毁坏(丢
失)。
**这种情况在使用独立的 EEPROM 器件时也会遇到。因而需要使用相同的保护方
案。
由于电压过低造成 EEPROM 数据损坏有两种可能:
一是电压低于 EEPROM 写操作所需要的最低电压;
二是 CPU 本身已经无法正常工作。
EEPROM 数据损坏的问题可以通过以下方法解决:
当电压过低时保持 AVR RESET 信号为低。
这可以通过使能芯片的掉电检测电路 BOD 来实现。如果 BOD 电平无法满足要求
则可以使用外部复位电路。
若写操作过程当中发生了复位,只要电压足够高,写操作仍将正常结束。
(EEPROM 在 2V 低压下也能进行写操作---有可以工作到 1.8V 的 AVR 芯片)
掉电检测 BOD 的误解
AVR 自带的 BOD(Brown-out Detection)电路,作用是在电压过低(低于设定值)时产
生复位信号,防止 CPU 意外动作.
对 EEPROM 的保护作用是当电压过低时保持 RESET 信号为低,防止 CPU 意外动
作,错误修改了 EEPROM 的内容
而我们所理解的掉电检测功能是指 具有预测功能的可以进行软件处理的功能。
例如,用户想在电源掉电时把 SRAM 数据转存到 EEPROM,可行的方法是
64
AVR 单片机学习开发板教程
dushibiao
2007 年 11 月
外接一个在 4.5V 翻转的电压比较器(VCC=5.0V,BOD=2.7V),输出接到外部中断
引脚(或其他中断)
一但电压低于 4.5V,马上触发中断,在中断服务程序中把数据写到 EEPROM 中
保护起来
注意: 写一个字节的 EEPROM 时间长达 8mS,所以不能写入太多数据,电源滤波电容也
要选大一些
*/
ICC--AVR 版本程序如下
/*
Title:
icc-avr eeprom record the power up times
Author: dushibiao
Date:
2007 10 18
Purpose: study how read and write internal eeprom
Frequency: internal 8M
Software: icc-avr
Hardware: AVR mega16 BOARD
Connect: [email protected]
*/
/*
记录开机次数,外部 8M 晶振奋
*/
2007 5 12 dushibiao
#include <iom16V.h>
#include <macros.h>
#include "shumaguan.h"
#define uchar unsigned char
#define uint unsigned int
uint ontime;
/*开机次数*/
/*EEPROM 读取函数*/
/*addr:地址*/
uchar eprom_read(unsigned int addr)
{
while(EECR & (1 << EEWE));
EEAR = addr ;
EECR |= (1 << EERE);
return EEDR;
}
65
AVR 单片机学习开发板教程
dushibiao
2007 年 11 月
/*EEPROM 写入函数*/
/*addr:地址;number:长度;p_buff:写入数据存放指针*/
void eprom_write(unsigned int addr, unsigned char data)
{
while(EECR & (1 << EEWE));
EEAR = addr;
EEDR = data;
EECR |= (1 << EEMWE);
EECR |= (1 << EEWE);
}
void main()
{
uchar temp1,temp2;
//temporary register
DDRA=0XFF;
//out
PORTA=0XFF;
DDRB=0X0F;
//low four bits out, high four bits in
PORTB=0XFF;
PORTC=0XFF;
//PUULL-UP
PORTD=0XFF;
//pull-up
temp1=eprom_read(0);
//read internal eeprom of address 0
temp2=eprom_read(1);
ontime=(unsigned int)(temp2<<8)+temp1;
ontime++;
if(ontime>=10000)
ontime=0;
eprom_write(0,ontime%256);
eprom_write(1,ontime/256);
while(1)
{
display(ontime);
}
}
/*
ATmega16 包含 512 字节的 EEPROM 数据存储器。
它是作为一个独立的数据空间而存在的,可以按字节读写。
EEPROM 的寿命至少为 100,000 次擦除周期。
EEPROM 的访问由地址寄存器 EEAR、数据寄存器 EEDR 和控制寄存器 EECR 决定。
也可以通过 ISP 和 JTAG 及并行电缆来固化 EEPROM 数据
EEPROM 数据的读取:
当 EEPROM 地址设置好之后,需置位 EERE 以便将数据读入 EEDR。
66
AVR 单片机学习开发板教程
dushibiao
2007 年 11 月
EEPROM 数据的读取需要一条指令,且无需等待。
读取 EEPROM 后 CPU 要停止 4 个时钟周期才可以执行下一条指令。
注意:用户在读取 EEPROM 时应该检测 EEWE。如果一个写操作正在进行,就无法读取
EEPROM,也无法改变寄存器 EEAR。
EEPROM 数据的写入:
1 EEPROM 的写访问时间(自定时时间,编程时间)
自定时功能可以让用户软件监测何时可以开始写下一字节。(可以采用中断方式)
经过校准的 1MHz 片内振荡器用于 EEPROM 定时,不倚赖 CKSEL 熔丝位的设置。
改变 OSCCAL 寄存器的值会影响内部 RC 振荡器的频率因而影响写 EEPROM 的时间。
EEPROM 自定时时间约为 8.5 ms 即 1MHz 片内振荡器的 8448 个周期
注意:这个时间是硬件定时的,数值比较保险,其实真正的写入时间根本就用不了 8.5mS
那么长,而且跟电压有关,但芯片没有提供其他的检测编程完成的方法
这个问题表现在旧版的 AT90S 系列上面,由于没有自定时,数值定得太短,ATMEL
给人投诉到头都爆,呵呵!
参考: 《用 ATmega8535 替换 AT90S8535》文档里面的
写 EEPROM 定时的改进
在 AT90S8535 中写 EEPROM 的时间取决于供电电压,通常为 [email protected]=5V,
[email protected]=2.7V。
ATmega8535 中写 EEPROM 的时间为 8448 个校准过的 RC 振荡器周期 (与系统时钟的
时钟源和频率无关)。
假定校准过的 RC 振荡器为 1.0MHz,则写时间的典型值为 8.4ms,与 VCC 无关。
2 为了防止无意识的 EEPROM 写操作,需要执行一个特定的写时序
(如果使用编译器的自带函数,无须自己操心)
写时序如下( 第 3 步和第 4 步的次序并不重要):
1. 等待 EEWE 位变为零
2. 等待 SPMCSR 中的 SPMEN 位变为零
3. 将新的 EEPROM 地址写入 EEAR( 可选)
4. 将新的 EEPROM 数据写入 EEDR( 可选)
5. 对 EECR 寄存器的 EEMWE 写"1",同时清零 EEWE
6. 在置位 EEMWE 的 4 个周期内,置位 EEWE
经过写访问时间之后,EEWE 硬件清零。
用户可以凭借这一位判断写时序是否已经完成。
EEWE 置位后,CPU 要停止两个时钟周期才会运行下一条指令。
注意:
1:在 CPU 写 Flash 存储器的时候不能对 EEPROM 进行编程。
在启动 EEPROM 写操作之前软件必须检查 Flash 写操作是否已经完成
步骤(2) 仅在软件包含引导程序并允许 CPU 对 Flash 进行编程时才有用。
如果 CPU 永远都不会写 Flash,步骤(2) 可省略。
2:如果在步骤 5 和 6 之间发生了中断,写操作将失败。
67
AVR 单片机学习开发板教程
dushibiao
2007 年 11 月
因为此时 EEPROM 写使能操作将超时。
如果一个操作 EEPROM 的中断打断了另一个 EEPROM 操作,EEAR 或 EEDR 寄存
器可能被修改,引起 EEPROM 操作失败。
建议此时关闭全局中断标志 I。
经过写访问时间之后,EEWE 硬件清零。用户可以凭借这一位判断写时序是否已经
完成。
EEWE 置位后,CPU 要停止两个时钟周期才会运行下一条指令。
在掉电休眠模式下的 EEPROM 写操作
若程序执行掉电指令时 EEPROM 的写操作正在进行, EEPROM 的写操作将继续,并
在指定的写访问时间之前完成。
但写操作结束后,振荡器还将继续运行,单片机并非处于完全的掉电模式。因此在执行
掉电指令之前应结束 EEPROM 的写操作。
防止 EEPROM 数据丢失
若电源电压过低,CPU 和 EEPROM 有可能工作不正常,造成 EEPROM 数据的毁坏(丢
失)。
**这种情况在使用独立的 EEPROM 器件时也会遇到。因而需要使用相同的保护方
案。
由于电压过低造成 EEPROM 数据损坏有两种可能:
一是电压低于 EEPROM 写操作所需要的最低电压;
二是 CPU 本身已经无法正常工作。
EEPROM 数据损坏的问题可以通过以下方法解决:
当电压过低时保持 AVR RESET 信号为低。
这可以通过使能芯片的掉电检测电路 BOD 来实现。如果 BOD 电平无法满足要求
则可以使用外部复位电路。
若写操作过程当中发生了复位,只要电压足够高,写操作仍将正常结束。
(EEPROM 在 2V 低压下也能进行写操作---有可以工作到 1.8V 的 AVR 芯片)
掉电检测 BOD 的误解
AVR 自带的 BOD(Brown-out Detection)电路,作用是在电压过低(低于设定值)时产
生复位信号,防止 CPU 意外动作.
对 EEPROM 的保护作用是当电压过低时保持 RESET 信号为低,防止 CPU 意外动
作,错误修改了 EEPROM 的内容
而我们所理解的掉电检测功能是指 具有预测功能的可以进行软件处理的功能。
例如,用户想在电源掉电时把 SRAM 数据转存到 EEPROM,可行的方法是
外接一个在 4.5V 翻转的电压比较器(VCC=5.0V,BOD=2.7V),输出接到外部中断
引脚(或其他中断)
一但电压低于 4.5V,马上触发中断,在中断服务程序中把数据写到 EEPROM 中
保护起来
注意: 写一个字节的 EEPROM 时间长达 8mS,所以不能写入太多数据,电源滤波电容也
要选大一些
68
AVR 单片机学习开发板教程
dushibiao
2007 年 11 月
*/
实验九
I2C 读写 24C02 实验
实验目的:学习 AVR 单片机的内部 I2C 模块使用方法
实验准备:请把 AVR 单片机的内部 I2C 模块一节了解一下,24C02 读写方法了解一下
实验要求:请把 84 数码管与液晶选择跳线打到 84 一侧,2402 选择跳线打到 2402 一侧
AVR—GCC 版本程序如下
/*
Title:
AVR-GCC twi test
Author: dushibiao
Date:
2007 10 21
Purpose: learn how to use twi interface
Frequency: internal 8M
Software: AVR-GCC to compile
Hardware: AVR mega16 BOARD
Connect: [email protected]
*/
/*
本程序简单的示范了如何使用 ATMEGA16 的 TWI 读写 AT24C02 IIC EEPROM
TWI 协议
(即 IIC 协议,请认真参考 IIC 协议的内容,否则根本就不能掌握)
一主多从的应用,M16 作主机
(M16 做从机和多主多从的应用不多,请自行参考相关文档)
中断模式
(因为 AVR 的速度很高,而 IIC 的速度相对较低,
采用查询模式会长时间独占 CPU,令 CPU 的利用率明显下降。
特别是 IIC 速度受环境影响只能低速通讯时,对系统的实时性产生严重的影响。
查询模式可以参考其它文档和软件模拟 IIC 的文档)
AT24C02/04/08 的操作特点
出于简化程序考虑,各种数据没有对外输出,学习时建议使用 JTAG ICE 硬件仿真器
*/
#include <avr/io.h>
#include <avr/signal.h>
#include <avr/interrupt.h>
#include <avr/delay.h>
#include "shumaguan.h"
//时钟定为外部晶振 8MHz,F_CPU=8000000
#include <compat/twi.h>
//定义了各种模式下的状态码列表(TWSR 已屏蔽预分频位),本文后面附上中文描述
69
AVR 单片机学习开发板教程
dushibiao
2007 年 11 月
//管脚定义
#define pinSCL
0
//PC0 SCL
#define pinSDA
1
//PC1 SDA
//为保险起见,最好在 SCL/SDA 接上 1~10K 的外部上拉电阻到 VCC。
#define fSCL
100000
//TWI 时钟为 100KHz
//预分频系数=1(TWPS=0)
#if F_CPU < fSCL*36
#define TWBR_SET
10;
//TWBR 必须大于等于 10
#else
#define TWBR_SET
(F_CPU/fSCL-16)/2; //计算 TWBR 值
#endif
#define TW_ACT
(1<<TWINT)|(1<<TWEN)|(1<<TWIE)
//TWCR 只能 IN/OUT,直接赋值比逻辑运算(|= &=)更节省空间
#define SLA_24CXX
0xA0
//24Cxx 系列的厂商器件地址(高四
位)
#define ADDR_24C02
0x00
//
AT24C02
的
地
址
线
A2/1/0
全
部
接
地
,
SLAW=0xA0+0x00<<1+0x00,SLAR=0xA0+0x00<<1+0x01
//TWI_操作状态
#define TW_BUSY
#define TW_OK
#define TW_FAIL
//TWI_读写命令状态
#define OP_BUSY
#define OP_RUN
//TWI 读写操作公共步骤
#define ST_FAIL
#define ST_START
#define ST_SLAW
#define ST_WADDR
//TWI 读操作步骤
#define ST_RESTART
#define ST_SLAR
#define ST_RDATA
//TWI 写操作步骤
#define ST_WDATA
0
1
2
0
1
1
0
//出错状态
//START 状态检查
2 //SLAW 状态检查
3
//ADDR 状态检查
4
5
6
//RESTART 状态检查
//SLAR 状态检查
//读取数据状态检查,循环 n 字节
7
//写数据状态检查,循环 n 字节
70
AVR 单片机学习开发板教程
#define FAIL_MAX
dushibiao
2007 年 11 月
20 //重试次数最大值
//定义全局变量
unsigned char ORGDATA[8]=
{0xAA,0xA5,0x55,0x5A,0x01,0x02,0x03,0x04}; //原始数据
unsigned char CMPDATA[8];
//比较数据
struct str_TWI
{
volatile unsigned char STATUS;
unsigned char SLA;
unsigned int ADDR;
unsigned char *pBUF;
unsigned int DATALEN;
unsigned char STATE;
unsigned char FAILCNT;
};
//TWI 数据结构
struct str_TWI strTWI;
//TWI 的数据结构变量
//TWI_操作状态
//从设备的器件地址
//从设备的数据地址
//数据缓冲区指针
//数据长度
//TWI 读写操作步骤
//失败重试次数
//仿真时在 watch 窗口,监控这些全局变量。
//AT24C02 的读写函数(包括随机读,连续读,字节写,页写)
//根据 sla 的最低位决定(由中断程序中判断)
//bit0=1 TW_READ 读
//bit0=0 TW_WRITE 写
// sla
器件地址(不能搞错)
// addr
EEPROM 地址(0~1023)
// *ptr
读写数据缓冲区
// len
读数据长度(1~1024),写数据长度(1 or 8 or 16)
// 返回值
是否能执行当前操作
unsigned char TWI_RW(unsigned char sla,unsigned int addr,unsigned char *ptr,unsigned int len)
{
unsigned char i;
if (strTWI.STATUS==TW_BUSY)
{//TWI 忙,不能进行操作
return OP_BUSY;
}
strTWI.STATUS=TW_BUSY;
i=(addr>>8)<<1;
i&=0x06;
//考虑了 24C04/08 的 EEPROM 地址高位
71
AVR 单片机学习开发板教程
放在 SLA 里面
strTWI.SLA=sla+i;
strTWI.ADDR=addr;
strTWI.pBUF=ptr;
strTWI.DATALEN=len;
strTWI.STATE=ST_START;
strTWI.FAILCNT=0;
TWCR=(1<<TWSTA)|TW_ACT;
return OP_RUN;
}
dushibiao
2007 年 11 月
//启动 start 信号
/*
TWI 中断函数
这个函数流程只是考虑了器件地址后有一个字节数据(命令)地址的 IIC 器件
(大部分 IIC 接口器件都是这种类型,常见的例如 AT24C01/02/04/08/16,DS1307,DS1721
等)
对于有两个字节数据地址的 IIC 器件(例如 AT24C32/64/128/256 等大容量 EEPROM),请
稍作改动
//根据 strTWI.SLA 的最低位决定
//bit0=1 TW_READ 读
//bit0=0 TW_WRITE 写
虽然中断服务程序很长,但每次只执行一个 case,所以耗时并不长。
*/
SIGNAL(SIG_2WIRE_SERIAL)
{//IIC 中断
unsigned char action,state,status;
action=strTWI.SLA&TW_READ;
state=strTWI.STATE;
status=TWSR&0xF8;
if ((status>=0x60)||(status==0x00))
{//总线错误或从机模式引发的中断,不予处理
return;
}
switch(state)
{
case ST_START: //START 状态检查
if(status==TW_START)
{//发送 start 信号成功
TWDR=strTWI.SLA&0xFE;
TWCR=TW_ACT;
发送标志
}
//取操作模式
//屏蔽预分频位
//发送器件地址写 SLAW
//触发下一步动作,同时清 start
72
AVR 单片机学习开发板教程
dushibiao
2007 年 11 月
else
{//发送 start 信号出错
state=ST_FAIL;
}
break;
case ST_SLAW: //SLAW 状态检查
if(status==TW_MT_SLA_ACK)
{//发送器件地址成功
TWDR=strTWI.ADDR;
//发送 eeprom 地址
TWCR=TW_ACT;
//触发下一步动作
}
else
{//发送器件地址出错
state=ST_FAIL;
}
break;
case ST_WADDR: //ADDR 状态检查
if(status==TW_MT_DATA_ACK)
{//发送 eeprom 地址成功
if (action==TW_READ)
{//读操作模式
TWCR=(1<<TWSTA)|TW_ACT;
//发送 restart 信号,下一步将跳
到 RESTART 分支
}
else
{//写操作模式
TWDR=*strTWI.pBUF++;
//写第一个字节
strTWI.DATALEN--;
state=ST_WDATA-1;
//下一步将跳到 WDATA 分支
TWCR=TW_ACT;
//触发下一步动作
}
}
else
{//发送 eeprom 地址出错
state=ST_FAIL;
}
break;
case ST_RESTART:
//RESTART 状态检查,只有读操作模式才能跳到这里
if(status==TW_REP_START)
{//发送 restart 信号成功
TWDR=strTWI.SLA;
//发器件地址读 SLAR
TWCR=TW_ACT;
//触发下一步动作,同时清 start
发送标志
}
73
AVR 单片机学习开发板教程
dushibiao
2007 年 11 月
else
{//重发 start 信号出错
state=ST_FAIL;
}
break;
case ST_SLAR:
//SLAR 状态检查,只有读操作模式才能跳到这里
if(status==TW_MR_SLA_ACK)
{//发送器件地址成功
if (strTWI.DATALEN--)
{//多个数据
TWCR=(1<<TWEA)|TW_ACT;
//设定 ACK,触发下一步动作
}
else
{//只有一个数据
TWCR=TW_ACT;
//设定 NAK,触发下一步动作
}
}
else
{//发送器件地址出错
state=ST_FAIL;
}
break;
case ST_RDATA: //读取数据状态检查,只有读操作模式才能跳到这里
state--;
//循环,直到读完指定长度数据
if(status==TW_MR_DATA_ACK)
{//读取数据成功,但不是最后一个数据
*strTWI.pBUF++=TWDR;
if (strTWI.DATALEN--)
{//还有多个数据
TWCR=(1<<TWEA)|TW_ACT;
//设定 ACK,触发下一步动作
}
else
{//准备读最后一个数据
TWCR=TW_ACT;
//设定 NAK,触发下一步动作
}
}
else if(status==TW_MR_DATA_NACK)
{//已经读完最后一个数据
*strTWI.pBUF++=TWDR;
TWCR=(1<<TWSTO)|TW_ACT;
//发送停止信号,不会再产生中
断了
strTWI.STATUS=TW_OK;
}
else
74
AVR 单片机学习开发板教程
dushibiao
2007 年 11 月
{//读取数据出错
state=ST_FAIL;
}
break;
case ST_WDATA: //写数据状态检查,只有写操作模式才能跳到这里
state--;
//循环,直到写完指定长度数据
if(status==TW_MT_DATA_ACK)
{//写数据成功
if (strTWI.DATALEN)
{//还要写
TWDR=*strTWI.pBUF++;
strTWI.DATALEN--;
TWCR=TW_ACT;
//触发下一步动作
}
else
{//写够了
TWCR=(1<<TWSTO)|TW_ACT;
//发送停止信号,不会再产生中
断了
strTWI.STATUS=TW_OK;
//启动写命令后需要 10ms(最大)的编程时间才能真正的把数据记录下来
//编程期间器件不响应任何命令
}
}
else
{//写数据失败
state=ST_FAIL;
}
break;
default:
//错误状态
state=ST_FAIL;
break;
}
if (state==ST_FAIL)
{//错误处理
strTWI.FAILCNT++;
if (strTWI.FAILCNT<FAIL_MAX)
{//重试次数未超出最大值,
TWCR=(1<<TWSTA)|TW_ACT;
}
else
{//否则停止
TWCR=(1<<TWSTO)|TW_ACT;
//发生错误,启动 start 信号
//发送停止信号,不会再产生中
75
AVR 单片机学习开发板教程
dushibiao
2007 年 11 月
断了
strTWI.STATUS=TW_FAIL;
}
}
state++;
strTWI.STATE=state;
//保存状态
}
int main(void)
{
unsigned char i;
//上电默认 DDRx=0x00,PORTx=0x00 输入,无上拉电阻
PORTA=0xFF;
DDRA=0XFF;
//OUT
PORTB=0xFF;
DDRB=0XFF;
PORTC=0xFF;
上拉电阻
PORTD=0xFF;
//TWI 初始化
TWSR=0x00;
TWBR=TWBR_SET;
TWAR=0x00;
TWCR=0x00;
sei();
strTWI.STATUS=TW_OK;
//OUT
//SCL,SDA 使能了内部的 10K
//不用的管脚使能内部上拉电阻。
//预分频=0^4=1
//主机模式,该地址无效
//关闭 TWI 模块
//使能全局中断
TWI_RW(SLA_24CXX+(ADDR_24C02<<1)+TW_WRITE,0x10,&ORGDATA[0],8);
//从 0x10 地址开始写入 8 个字节数据
while(strTWI.STATUS==TW_BUSY);
//等待操作完成
if (strTWI.STATUS==TW_FAIL)
{
//操作失败?
unsigned char i;
for(i=0;i<255;i++)
displayerror();
}
_delay_ms(10);
//延时等待编程完成
while(1)
{
76
AVR 单片机学习开发板教程
dushibiao
2007 年 11 月
i=TWI_RW(SLA_24CXX+(ADDR_24C02<<1)+TW_READ,0x10,&CMPDATA[0],8);
//从 0x10 地址开始读出 8 个字节数据
while(strTWI.STATUS==TW_BUSY);
//等待操作完成
//如果不加等待,则需要检测返回值 i 才能知道当前操作是否执行了
// 0 OP_BUSY 之前的操作没完成,没执行当前操作
// 1 OP_RUN 当前操作执行中
if (strTWI.STATUS==TW_FAIL)
{
//操作失败?
unsigned char i;
for(i=0;i<255;i++)
displayerror();
}
//读取成功,对比 ORGDATA 和 CMPDATA 的数据
unsigned char temp;
for(temp=0;temp<8;temp++)
{
unsigned char i;
for(i=0;i<255;i++)
display1(ORGDATA[temp],CMPDATA[temp]);
}
}
}
/*
两线串行接口总线定义
两线接口 TWI 很适合于典型的处理器应用。
TWI 协议允许系统设计者只用两根双向传输线就可以将 128 个不同的设备互连到一起。
这两根线一是时钟 SCL,一是数据 SDA。外部硬件只需要两个上拉电阻,每根线上一
个。
所有连接到总线上的设备都(必须)有自己的地址。
注意:就是说不能有两个相同地址的设备
TWI 协议解决了总线仲裁的问题。
所有 TWI 兼容的器件的总线驱动都是漏极开路或集电极开路的。这样就实现了对接口
操作非常关键的线与功能。
TWI 器件输出为"0”时,TWI 总线会产生低电平。
当所有的 TWI 器件输出为三态时,总线会输出高电平,允许上拉电阻将电压拉高。
注意:为保证所有的总线操作,凡是与 TWI 总线连接的 AVR 器件必须上电。
与总线连接的器件数目受如下条件限制:
总线电容要低于 400pF,而且可以用 7 位从机地址进行寻址。
两个不同的规范,一种是总线速度低于 100 kHz,而另外一种是总线速度高达 400 kHz。
77
AVR 单片机学习开发板教程
dushibiao
2007 年 11 月
SCL 和 SDA 引脚
SCL 与 SDA 为 MCU 的 TWI 接口引脚。
引脚的输出驱动器包含一个波形斜率限制器以满足 TWI 规范。
引脚的输入部分包括尖峰抑制单元以去除小于 50ns 的毛刺。
当相应的端口设置为 SCL 与 SDA 引脚时,可以使能 I/O 口内部的 10K 上拉电阻,这样
可省掉外部的上拉电阻
注意:如果要作高速通讯或者从机数量较多,最好还是外接合适的上拉电阻
比特率发生器单元
TWI 工作于主机模式时,比特率发生器控制时钟信号 SCL 的周期。
具体由 TWI 状态寄存器 TWSR 的预分频系数以及比特率寄存器 TWBR 设定。
当 TWI 工作在从机模式时,不需要对比特率或预分频进行设定,但从机的 CPU 时钟频
率必须大于 TWI 时钟线 SCL 频率的 16 倍。
注意,从机可能会延长 SCL 低电平的时间,从而降低 TWI 总线的平均时钟周期。
SCL 的频率根据以下的公式产生:
fSCL=fCPU/((16+2(TWBR)(4^TWPS))
TWBR = TWI 比特率寄存器的数值
TWPS = TWI 状态寄存器预分频的数值
Note:TWI 工作在主机模式时,TWBR 值应该不小于 10,否则主机会在 SDA 与 SCL 产生错
误输出作为提示信号。
问题出现于 TWI 工作在主机模式下,向从机发送 Start + SLA + R/W 的时候(不需要真
的有从机与总线连接)。
控制单元
控制单元监听 TWI 总线,并根据 TWI 控制寄存器 TWCR 的设置作出相应的响应。
当 TWI 总线上产生需要应用程序干预处理的事件时,TWI 中断标志位 TWINT 置位。
在下一个时钟周期, TWI 状态寄存器 TWSR 被表示这个事件的状态码字所更新。
在其它时间里,TWSR 的内容为一个表示无事件发生的特殊状态字。
一旦 TWINT 标志位置"1”,时钟线 SCL 即被拉低,暂停 TWI 总线上的数据传输,让
用户程序处理事件。
在下列状况出现时, TWINT 标志位置位:
? 在 TWI 传送完 START/REPEATED START 信号之后
? 在 TWI 传送完 SLA+R/W 数据之后
? 在 TWI 传送完地址字节之后
? 在 TWI 总线仲裁失败之后
? 在 TWI 被主机寻址之后( 广播方式或从机地址匹配)
? 在 TWI 接收到一个数据字节之后
? 作为从机工作时, TWI 接收到 STOP 或 REPEATED START 信号之后
? 由于非法的 START 或 STOP 信号造成总线错误时
TWI 寄存器说明
TWI 比特率寄存器- TWBR
? Bits 7..0 – TWI 比特率寄存器
78
AVR 单片机学习开发板教程
dushibiao
2007 年 11 月
TWBR 为比特率发生器分频因子。
比特率发生器是一个分频器,在主机模式下产生 SCL 时钟频率。
比特率计算公式请见前面的[比特率发生器单元]
TWI 控制寄存器- TWCR
TWCR 用来控制 TWI 操作。
它用来使能 TWI,通过施加 START 到总线上来启动主机访问,产生接收器应答,产生
STOP 状态,以及在写入数据到 TWDR 寄存器时控制总线的暂停等。
这个寄存器还可以给出在 TWDR 无法访问期间,试图将数据写入到 TWDR 而引起的
写入冲突信息。
? Bit 7 – TWINT: TWI 中断标志
当 TWI 完成当前工作,希望应用程序介入时 TWINT 置位。
若 SREG 的 I 标志以及 TWCR 寄存器的 TWIE 标志也置位,则 MCU 执行 TWI 中
断例程。
当 TWINT 置位时, SCL 信号的低电平被延长。
TWINT 标志的清零必须通过软件写"1” 来完成。
执行中断时硬件不会自动将其改写为"0”。
要注意的是,只要这一位被清零,TWI 立即开始工作。
因此,在清零 TWINT 之前一定要首先完成对地址寄存器 TWAR,状态寄存器
TWSR,以及数据寄存器 TWDR 的访问。
? Bit 6 – TWEA: 使能 TWI 应答
TWEA 标志控制应答脉冲的产生。
若 TWEA 置位,出现如下条件时接口发出 ACK 脉冲:
1. 器件的从机地址与主机发出的地址相符合
2. TWAR 的 TWGCE 置位时接收到广播呼叫
3. 在主机/ 从机接收模式下接收到一个字节的数据
将 TWEA 清零可以使器件暂时脱离总线。
置位后器件重新恢复地址识别。
? Bit 5 – TWSTA: TWI START 状态标志
当 CPU 希望自己成为总线上的主机时需要置位 TWSTA。
TWI 硬件检测总线是否可用。
若总线空闲,接口就在总线上产生 START 状态。
若总线忙,接口就一直等待,直到检测到一个 STOP 状态 ,然后产生 START 以
声明自己希望成为主机。
发送 START 之后软件必须清零 TWSTA。
? Bit 4 – TWSTO: TWI STOP 状态标志
在主机模式下,如果置位 TWSTO,TWI 接口将在总线上产生 STOP 状态,然后
TWSTO 自动清零。
在从机模式下,置位 TWSTO 可以使接口从错误状态恢复到未被寻址的状态。
此时总线上不会有 STOP 状态产生,但 TWI 返回一个定义好的未被寻址的从机模
式且释放 SCL 与 SDA 为高阻态。
? Bit 3 – TWWC: TWI 写碰撞标志
当 TWINT 为低时写数据寄存器 TWDR 将置位 TWWC。
当 TWINT 为高时,每一次对 TWDR 的写访问都将更新此标志。
79
AVR 单片机学习开发板教程
dushibiao
2007 年 11 月
? Bit 2 – TWEN: TWI 使能
TWEN 位用于使能 TWI 操作与激活 TWI 接口。
当 TWEN 位被写为"1”时,TWI 引脚将 I/O 引脚切换到 SCL 与 SDA 引脚,使能
波形斜率限制器与尖峰滤波器。
如果该位清零, TWI 接口模块将被关闭,所有 TWI 传输将被终止。
? Bit 0 – TWIE: 使能 TWI 中断
当 SREG 的 I 以及 TWIE 置位时,只要 TWINT 为"1”, TWI 中断就激活。
TWI 状态寄存器- TWSR
? Bits 7..3 – TWS: TWI 状态
这 5 位用来反映 TWI 逻辑和总线的状态。
不同的状态代码将会在后面的部分描述。
注意从 TWSR 读出的值包括 5 位状态值与 2 位预分频值。
检测状态位时设计者应屏蔽预分频位为"0”。这使状态检测独立于预分频器设置。
? Bits 1..0 – TWPS: TWI 预分频位
这两位可读/ 写,用于控制比特率预分频因子。
预分频系数为 4 的 n 次方
计算比特率的公式见前面的[比特率发生器单元]
TWI 数据寄存器- TWDR
在发送模式, TWDR 包含了要发送的字节;
在接收模式, TWDR 包含了接收到的数据。
当 TWI 接口没有进行移位工作(TWINT 置位) 时这个寄存器是可写的。
在第一次中断发生之前用户不能够初始化数据寄存器。
只要 TWINT 置位,TWDR 的数据就是稳定的。
在数据移出时,总线上的数据同时移入寄存器。
TWDR 总是包含了总线上出现的最后一个字节,除非 MCU 是从掉电或省电模式被
TWI 中断唤醒。此时 TWDR 的内容没有定义。
总线仲裁失败时,主机将切换为从机,但总线上出现的数据不会丢失。
ACK 的处理由 TWI 逻辑自动管理, CPU 不能直接访问 ACK。
? Bits 7..0 – TWD: TWI 数据寄存器
根据状态的不同,其内容为要发送的下一个字节,或是接收到的数据。
TWI(从机) 地址寄存器-TWAR
TWAR 的高 7 位为从机地址。
工作于从机模式时,TWI 将根据这个地址进行响应。
主机模式不需要此地址。
在多主机系统中, TWAR 需要进行设置以便其他主机访问自己。
TWAR 的 LSB 用于识别广播地址 (0x00)。
器件内有一个地址比较器。一旦接收到的地址和本机地址一致,芯片就请求中断。
? Bits 7..1 – TWA: TWI 从机地址寄存器
其值为从机地址。
? Bit 0 – TWGCE: 使能 TWI 广播识别
置位后 MCU 可以识别 TWI 总线广播。
80
AVR 单片机学习开发板教程
dushibiao
2007 年 11 月
使用 TWI
AVR 的 TWI 接口是面向字节和基于中断的。
所有的总线事件,如接收到一个字节或发送了一个 START 信号等,都会产生一个 TWI
中断。
由于 TWI 接口是基于中断的,因此 TWI 接口在字节发送和接收过程中,不需要应用程
序的干预。
TWCR 寄存器的 TWI 中断允许位[TWIE]和全局中断允许位[I]一起决定了应用程序是否
响应 TWINT 标志位产生的中断请求。
如果 TWIE 被清零,应用程序只能采用轮询 TWINT 标志位的方法来检测 TWI 总线状
态。
当 TWINT 标志位置"1” 时,表示 TWI 接口完成了当前的操作,等待应用程序的响应。
在这种情况下,TWI 状态寄存器 TWSR 包含了表明当前 TWI 总线状态的值。
应用程序可以读取 TWCR 的状态码,判别此时的状态是否正确,并通过设置 TWCR 与
TWDR 寄存器,决定在下一个 TWI 总线周期 TWI 接口应该如何工作。
各种模式下的状态码列表(TWSR 已屏蔽预分频位)
twi.h 里面有定义,现附上中文描述
主机发送状态码
#define TW_START
#define TW_REP_START
#define TW_MT_SLA_ACK
#define TW_MT_SLA_NACK
#define TW_MT_DATA_ACK
#define TW_MT_DATA_NACK
#define TW_MT_ARB_LOST
0x08
//START 已发送
0x10
//重复 START 已发送
0x18
//SLA+W 已发送收到 ACK
0x20
//SLA+W 已发送接收到 NOT ACK
0x28
//数据已发送接收到 ACK
0x30
//数据已发送接收到 NOT ACK
0x38
//SLA+W 或数据的仲裁失败
主机接收状态码
//#define TW_START
//#define TW_REP_START
#define TW_MR_ARB_LOST
#define TW_MR_SLA_ACK
#define TW_MR_SLA_NACK
#define TW_MR_DATA_ACK
#define TW_MR_DATA_NACK
0x08
//START 已发送
0x10
//重复 START 已发送
0x38
//SLA+R 或 NOT ACK 的仲裁失败
0x40
//SLA+R 已发送接收到 ACK
0x48
//SLA+R 已发送接收到 NOT ACK
0x50
//接收到数据 ACK 已返回
0x58
//接收到数据 NOT ACK 已返回
从机接收状态码
#define TW_SR_SLA_ACK
0x60
//自己的 SLA+W 已经被接收 ACK 已返回
#define TW_SR_ARB_LOST_SLA_ACK 0x68
//SLA+R/W 作为主机的仲裁失败;自己
的 SLA+W 已经被接收 ACK 已返回
#define TW_SR_GCALL_ACK
0x70
//接收到广播地址 ACK 已返回
#define TW_SR_ARB_LOST_GCALL_ACK 0x78 //SLA+R/W 作为主机的仲裁失败;接收
81
AVR 单片机学习开发板教程
dushibiao
2007 年 11 月
到广播地址 ACK 已返回
#define TW_SR_DATA_ACK
0x80
//以前以自己的 SLA+W 被寻址;数据已
经被接收 ACK 已返回
#define TW_SR_DATA_NACK
0x88
//以前以自己的 SLA+W 被寻址;数据已
经被接收 NOT ACK 已返回
#define TW_SR_GCALL_DATA_ACK
0x90
//以前以广播方式被寻址;数据已经被接
收 ACK 已返回
#define TW_SR_GCALL_DATA_NACK 0x98
//以前以广播方式被寻址;数据已经被接
收 NOT ACK 已返回
#define TW_SR_STOP
0xA0
//在以从机工作时接收到 STOP 或重复 START
从发送状态码
#define TW_ST_SLA_ACK
0xA8
//自己的 SLA+R 已经被接收 ACK 已返回
#define TW_ST_ARB_LOST_SLA_ACK 0xB0
//SLA+R/W 作为主机的仲裁失败;自己
的 SLA+R 已经被接收 ACK 已返回
#define TW_ST_DATA_ACK
0xB8
//TWDR 里数据已经发送接收到 ACK
#define TW_ST_DATA_NACK
0xC0
//TWDR 里数据已经发送接收到 NOT
ACK
#define TW_ST_LAST_DATA
0xC8
//TWDR 的一字节数据已经发送(TWAE
= “0”);接收到 ACK
其它状态码
#define TW_NO_INFO
#define TW_BUS_ERROR
误
0xF8
0x00
//没有相关的状态信息;TWINT = “0”
//由于非法的 START 或 STOP 引起的总线错
AT24C02/04/08 IIC 接口 EEPROM 的特点
(不同公司的 24 系列 EEPROM 特性有部分不同,请参考数据手册)
1 AT24C02/04/08 是一个 2K/4K/8K 位串行 CMOS E2PROM 内部含有 256/512/1024 个
8 位字节
2 AT24C02 有一个 8 字节页写缓冲器,AT24C04/08/16 有一个 16 字节页写缓冲器
3 通过器件地址输入端 A0,A1,A2 可以实现将最多
8 个 24C02 器件
4 个 24C04 器件
2 个 24C08 器件
同时连接到总线上
4 写操作
1 字节写
2 页写 AT24C02 是 8 字节/页 AT24C04/08 是 16 字节/页
注意:页写的地址只在当前页自动累加,页地址范围内循环。
启动写命令后需要 10ms(最大)的编程时间才能真正的把数据记录下来,编程期间
82
AVR 单片机学习开发板教程
dushibiao
2007 年 11 月
器件不响应任何命令
5 读操作
1 立即地址读 地址自动累加,即为上次读/写操作地址+1(本程序不支持该操作)
2 随机读
指定地址读一个字节
3 连续读
连续读操作可通过立即读或随机读操作启动,由主机发出 NAK 和
STOP 来停止读操作
读操作时地址计数器在 AT24C02/04/08 整个地址内增加,这样整个寄
存器区域在可在一个读操作内全部读出
循环读取,读到最后一个地址后,从第一个地址继续开始读
6 写保护功能,由 WP 引脚控制,WP=VCC 时,24C02 的高 1K 位,24C04 的高 2K
位,24C08 的全部 8K 位都变成只读,不能写入
*/
ICC--AVR 版本程序如下
/*
Title:
icc-avr eeprom i2c test
Author: dushibiao
Date:
2007 10 18
Purpose: study how use twi interface with a eeprom AT24C02
Frequency: internal 8M
Software: icc-avr
Hardware: AVR mega16 BOARD
Connect: [email protected]
*/
//外部 8M 晶振
#include<iom16v.h>
#include"shumaguan.h"
/*
本程序简单的示范了如何使用 ATMEGA16 的 TWI 读写 AT24C02 IIC EEPROM
TWI 协议
(即 IIC 协议,请认真参考 IIC 协议的内容,否则根本就不能掌握)
一主多从的应用,M16 作主机
(M16 做从机和多主多从的应用不多,请自行参考相关文档)
中断模式
(因为 AVR 的速度很高,而 IIC 的速度相对较低,
采用查询模式会长时间独占 CPU,令 CPU 的利用率明显下降。
特别是 IIC 速度受环境影响只能低速通讯时,对系统的实时性产生严重的影响。
查询模式可以参考其它文档和软件模拟 IIC 的文档)
AT24C02/04/08 的操作特点
出于简化程序考虑,各种数据没有对外输出,学习时建议使用 JTAG ICE 硬件仿真器
83
AVR 单片机学习开发板教程
dushibiao
2007 年 11 月
*/
unsigned char twi_write(unsigned char addr, unsigned char dd);
unsigned char twi_read(unsigned char addr, unsigned char *dd);
void error(unsigned char type);
void main()
{
PORTC = 0xFF;
DDRC = 0x00;
//pull up
//scl sck
--in
PORTA=0XFF;
DDRA=0XFF;
//out
DDRB=0XFF;
//out
PORTD=0XFF;
//pull up
while(1)
{
unsigned char add,data,j;
for(add=0;add<255;add++)
{
twi_write(add,add);
//write i(data) to the address i
delay_nms(10);
twi_read(add,&data);
//read the address of i,and write it to temp
for(j=0;j<255;j++)
display1(add,data);
//the high two bits display the address
//the low two bits display the data
}
}
}
/*--------------------------------------------------------------fuction: dispose the failture
pata:
type---the error reason
*-----------------------------------------------------------------*/
void error(unsigned char type)
{
switch (type & 0xF8)
{
case 0x20:
/*址址写失败*/
/*stop 停止*/
TWCR = (1 << TWINT) | (1 << TWEN) | (1 << TWSTO);
break;
84
AVR 单片机学习开发板教程
dushibiao
2007 年 11 月
case 0x30:
/*数据写失败*/
/*stop 停止*/
TWCR = (1 << TWINT) | (1 << TWEN) | (1 << TWSTO);
break;
case 0x38:
/*仲裁失败*/
break;
case 0x48:
/*址址读失败*/
/*stop 停止*/
TWCR = (1 << TWINT) | (1 << TWEN) | (1 << TWSTO);
break;
}
}
/*--------------------------------------------------------------fuction: write a byte to AT24C02
para
addr----the address to write
dd---data to write
return
0--fail
0--success
----------------------------------------------------------------*/
/*I2C 总线单字节写入*/
unsigned char twi_write(unsigned char addr, unsigned char dd)
{
TWBR = 2;
/*start 启动*/
TWCR = (1 << TWINT) | (1 << TWSTA) | (1 << TWEN);
while (!(TWCR & (1 << TWINT)));
if ((TWSR & 0xF8) != 0x08)
{
error(TWSR);
return 0;
}
/*SLA_W 芯片地址*/
TWDR = 0xA0;
TWCR = (1 << TWINT) | (1 << TWEN);
while (!(TWCR & (1 << TWINT)));
if ((TWSR & 0xF8) != 0x18)
{
error(TWSR);
return 0;
}
85
AVR 单片机学习开发板教程
dushibiao
2007 年 11 月
/*addr 操作地址*/
TWDR = addr;
TWCR = (1 << TWINT) | (1 << TWEN);
while (!(TWCR & (1 << TWINT)));
if ((TWSR & 0xF8) != 0x28)
{
error(TWSR);
return 0;
}
/*dd 写入数据*/
TWDR = dd;
TWCR = (1 << TWINT) | (1 << TWEN);
while (!(TWCR & (1 << TWINT)));
if ((TWSR & 0xF8) != 0x28)
{
error(TWSR);
return 0;
}
/*stop 停止*/
TWCR = (1 << TWINT) | (1 << TWEN) | (1 << TWSTO);
return 1;
}
/*-----------------------------------------------------------fuction: read a byte in the AT24C02
para:
addr--the address you want to read
dd---the pointer to a buffer to load the data
return: 0---fail
1---success
-------------------------------------------------------------*/
/*I2C 总线单字节读取*/
unsigned char twi_read(unsigned char addr, unsigned char *dd)
{
TWBR = 2;
/*start 启动*/
TWCR=(1<<TWINT)|(1<<TWSTA)|(1<<TWEN);
while(!(TWCR&(1<<TWINT)));
if ((TWSR & 0xF8) != 0x08)
{
error(TWSR);
return 0;
}
86
AVR 单片机学习开发板教程
dushibiao
2007 年 11 月
/*SLA_W 芯片地址*/
TWDR = 0xA0;
TWCR = (1 << TWINT) | (1 << TWEN);
while (!(TWCR & (1 << TWINT)));
if ((TWSR & 0xF8) != 0x18)
{
error(TWSR);
return 0;
}
/*addr 操作地址*/
TWDR = addr;
TWCR = (1 << TWINT) | (1 << TWEN);
while (!(TWCR & (1 << TWINT)));
if ((TWSR & 0xF8) != 0x28)
{
error(TWSR);
return 0;
}
/*start 启动*/
TWCR = (1 << TWINT) | (1 << TWSTA) | (1 << TWEN);
while (!(TWCR & (1 << TWINT)));
if ((TWSR & 0xF8) != 0x10)
{
error(TWSR);
return 0;
}
/*SLA_R 芯片地址*/
TWDR = 0xA1;
TWCR = (1 << TWINT) | (1 << TWEN);
while (!(TWCR & (1 << TWINT)));
if ((TWSR & 0xF8) != 0x40)
{
error(TWSR);
return 0;
}
/*读取数据*/
TWCR = (1 << TWINT) | (1 << TWEN);
87
AVR 单片机学习开发板教程
dushibiao
2007 年 11 月
while (!(TWCR & (1 << TWINT)));
if ((TWSR & 0xF8) != 0x58)
{
error(TWSR);
return 0;
}
*dd = TWDR;
TWCR = (1 << TWINT) | (1 << TWSTO) | (1 << TWEN);
return 1;
}
/*
两线串行接口总线定义
两线接口 TWI 很适合于典型的处理器应用。
TWI 协议允许系统设计者只用两根双向传输线就可以将 128 个不同的设备互连到一起。
这两根线一是时钟 SCL,一是数据 SDA。外部硬件只需要两个上拉电阻,每根线上一
个。
所有连接到总线上的设备都(必须)有自己的地址。
注意:就是说不能有两个相同地址的设备
TWI 协议解决了总线仲裁的问题。
所有 TWI 兼容的器件的总线驱动都是漏极开路或集电极开路的。这样就实现了对接口
操作非常关键的线与功能。
TWI 器件输出为"0”时,TWI 总线会产生低电平。
当所有的 TWI 器件输出为三态时,总线会输出高电平,允许上拉电阻将电压拉高。
注意:为保证所有的总线操作,凡是与 TWI 总线连接的 AVR 器件必须上电。
与总线连接的器件数目受如下条件限制:
总线电容要低于 400pF,而且可以用 7 位从机地址进行寻址。
两个不同的规范,一种是总线速度低于 100 kHz,而另外一种是总线速度高达 400 kHz。
SCL 和 SDA 引脚
SCL 与 SDA 为 MCU 的 TWI 接口引脚。
引脚的输出驱动器包含一个波形斜率限制器以满足 TWI 规范。
引脚的输入部分包括尖峰抑制单元以去除小于 50ns 的毛刺。
当相应的端口设置为 SCL 与 SDA 引脚时,可以使能 I/O 口内部的 10K 上拉电阻,这样
可省掉外部的上拉电阻
注意:如果要作高速通讯或者从机数量较多,最好还是外接合适的上拉电阻
比特率发生器单元
TWI 工作于主机模式时,比特率发生器控制时钟信号 SCL 的周期。
具体由 TWI 状态寄存器 TWSR 的预分频系数以及比特率寄存器 TWBR 设定。
当 TWI 工作在从机模式时,不需要对比特率或预分频进行设定,但从机的 CPU 时钟频
88
AVR 单片机学习开发板教程
dushibiao
2007 年 11 月
率必须大于 TWI 时钟线 SCL 频率的 16 倍。
注意,从机可能会延长 SCL 低电平的时间,从而降低 TWI 总线的平均时钟周期。
SCL 的频率根据以下的公式产生:
fSCL=fCPU/((16+2(TWBR)(4^TWPS))
TWBR = TWI 比特率寄存器的数值
TWPS = TWI 状态寄存器预分频的数值
Note:TWI 工作在主机模式时,TWBR 值应该不小于 10,否则主机会在 SDA 与 SCL 产生错
误输出作为提示信号。
问题出现于 TWI 工作在主机模式下,向从机发送 Start + SLA + R/W 的时候(不需要真
的有从机与总线连接)。
控制单元
控制单元监听 TWI 总线,并根据 TWI 控制寄存器 TWCR 的设置作出相应的响应。
当 TWI 总线上产生需要应用程序干预处理的事件时,TWI 中断标志位 TWINT 置位。
在下一个时钟周期, TWI 状态寄存器 TWSR 被表示这个事件的状态码字所更新。
在其它时间里,TWSR 的内容为一个表示无事件发生的特殊状态字。
一旦 TWINT 标志位置"1”,时钟线 SCL 即被拉低,暂停 TWI 总线上的数据传输,让
用户程序处理事件。
在下列状况出现时, TWINT 标志位置位:
? 在 TWI 传送完 START/REPEATED START 信号之后
? 在 TWI 传送完 SLA+R/W 数据之后
? 在 TWI 传送完地址字节之后
? 在 TWI 总线仲裁失败之后
? 在 TWI 被主机寻址之后( 广播方式或从机地址匹配)
? 在 TWI 接收到一个数据字节之后
? 作为从机工作时, TWI 接收到 STOP 或 REPEATED START 信号之后
? 由于非法的 START 或 STOP 信号造成总线错误时
TWI 寄存器说明
TWI 比特率寄存器- TWBR
? Bits 7..0 – TWI 比特率寄存器
TWBR 为比特率发生器分频因子。
比特率发生器是一个分频器,在主机模式下产生 SCL 时钟频率。
比特率计算公式请见前面的[比特率发生器单元]
TWI 控制寄存器- TWCR
TWCR 用来控制 TWI 操作。
它用来使能 TWI,通过施加 START 到总线上来启动主机访问,产生接收器应答,产生
STOP 状态,以及在写入数据到 TWDR 寄存器时控制总线的暂停等。
这个寄存器还可以给出在 TWDR 无法访问期间,试图将数据写入到 TWDR 而引起的
写入冲突信息。
? Bit 7 – TWINT: TWI 中断标志
当 TWI 完成当前工作,希望应用程序介入时 TWINT 置位。
89
AVR 单片机学习开发板教程
dushibiao
2007 年 11 月
若 SREG 的 I 标志以及 TWCR 寄存器的 TWIE 标志也置位,则 MCU 执行 TWI 中
断例程。
当 TWINT 置位时, SCL 信号的低电平被延长。
TWINT 标志的清零必须通过软件写"1” 来完成。
执行中断时硬件不会自动将其改写为"0”。
要注意的是,只要这一位被清零,TWI 立即开始工作。
因此,在清零 TWINT 之前一定要首先完成对地址寄存器 TWAR,状态寄存器
TWSR,以及数据寄存器 TWDR 的访问。
? Bit 6 – TWEA: 使能 TWI 应答
TWEA 标志控制应答脉冲的产生。
若 TWEA 置位,出现如下条件时接口发出 ACK 脉冲:
1. 器件的从机地址与主机发出的地址相符合
2. TWAR 的 TWGCE 置位时接收到广播呼叫
3. 在主机/ 从机接收模式下接收到一个字节的数据
将 TWEA 清零可以使器件暂时脱离总线。
置位后器件重新恢复地址识别。
? Bit 5 – TWSTA: TWI START 状态标志
当 CPU 希望自己成为总线上的主机时需要置位 TWSTA。
TWI 硬件检测总线是否可用。
若总线空闲,接口就在总线上产生 START 状态。
若总线忙,接口就一直等待,直到检测到一个 STOP 状态 ,然后产生 START 以
声明自己希望成为主机。
发送 START 之后软件必须清零 TWSTA。
? Bit 4 – TWSTO: TWI STOP 状态标志
在主机模式下,如果置位 TWSTO,TWI 接口将在总线上产生 STOP 状态,然后
TWSTO 自动清零。
在从机模式下,置位 TWSTO 可以使接口从错误状态恢复到未被寻址的状态。
此时总线上不会有 STOP 状态产生,但 TWI 返回一个定义好的未被寻址的从机模
式且释放 SCL 与 SDA 为高阻态。
? Bit 3 – TWWC: TWI 写碰撞标志
当 TWINT 为低时写数据寄存器 TWDR 将置位 TWWC。
当 TWINT 为高时,每一次对 TWDR 的写访问都将更新此标志。
? Bit 2 – TWEN: TWI 使能
TWEN 位用于使能 TWI 操作与激活 TWI 接口。
当 TWEN 位被写为"1”时,TWI 引脚将 I/O 引脚切换到 SCL 与 SDA 引脚,使能
波形斜率限制器与尖峰滤波器。
如果该位清零, TWI 接口模块将被关闭,所有 TWI 传输将被终止。
? Bit 0 – TWIE: 使能 TWI 中断
当 SREG 的 I 以及 TWIE 置位时,只要 TWINT 为"1”, TWI 中断就激活。
TWI 状态寄存器- TWSR
? Bits 7..3 – TWS: TWI 状态
这 5 位用来反映 TWI 逻辑和总线的状态。
不同的状态代码将会在后面的部分描述。
90
AVR 单片机学习开发板教程
dushibiao
2007 年 11 月
注意从 TWSR 读出的值包括 5 位状态值与 2 位预分频值。
检测状态位时设计者应屏蔽预分频位为"0”。这使状态检测独立于预分频器设置。
? Bits 1..0 – TWPS: TWI 预分频位
这两位可读/ 写,用于控制比特率预分频因子。
预分频系数为 4 的 n 次方
计算比特率的公式见前面的[比特率发生器单元]
TWI 数据寄存器- TWDR
在发送模式, TWDR 包含了要发送的字节;
在接收模式, TWDR 包含了接收到的数据。
当 TWI 接口没有进行移位工作(TWINT 置位) 时这个寄存器是可写的。
在第一次中断发生之前用户不能够初始化数据寄存器。
只要 TWINT 置位,TWDR 的数据就是稳定的。
在数据移出时,总线上的数据同时移入寄存器。
TWDR 总是包含了总线上出现的最后一个字节,除非 MCU 是从掉电或省电模式被
TWI 中断唤醒。此时 TWDR 的内容没有定义。
总线仲裁失败时,主机将切换为从机,但总线上出现的数据不会丢失。
ACK 的处理由 TWI 逻辑自动管理, CPU 不能直接访问 ACK。
? Bits 7..0 – TWD: TWI 数据寄存器
根据状态的不同,其内容为要发送的下一个字节,或是接收到的数据。
TWI(从机) 地址寄存器-TWAR
TWAR 的高 7 位为从机地址。
工作于从机模式时,TWI 将根据这个地址进行响应。
主机模式不需要此地址。
在多主机系统中, TWAR 需要进行设置以便其他主机访问自己。
TWAR 的 LSB 用于识别广播地址 (0x00)。
器件内有一个地址比较器。一旦接收到的地址和本机地址一致,芯片就请求中断。
? Bits 7..1 – TWA: TWI 从机地址寄存器
其值为从机地址。
? Bit 0 – TWGCE: 使能 TWI 广播识别
置位后 MCU 可以识别 TWI 总线广播。
使用 TWI
AVR 的 TWI 接口是面向字节和基于中断的。
所有的总线事件,如接收到一个字节或发送了一个 START 信号等,都会产生一个 TWI
中断。
由于 TWI 接口是基于中断的,因此 TWI 接口在字节发送和接收过程中,不需要应用程
序的干预。
TWCR 寄存器的 TWI 中断允许位[TWIE]和全局中断允许位[I]一起决定了应用程序是否
响应 TWINT 标志位产生的中断请求。
如果 TWIE 被清零,应用程序只能采用轮询 TWINT 标志位的方法来检测 TWI 总线状
态。
当 TWINT 标志位置"1” 时,表示 TWI 接口完成了当前的操作,等待应用程序的响应。
91
AVR 单片机学习开发板教程
dushibiao
2007 年 11 月
在这种情况下,TWI 状态寄存器 TWSR 包含了表明当前 TWI 总线状态的值。
应用程序可以读取 TWCR 的状态码,判别此时的状态是否正确,并通过设置 TWCR 与
TWDR 寄存器,决定在下一个 TWI 总线周期 TWI 接口应该如何工作。
各种模式下的状态码列表(TWSR 已屏蔽预分频位)
AT24C02/04/08 IIC 接口 EEPROM 的特点
(不同公司的 24 系列 EEPROM 特性有部分不同,请参考数据手册)
1 AT24C02/04/08 是一个 2K/4K/8K 位串行 CMOS E2PROM 内部含有 256/512/1024 个
8 位字节
2 AT24C02 有一个 8 字节页写缓冲器,AT24C04/08/16 有一个 16 字节页写缓冲器
3 通过器件地址输入端 A0,A1,A2 可以实现将最多
8 个 24C02 器件
4 个 24C04 器件
2 个 24C08 器件
同时连接到总线上
4 写操作
1 字节写
2 页写 AT24C02 是 8 字节/页 AT24C04/08 是 16 字节/页
注意:页写的地址只在当前页自动累加,页地址范围内循环。
启动写命令后需要 10ms(最大)的编程时间才能真正的把数据记录下来,编程期间
器件不响应任何命令
5 读操作
1 立即地址读 地址自动累加,即为上次读/写操作地址+1(本程序不支持该操作)
2 随机读
指定地址读一个字节
3 连续读
连续读操作可通过立即读或随机读操作启动,由主机发出 NAK 和
STOP 来停止读操作
读操作时地址计数器在 AT24C02/04/08 整个地址内增加,这样整个寄
存器区域在可在一个读操作内全部读出
循环读取,读到最后一个地址后,从第一个地址继续开始读
6 写保护功能,由 WP 引脚控制,WP=VCC 时,24C02 的高 1K 位,24C04 的高 2K
位,24C08 的全部 8K 位都变成只读,不能写入
*/
实验十
定时器 0 时钟实验
实验目的:学习 AVR 单片机的内部定时器 0 模块使用方法
实验准备:请把 AVR 单片机的内部定时器 0 模块一节了解一下
实验要求:请把 84 数码管与液晶选择跳线打到 84 一侧
AVR—GCC 版本程序如下
92
AVR 单片机学习开发板教程
dushibiao
2007 年 11 月
/*
Title:
AVR-GCC t0 ctc timer
Author: dushibiao
Date:
2007 10 21
Purpose: generate a timer use t0 ctc mode
Frequency: Ext 8M
Software: AVR-GCC to compile
Hardware: AVR mega16 BOARD
Connect: [email protected]
*/
#include <avr/io.h>
#include <avr/signal.h>
#include <avr/interrupt.h>
#include "shumaguan.h"
#define uint unsigned int
#define uchar unsigned char
volatile uint micsec;
volatile uchar second, minute;
//毫秒
void initial(void);
int main(void)
{
initial();
while(1)
{
unsigned int disvalue;
disvalue=(unsigned int)minute*100+second;
display(disvalue);
}
}
/*------------------------------------------------------------fuction: initialize ports and timer0 register
--------------------------------------------------------------*/
void initial(void)
{
PORTA=0XFF;
//A 口初始化
DDRA=0XFF;
//out
PORTB=0XFF;
//B 口初始化
93
AVR 单片机学习开发板教程
dushibiao
2007 年 11 月
DDRB=0XFF;
PORTC=0XFF;
//其它口上拉电阻有效
PORTD=0XFF;
sei();
OCR0=249; //定时 250us
ORC0+1
TIMSK|=(1<<OCIE0); //比较匹配中断允许
TCCR0|=(1<<WGM01)|(1<<CS01); //CTC 模式,8 分频
/*--------------------------------------------------------TCCR0
7:
FOC0--------Force Output Campare
6,3
WGM01:0-----Waveform Generator Mode
00----------Normal
01----------PWM,Phase Correct
10----------CTC
11----------Fast PWM
5,4
COM11:0-----Compare Match Output Mode
---------------------------------------Non PWM mode
---------------------------------------00----------Normal Port Operation, OC0 disconnected
01----------Toggle OC0 on compare match
10----------Clear OC0 on compare match
11----------Set OC0 on compare match
2--0 CS02:0------Clock Select
000---------None clock select
001---------clk(io)
010---------clk(io)/8
011---------clk(io)/64
100---------clk(io)/256
101---------clk(io)/1024
110---------T0 pin,falling edge
111---------T0 pin,rising edge
----------------------------------------------------------*/
}
/*-----------------------------------------------------------function: interrupt service routine,it increases micsec,minute
second , and the main loop routine display the current
time
-------------------------------------------------------------*/
SIGNAL(SIG_OUTPUT_COMPARE0)
{
micsec += 1;
94
AVR 单片机学习开发板教程
dushibiao
2007 年 11 月
if(micsec==4000)
{
micsec=0;
second +=1 ;
if(second==60)
{
second = 0;
minute += 1 ;
if(minute==60)
minute=0;
}
}
}
ICC--AVR 版本程序如下
/*
Title:
icc-avr timer0 ctc timer
Author: dushibiao
Date:
2007 10 18
Purpose: use timer0 ctc mode to generate a timer
Frequency: internal 8M
Software: icc-avr
Hardware: AVR mega16 BOARD
Connect: [email protected]
*/
//内部 8M 晶振,T0
CTC 方式
DUSHIBIAO 2007 7 7
#include<iom16v.h>
#include<macros.h>
#include "shumaguan.h"
#define uint unsigned int
#define uchar unsigned char
uint micsec;
uchar second, minute;
//毫秒
void initial(void);
void main()
{
initial();
95
AVR 单片机学习开发板教程
dushibiao
2007 年 11 月
while(1)
{
unsigned int disvalue;
disvalue=(unsigned int)minute*100+second;
display(disvalue);
}
}
/*------------------------------------------------------------fuction: initialize ports and timer0 register
--------------------------------------------------------------*/
void initial(void)
{
PORTA=0XFF;
//A 口初始化
DDRA=0XFF;
//out
PORTB=0XFF;
//B 口初始化
DDRB=0XFF;
PORTC=0XFF;
//其它口上拉电阻有效
PORTD=0XFF;
_SEI();
OCR0=249; //定时 250us
ORC0+1
TIMSK|=(1<<OCIE0); //比较匹配中断允许
TCCR0|=(1<<WGM01)|(1<<CS01); //CTC 模式,8 分频
/*--------------------------------------------------------TCCR0
7:
FOC0--------Force Output Campare
6,3
WGM01:0-----Waveform Generator Mode
00----------Normal
01----------PWM,Phase Correct
10----------CTC
11----------Fast PWM
5,4
COM11:0-----Compare Match Output Mode
---------------------------------------Non PWM mode
---------------------------------------00----------Normal Port Operation, OC0 disconnected
01----------Toggle OC0 on compare match
10----------Clear OC0 on compare match
11----------Set OC0 on compare match
2--0 CS02:0------Clock Select
000---------None clock select
001---------clk(io)
010---------clk(io)/8
011---------clk(io)/64
96
AVR 单片机学习开发板教程
dushibiao
2007 年 11 月
100---------clk(io)/256
101---------clk(io)/1024
110---------T0 pin,falling edge
111---------T0 pin,rising edge
----------------------------------------------------------*/
}
/*-----------------------------------------------------------function: interrupt service routine,it increases micsec,minute
second , and the main loop routine display the current
time
-------------------------------------------------------------*/
#pragma interrupt_handler T0INT: iv_TIMER0_COMP
void T0INT(void)
{
micsec += 1;
if(micsec==4000)
{
micsec=0;
second +=1 ;
if(second==60)
{
second = 0;
minute += 1 ;
if(minute==60)
minute=0;
}
}
}
实验十一
定时器 1 时钟实验
实验目的:学习 AVR 单片机的内部定时器 1 模块使用方法
实验准备:请把 AVR 单片机的内部定时器 1 模块一节了解一下
实验要求:请把 84 数码管与液晶选择跳线打到 84 一侧
AVR—GCC 版本程序如下
/*
Title:
Author:
AVR-GCC t1 ctc timer
dushibiao
97
AVR 单片机学习开发板教程
dushibiao
2007 年 11 月
Date:
2007 10 21
Purpose: generate a timer use t1 ctc mode
Frequency: internal 8M
Software: AVR-GCC to compile
Hardware: AVR mega16 BOARD
Connect: [email protected]
*/
//内部 8M 晶振 dushibiao
2007 8 29
#include <avr/io.h>
#include <avr/signal.h>
#include <avr/interrupt.h>
#include "shumaguan.h"
#define uchar unsigned char
#define unit usigned int;
volatile uchar micsec=0;
volatile uchar second=0, minute=0;
void portinitial(void);
void t1initial(void);
int main(void)
{
portinitial();
t1initial();
sei();
TCCR1B|=(1<<CS11);
//8 分频,并开始计数
while(1)
{
display((unsigned int)(minute*100)+second);
}
}
/*------------------------------------------------------------fuction: initialize ports
--------------------------------------------------------------*/
void portinitial(void)
{
PORTA=0XFF;
DDRA=0XFF;
//OUT
PORTB=0XFF;
DDRB=0XFF;
//OUT
PORTC=0XFF;
//PULL-UP
PORTD=0XFF;
//PULL-UP
}
98
AVR 单片机学习开发板教程
dushibiao
2007 年 11 月
/*------------------------------------------------------------fuction: initialize timer1 register
--------------------------------------------------------------*/
void t1initial(void)
{
TCCR1A=0X00;
/*-------------------------------------------------------TCCR1A
-------------------------------------------------------No-PWM
--------------------------------------------------------7,6
COM1A1:0-----------Compare Output Mode for Chanel A
5,4
COM1B1:0-----------Compare Output Mode for Chanel B
00-----------------Normal port opration
01-----------------Toggle OC1A/B on compare match
10-----------------Clear OC1A/B on compare match
11-----------------Set OC1A/B on compare match
3
2
1,0
FOC1A--------------Force output compare for channel A
FOC1B--------------Force output compare for channel B
WGM11:0------------Waveform Generation Mode
*********************************************************
WGM13:0
0100--------------- ctc,top--OCR1A
---------------------------------------------------------*/
TCCR1B=1<<WGM12;
//CTC 模式,计数上限 OCR1A
/*---------------------------------------------------------7
ICNC1
6
ICES1
5
Reserved bit
4,3
WGM13:2-----------Waveform Generation Mode
2--0
CS12:0------------Clock Select
000---------------No clock select
001---------------clk(io)
010---------clk(io)/8
011---------clk(io)/64
100---------clk(io)/256
101---------clk(io)/1024
110---------T0 pin,falling edge
111---------T0 pin,rising edge
99
AVR 单片机学习开发板教程
dushibiao
2007 年 11 月
-----------------------------------------------------------*/
OCR1A=4999;
TIMSK=1<<OCIE1A;
//Output Compare A Match Interrupt Enable
}
/*-----------------------------------------------------------function: interrupt service routine,it increases micsec,minute
second , and the main loop routine display the current
time
-------------------------------------------------------------*/
SIGNAL(SIG_OUTPUT_COMPARE1A)
{
micsec++;
if(micsec>=200)
{
micsec=0;
second++;
if(second>=60)
{
second=0;
minute++;
if(minute>=60)
{
minute=0;
}
}
}
}
ICC--AVR 版本程序如下
/*
Title:
icc-avr timer1 ctc timer
Author: dushibiao
Date:
2007 10 21
Purpose: use timer1 ctc mode to generate a timer
Frequency: internal 8M
Software: icc-avr
Hardware: AVR mega16 BOARD
Connect: [email protected]
*/
//内部 8M 晶振 dushibiao
2007 8 29
100
AVR 单片机学习开发板教程
dushibiao
2007 年 11 月
#include <iom16v.h>
#include <macros.h>
#include "shumaguan.h"
#define uchar unsigned char
#define unit usigned int;
volatile uchar micsec=0;
volatile uchar second=0, minute=0;
void portinitial(void);
void t1initial(void);
void main(void)
{
portinitial();
t1initial();
SEI();
TCCR1B|=BIT(CS11);
//8 分频,并开始计数
while(1)
{
display((unsigned int)(minute*10)+second);
}
}
/*------------------------------------------------------------fuction: initialize ports
--------------------------------------------------------------*/
void portinitial(void)
{
PORTA=0XFF;
DDRA=0XFF;
//OUT
PORTB=0XFF;
DDRB=0XFF;
//OUT
PORTC=0XFF;
//PULL-UP
PORTD=0XFF;
//PULL-UP
}
/*------------------------------------------------------------fuction: initialize timer1 register
--------------------------------------------------------------*/
void t1initial(void)
{
TCCR1A=0X00;
/*-------------------------------------------------------TCCR1A
101
AVR 单片机学习开发板教程
dushibiao
2007 年 11 月
-------------------------------------------------------No-PWM
--------------------------------------------------------7,6
COM1A1:0-----------Compare Output Mode for Chanel A
5,4
COM1B1:0-----------Compare Output Mode for Chanel B
00-----------------Normal port opration
01-----------------Toggle OC1A/B on compare match
10-----------------Clear OC1A/B on compare match
11-----------------Set OC1A/B on compare match
3
2
1,0
FOC1A--------------Force output compare for channel A
FOC1B--------------Force output compare for channel B
WGM11:0------------Waveform Generation Mode
*********************************************************
WGM13:0
0100--------------- ctc,top--OCR1A
---------------------------------------------------------*/
TCCR1B=BIT(WGM12);
//CTC 模式,计数上限 OCR1A
/*---------------------------------------------------------7
ICNC1
6
ICES1
5
Reserved bit
4,3
WGM13:2-----------Waveform Generation Mode
2--0
CS12:0------------Clock Select
000---------------No clock select
001---------------clk(io)
010---------clk(io)/8
011---------clk(io)/64
100---------clk(io)/256
101---------clk(io)/1024
110---------T0 pin,falling edge
111---------T0 pin,rising edge
-----------------------------------------------------------*/
OCR1A=4999;
TIMSK=BIT(OCIE1A);
//Output Compare A Match Interrupt Enable
}
/*-----------------------------------------------------------function: interrupt service routine,it increases micsec,minute
second , and the main loop routine display the current
time
102
AVR 单片机学习开发板教程
dushibiao
2007 年 11 月
-------------------------------------------------------------*/
#pragma interrupt_handler
t1ctc:iv_TIMER1_COMPA
void t1ctc(void)
{
micsec++;
if(micsec>=200)
{
micsec=0;
second++;
if(second>=60)
{
second=0;
minute++;
if(minute>=60)
{
minute=0;
}
}
}
}
实验十二
定时器 2 外部时钟实验
实验目的:学习 AVR 单片机的内部定时器 2 模块使用方法
实验准备:请把 AVR 单片机的内部定时器 2 模块一节了解一下
实验要求:请把 84 数码管与液晶选择跳线打到 84 一侧
AVR—GCC 版本程序如下
/*
Title:
win-avr t2 clock
Author: dushibiao
Date:
2007 10 22
Purpose: use external 32khz Watch Crystal
Frequency: Ext 8M
Software: win-avr to compile
Hardware: AVR mega16 BOARD
Connect: [email protected]
*/
#include <avr/io.h>
#include <avr/signal.h>
#include <avr/interrupt.h>
103
AVR 单片机学习开发板教程
dushibiao
2007 年 11 月
#include "shumaguan.h"
#define uchar unsigned char
#define unit usigned int
#define EXT_INT0
#define EXT_INT1
2
3
//PD2
//PD3
按键 0
按键 1
volatile unsigned char second=0, minute=0;
/*------------------------------------------------------------fuction: initialize ports
--------------------------------------------------------------*/
void portinitial(void)
{
PORTA=0XFF;
DDRA=0XFF;
//OUT
PORTB=0XFF;
DDRB=0XFF;
//OUT
PORTC=0XFF;
//PULL-UP
PORTD=0XFF;
//PULL-UP
}
/*----------------------------------------------------------------fuction initialize t2
------------------------------------------------------------------*/
void t2init(void)
{
ASSR=(1<<AS2);
//异步方式
TCCR2=0X00;
//普通方式,未启动定时器
/*---------------------------------------------------------------TCCR2
7
FOC2
6,3
WGM21:0------------Waveform Generator Mode
00-----------------Normal
01-----------------PWM,Phase Correct
10-----------------CTC
TOP---OCR2
11-----------------Fast PWM
5,4
COM21:0------------Compare Match Outut Mode
None PWM
00-----------------Normal port operation
01-----------------Toggle OC2 on compare match
10-----------------Clear OC2 on compare match
11-----------------Set OC2 on compare match
2:0
CS22:0-------------Clock Select
000----------------No clock source
001----------------clk(t2s)
104
AVR 单片机学习开发板教程
dushibiao
2007 年 11 月
010----------------clk(t2s)/8
011----------------clk(t2s)/32
100----------------clk(t2s)/64
101----------------clk(t2s)/128
110----------------clk(t2s)/256
111----------------clk(t2s)/1024
-----------------------------------------------------------------*/
TIMSK=(1<<TOIE2);
//定时器 2 溢出中断
}
int main(void)
{
portinitial();
//外部中断 INT0,1 做按键输入,使能内部上拉,就可以不用外接电阻了
MCUCR=(1<<ISC11)|(0<<ISC10)|(1<<ISC01)|(0<<ISC00);
// 注意该寄 存器有 多个 功
能
/*
ISCx1:0=00 INTx 引脚为低电平时产生中断请求
ISCx1:0=01 INTx 引脚上任意的逻辑电平变化都将引发中断
ISCx1:0=10 INTx 引脚的下降沿产生中断请求
ISCx1:0=11 INTx 引脚的上升沿产生中断请求
*/
GIFR=(1<<INTF1)|(1<<INTF0);//写 1 清除标志位,在使能中断前最好先把对应的标志
位清除,以免误触发
GICR=(1<<INT1)|(1<<INT0); //使能两个外部中断
t2init();
TCCR2=(1<<CS22)|(1<<CS20);
//128 分频并开始计时
sei();
while(1)
{
display((unsigned int)(minute*100)+second);
//显示占空比
}
}
/*----------------------------------------------------------------------------fuction
ext0 interrupt service routine, it increses minute by 1
------------------------------------------------------------------------------*/
SIGNAL(SIG_INTERRUPT0) //INT0 中断服务程序
{
//硬件自动清除 INTF0 标志位
_delay_ms(10);
//延时
if ((PIND&(1<<EXT_INT0))==0)
//重复检测,防抖动
105
AVR 单片机学习开发板教程
dushibiao
2007 年 11 月
{
while(!(PIND&(1<<EXT_INT0)));
//等待按键释放(变为高电平)
_delay_ms(10);
//延时 按键释放时也会抖动。
// 即使同时发生其它的中断事件,如果在这里把相应的中断标志位清除,那么该中断
将不能触发进入中断服务
/*
注意
读端口用
写端口用
PINx
PORTx
*/
if(minute>=59)
{
minute=0;
}
else
{
minute++;
}
}
}
/*----------------------------------------------------------------------------fuction
ext1 interrupt service routine, it increses second by 1
------------------------------------------------------------------------------*/
INTERRUPT(SIG_INTERRUPT1) //INT1 中断服务程序
{
//硬件自动清除 INTF1 标志位
//这里全局中断被打开,将允许其他中断嵌套执行
_delay_ms(10);
if ((PIND&(1<<EXT_INT1))==0)
{
while(!(PIND&(1<<EXT_INT1)));
_delay_ms(10);
if(second>=59)
{
second=0;
}
else
{
second++;
}
106
AVR 单片机学习开发板教程
dushibiao
2007 年 11 月
}
}
/*-------------------------------------------------------------fuction:
T2 overflow interrupt service fuction ----1s
----------------------------------------------------------------*/
INTERRUPT(SIG_OVERFLOW2) //t2 overflow 中断服务程序
{
second++;
if(second>=60)
{
second=0;
minute++;
if(minute>=60)
{
minute=0;
}
}
}
ICC--AVR 版本程序如下
/*
Title:
icc-avr t2 clock
Author: dushibiao
Date:
2007 10 21
Purpose: use external 32khz Watch Crystal
Frequency: internal 8M
Software: icc-avr to compile
Hardware: AVR mega16 BOARD
Connect: [email protected]
*/
#include <iom16v.h>
#include <macros.h>
#include "shumaguan.h"
#define EXT_INT0
2 //PD2 按键 0
#define EXT_INT1
3 //PD3 按键 1
volatile unsigned char second=0, minute=0;
/*------------------------------------------------------------fuction: initialize ports
--------------------------------------------------------------*/
void portinitial(void)
{
PORTA=0XFF;
107
AVR 单片机学习开发板教程
DDRA=0XFF;
PORTB=0XFF;
DDRB=0XFF;
PORTC=0XFF;
PORTD=0XFF;
dushibiao
2007 年 11 月
//OUT
//OUT
//PULL-UP
//PULL-UP
}
/*----------------------------------------------------------------fuction initialize t2
------------------------------------------------------------------*/
void t2init(void)
{
ASSR=BIT(AS2);
//异步方式
TCCR2=0X00;
//普通方式,未启动定时器
/*---------------------------------------------------------------TCCR2
7
FOC2
6,3
WGM21:0------------Waveform Generator Mode
00-----------------Normal
01-----------------PWM,Phase Correct
10-----------------CTC
TOP---OCR2
11-----------------Fast PWM
5,4
COM21:0------------Compare Match Outut Mode
None PWM
00-----------------Normal port operation
01-----------------Toggle OC2 on compare match
10-----------------Clear OC2 on compare match
11-----------------Set OC2 on compare match
2:0
CS22:0-------------Clock Select
000----------------No clock source
001----------------clk(t2s)
010----------------clk(t2s)/8
011----------------clk(t2s)/32
100----------------clk(t2s)/64
101----------------clk(t2s)/128
110----------------clk(t2s)/256
111----------------clk(t2s)/1024
-----------------------------------------------------------------*/
TIMSK=BIT(TOIE2);
//定时器 2 溢出中断
}
void main(void)
{
portinitial();
//外部中断 INT0,1 做按键输入,使能内部上拉,就可以不用外接电阻了
MCUCR=(1<<ISC11)|(0<<ISC10)|(1<<ISC01)|(0<<ISC00);
// 注意该寄 存器有 多个 功
108
AVR 单片机学习开发板教程
dushibiao
2007 年 11 月
能
/*
ISCx1:0=00 INTx 引脚为低电平时产生中断请求
ISCx1:0=01 INTx 引脚上任意的逻辑电平变化都将引发中断
ISCx1:0=10 INTx 引脚的下降沿产生中断请求
ISCx1:0=11 INTx 引脚的上升沿产生中断请求
*/
GIFR=(1<<INTF1)|(1<<INTF0);//写 1 清除标志位,在使能中断前最好先把对应的标志
位清除,以免误触发
GICR=(1<<INT1)|(1<<INT0); //使能两个外部中断
t2init();
TCCR2=BIT(CS22)|BIT(CS20);
//128 分频并开始计时
SEI();
while(1)
{
display((unsigned int)(minute*100)+second);
//显示占空比
}
}
/*----------------------------------------------------------------------------fuction
ext0 interrupt service routine, it increses minute by 1
------------------------------------------------------------------------------*/
#pragma interrupt_handler int0pro: iv_INT0
void int0pro(void)
{
//硬件自动清除 INTF0 标志位
delay_nms(10);
//延时
if ((PIND&(1<<EXT_INT0))==0)
//重复检测,防抖动
{
while(!(PIND&(1<<EXT_INT0)));
//等待按键释放(变为高电平)
delay_nms(10);
//延时 按键释放时也会抖动。
// 即使同时发生其它的中断事件,如果在这里把相应的中断标志位清除,那么该中断
将不能触发进入中断服务
/*
注意
读端口用
写端口用
PINx
PORTx
*/
if(minute>=59)
109
AVR 单片机学习开发板教程
dushibiao
2007 年 11 月
{
minute=0;
}
else
{
minute++;
}
}
}
/*----------------------------------------------------------------------------fuction
ext1 interrupt service routine, it increses second by 1
------------------------------------------------------------------------------*/
#pragma interrupt_handler int1pro: iv_INT1
void int1pro(void)
{
//硬件自动清除 INTF1 标志位
//这里全局中断被打开,将允许其他中断嵌套执行
delay_nms(10);
if ((PIND&(1<<EXT_INT1))==0)
{
while(!(PIND&(1<<EXT_INT1)));
delay_nms(10);
if(second>=59)
{
second=0;
}
else
{
second++;
}
}
}
/*-------------------------------------------------------------fuction:
T2 overflow interrupt service fuction ----1s
----------------------------------------------------------------*/
#pragma interrupt_handler t2pro:iv_TIMER2_OVF
void t2pro(void)
{
second++;
if(second>=60)
{
second=0;
110
AVR 单片机学习开发板教程
dushibiao
2007 年 11 月
minute++;
if(minute>=60)
{
minute=0;
}
}
}
实验十三
简易 DA 实验
实验目的:学习 AVR 单片机用 PWM 方式构成简易 DA
实验准备:请把 AVR 单片机的内部定时器 1 模块一节了解一下,了解一下 RC 型 DA 转换的
原理
实验要求:请把 84 数码管与液晶选择跳线打到 84 一侧
实验现象:用万用测量 DA 口输出电压,按下 K1 可以看到万用表读数增高,按下 K2 可以
看到万用表读数减小,数码管显示的是 PWM 波形的占空比。
AVR—GCC 版本程序如下
/*
Title:
win-avr easy da convertter
Author: dushibiao
Date:
2007 10 21
Purpose: do a easy da converter
Frequency: Ext 8M
Software: win-avr to compile
Hardware: AVR mega16 BOARD
Connect: [email protected]
*/
#include <avr/io.h>
#include <avr/signal.h>
#include <avr/interrupt.h>
#include "shumaguan.h"
#define uchar unsigned char
#define unit usigned int
#define EXT_INT0
2
#define EXT_INT1
3
#define START_TIMER
#define DA_OUT
//PD2 按键 0
//PD3 按键 1
TCCR1B|=(1<<CS10)
5
//PD5
//start timer1
/*------------------------------------------------------------fuction: initialize ports
--------------------------------------------------------------*/
void portinitial(void)
111
AVR 单片机学习开发板教程
dushibiao
2007 年 11 月
{
PORTA=0XFF;
DDRA=0XFF;
PORTB=0XFF;
DDRB=0XFF;
PORTC=0XFF;
PORTD=0XFF;
DDRD=1<<DA_OUT;
//OUT
//OUT
//PULL-UP
//PULL-UP
//PD5--OUT
}
/*------------------------------------------------------------fuction: initialize timer1 register
Fast pwm top--ICR1
--------------------------------------------------------------*/
void t1initial(void)
{
TCCR1A =(1<<WGM11)|(1<<COM1A1);;
/*-------------------------------------------------------TCCR1A
-------------------------------------------------------Fast-PWM
--------------------------------------------------------7,6
COM1A1:0-----------Compare Output Mode for Chanel A
5,4
COM1B1:0-----------Compare Output Mode for Chanel B
00-----------------Normal port opration
01-----------------Toggle OC1A/B on compare match
10-----------------Clear OC1A/B on compare match,set OC1A/B at TOP
11-----------------Set OC1A/B on compare match,clear OC1A/B at TOP
3
2
1,0
FOC1A--------------Force output compare for channel A
FOC1B--------------Force output compare for channel B
WGM11:0------------Waveform Generation Mode
*********************************************************
WGM13:0
1111--------------- Fast pwm,top--OCR1A
---------------------------------------------------------*/
TCCR1B =(1<<WGM13)|(1<<WGM12) ;
/*---------------------------------------------------------7
ICNC1
6
ICES1
5
Reserved bit
112
AVR 单片机学习开发板教程
dushibiao
2007 年 11 月
4,3
2--0
WGM13:2-----------Waveform Generation Mode
CS12:0------------Clock Select
000---------------No clock select
001---------------clk(io)
010---------clk(io)/8
011---------clk(io)/64
100---------clk(io)/256
101---------clk(io)/1024
110---------T0 pin,falling edge
111---------T0 pin,rising edge
-----------------------------------------------------------*/
ICR1=5000;
OCR1A=1000;
}
int main(void)
{
portinitial();
//外部中断 INT0,1 做按键输入,使能内部上拉,就可以不用外接电阻了
MCUCR=(1<<ISC11)|(0<<ISC10)|(1<<ISC01)|(0<<ISC00);
//注意该寄 存器有 多个 功
能
/*
ISCx1:0=00 INTx 引脚为低电平时产生中断请求
ISCx1:0=01 INTx 引脚上任意的逻辑电平变化都将引发中断
ISCx1:0=10 INTx 引脚的下降沿产生中断请求
ISCx1:0=11 INTx 引脚的上升沿产生中断请求
*/
GIFR=(1<<INTF1)|(1<<INTF0);//写 1 清除标志位,在使能中断前最好先把对应的标志位
清除,以免误触发
GICR=(1<<INT1)|(1<<INT0);
//使能三个外部中断
t1initial();
START_TIMER;
sei();
while(1)
{
unsigned int temp;
temp=OCR1A;
display(temp/50);
}
//显示占空比
}
113
AVR 单片机学习开发板教程
dushibiao
2007 年 11 月
/*----------------------------------------------------------------------------fuction
ext0 interrupt service routine, it increses duration
------------------------------------------------------------------------------*/
SIGNAL(SIG_INTERRUPT0) //INT0 中断服务程序
{
//硬件自动清除 INTF0 标志位
_delay_ms(10);
//延时
if ((PIND&(1<<EXT_INT0))==0)
//重复检测,防抖动
{
while(!(PIND&(1<<EXT_INT0)));
//等待按键释放(变为高电平)
_delay_ms(10);
//延时 按键释放时也会抖动。
// 即使同时发生其它的中断事件,如果在这里把相应的中断标志位清除,那么该中断
将不能触发进入中断服务
/*
注意
读端口用
写端口用
PINx
PORTx
*/
if(OCR1A>=4950)
{
OCR1A=10;
}
else
{
OCR1A+=30;
}
}
}
/*----------------------------------------------------------------------------fuction
ext1 interrupt service routine, it decreses duration
------------------------------------------------------------------------------*/
INTERRUPT(SIG_INTERRUPT1) //INT1 中断服务程序
{
//硬件自动清除 INTF1 标志位
//这里全局中断被打开,将允许其他中断嵌套执行
_delay_ms(10);
if ((PIND&(1<<EXT_INT1))==0)
{
while(!(PIND&(1<<EXT_INT1)));
_delay_ms(10);
114
AVR 单片机学习开发板教程
dushibiao
2007 年 11 月
if(OCR1A<=50)
{
OCR1A=4950;
}
else
{
OCR1A-=30;
}
}
}
ICC--AVR 版本程序如下
/*
Title:
icc-avr easy da converter
Author: dushibiao
Date:
2007 10 21
Purpose: do a easy da convertter
Frequency: internal 8M
Software: icc-avr to compile
Hardware: AVR mega16 BOARD
Connect: [email protected]
*/
/*
外部 8M 晶振,OC1A 输出
dushibiao
2007
快速 PWM,计数上限 OCR1A,比较输出 OCR1B
*/
5 13
#include <iom16v.h>
#include <macros.h>
#include "shumaguan.h"
#define EXT_INT0
2
#define EXT_INT1
3
#define START_TIMER
#define DA_OUT
//PD2 按键 0
//PD3 按键 1
TCCR1B|=(1<<CS10)
5
//PD5
//start timer1
/*------------------------------------------------------------fuction: initialize ports
--------------------------------------------------------------*/
void portinitial(void)
{
PORTA=0XFF;
115
AVR 单片机学习开发板教程
DDRA=0XFF;
PORTB=0XFF;
DDRB=0XFF;
PORTC=0XFF;
PORTD=0XFF;
DDRD=1<<DA_OUT;
dushibiao
2007 年 11 月
//OUT
//OUT
//PULL-UP
//PULL-UP
//PD5--OUT
}
/*------------------------------------------------------------fuction: initialize timer1 register
Fast pwm top--ICR1
--------------------------------------------------------------*/
void t1initial(void)
{
TCCR1A =(1<<WGM11)|(1<<COM1A1);;
/*-------------------------------------------------------TCCR1A
-------------------------------------------------------Fast-PWM
--------------------------------------------------------7,6
COM1A1:0-----------Compare Output Mode for Chanel A
5,4
COM1B1:0-----------Compare Output Mode for Chanel B
00-----------------Normal port opration
01-----------------Toggle OC1A/B on compare match
10-----------------Clear OC1A/B on compare match,set OC1A/B at TOP
11-----------------Set OC1A/B on compare match,clear OC1A/B at TOP
3
2
1,0
FOC1A--------------Force output compare for channel A
FOC1B--------------Force output compare for channel B
WGM11:0------------Waveform Generation Mode
*********************************************************
WGM13:0
1111--------------- Fast pwm,top--OCR1A
---------------------------------------------------------*/
TCCR1B =(1<<WGM13)|(1<<WGM12) ;
/*---------------------------------------------------------7
ICNC1
6
ICES1
5
Reserved bit
4,3
WGM13:2-----------Waveform Generation Mode
2--0
CS12:0------------Clock Select
116
AVR 单片机学习开发板教程
//
}
dushibiao
2007 年 11 月
000---------------No clock select
001---------------clk(io)
010---------clk(io)/8
011---------clk(io)/64
100---------clk(io)/256
101---------clk(io)/1024
110---------T0 pin,falling edge
111---------T0 pin,rising edge
-----------------------------------------------------------*/
ICR1=5000;
OCR1A=1000;
TIMSK=1<<OCIE1A;
//Output Compare A Match Interrupt Enable
void main(void)
{
portinitial();
//外部中断 INT0,1 做按键输入,使能内部上拉,就可以不用外接电阻了
MCUCR=(1<<ISC11)|(0<<ISC10)|(1<<ISC01)|(0<<ISC00);
// 注意该寄 存器有 多个 功
能
/*
ISCx1:0=00 INTx 引脚为低电平时产生中断请求
ISCx1:0=01 INTx 引脚上任意的逻辑电平变化都将引发中断
ISCx1:0=10 INTx 引脚的下降沿产生中断请求
ISCx1:0=11 INTx 引脚的上升沿产生中断请求
*/
GIFR=(1<<INTF1)|(1<<INTF0);//写 1 清除标志位,在使能中断前最好先把对应的标志位
清除,以免误触发
GICR=(1<<INT1)|(1<<INT0);
//使能三个外部中断
t1initial();
START_TIMER;
SEI();
while(1)
{
unsigned int temp;
temp=OCR1A;
display(temp/50);
}
//显示占空比
}
/*-----------------------------------------------------------------------------
117
AVR 单片机学习开发板教程
dushibiao
2007 年 11 月
fuction
ext0 interrupt service routine, it increses duration
------------------------------------------------------------------------------*/
#pragma interrupt_handler int0pro: iv_INT0
void int0pro(void)
{
//硬件自动清除 INTF0 标志位
delay_nms(10);
//延时
if ((PIND&(1<<EXT_INT0))==0)
//重复检测,防抖动
{
while(!(PIND&(1<<EXT_INT0)));
//等待按键释放(变为高电平)
delay_nms(10);
//延时 按键释放时也会抖动。
// 即使同时发生其它的中断事件,如果在这里把相应的中断标志位清除,那么该中断
将不能触发进入中断服务
/*
注意
读端口用
写端口用
PINx
PORTx
*/
if(OCR1A>=4950)
{
OCR1A=10;
}
else
{
OCR1A+=30;
}
}
}
/*----------------------------------------------------------------------------fuction
ext1 interrupt service routine, it decreses duration
------------------------------------------------------------------------------*/
#pragma interrupt_handler int1pro: iv_INT1
void int1pro(void)
{
//硬件自动清除 INTF1 标志位
//这里全局中断被打开,将允许其他中断嵌套执行
delay_nms(10);
if ((PIND&(1<<EXT_INT1))==0)
{
while(!(PIND&(1<<EXT_INT1)));
delay_nms(10);
118
AVR 单片机学习开发板教程
dushibiao
2007 年 11 月
if(OCR1A<=50)
{
OCR1A=4950;
}
else
{
OCR1A-=30;
}
}
}
实验十四
PWM 蜂鸣器实验
实验目的:学习 AVR 单片机用 PWM 方式驱动蜂鸣器
实验准备:请把 AVR 单片机的内部定时器 1 模块一节了解一下,特别是 PWM 方式
实验要求:请把 84 数码管与液晶选择跳线打到 84 一侧,把 Motorp 跳线打到 Motorp 一侧
实验现象:按 K1,K2 增大或减小输出频率,影响蜂鸣器的声音效果,数码管显示的是
PWM 的占空比
AVR—GCC 版本程序如下
/*
Title:
AVR-GCC speaker
Author: dushibiao
Date:
2007 10 21
Purpose:
pwm---speaker
Frequency: Ext 8M
Software: AVR-GCC to compile
Hardware: AVR mega16 BOARD
Connect: [email protected]
*/
#include <avr/io.h>
#include <avr/signal.h>
#include <avr/interrupt.h>
#include "shumaguan.h"
#define uchar unsigned char
#define unit usigned int
#define SPKER_PIN
#define EXT_INT0
#define EXT_INT1
2
3
4 //speak line
//PD2 按键 0
//PD3 按键 1
119
AVR 单片机学习开发板教程
volatile unsigned int
2007 年 11 月
dushibiao
ocr1a_value;
void portinitial(void);
void t1initial(void);
int main(void)
{
portinitial();
t1initial();
//外部中断 INT0,1 做按键输入,使能内部上拉,就可以不用外接电阻了
MCUCR=(1<<ISC11)|(0<<ISC10)|(1<<ISC01)|(0<<ISC00);
//注意该寄 存器有 多个 功
能
/*
ISCx1:0=00 INTx 引脚为低电平时产生中断请求
ISCx1:0=01 INTx 引脚上任意的逻辑电平变化都将引发中断
ISCx1:0=10 INTx 引脚的下降沿产生中断请求
ISCx1:0=11 INTx 引脚的上升沿产生中断请求
*/
GIFR=(1<<INTF1)|(1<<INTF0);//写 1 清除标志位,在使能中断前最好先把对应的标志位
清除,以免误触发
GICR=(1<<INT1)|(1<<INT0);
//使能三个外部中断
sei();
TCCR1B|=(1<<CS11)|(1<<CS10);
while(1)
{
unsigned int frequency;
frequency=62500/(ocr1a_value+1);
clk/(2*N*(OCR1A+1))
//64 分频,并开始计时
//the output frequency =
//
N=64,
OCR1A=ocr1a_value
display(frequency);
}
}
/*------------------------------------------------------------fuction: initialize ports
--------------------------------------------------------------*/
void portinitial(void)
{
PORTA=0XFF;
DDRA=0XFF;
PORTB=0XFF;
DDRB=0XFF;
//OUT
//OUT
120
AVR 单片机学习开发板教程
PORTC=0XFF;
PORTD=0XFF;
DDRD=1<<SPKER_PIN;
dushibiao
2007 年 11 月
//PULL-UP
//PULL-UP
//PD4--OUT
}
/*------------------------------------------------------------fuction: initialize timer1 register
--------------------------------------------------------------*/
void t1initial(void)
{
TCCR1A=(1<<COM1B0);
//Tollgle OC1B
/*-------------------------------------------------------TCCR1A
-------------------------------------------------------No-PWM
--------------------------------------------------------7,6
COM1A1:0-----------Compare Output Mode for Chanel A
5,4
COM1B1:0-----------Compare Output Mode for Chanel B
00-----------------Normal port opration
01-----------------Toggle OC1A/B on compare match
10-----------------Clear OC1A/B on compare match
11-----------------Set OC1A/B on compare match
3
2
1,0
FOC1A--------------Force output compare for channel A
FOC1B--------------Force output compare for channel B
WGM11:0------------Waveform Generation Mode
*********************************************************
WGM13:0
0100--------------- ctc,top--OCR1A
---------------------------------------------------------*/
TCCR1B=1<<WGM12;
//CTC 模式,计数上限 OCR1A
/*---------------------------------------------------------7
ICNC1
6
ICES1
5
Reserved bit
4,3
WGM13:2-----------Waveform Generation Mode
2--0
CS12:0------------Clock Select
000---------------No clock select
001---------------clk(io)
010---------clk(io)/8
011---------clk(io)/64
121
AVR 单片机学习开发板教程
//
}
dushibiao
2007 年 11 月
100---------clk(io)/256
101---------clk(io)/1024
110---------T0 pin,falling edge
111---------T0 pin,rising edge
-----------------------------------------------------------*/
OCR1A=100;
ocr1a_value=100;
TIMSK=1<<OCIE1A;
//Output Compare A Match Interrupt Enable
/*-----------------------------------------------------------function: interrupt service routine,it toggle pd4 to generate a pulse waveform
-------------------------------------------------------------*/
//SIGNAL(SIG_OUTPUT_COMPARE1A)
//{
// PORTD^=(1<<SPKER_PIN) ;
//Toggle pd4 to generate a pulse waveform
//}
/*----------------------------------------------------------------------------fuction
ext0 interrupt service routine, it decreses frequecy
------------------------------------------------------------------------------*/
SIGNAL(SIG_INTERRUPT0) //INT0 中断服务程序
{
//硬件自动清除 INTF0 标志位
_delay_ms(10);
//延时
if ((PIND&(1<<EXT_INT0))==0)
//重复检测,防抖动
{
while(!(PIND&(1<<EXT_INT0)));
//等待按键释放(变为高电平)
_delay_ms(10);
//延时 按键释放时也会抖动。
// 即使同时发生其它的中断事件,如果在这里把相应的中断标志位清除,那么该中断
将不能触发进入中断服务
/*
注意
读端口用
写端口用
PINx
PORTx
*/
if(ocr1a_value>2000)
{
ocr1a_value=6;
OCR1A=ocr1a_value;
}
122
AVR 单片机学习开发板教程
dushibiao
2007 年 11 月
else
{
ocr1a_value+=5;
OCR1A=ocr1a_value;
}
}
}
/*----------------------------------------------------------------------------fuction
ext1 interrupt service routine, it increses frequecy
------------------------------------------------------------------------------*/
INTERRUPT(SIG_INTERRUPT1) //INT1 中断服务程序
{
//硬件自动清除 INTF1 标志位
//这里全局中断被打开,将允许其他中断嵌套执行
_delay_ms(10);
if ((PIND&(1<<EXT_INT1))==0)
{
while(!(PIND&(1<<EXT_INT1)));
_delay_ms(10);
if(ocr1a_value<=6)
{
ocr1a_value=2000;
OCR1A=ocr1a_value;
}
else
{
ocr1a_value-=5;
OCR1A=ocr1a_value;
}
}
}
ICC--AVR 版本程序如下
/*
Title:
icc-avr speaker
Author: dushibiao
Date:
2007 10 21
Purpose: to generate a wide range of frequency to the speaker
Frequency: internal 8M
Software: icc-avr to compile
Hardware: AVR mega16 BOARD
123
AVR 单片机学习开发板教程
dushibiao
2007 年 11 月
Connect: [email protected]
*/
#include <iom16v.h>
#include <macros.h>
#include "shumaguan.h"
#define uchar unsigned char
#define unit usigned int;
#define SPKER_PIN
#define EXT_INT0
#define EXT_INT1
volatile unsigned int
2
3
4 //speak line
//PD2 按键 0
//PD3 按键 1
ocr1a_value;
void portinitial(void);
void t1initial(void);
int main(void)
{
portinitial();
t1initial();
//外部中断 INT0,1 做按键输入,使能内部上拉,就可以不用外接电阻了
MCUCR=(1<<ISC11)|(0<<ISC10)|(1<<ISC01)|(0<<ISC00);
//注意该寄 存器有 多个 功
能
/*
ISCx1:0=00 INTx 引脚为低电平时产生中断请求
ISCx1:0=01 INTx 引脚上任意的逻辑电平变化都将引发中断
ISCx1:0=10 INTx 引脚的下降沿产生中断请求
ISCx1:0=11 INTx 引脚的上升沿产生中断请求
*/
GIFR=(1<<INTF1)|(1<<INTF0);//写 1 清除标志位,在使能中断前最好先把对应的标志位
清除,以免误触发
GICR=(1<<INT1)|(1<<INT0);
//使能三个外部中断
SEI();
TCCR1B|=(1<<CS11)|(1<<CS10);
while(1)
{
unsigned int frequency;
frequency=62500/(ocr1a_value+1);
//64 分频,并开始计时
//the output frequency =
124
AVR 单片机学习开发板教程
2007 年 11 月
dushibiao
clk/(2*N*(OCR1A+1))
//
N=64,
OCR1A=ocr1a_value
display(frequency);
}
}
/*------------------------------------------------------------fuction: initialize ports
--------------------------------------------------------------*/
void portinitial(void)
{
PORTA=0XFF;
DDRA=0XFF;
//OUT
PORTB=0XFF;
DDRB=0XFF;
//OUT
PORTC=0XFF;
//PULL-UP
PORTD=0XFF;
//PULL-UP
DDRD=1<<SPKER_PIN;
//PD4--OUT
}
/*------------------------------------------------------------fuction: initialize timer1 register
--------------------------------------------------------------*/
void t1initial(void)
{
TCCR1A=0x00;
//Normal opreration
/*-------------------------------------------------------TCCR1A
-------------------------------------------------------No-PWM
--------------------------------------------------------7,6
COM1A1:0-----------Compare Output Mode for Chanel A
5,4
COM1B1:0-----------Compare Output Mode for Chanel B
00-----------------Normal port opration
01-----------------Toggle OC1A/B on compare match
10-----------------Clear OC1A/B on compare match
11-----------------Set OC1A/B on compare match
3
2
1,0
FOC1A--------------Force output compare for channel A
FOC1B--------------Force output compare for channel B
WGM11:0------------Waveform Generation Mode
125
AVR 单片机学习开发板教程
dushibiao
2007 年 11 月
*********************************************************
WGM13:0
0100--------------- ctc,top--OCR1A
---------------------------------------------------------*/
TCCR1B=1<<WGM12;
//CTC 模式,计数上限 OCR1A
/*---------------------------------------------------------7
ICNC1
6
ICES1
5
Reserved bit
4,3
WGM13:2-----------Waveform Generation Mode
2--0
CS12:0------------Clock Select
000---------------No clock select
001---------------clk(io)
010---------clk(io)/8
011---------clk(io)/64
100---------clk(io)/256
101---------clk(io)/1024
110---------T0 pin,falling edge
111---------T0 pin,rising edge
-----------------------------------------------------------*/
OCR1A=100;
ocr1a_value=100;
TIMSK=1<<OCIE1A;
//Output Compare A Match Interrupt Enable
}
/*-----------------------------------------------------------function: interrupt service routine,it toggle pd4 to generate a pulse waveform
-------------------------------------------------------------*/
#pragma interrupt_handler
t1ctc:iv_TIMER1_COMPA
void t1ctc(void)
{
PORTD^=(1<<SPKER_PIN) ;
//Toggle pd4 to generate a pulse waveform
}
/*----------------------------------------------------------------------------fuction
ext0 interrupt service routine, it decreses frequecy
------------------------------------------------------------------------------*/
#pragma interrupt_handler int0pro: iv_INT0
void int0pro(void)
{
//硬件自动清除 INTF0 标志位
delay_nms(10);
//延时
126
AVR 单片机学习开发板教程
dushibiao
if ((PIND&(1<<EXT_INT0))==0)
//重复检测,防抖动
2007 年 11 月
{
while(!(PIND&(1<<EXT_INT0)));
//等待按键释放(变为高电平)
delay_nms(10);
//延时 按键释放时也会抖动。
// 即使同时发生其它的中断事件,如果在这里把相应的中断标志位清除,那么该中断
将不能触发进入中断服务
/*
注意
读端口用
写端口用
PINx
PORTx
*/
if(ocr1a_value>2000)
{
ocr1a_value=6;
OCR1A=ocr1a_value;
}
else
{
ocr1a_value+=5;
OCR1A=ocr1a_value;
}
}
}
/*----------------------------------------------------------------------------fuction
ext1 interrupt service routine, it increses frequecy
------------------------------------------------------------------------------*/
#pragma interrupt_handler int1pro: iv_INT1
void int1pro(void)
{
//硬件自动清除 INTF1 标志位
//这里全局中断被打开,将允许其他中断嵌套执行
delay_nms(10);
if ((PIND&(1<<EXT_INT1))==0)
{
while(!(PIND&(1<<EXT_INT1)));
delay_nms(10);
if(ocr1a_value<=6)
{
ocr1a_value=2000;
OCR1A=ocr1a_value;
127
AVR 单片机学习开发板教程
dushibiao
2007 年 11 月
}
else
{
ocr1a_value-=5;
OCR1A=ocr1a_value;
}
}
}
实验十五
AD 转换实验
实验目的:学习 AVR 单片机 AD 转换
实验准备:请把 AVR 单片机的 AD 转换模块一节了解一下
实验要求:由于要用到 PA0 引脚,故在此利用串口进行调试,波特率为 9600
其它设置请参考前面实验。请把 LED 跳线打到右侧。请把 84 数码管与 LCD
选择跳线打到右侧。将 AD 转换实验跳线打到左侧。
实验现象:调切电位器,可以看到输出到终端的电压变化
AVR—GCC 版本程序如下
/*
Title:
win-avr ad converter
Author: dushibiao
Date:
2007 10 21
Purpose: use m16's internal 10 bits ad
Frequency: internal 8M
Software: win-avr to compile
Hardware: AVR mega16 BOARD
Connect: [email protected]
*/
/*--------------------------------------------------------------Notes: 在我们的 AVR mega16 BOARD 学习开发板中,POATA 被用控制 LED 和
数码,这占用 8 个 AD 通通,这里我们用通道 1 采集电压,并发送串口
在 Windows 的串口终端调试软件中可以显示出来
----------------------------------------------------------------*/
#include <avr/io.h>
#include <avr/delay.h>
#define PIN_RXD
0
//PD0
RXD
#define PIN_TXD
1 //PD1
TXD
#define BAUDRATE
9600 //baudrate
//#define F_CPU
8000000 //the frequency of the global clock
unsigned int advalue;
void init_USART(void);
128
AVR 单片机学习开发板教程
dushibiao
2007 年 11 月
void put_s(unsigned char *ptr);
void put_c(unsigned char c);
int main(void)
{
//上电默认 DDRx=0x00,PORTx=0x00 输入,无上拉电阻
PORTA=0XFF;
//上拉
PORTB =0xFF;
//不用的管脚使能内
部上拉电阻。
PORTC=0XFF;
DDRD =(1<<PIN_TXD);
//TXD 为输出
PORTD =0xFF;
init_USART();
while(1)
{
unsigned char highvalue,lowvalue;
ADMUX=(1<<REFS0);
//AVCC 基准电压,通道 0,10bits ad value
/*---------------------------------------------------------------ADMUX
7,6
REFS1:0--------Rrference Selection Bits
00-------------AREF,Internal Vref truned off
01-------------AVCC with external capacior at AREF pin
10-------------Reserved
11-------------internal 2.56V Voltage Reference with external
capacitior at AREF pin
5
ADELAR---------ADC Left Adjust Result
1--------------Left Adjust-----8 bits resolution
0--------------Right Adjust----10 bits resolution
4:0
MUX4:0---------Analog Channel and Gain Selection Bits
00000----------ADC0
00001----------ADC1
||||||||||||||||||||||||||||||||||||
00111----------ADC7
|||||||||||||||||||||||||||||||||||||
----------------------------------------------------------------*/
ADCSRA=(1<<ADEN)|(1<<ADSC)|(1<<ADPS2)|(1<<ADPS1)|(1<<ADPS0);
//AD 使能,单次转换,128 分频
/*-------------------------------------------------------------------ADCSRA
7
ADEN-----------ADC Enable
6
ADSC-----------ADC Start Comversion
5
ADATE----------ADC Auto Trigger Enable
4
ADIF-----------ADC Interrupt Flag
3
ADIE-----------ADC Interrupt Enable
2:0
ADPS2:0--------ADC Prescaler Select Bits
129
AVR 单片机学习开发板教程
dushibiao
2007 年 11 月
000------------2
001------------2
010------------4
011------------8
100------------16
101------------32
110------------64
111------------128
---------------------------------------------------------------------*/
while(!(ADCSRA & (1 << ADIF))); /*等待*/
advalue=ADC;
ADCSRA&=(~(1<<ADEN))|(~(1<<ADSC));
advalue=advalue*4.89;
highvalue=(unsigned char)(advalue/100);
lowvalue=(unsigned char)(advalue%100);
put_s("AD 转换电压值为:");
put_c(highvalue/10+0x30);
put_c('.');
put_c(highvalue%10+0x30);
put_c(lowvalue/10+0x30);
put_c(lowvalue%10+0x30);
put_s("V\n");
unsigned char i;
for(i=0;i<255;i++)
_delay_ms(20);
}
}
/*--------------------------------------------------------------------------function
send an unsigned char to the uart
----------------------------------------------------------------------------*/
void put_c(unsigned char c) //发送采用查询方式
{
while( !(UCSRA & (1<<UDRE)) );
//wait until the uart is empty
UDR=c;
//write data to uart
}
/*---------------------------------------------------------------------------function
send a string to the uart and with return back
-----------------------------------------------------------------------------*/
void put_s(unsigned char *ptr)
{
while (*ptr)
{
put_c(*ptr++);
}
130
AVR 单片机学习开发板教程
put_c(0x0D);
put_c(0x0A);
dushibiao
2007 年 11 月
//结尾发送回车换行
}
/*--------------------------------------------------------------------------fuction
initialize the uart unit
----------------------------------------------------------------------------*/
void init_USART(void)//USART 初始化
{
//USART 9600 8, n,1 PC 上位机软件(超级终端等)也要设成同样的设置才能通讯
UCSRC = (1<<URSEL) | 0x06;
//异步,8 位数据,无奇偶校验,一个停止位,无倍速
/*
UBRRH 与 UCSRC 共用 I/O 地址。因此访问该地址时需注意以下问题。
写访问
当在该地址执行写访问时, USART 寄存器选择位(URSEL)控制被写入的寄存器。
若 URSEL 为 0,对 UBRRH 值更新;若 URSEL 为 1,对 UCSRC 设置更新
读访问
对 UBRRH 或 UCSRC 寄存器的读访问则较为复杂。但在大多数应用中,基本不需要
读这些寄存器
没有 UBRR 这个 16 位寄存器,因为 UBRRL(0x09)/UBRRH(0x20)的地址不连续,而且
UBRRH 跟 UCSRC 共用地址
*/
//U2X=0 时的公式计算
UBRRL= (F_CPU/BAUDRATE/16-1)%256;
UBRRH= (F_CPU/BAUDRATE/16-1)/256;
//U2X=1 时的公式计算
//UBRRL= (F_CPU/BAUDRATE/8-1)%256;
//UBRRH= (F_CPU/BAUDRATE/8-1)/256;
//也可根据数据手册的[波特率设置的例子]查得
//UBRRL = 0x2F; //set baud rate lo
//UBRRH = 0x00; //set baud rate hi
UCSRA = 0x00;
//无倍速
UCSRB = (1<<RXEN)|(1<<TXEN);
//使能接收,使能发送
}
ICC--AVR 版本程序如下
131
AVR 单片机学习开发板教程
dushibiao
2007 年 11 月
/*
Title:
icc-avr ad converter
Author: dushibiao
Date:
2007 10 21
Purpose: use m16's internal 10 bits ad
Frequency: internal 8M
Software: icc-avr to compile
Hardware: AVR mega16 BOARD
Connect: [email protected]
*/
/*--------------------------------------------------------------Notes: 在我们的 AVR mega16 BOARD 学习开发板中,POATA 被用控制 LED 和
数码,这占用 8 个 AD 通通,这里我们用通道 1 采集电压,并发送串口
在 Windows 的串口终端调试软件中可以显示出来
----------------------------------------------------------------*/
#include <iom16v.h>
#include <macros.h>
#include <stdio.h>
#include "shumaguan.h"
#define PIN_RXD
0
//PD0
RXD
#define PIN_TXD
1 //PD1
TXD
#define BAUDRATE
9600 //baudrate
#define F_CPU
8000000 //the frequency of the global clock
unsigned int advalue;
void init_USART(void);
void main(void)
{
//上电默认 DDRx=0x00,PORTx=0x00 输入,无上拉电阻
PORTA=0XFF;
//上拉
PORTB =0xFF;
//不用的管脚使能内
部上拉电阻。
PORTC=0XFF;
DDRD =(1<<PIN_TXD);
//TXD 为输出
PORTD =0xFF;
init_USART();
while(1)
{
unsigned char highvalue,lowvalue;
ADMUX=BIT(REFS0);
//AVCC 基准电压,通道 0,10bits ad value
/*---------------------------------------------------------------ADMUX
7,6
REFS1:0--------Rrference Selection Bits
00-------------AREF,Internal Vref truned off
132
AVR 单片机学习开发板教程
dushibiao
2007 年 11 月
01-------------AVCC with external capacior at AREF pin
10-------------Reserved
11-------------internal 2.56V Voltage Reference with external
capacitior at AREF pin
5
ADELAR---------ADC Left Adjust Result
1--------------Left Adjust-----8 bits resolution
0--------------Right Adjust----10 bits resolution
4:0
MUX4:0---------Analog Channel and Gain Selection Bits
00000----------ADC0
00001----------ADC1
||||||||||||||||||||||||||||||||||||
00111----------ADC7
|||||||||||||||||||||||||||||||||||||
----------------------------------------------------------------*/
ADCSRA=BIT(ADEN)|BIT(ADSC)|BIT(ADPS2)|BIT(ADPS1)|BIT(ADPS0);
//AD 使能,单次转换,128 分频
/*-------------------------------------------------------------------ADCSRA
7
ADEN-----------ADC Enable
6
ADSC-----------ADC Start Comversion
5
ADATE----------ADC Auto Trigger Enable
4
ADIF-----------ADC Interrupt Flag
3
ADIE-----------ADC Interrupt Enable
2:0
ADPS2:0--------ADC Prescaler Select Bits
000------------2
001------------2
010------------4
011------------8
100------------16
101------------32
110------------64
111------------128
---------------------------------------------------------------------*/
while(!(ADCSRA & (1 << ADIF))); /*等待*/
advalue=ADC;
ADCSRA&=(~BIT(ADEN))|(~BIT(ADSC));
advalue=advalue*4.89;
highvalue=(unsigned char)(advalue/100);
lowvalue=(unsigned char)(advalue%100);
printf("AD 转换电压值为:");
putchar(highvalue/10+0x30);
putchar('.');
putchar(highvalue%10+0x30);
putchar(lowvalue/10+0x30);
133
AVR 单片机学习开发板教程
dushibiao
2007 年 11 月
putchar(lowvalue%10+0x30);
puts("V\n");
delay_nms(3000);
}
}
/*--------------------------------------------------------------------------function
send an unsigned char to the uart
----------------------------------------------------------------------------*/
void put_c(unsigned char c) //发送采用查询方式
{
while( !(UCSRA & (1<<UDRE)) );
//wait until the uart is empty
UDR=c;
//write data to uart
}
/*---------------------------------------------------------------------------function
send a string to the uart and with return back
-----------------------------------------------------------------------------*/
void put_s(unsigned char *ptr)
{
while (*ptr)
{
put_c(*ptr++);
}
put_c(0x0D);
put_c(0x0A); //结尾发送回车换行
}
/*--------------------------------------------------------------------------fuction
initialize the uart unit
----------------------------------------------------------------------------*/
void init_USART(void)//USART 初始化
{
//USART 9600 8, n,1 PC 上位机软件(超级终端等)也要设成同样的设置才能通讯
UCSRC = (1<<URSEL) | 0x06;
//异步,8 位数据,无奇偶校验,一个停止位,无倍速
/*
UBRRH 与 UCSRC 共用 I/O 地址。因此访问该地址时需注意以下问题。
写访问
当在该地址执行写访问时, USART 寄存器选择位(URSEL)控制被写入的寄存器。
若 URSEL 为 0,对 UBRRH 值更新;若 URSEL 为 1,对 UCSRC 设置更新
读访问
对 UBRRH 或 UCSRC 寄存器的读访问则较为复杂。但在大多数应用中,基本不需要
读这些寄存器
134
AVR 单片机学习开发板教程
dushibiao
2007 年 11 月
没有 UBRR 这个 16 位寄存器,因为 UBRRL(0x09)/UBRRH(0x20)的地址不连续,而且
UBRRH 跟 UCSRC 共用地址
*/
//U2X=0 时的公式计算
UBRRL= (F_CPU/BAUDRATE/16-1)%256;
UBRRH= (F_CPU/BAUDRATE/16-1)/256;
//U2X=1 时的公式计算
//UBRRL= (F_CPU/BAUDRATE/8-1)%256;
//UBRRH= (F_CPU/BAUDRATE/8-1)/256;
//也可根据数据手册的[波特率设置的例子]查得
//UBRRL = 0x2F; //set baud rate lo
//UBRRH = 0x00; //set baud rate hi
UCSRA = 0x00;
//无倍速
UCSRB = (1<<RXEN)|(1<<TXEN);
//使能接收,使能发送
}
实验十六
spi 读取拨码盘开关实验
实验目的:学习 AVR 单片机 SPI 接口方式
实验准备:请把 AVR 单片机的 SPI 模块一节了解一下
实验要求:请把 LED 跳线打到左侧。将 spi 实验跳线打到 165 侧。
实验现象:8 个 LED 实时指示 8 个拨码盘开关的状态
AVR—GCC 版本程序如下
/*
Title:
win-avr spi test
Author: dushibiao
Date:
2007 10 22
Purpose: use spi in mode to extend 8 bits switches
Frequency: internal 8M
Software: win-avr to compile
Hardware: AVR mega16 BOARD
Connect: [email protected]
*/
#include <avr/io.h>
#include <avr/delay.h>
#define SCK 7
#define MISO 6
#define PL
4
//PB7
//PB6
//PB4
135
AVR 单片机学习开发板教程
/*------------------------------------------------------------fuction: initialize ports
--------------------------------------------------------------*/
void portinitial(void)
{
PORTA=0XFF;
DDRA=0XFF;
PORTB=0XFF;
DDRB=(1<<SCK)|(1<<PL);
PORTC=0XFF;
PORTD=0XFF;
}
2007 年 11 月
dushibiao
//OUT
//PB7,PB4 OUT
//PULL-UP
//PULL-UP
/*---------------------------------------------------------------fuction: initialize spi unit-----Master Mode
------------------------------------------------------------------*/
void spiinit(void)
{
SPCR=(1<<SPE)|(1<<MSTR)|(1<<SPR1)|(1<<SPR0);
//enable
spi,Master Mode
//Msb
first,rising edge sample,prescaler 128
/*-----------------------------------------------------------7
6
5
4
SPCR
SPIE---------SPI Interrupt Enable 1---enable,0---disable
SPE----------SPI Enable
1---enable,0---disable
DORD---------Data Order
1---LSB first, 0---MSB first
MSTR---------Mastr/Slave Select
1---Master Mode,0----
Slave Mode
3
CPOL---------Clock Polarity
CPOL
Leading Edge
Trailing Edge
0
rising
falling
1
falling
rising
2
CPHA---------Clock Phase
CPHA
Leading Edge
Trailing Edge
0
sample
setup
1
setup
sample
1:0
SPR1:0
SPI Clock Rate Select 1 and 0
SPI2X
SPR1
SPR0
SCK Frequency
136
AVR 单片机学习开发板教程
0
dushibiao
0
2007 年 11 月
0
f(osc)/4
0
0
1
f(osc)/16
0
1
0
f(osc)/64
0
1
1
f(osc)/128
1
0
0
f(osc)/2
1
0
1
f(osc)/8
1
1
0
f(osc)/32
1
1
1
f(osc)/64
-------------------------------------------------------------*/
}
int main(void)
{
portinitial();
spiinit();
while(1)
{
PORTB&=~(1<<PL);
_delay_us(10);
PORTB|=(1<<PL);
SPDR=0XAA;
while(!(SPSR&(1<<SPIF)));
//wait until spi
receive succeed
PORTA=~SPDR;
//light led
to the
corresponding bit
}
}
ICC--AVR 版本程序如下
/*
Title:
icc avr spi test
Author: dushibiao
Date:
2007 10 22
Purpose: use spi in mode to extend 8 bits switches
Frequency: internal 8M
Software: icc avr to compile
Hardware: AVR mega16 BOARD
Connect: [email protected]
*/
#include<iom16v.h>
137
AVR 单片机学习开发板教程
2007 年 11 月
dushibiao
#include <macros.h>
#define SCK 7
#define MISO 6
#define PL
4
//PB7
//PB6
//PB4
/*----------------------------------------------------------------------延时函数
系统时钟:8M
-----------------------------------------------------------------------*/
void delay_1us(void)
//1us 延时函数
{
asm("nop");
}
void delay_nus(unsigned int n)
{
unsigned int i=0;
for (i=0;i<n;i++)
delay_1us();
}
//N us 延时函数
/*------------------------------------------------------------fuction: initialize ports
--------------------------------------------------------------*/
void portinitial(void)
{
PORTA=0XFF;
DDRA=0XFF;
//OUT
PORTB=0XFF;
DDRB=(1<<SCK)|(1<<PL);
//PB7,PB4 OUT
PORTC=0XFF;
//PULL-UP
PORTD=0XFF;
//PULL-UP
}
/*---------------------------------------------------------------fuction: initialize spi unit-----Master Mode
------------------------------------------------------------------*/
void spiinit(void)
{
SPCR=(1<<SPE)|(1<<MSTR)|(1<<SPR1)|(1<<SPR0);
//enable spi,Master Mode
//Msb
first,rising
edge
sample,prescaler 128
/*-------------------------------------------------------------
138
AVR 单片机学习开发板教程
dushibiao
2007 年 11 月
SPCR
7
6
5
4
3
SPIE---------SPI Interrupt Enable 1---enable,0---disable
SPE----------SPI Enable
1---enable,0---disable
DORD---------Data Order
1---LSB first, 0---MSB first
MSTR---------Mastr/Slave Select
1---Master Mode,0----Slave Mode
CPOL---------Clock Polarity
CPOL
Leading Edge
Trailing Edge
0
rising
falling
1
falling
rising
2
CPHA---------Clock Phase
CPHA
Leading Edge
Trailing Edge
0
sample
setup
1
setup
sample
1:0
SPR1:0
SPI Clock Rate Select 1 and 0
SPI2X
SPR1
SPR0
SCK Frequency
0
0
0
f(osc)/4
0
0
1
f(osc)/16
0
1
0
f(osc)/64
0
1
1
f(osc)/128
1
0
0
f(osc)/2
1
0
1
f(osc)/8
1
1
0
f(osc)/32
1
1
1
f(osc)/64
--------------------------------------------------------------*/
}
void main(void)
{
portinitial();
spiinit();
while(1)
{
PORTB&=~(1<<PL);
delay_nus(10);
PORTB|=(1<<PL);
SPDR=0XAA;
while(!(SPSR&(1<<SPIF)));
PORTA=~SPDR;
}
}
//wait until spi receive succeed
//light led to the corresponding bit
实验十七
电源管理
实验目的:学习 AVR 单片机电源管理方式
实验准备:请把 AVR 单片机的电源管理模块一节了解一下
139
AVR 单片机学习开发板教程
dushibiao
2007 年 11 月
实验现象:LED1 闪动 10 次后进入掉电模式的睡眠状态. 按下 INT0 按键,将会发现 LED
长亮 5 秒钟,熄灭 1 秒钟后,退回主程序,LED 闪动 10 次后进入掉电模式的睡
眠状态
AVR—GCC 版本程序如下
/*
Title:
win-avr power management
Author: dushibiao
Date:
2007 10 22
Purpose: study how to magage power
Frequency: internal 8M
Software: win-avr to compile
Hardware: AVR mega16 BOARD
Connect: [email protected]
*/
/*
本程序简单的示范了如何令 AVR ATMEGA16 进入睡眠状态及唤醒
电源管理及睡眠模式的介绍
进入最低耗电的掉电模式
关闭各种模块
外部中断唤醒
M16 掉电模式的耗电情况(看门狗关闭),时钟为内部 RC 1MHz
[email protected]=5.0V [手册的图表约为 1.1uA]
[email protected]=3.3V [手册的图表约为 0.4uA]
//测量的数字万用表是 FLUKE 15B,分辨率 0.1uA
这个程序需要 MCU 进入休眠状态,为实现最低功耗,JTAG 接口会被关闭,只能通过 LED
的变化来观察程序的运行。
这个实验里面,用 STK500(AVRISP) ISP 下载线来烧录更方便。
熔丝位设置
1 关断 BOD 功能 BODEN=1
2 如果用 ISP 方式烧录,就可以完全关闭 JTAG 口了
OCEEN=1,JTAGEN=1
*/
#include <avr/io.h>
#include <avr/signal.h>
#include <avr/interrupt.h>
#include <avr/delay.h>
#include <avr/sleep.h>
/*
140
AVR 单片机学习开发板教程
dushibiao
2007 年 11 月
sleep.h 里面定义的常数,对应各种睡眠模式
#define SLEEP_MODE_IDLE
0
空闲模式
#define SLEEP_MODE_ADC
_BV(SM0)
ADC 噪声抑制模式
#define SLEEP_MODE_PWR_DOWN
_BV(SM1)
掉电模式
#define SLEEP_MODE_PWR_SAVE
(_BV(SM0) | _BV(SM1))
省电模式
#define SLEEP_MODE_STANDBY
(_BV(SM1) | _BV(SM2))
Standby 模式
#define SLEEP_MODE_EXT_STANDBY (_BV(SM0) | _BV(SM1) | _BV(SM2))
扩展 Standby 模式
函数
void set_sleep_mode (uint8_t mode);
设定睡眠模式
void sleep_mode (void);
进入睡眠状态
*/
//管脚定义
#define LED
#define KEY_INT02
0
//PA0 驱动 LED,低电平有效
//PB2 按键,
低电平有效
void delay_10ms(unsigned int t)
{
/*
由于内部函数_delay_ms() 最高延时较短
[email protected] / [email protected] / [email protected]
故编写了这条函数,实现更长的延时,并能令程序能适应各种时钟频率
*/
while(t--)
_delay_ms(10);
}
int main(void)
{
unsigned char i;
//上电默认 DDRx=0x00,PORTx=0x00 输入,无上拉电阻
PORTB=0xFF;
//不用的管脚使能内部上拉电阻。
PORTC=0xFF;
PORTD=0xFF;
141
AVR 单片机学习开发板教程
dushibiao
2007 年 11 月
PORTA=0xFF;
DDRA =(1<<LED);
//PA0 设为输出高电平,灯灭
/*
端口引脚
进入休眠模式时,所有的端口引脚都应该配置为只消耗最小的功耗。
最重要的是避免驱动电阻性负载。
在休眠模式下 I/O 时钟 clkI/O 和 ADC 时钟 clkADC 都被停止了,输入缓冲器也禁止
了,从而保证输入电路不会消耗电流。
在某些情况下输入逻辑是使能的,用来检测唤醒条件。用于此功能的具体引脚请参见
“ 数字输入使能和休眠模式” 。
如果输入缓冲器是使能的,此时输入不能悬空,信号电平也不应该接近 VCC/2,否则
输入缓冲器会消耗额外的电流。
IO 作输出(DDR=1)时,维持状态不变
*/
/*
看门狗定时器(上电默认是关闭的)
如果系统无需利用看门狗,这个模块也可以关闭。
若使能,则在任何休眠模式下都持续工作,从而消耗电流。
在深层次的睡眠模式下,这个电流将占总电流的很大比重。
假设看门狗定时器使能了,关闭程式如下
1. 在同一个指令内对 WDTOE 和 WDE 写"1“,即使 WDE 已经为"1“
2. 在紧接的 4 个时钟周期之内对 WDE 写"0”
*/
WDTCR=(1<<WDTOE)|(1<<WDE);
WDTCR=(0<<WDE);
//或使用 wdt.h 里面的 wdt_disable()函数
/*
模数转换器(上电默认是关闭的)
使能时, ADC 在睡眠模式下继续工作。
为了降低功耗,在进入睡眠模式之前需要禁止 ADC。
重新启动后的第一次转换为扩展的转换。
假设模数转换器使能了,关闭程式如下
*/
ADCSRA=(0<<ADEN);
/*
模拟比较器(上电默认是打开的,需要手工关闭)
在空闲模式时,如果没有使用模拟比较器,可以将其关闭。在 ADC 噪声抑制模式下也
是如此。
在其他睡眠模式模拟比较器是自动关闭的。
142
AVR 单片机学习开发板教程
dushibiao
2007 年 11 月
如果模拟比较器使用了内部电压基准源,则不论在什么睡眠模式下都需要关闭它。否则
内部电压基准源将一直使能。
关闭程式如下
*/
ACSR=(1<<ACD);
/*
掉电检测 BOD (由熔丝位 BODEN 控制)
如果系统没有利用掉电检测器 BOD,这个模块也可以关闭。
如果熔丝位 BODEN 被编程,从而使能了 BOD 功能,它将在各种休眠模式下继
续工作。
在深层次的休眠模式下,这个电流将占总电流的很大比重。
设置熔丝位 BODEN=1 关断 BOD 功能
*/
/*
片内基准电压
使用 BOD、模拟比较器和 ADC 时可能需要内部电压基准源。
若这些模块都禁止了,则基准源也可以禁止。
重新使能后用户必须等待基准源稳定之后才可以使用它。
如果基准源在休眠过程中是使能的,其输出立即可以使用。
当 BOD、模拟比较器和 ADC 都禁止了,则基准源也自动禁止了。
*/
/*
JTAG 接口与片上调试系统
如果通过熔丝位 OCDEN 使能了片上调试系统,当芯片进入掉电或省电模式时主
时钟保持运行。
在休眠模式中这个电流占总电流的很大比重。
下面有三种替代方法:
1 不编程 OCDEN
2 不编程 JTAGEN
3 置位 MCUCSR 的 JTD
当 JTAG 接口使能而 JTAG TAP 控制器没有进行数据交换时,引脚 TDO 将悬
空。
如果与 TDO 引脚连接的硬件电路没有上拉电阻,功耗将增加。
器件的引脚 TDI 包含一个上拉电阻,因此在扫描链中无需为下一个芯片的 TDO
引脚设置上拉电阻。
通过置位 MCUCSR 寄存器的 JTD 或不对 JTAG 熔丝位编程可以禁止 JTAG 接
口。
JTD: 禁止 JTAG 接口(MCU 控制与状态寄存器 MCUCSR Bit7)
143
AVR 单片机学习开发板教程
dushibiao
2007 年 11 月
此位为 0 时,如果 JTAGEN 熔丝位被编程则 JTAG 接口使能。
如果这位为 1, JTAG 接口禁止。
为了避免无意的禁止或使能 JTAG 接口,必须通过一个时间序列来改变 JTD 位。
应用软件必须在四个时钟周期内将期望的数值两次写入 JTD。
如果 JTAG 接口没有与其他 JTAG 电路连接, JTD 应该置位。这样做的原因是
为了避免 JTAG 接口 TDO 引脚的静态电流。
在软件中关闭 JTAG 接口的方法
*/
MCUCSR=(1<<JTD);
MCUCSR=(1<<JTD);
/*
掉电模式
当 SM2..0 为 010 时, SLEEP 指令将使 MCU 进入掉电模式。
在此模式下,外部晶体停振,而外部中断、两线接口地址匹配及看门狗(如果使
能的话)继续工作。
只有外部复位、看门狗复位、BOD 复位、两线接口地址匹配中断、外部电平中
断 INT0 或 INT1,或外部中断 INT2 可以使 MCU 脱离掉电模式。
这个睡眠模式停止了所有的时钟,只有异步模块可以继续工作。
当使用外部电平中断方式将 MCU 从掉电模式唤醒时,必须保持外部电平一定的
时间。
从施加掉电唤醒条件到真正唤醒有一个延迟时间,此时间用于时钟重新启动并稳
定下来。
唤醒周期与由熔丝位 CKSEL 定义的复位周期是一样的。
如果在睡眠过程中发生了复位,则 MCU 唤醒后从中断向量开始执行
使能的中断可以将进入睡眠模式的 MCU 唤醒, 经过启动时间,外加 4 个时钟周
期后,MCU 就可以运行中断例程了。然后返回到 SLEEP 的下一条指令。
*/
MCUCSR=(0<<ISC00);
//INT0 的下降沿激活中断(默认的,这句话可以不
写)
GICR=(1<<INT0);
//使能外部中断 INT0
sei();
//使能全局中断
while(1)
{
for (i=0;i<10;i++) //LED 闪动 10 次后进入掉电模式的睡眠状态
{
delay_10ms(30);
PORTA&=~(1<<LED); //点亮 LED
delay_10ms(30);
PORTA|=(1<<LED);
//熄灭 LED
}
144
AVR 单片机学习开发板教程
dushibiao
2007 年 11 月
set_sleep_mode(SLEEP_MODE_PWR_DOWN);//设定为掉电模式
sleep_mode();
//进入睡眠状态
/*
也可以自行编写
MCUCR=(0<<SM2)|(1<<SM1)|(0<<SM0);
//设定为掉电模式
asm volatile(“sleep” : : );
//进入睡眠状态
*/
}
}
SIGNAL(SIG_INTERRUPT0) //外部中断 0 服务程序 唤醒源
{
PORTA&=~(1<<LED); //点亮 LED
delay_10ms(500);
PORTA|=(1<<LED); //熄灭 LED
delay_10ms(100);
/*LED 长亮 5 秒钟,熄灭 1 秒钟后,退出中断服务程序,然后返回到 SLEEP 的
下一条指令*/
}
/*
程序运行效果
万用表打到直流电流的最小档位(uA 分辨率),接到开关的两头
烧录后要把 STK500 拔出,否则无法测得正确的电流数据。
上电后 LED 闪动 10 次后进入掉电模式的睡眠状态
此时可断开开关
看看万用表的读数
然后接通开关
按下 INT0 按键,将会发现 LED 长亮 5 秒钟,熄灭 1 秒钟后,退回主程序,LED 闪
动 10 次后进入掉电模式的睡眠状态
如果按下复位按键,马上复位。
*/
/*
电源管理及睡眠模式
睡眠模式可以使应用程序关闭 MCU 中没有使用的模块,从而降低功耗。
AVR 具有不同的睡眠模式,允许用户根据自己的应用要求实施剪裁。
进入睡眠模式的条件是置位寄存器 MCUCR 的 SE,然后执行 SLEEP 指令。
145
AVR 单片机学习开发板教程
dushibiao
2007 年 11 月
具体哪一种模式( 空闲模式、ADC 噪声抑制模式、掉电模式、省电模式、Standby
模式和扩展 Standby 模式) 由 MCUCR 的 SM2、SM1 和 SM0 决定。
使能的中断可以将进入睡眠模式的 MCU 唤醒。
经过启动时间,外加 4 个时钟周期后,MCU 就可以运行中断例程了。然后返回
到 SLEEP 的下一条指令。
唤醒时不会改变寄存器文件和 SRAM 的内容。
如果在睡眠过程中发生了复位,则 MCU 唤醒后从中断向量开始执行
需要了解 AVR 芯片内部不同的时钟系统及其分布,在选择合适的睡眠模式时非
常有用。
MCU 控制寄存器-MCUCR
MCU 控制寄存器包含了电源管理的控制位。
Bits 7, 5, 4 – SM2..0: 休眠模式选择位 2、1 和 0
这些位用于选择具体的休眠模式。
SM2 SM1 SM0 休眠模式
0 0 0 空闲模式
0
0
1 ADC 噪声抑制模式
0 1 0 掉电模式
0 1 1 省电模式
1 0 0 保留
1 0 1 保留
1 1 0 Standby 模式(1)
1 1 1 扩展 Standby 模式(1)
Note:1 仅在使用外部晶体或谐振器时 Standby 模式与扩展 Standby 模式才可
用。
Bit 6 – SE: 休眠使能
为了使 MCU 在执行 SLEEP 指令后进入休眠模式, SE 必须置位。
为了确保进入休眠模式是程序员的有意行为,建议仅在 SLEEP 指令的前一条
指令置位 SE。
MCU 一旦唤醒立即清除 SE。
关于各种睡眠模式的特点与唤醒要求,内容繁多,请参考数据手册
*/
ICC--AVR 版本程序如下
/*
Title:
icc-avr power management
Author: dushibiao
Date:
2007 10 22
Purpose: study how to magage power
Frequency: internal 8M
146
AVR 单片机学习开发板教程
dushibiao
2007 年 11 月
Software: icc-avr to compile
Hardware: AVR mega16 BOARD
Connect: [email protected]
*/
/*
本程序简单的示范了如何令 AVR ATMEGA16 进入睡眠状态及唤醒
电源管理及睡眠模式的介绍
进入最低耗电的掉电模式
关闭各种模块
外部中断唤醒
M16 掉电模式的耗电情况(看门狗关闭),时钟为内部 RC 1MHz
[email protected]=5.0V [手册的图表约为 1.1uA]
[email protected]=3.3V [手册的图表约为 0.4uA]
//测量的数字万用表是 FLUKE 15B,分辨率 0.1uA
这个程序需要 MCU 进入休眠状态,为实现最低功耗,JTAG 接口会被关闭,只能通过 LED
的变化来观察程序的运行。
这个实验里面,用 STK500(AVRISP) ISP 下载线来烧录更方便。
熔丝位设置
1 关断 BOD 功能 BODEN=1
2 如果用 ISP 方式烧录,就可以完全关闭 JTAG 口了
OCEEN=1,JTAGEN=1
*/
#include<iom16v.h>
#include <macros.h>
#include "delay.h"
//管脚定义
#define LED
#define KEY_INT02
0
//PA0 驱动 LED,低电平有效
//PB2 按键,
低电平有效
int main(void)
{
unsigned char i;
//上电默认 DDRx=0x00,PORTx=0x00 输入,无上拉电阻
PORTB=0xFF;
//不用的管脚使能内部上拉电阻。
PORTC=0xFF;
PORTD=0xFF;
PORTA=0xFF;
DDRA =(1<<LED);
//PA0 设为输出高电平,灯灭
147
AVR 单片机学习开发板教程
dushibiao
2007 年 11 月
/*
端口引脚
进入休眠模式时,所有的端口引脚都应该配置为只消耗最小的功耗。
最重要的是避免驱动电阻性负载。
在休眠模式下 I/O 时钟 clkI/O 和 ADC 时钟 clkADC 都被停止了,输入缓冲器也禁止
了,从而保证输入电路不会消耗电流。
在某些情况下输入逻辑是使能的,用来检测唤醒条件。用于此功能的具体引脚请参见
“ 数字输入使能和休眠模式” 。
如果输入缓冲器是使能的,此时输入不能悬空,信号电平也不应该接近 VCC/2,否则
输入缓冲器会消耗额外的电流。
IO 作输出(DDR=1)时,维持状态不变
*/
/*
看门狗定时器(上电默认是关闭的)
如果系统无需利用看门狗,这个模块也可以关闭。
若使能,则在任何休眠模式下都持续工作,从而消耗电流。
在深层次的睡眠模式下,这个电流将占总电流的很大比重。
假设看门狗定时器使能了,关闭程式如下
1. 在同一个指令内对 WDTOE 和 WDE 写"1“,即使 WDE 已经为"1“
2. 在紧接的 4 个时钟周期之内对 WDE 写"0”
*/
WDTCR=(1<<WDTOE)|(1<<WDE);
WDTCR=(0<<WDE);
//或使用 wdt.h 里面的 wdt_disable()函数
/*
模数转换器(上电默认是关闭的)
使能时, ADC 在睡眠模式下继续工作。
为了降低功耗,在进入睡眠模式之前需要禁止 ADC。
重新启动后的第一次转换为扩展的转换。
假设模数转换器使能了,关闭程式如下
*/
ADCSRA=(0<<ADEN);
/*
模拟比较器(上电默认是打开的,需要手工关闭)
在空闲模式时,如果没有使用模拟比较器,可以将其关闭。在 ADC 噪声抑制模式下也
是如此。
在其他睡眠模式模拟比较器是自动关闭的。
如果模拟比较器使用了内部电压基准源,则不论在什么睡眠模式下都需要关闭它。否则
内部电压基准源将一直使能。
148
AVR 单片机学习开发板教程
dushibiao
2007 年 11 月
关闭程式如下
*/
ACSR=(1<<ACD);
/*
掉电检测 BOD (由熔丝位 BODEN 控制)
如果系统没有利用掉电检测器 BOD,这个模块也可以关闭。
如果熔丝位 BODEN 被编程,从而使能了 BOD 功能,它将在各种休眠模式下继
续工作。
在深层次的休眠模式下,这个电流将占总电流的很大比重。
设置熔丝位 BODEN=1 关断 BOD 功能
*/
/*
片内基准电压
使用 BOD、模拟比较器和 ADC 时可能需要内部电压基准源。
若这些模块都禁止了,则基准源也可以禁止。
重新使能后用户必须等待基准源稳定之后才可以使用它。
如果基准源在休眠过程中是使能的,其输出立即可以使用。
当 BOD、模拟比较器和 ADC 都禁止了,则基准源也自动禁止了。
*/
/*
JTAG 接口与片上调试系统
如果通过熔丝位 OCDEN 使能了片上调试系统,当芯片进入掉电或省电模式时主
时钟保持运行。
在休眠模式中这个电流占总电流的很大比重。
下面有三种替代方法:
1 不编程 OCDEN
2 不编程 JTAGEN
3 置位 MCUCSR 的 JTD
当 JTAG 接口使能而 JTAG TAP 控制器没有进行数据交换时,引脚 TDO 将悬
空。
如果与 TDO 引脚连接的硬件电路没有上拉电阻,功耗将增加。
器件的引脚 TDI 包含一个上拉电阻,因此在扫描链中无需为下一个芯片的 TDO
引脚设置上拉电阻。
通过置位 MCUCSR 寄存器的 JTD 或不对 JTAG 熔丝位编程可以禁止 JTAG 接
口。
JTD: 禁止 JTAG 接口(MCU 控制与状态寄存器 MCUCSR Bit7)
此位为 0 时,如果 JTAGEN 熔丝位被编程则 JTAG 接口使能。
如果这位为 1, JTAG 接口禁止。
149
AVR 单片机学习开发板教程
dushibiao
2007 年 11 月
为了避免无意的禁止或使能 JTAG 接口,必须通过一个时间序列来改变 JTD 位。
应用软件必须在四个时钟周期内将期望的数值两次写入 JTD。
如果 JTAG 接口没有与其他 JTAG 电路连接, JTD 应该置位。这样做的原因是
为了避免 JTAG 接口 TDO 引脚的静态电流。
在软件中关闭 JTAG 接口的方法
*/
MCUCSR=(1<<JTD);
MCUCSR=(1<<JTD);
/*
掉电模式
当 SM2..0 为 010 时, SLEEP 指令将使 MCU 进入掉电模式。
在此模式下,外部晶体停振,而外部中断、两线接口地址匹配及看门狗(如果使
能的话)继续工作。
只有外部复位、看门狗复位、BOD 复位、两线接口地址匹配中断、外部电平中
断 INT0 或 INT1,或外部中断 INT2 可以使 MCU 脱离掉电
模式。
这个睡眠模式停止了所有的时钟,只有异步模块可以继续工作。
当使用外部电平中断方式将 MCU 从掉电模式唤醒时,必须保持外部电平一定的
时间。
从施加掉电唤醒条件到真正唤醒有一个延迟时间,此时间用于时钟重新启动并稳
定下来。
唤醒周期与由熔丝位 CKSEL 定义的复位周期是一样的。
如果在睡眠过程中发生了复位,则 MCU 唤醒后从中断向量开始执行
使能的中断可以将进入睡眠模式的 MCU 唤醒, 经过启动时间,外加 4 个时钟周
期后,MCU 就可以运行中断例程了。然后返回到
SLEEP 的下一条指令。
*/
MCUCSR=(0<<ISC00);
//INT0 的下降沿激活中断(默认的,这句话可以不
写)
GICR=(1<<INT0);
//使能外部中断 INT0
SEI();
//使能全局中断
while(1)
{
for (i=0;i<10;i++) //LED 闪动 10 次后进入掉电模式的睡眠状态
{
delay_nms(300);
PORTA&=~(1<<LED); //点亮 LED
delay_nms(300);
PORTA|=(1<<LED);
//熄灭 LED
150
AVR 单片机学习开发板教程
dushibiao
2007 年 11 月
}
MCUCR=(1<<SE)|(0<<SM2)|(1<<SM1)|(0<<SM0);
asm("SLEEP");
//进入睡眠状态
//设定为掉电模式
}
}
#pragma interrupt_handler int0pro: iv_INT0
void int0pro(void) //外部中断 0 服务程序 唤醒源
{
PORTA&=~(1<<LED); //点亮 LED
delay_nms(5000);
PORTA|=(1<<LED); //熄灭 LED
delay_nms(1000);
/*LED 长亮 5 秒钟,熄灭 1 秒钟后,退出中断服务程序,然后返回到 SLEEP 的
下一条指令*/
}
/*
程序运行效果
万用表打到直流电流的最小档位(uA 分辨率),接到开关的两头
烧录后要把 STK500 拔出,否则无法测得正确的电流数据。
上电后 LED 闪动 10 次后进入掉电模式的睡眠状态
此时可断开开关
看看万用表的读数
然后接通开关
按下 INT0 按键,将会发现 LED 长亮 5 秒钟,熄灭 1 秒钟后,退回主程序,LED 闪
动 10 次后进入掉电模式的睡眠状态
如果按下复位按键,马上复位。
*/
/*
电源管理及睡眠模式
睡眠模式可以使应用程序关闭 MCU 中没有使用的模块,从而降低功耗。
AVR 具有不同的睡眠模式,允许用户根据自己的应用要求实施剪裁。
进入睡眠模式的条件是置位寄存器 MCUCR 的 SE,然后执行 SLEEP 指令。
151
AVR 单片机学习开发板教程
dushibiao
2007 年 11 月
具体哪一种模式( 空闲模式、ADC 噪声抑制模式、掉电模式、省电模式、Standby
模式和扩展 Standby 模式) 由 MCUCR 的 SM2、SM1 和
SM0 决定。
使能的中断可以将进入睡眠模式的 MCU 唤醒。
经过启动时间,外加 4 个时钟周期后,MCU 就可以运行中断例程了。然后返回
到 SLEEP 的下一条指令。
唤醒时不会改变寄存器文件和 SRAM 的内容。
如果在睡眠过程中发生了复位,则 MCU 唤醒后从中断向量开始执行
需要了解 AVR 芯片内部不同的时钟系统及其分布,在选择合适的睡眠模式时非
常有用。
MCU 控制寄存器-MCUCR
MCU 控制寄存器包含了电源管理的控制位。
Bits 7, 5, 4 – SM2..0: 休眠模式选择位 2、1 和 0
这些位用于选择具体的休眠模式。
SM2 SM1 SM0 休眠模式
0 0 0 空闲模式
0
0
1 ADC 噪声抑制模式
0 1 0 掉电模式
0 1 1 省电模式
1 0 0 保留
1 0 1 保留
1 1 0 Standby 模式(1)
1 1 1 扩展 Standby 模式(1)
Note:1 仅在使用外部晶体或谐振器时 Standby 模式与扩展 Standby 模式才可
用。
Bit 6 – SE: 休眠使能
为了使 MCU 在执行 SLEEP 指令后进入休眠模式, SE 必须置位。
为了确保进入休眠模式是程序员的有意行为,建议仅在 SLEEP 指令的前一条
指令置位 SE。
MCU 一旦唤醒立即清除 SE。
关于各种睡眠模式的特点与唤醒要求,内容繁多,请参考数据手册
*/
实验十八
看门狗实验
实验目的:学习 AVR 单片机看门狗管理方式
实验准备:请把 AVR 单片机的看门狗模块一节了解一下
实验现象:程序运行效果为:8 路 LED 亮灭十次,然后进入死循环,LED0 不停的闪烁,
在循环中不停的喂狗,假如你按下长按 KEY1,便停止了喂狗,于是便发生了
看门狗复位,它保证程序跑飞后能够进行复位
152
AVR 单片机学习开发板教程
dushibiao
2007 年 11 月
AVR—GCC 版本程序如下
/*
Title:
avr-gcc watch dog test
Author: dushibiao
Date:
2007 10 22
Purpose: study how to use watch dog
Frequency: internal 8M
Software: avr-gcc to compile
Hardware: AVR mega16 BOARD
Connect: [email protected]
*/
/*------------------------------------------------------------------程序运行效果为:8 路 LED 亮灭十次,然后进入死循环,LED0 不停的闪烁,
在循环中不停的喂狗,假如你按下长按 KEY1,便停止了喂狗,于是便发生了
看门狗复位,它保证程序跑飞后能够进行复位
--------------------------------------------------------------------*/
#include <avr/io.h>
#include <avr/delay.h>
#include <avr/wdt.h>
#define LED0
0
//PA0
#define KEY0
2
//PD2
/*-----------------------------------------------------------*/
int main(void)
{
unsigned char temp;
PORTA=0XFF;
DDRA=0XFF;
//OUT
PORTB=0XFF;
//PULL-UP
PORTC=0XFF;
//PULL-UP
PORTD=0XFF;
//PULL-UP
for(temp=0;temp<10;temp++)
{
unsigned char i;
PORTA=0X00;
//all leds turn on
for(i=0;i<50;i++)
_delay_ms(20);
PORTA=0XFF;
//all leds turn off
for(i=0;i<50;i++)
_delay_ms(20);
}
WDTCR |= ((1 << WDTOE) | (1 << WDE)); /*启动时序*/
153
AVR 单片机学习开发板教程
dushibiao
2007 年 11 月
WDTCR = ((1<< WDE) | (1 << WDP2) | (1 <<WDP1)); /*设定周期为 1S*/
while(1)
{
unsigned char i;
for(i=0;i<100;i++)
{
PORTA &= ~(1<<LED0);
//led0 turn on
_delay_ms(20);
wdt_reset();
}
for(i=0;i<100;i++)
{
PORTA |= (1<<LED0);
//led0 turn off
_delay_ms(20);
wdt_reset();
}
while(!(PIND&(1<<KEY0)));
//检测按键
}
}
ICC--AVR 版本程序如下
/*
Title:
icc avr watch dog test
Author: dushibiao
Date:
2007 10 22
Purpose: study how to use watch dog
Frequency: internal 8M
Software: icc avr to compile
Hardware: AVR mega16 BOARD
Connect: [email protected]
*/
/*------------------------------------------------------------------程序运行效果为:8 路 LED 亮灭十次,然后进入死循环,LED0 不停的闪烁,
在循环中不停的喂狗,假如你按下长按 KEY1,便停止了喂狗,于是便发生了
看门狗复位,它保证程序跑飞后能够进行复位
--------------------------------------------------------------------*/
#include <iom16v.h>
#include <macros.h>
#define LED0
0
#define KEY0
PD2
/*-----------------------------------------------------------*/
/*-----------------------------------------------------------------------
154
AVR 单片机学习开发板教程
dushibiao
2007 年 11 月
延时函数
系统时钟:8M
-----------------------------------------------------------------------*/
void delay_1ms(void)
{
unsigned int i;
for (i=0;i<1140;i++);
}
//1ms 延时函数
void delay_nms(unsigned int n)
//N ms 延时函数
{
unsigned int i=0;
for (i=0;i<n;i++)
delay_1ms();
}
/*-----------------------------------------------------------*/
void main(void)
{
unsigned char temp;
PORTA=0XFF;
DDRA=0XFF;
//OUT
PORTB=0XFF;
//PULL-UP
PORTC=0XFF;
//PULL-UP
PORTD=0XFF;
//PULL-UP
for(temp=0;temp<10;temp++)
{
PORTA=0X00;
//all leds turn on
delay_nms(1000);
PORTA=0XFF;
//all leds turn off
delay_nms(1000);
}
WDTCR |= ((1 << WDTOE) | (1 << WDE)); /*启动时序*/
WDTCR = ((1<< WDE) | (1 << WDP2) | (1 <<WDP1)); /*设定周期为 1S*/
while(1)
{
unsigned char i;
for(i=0;i<10;i++)
{
PORTA &= ~BIT(LED0);
//led0 turn on
delay_nms(200);
WDR();
}
155
AVR 单片机学习开发板教程
for(i=0;i<10;i++)
{
PORTA |= BIT(LED0);
delay_nms(200);
WDR();
}
while(!(PIND&(1<<KEY0)));
dushibiao
2007 年 11 月
//led0 turn off
//检测按键
}
}
实验十九
bootloader 实验
实验目的:学习 AVR 单片机自编程方式
实验准备:请把 AVR 单片机的自编程方式了解一下
实验说明:本开发自带的就是本实验的 bootloader
AVR—GCC 版本程序如下
/*
Title:
avr--gcc bootloader
Author: dushibiao
Date:
2007 10 25
Purpose:
make a bootloader you can update it via RS232
Frequency: internal 8M
Software: avr--gcc
Hardware: AVR mega16 BOARD
Connect: [email protected]
*/
/*
本程序简单的示范了 AVR ATMEGA16 的 IAP 应用,实现智能升级
Boot Loader
XMODEM-CRC 传输协议
CRC16 校验
出于简化程序考虑,各种数据没有对外输出,学习时建议使用 JTAG ICE 硬件仿真器。
熔丝位设置
BOOTSZ1=0
BOOTSZ0=0 Boot 区为 1K 字(2K 字节)大小。
BOOTRST=0 复位向量位于 Boot 区。
makefile 中的程序基地址偏移
LDFLAGS += -Wl,--section-start=.text=0x3800
//0x3800 字节=0x1C00 字
156
AVR 单片机学习开发板教程
dushibiao
2007 年 11 月
移植程序时,可根据实际大小设定 Boot 区,但要注意更改 makefile 和更改 BootAdd 常数,
以及页写的大小分配;
采用 38400bps 的通讯速率,升级 14KB 程序需要耗时约 5 秒[上位机是 WINDOWS 2000 的
超级终端]
疑问:
1 用 HEX 文件烧录工作正常,但 elf 仿真有问题。
用 AVRstudio 仿真 elf(熔丝设定 BOOTRST=0,程序基地址偏移=0x3800)时,所有 SRAM
变量丢失初始化,
表现为 put_s()的都是乱码或不可见字符。
但如果改成应用程序(熔丝设定 BOOTRST=1,没有程序基地址偏移),则 put_s()可以正
常显示
2 XMODEM 的结束应答(EOT/CAN)后需加 delay_ms(500)的延时(程序优化,统一写在跳
转到用户程序前),
否则在下面的情况将会无法正常结束 XMODEM 的传输,但其实程序已经升级成功
特殊情况:用户程序里面使用了串口,而且波特率较低(如 9600bps)且开机即发送大量数
据
*/
#include <avr/io.h>
#include <avr/delay.h>
//时钟定为外部晶体 8MHz,F_CPU=8000000 使用 USART,38400bps
#include <avr/boot.h>
/*
boot_page_erase ( address )
擦除 FLASH 指定页,其中 address 是以字节为单位的 FLASH 地址
boot_page_fill ( address, data )
填充 BootLoader 缓冲页,address 为以字节为单位的缓冲页地址(对 mega16 :0~128)
,
而 data 是长度为两个字节的字数据,因此调用前 address 的增量应为 2。
此时 data 的高字节写入到高地址,低字节写入到低地址。
boot_page_write ( address )
boot_page_write 执行一次的 SPM 指令,将缓冲页数据写入到 FLASH 指定页。
boot_rww_enable ( )
RWW 区读使能
根据自编程的同时是否允许读 FLASH 存储器,FLASH 存储器可分为两种类型:
可 同 时 读 写 区 ( RWW Read-While-Write ) 和 非 同 时 读 写 区 ( NRWW
NotRead-While-Write)。
对于 MEGA16 RWW 为前 14K 字节 NRWW 为后 2K 字节。
引导加载程序对 RWW 区编程时 MCU 仍可以从 NRWW 区读取指令并执行,而对 NRWW
区编程时 MCU 处于挂起暂停状态。
157
AVR 单片机学习开发板教程
dushibiao
2007 年 11 月
在对 RWW 区自编程(页写入或页擦除)时,由硬件锁定 RWW 区 , RWW 区的读操作被禁
止
在对 RWW 区的编程结束后应当调用 boot_rww_enable() 使 RWW 区开放。
*/
#include <avr/crc16.h>
/*
GCCAVR 内置函数,可以不用头痛 CRC16 了
关于 CRC 的详细说明,可以查看一下网站:
http://www.nongnu.org/avr-libc/user-manual/group__avr__crc.html
函数原形
static __inline__ uint16_t _crc16_update(uint16_t __crc, uint8_t __data);
多项式 Polynomial: x^16 + x^15 + x^2 + 1 (0xa001)
crc 初始值 Initial value: 0xffff
通常用于磁盘控制器(disk-drive controllers)
static __inline__ uint16_t _crc_xmodem_update(uint16_t __crc, uint8_t __data);
多项式 Polynomial: x^16 + x^12 + x^5 + 1 (0x1021)
crc 初始值 Initial value: 0x0
专用于 XMODEM 通讯协议,等效于 C 写的
uint16_t crc_xmodem_update (uint16_t crc, uint8_t data)
{
int i;
crc = crc ^ ((uint16_t)data << 8);
for (i=0; i<8; i++)
{
if (crc & 0x8000)
crc = (crc << 1) ^ 0x1021;
else
crc <<= 1;
}
return crc;
}
static __inline__ uint16_t _crc_ccitt_update (uint16_t __crc, uint8_t __data)
多项式 Polynomial: x^16 + x^12 + x^5 + 1 (0x8408)
crc 初始值 Initial value: 0xffff
专用于 PPP 和 IrDA 通讯协议
*/
//管脚定义
#define PIN_RXD
#define PIN_TXD
0
1
//常数定义
#define SPM_PAGESIZE
#define DATA_BUFFER_SIZE
128
//M16 的一个 Flash 页为 128 字节(64 字)
SPM_PAGESIZE //定义接收缓冲区长度
//PD0
//PD1
158
AVR 单片机学习开发板教程
#define BAUDRATE
//#define F_CPU
dushibiao
38400
8000000
//定义 Xmoden 控制字符
#define XMODEM_NUL
#define XMODEM_SOH
#define XMODEM_STX
#define XMODEM_EOT
#define XMODEM_ACK
#define XMODEM_NAK
#define XMODEM_CAN
#define XMODEM_EOF
#define XMODEM_WAIT_CHAR
2007 年 11 月
//波特率采用 38400bps
//系统时钟 8MHz
0x00
0x01
0x02
0x04
0x06
0x15
0x18
0x1A
'C'
//定义全局变量
struct str_XMODEM
{
unsigned char SOH;
unsigned char BlockNo;
unsigned char nBlockNo;
unsigned char Xdata[128];
unsigned char CRC16hi;
unsigned char CRC16lo;
}
strXMODEM;
//起始字节
//数据块编号
//数据块编号反码
//数据 128 字节
//CRC16 校验数据高位
//CRC16 校验数据低位
//XMODEM 的接收数据结构
unsigned long FlashAddress;
//FLASH 地址
#define BootAdd
0x3800
//Boot 区的首地址(应用区的最高地址)
/* GCC 里面地址使用 32 位长度,适应所有 AVR 的容量*/
unsigned char BlockCount;
//数据块累计(仅 8 位,无须考虑溢出)
unsigned char STATUS;
#define ST_WAIT_START
#define ST_BLOCK_OK
#define ST_BLOCK_FAIL
#define ST_OK
//运行状态
//等待启动
//接收一个数据块成功
//接收一个数据块失败
//完成
0x00
0x01
0x02
0x03
//长延时 max 65536ms
void delay_ms(unsigned int t)
{
while(t--)
{
159
AVR 单片机学习开发板教程
dushibiao
2007 年 11 月
_delay_ms(1);
}
}
//更新一个 Flash 页的完整处理
void write_one_page(void)
{
unsigned char i;
unsigned char *buf;
unsigned int w;
boot_page_erase(FlashAddress);
boot_spm_busy_wait();
buf=&strXMODEM.Xdata[0];
for(i=0;i<SPM_PAGESIZE;i+=2)
{
w =*buf++;
w+=(*buf++)<<8;
//boot_page_fill(FlashAddress+i, w);
boot_page_fill(i, w);
}
boot_page_write(FlashAddress);
boot_spm_busy_wait();
}
//擦除一个 Flash 页
//等待页擦除完成
//将数据填入 Flash 缓冲页中
//原句
//只是低 7 位(128 字节/页)有效
//将缓冲页数据写入一个 Flash 页
//等待页编程完成
//发送采用查询方式
void put_c(unsigned char c) //发送采用查询方式
{
loop_until_bit_is_set(UCSRA,UDRE);
UDR=c;
}
//发送字符串
void put_s(unsigned char *ptr)
{
while (*ptr)
{
put_c(*ptr++);
}
put_c(0x0D);
put_c(0x0A); //结尾发送回车换行
}
//接收指定字节数据(带超时控制,Timer0 的 1ms 时基)
160
AVR 单片机学习开发板教程
dushibiao
2007 年 11 月
// *ptr
数据缓冲区
// len
数据长度
// timeout
超时设定,最长 65.536S
// 返回值
已接收字节数目
unsigned char get_data(unsigned char *ptr,unsigned char len,unsigned int timeout)
{
unsigned count=0;
do
{
if (UCSRA & (1<<RXC))
{
*ptr++=UDR;
//如果接收到数据,读出
count++;
if (count>=len)
{
break;
//够了?退出
}
}
if(TIFR & (1<<OCF0))
//T0 溢出 1ms
{
TIFR|=(1<<OCF0);
//清除标志位
timeout--;
//倒计时
}
}
while (timeout);
return count;
}
//计算 CRC16
unsigned int calcrc(unsigned char *ptr, unsigned char count)
{
unsigned int crc = 0;
while (count--)
{
crc =_crc_xmodem_update(crc,*ptr++);
}
return crc;
}
//主程序
int main(void)
{
unsigned char c;
unsigned char i;
161
AVR 单片机学习开发板教程
dushibiao
2007 年 11 月
unsigned int crc;
//考虑到 BootLoader 可能由应用程序中跳转过来,所以所用到的模块需要全面初始化
DDRA=0x00;
DDRB=0x00;
DDRC=0x00;
PORTA=0xFF;
//不用的管脚使能内部上拉电
阻。
PORTB=0xFF;
PORTC=0xFF;
PORTD=0xFF;
DDRD=(1<<PIN_TXD);
//串口的输出
GICR = (1<<IVCE);
GICR = (0<<IVCE)|(1<<IVSEL);
//将中断向量表迁移到 Boot 区头部
asm volatile("cli": : );
//关全局中断
//这个 BootLoader 没有使用中断。
//初始化 USART 38400, 8, n,1 PC 上位机软件(超级终端)也要设成同样的设置才能通讯
UCSRC = (1<<URSEL)|(1<<UCSZ1)|(1<<UCSZ0); //异步,8 位数据,无奇偶校验,一
个停止位,无倍速
UBRRL = (F_CPU/BAUDRATE/16-1)%256;
//设定波特率
UBRRH = (F_CPU/BAUDRATE/16-1)/256;
UCSRA = 0x00;
UCSRB = (1<<RXEN)|(1<<TXEN);
//使能接收,使能发送
//初始化 T/C0,CTC 模式,256 分频,1ms 自动重载
OCR0 = 28;
TCCR0 = (1<<WGM01)|(1<<CS02)|(0<<CS01)|(0<<CS00);
//CTC 模式下,溢出标志是输出比较匹配 OCF0,对应的中断是输出比较匹配中断;
//向 PC 机发送开始提示信息
put_s(" ");
put_s(" ");
put_s("这是 AT mega16 开发板的 bootloader 升级程序");
put_s("dushibiao 2007 10 26");
put_s("[email protected]");
put_s("如需更新用户程序,请在 5 秒钟内按下[Y]键,否则 5 秒后运行用户程序");
//5 秒种等待 PC 下发“y”,否则退出 Bootloader 程序,从 0x0000 处执行应用程序
c=0;
get_data(&c,1,20000);
//限时 5 秒,接收一个数据
if ((c=='y')||(c=='Y'))
{
STATUS=ST_WAIT_START;
// 并 且 数 据 ='d' 或 'D', 进 入
XMODEM
put_s("请使用 XMODEM 协议传输 BIN 文件,最大 14KB");
162
AVR 单片机学习开发板教程
dushibiao
2007 年 11 月
put_s("必须是 BIN 文件,如果是 HEX 文件,请转换为 BIN 文件后再传送");
}
else
{
STATUS=ST_OK;
//退出 Bootloader 程序
}
//进入 XMODEM 模式
FlashAddress=0x0000;
BlockCount=0x01;
while(STATUS!=ST_OK)
//循环接收,直到全部发完
{
if (STATUS==ST_WAIT_START)
{//XMODEM 未启动
put_c(XMODEM_WAIT_CHAR);
//发送请求 XMODEM_WAIT_CHAR
}
i=get_data(&strXMODEM.SOH,133,6000); //限时 6 秒,接收 133 字节数据
if(i)
{
//分析数据包的第一个数据 SOH/EOT/CAN
switch(strXMODEM.SOH)
{
case XMODEM_SOH:
//收到开始符 SOH
if (i>=133)
{
STATUS=ST_BLOCK_OK;
}
else
{
STATUS=ST_BLOCK_FAIL;
//如果数据不足,要求重发当前
数据块
put_c(XMODEM_NAK);
}
break;
case XMODEM_EOT:
//收到结束符 EOT
put_c(XMODEM_ACK);
//通知 PC 机全部收到
STATUS=ST_OK;
put_s("用户程序升级成功");
break;
case XMODEM_CAN:
//收到取消符 CAN
put_c(XMODEM_ACK);
//回应 PC 机
STATUS=ST_OK;
put_s("警告:用户取消升级,用户程序可能不完整");
break;
163
AVR 单片机学习开发板教程
default:
put_c(XMODEM_NAK);
STATUS=ST_BLOCK_FAIL;
break;
}
}
if (STATUS==ST_BLOCK_OK)
dushibiao
2007 年 11 月
//起始字节错误
//要求重发当前数据块
//接收 133 字节 OK,且起始字节正
确
{
if (BlockCount != strXMODEM.BlockNo)//核对数据块编号正确
{
put_c(XMODEM_NAK);
//数据块编号错误,要求重发当
前数据块
continue;
}
if (BlockCount !=(unsigned char)(~strXMODEM.nBlockNo))
{
put_c(XMODEM_NAK);
//数据块编号反码错误,要求重
发当前数据块
continue;
}
crc=strXMODEM.CRC16hi<<8;
crc+=strXMODEM.CRC16lo;
//AVR 的 16 位整数是低位在先,XMODEM 的 CRC16 是高位在先
if(calcrc(&strXMODEM.Xdata[0],128)!=crc)
{
put_c(XMODEM_NAK);
//CRC 错误,要求重发当前数据
块
continue;
}
//正确接收 128 个字节数据,刚好是 M16 的一页
if (FlashAddress<(BootAdd-SPM_PAGESIZE))
{
//如果地址在应用区内
write_one_page();
//将收到 128 字节写入一页 Flash 中
FlashAddress+=SPM_PAGESIZE;
//Flash 页加 1
}
else
{
put_c(XMODEM_CAN);
//程序已满,取消传送
put_c(XMODEM_CAN);
put_c(XMODEM_CAN);
STATUS=ST_OK;
put_s(" 程序已满,取消传送");
break;
164
AVR 单片机学习开发板教程
}
put_c(XMODEM_ACK);
BlockCount++;
dushibiao
2007 年 11 月
//回应已正确收到一个数据块
//数据块累计加 1
}
}
//退出 Bootloader 程序,从 0x0000 处执行应用程序
put_s("程序开始运行");
delay_ms(500);
//很奇怪,见顶部的说明
loop_until_bit_is_set(UCSRA,UDRE);
//等待结束提示信息回送完成
GICR = (1<<IVCE);
GICR = (0<<IVCE)|(0<<IVSEL);
//将中断向量表迁移到应用程序区头部
/* 无论 BootLoader 是否使用中断,将中断向量表迁移到应用程序区头部,会增强程序
的健壮性*/
boot_rww_enable ();
//RWW 区读允许,否则无法马上执
行用户的应用程序
asm volatile("jmp 0x0000": : );
//跳转到 Flash 的 0x0000 处,执行用户的
应用程序
}
/*
FLASH 程序存储器的编程方法常见的有以下几种:
(1)传统的并行编程方法;
(2)通过串行口进行在线编程 ISP(In System Programmability) 对器件或电路甚至整个系统进
行现场升级或功能重构;
(3)在运行中,应用程序控制下的应用在线编程 IAP (In Applocation Programing) 简单地说就
是在某一个 section 中运行程序,同时对另一个 section 进行擦除、读取、写入等操作。
ISP 方式相对于传统方式有了极大的进步,它不需要将芯片从电路板上卸下就可对芯片进行
编程,减少了开发时间,简化了产品制造流程,并大大降低了现场升级的困难。
而 IAP 方式是对芯片的编程处于应用程序控制之下,对芯片的编程融入在通信系统当中,
通过各种接口(UART/SPI/IIC 等)来升级指定目标芯片的软件。
BootLoader 功能介绍
BootLoader 提供我们通常所说的 IAP(In Applicaion Program)功能。
多数 Mega 系列单片机具有片内引导程序自编程功能(BootLoader)。
MCU 通过运行一个常驻 FLASH 的 BootLoader 程序,利用任何可用的数据接口读取
代码后写入自身 FLASH 存储器中 ,实现自编程目的
基本设计思想(参考了马潮老师的文章)
1. Boot Loader 程序的设计要点
Boot Loader 程序的设计是实现 IAP 的关键,它必须能过通过一个通信接口,采用某种
协议正确的接收数据,再将完整的数据写入到用户程序区中。本例 Boot Loader 程序的设计
要点有:
165
AVR 单片机学习开发板教程
dushibiao
2007 年 11 月
1 采用 ATmega16 的 USART 口实现与 PC 之间的简易 RS232 三线通信;
2 采用 Xmodem 通信协议完成与 PC 机之间的数据交换;
3 用户程序更新完成后自动转入用户程序执行;
2. Xmodem 通信协议
Xmodem 协议是一种使用拨号调制解调器的个人计算机通信中广泛使用的异步文件运
输协议。
这种协议以 128 字节块的形式传输数据,并且每个块都使用一个校验和过程来进行错误
检测。
如果接收方关于一个块的校验和与它在发送方的校验和相同时,接收方就向发送方发送
一个认可字节。
为了便于读者阅读程序,下面简要说明该协议的主要特点,有关 Xmoden 的完整的协议
请参考其它相关的资料。
1 Xmodem 的控制字符:<soh> 01H、<eot> 04H、<ack> 06H、<nak> 15H、<can> 18H、
<eof> 1AH、'c' 43H。
2 XMODEM 有两种校验模式:
一种是一字节的 checksum 校验模式,不常用。
另一种是 2 字节的 CRC16 校验模式(X^16 + X^12 + X^5 + 1),纠错率高达
99.9984%。
两种模式的选择由接收端发送的启动控制符来决定,启动发送后不能切换。
当发送端收到“NAK”控制字符时,它将会开始以 checksum 校验方式发送数据块。
当发送端收到“C”控制字符时,它将会开始以 CRC 校验方式发送数据块。
3 Xmodem-CRC 传输数据块格式:
“<soh> <BlockNO> <255-BlockNO> <…128 个字节的
数据块…> <checksum_crc16>”。
其中<soh>为起始字节;
<BlockNO>为数据块编号字节,每次加一;
<255-BlockNO>是前一字节的反码;
接下来是长度为 128 字节的数据块;
最 后 的 <checksum_crc16> 是 128 字 节 数 据 的 CRC 校 验 码 , 长 度 为 2 个 字
节,crc16hi,crc16lo。
5 接收端收到一个数据块并校验正确时,回送<ack>;接收错误回送<nak>;而回送<can>
表示要发送端停止发送。
6 BlockNO 的初值为 0x01,每发送一个新的数据块<BlockNO>加 1,加到 OxFF 后下一
个数据块的<BlockNO>为零,即 8 位无符号数。
7 发送端收到<ack>后,可继续发送下一个数据块(BlockNO+1);而收到<nak>则可再
次重发上一个数据块。
8 发送端发送<eot>表示全部数据发送完成。如果最后需要发送的数据不足 128 个字节,
用<eof>填满一个数据块。
*/
166
AVR 单片机学习开发板教程
dushibiao
第五章
2007 年 11 月
外围扩展器件实验
实验二十
红外控制 LED 实验
实验目的:学习 AVR 单片机与红外接口的方法
实验准备:请把红外知识学习一下
实验要求:把 LED 跳线打开左侧,把遥控跳线打到左侧
实验现象:无论按下遥控任何一个按键 LED1 均闪亮,闪几次后熄灭
AVR—GCC 版本程序如下
/*
Title:
AVR-GCC irtest
Author: dushibiao
Date:
2007 10 19
Purpose:
use irfred to control led1
Frequency: internal 8M
needed
Software: AVR-GCC to compile
needed
Hardware: AVR mega16 BOARD
Connect: [email protected]
*/
#include <avr/io.h>
#include <avr/delay.h>
#include <avr/signal.h>
#include <avr/interrupt.h>
#define LED_PIN
0
int main(void)
{
DDRA=0XFF;
PORTA=0XFF;
PORTB=0XFF;
PORTC=0XFF;
PORTD=0XFF;
MCUCSR &=~(1<<ISC2);
GIFR=(1<<INTF2);
志位清除,以免误触发
GICR=(1<<INT2);
//LED1 to indicate that an interrupt has occrured
//OUT
//HIGH LEVEL
//PULL UP
//falling edge interrupt
//写 1 清除标志位,在使能中断前最好先把对应的标
//使能 INT2 外部中断
167
AVR 单片机学习开发板教程
dushibiao
sei();
while(1);
2007 年 11 月
//使能全局中断
}
INTERRUPT(SIG_INTERRUPT2) //INT2 中断服务程序
{
int i;
//硬件自动清除 INTF2 标志位
//这里全局中断被打开,将允许其他中断嵌套执行
PORTA^=(1<<LED_PIN);
for(i=0;i<10;i++)
_delay_ms(20);
}
ICC--AVR 版本程序如下
/*
Title:
icc-avr irtest
Author: dushibiao
Date:
2007 10 19
Purpose:
use irfred to control led1
Frequency: internal 8M
needed
Software: icc-avr to compile
needed
Hardware: AVR mega16 BOARD
Connect: [email protected]
*/
#include <iom16v.h>
#include <macros.h>
#define LED_PIN
0
void delay_1ms(void)
{
unsigned int i;
for (i=0;i<1140;i++);
}
void delay_nms(unsigned int n)
{
unsigned int i=0;
for (i=0;i<n;i++)
delay_1ms();
//LED1 to indicate that an interrupt has occrured
//1ms 延时函数
//N ms 延时函数
168
AVR 单片机学习开发板教程
dushibiao
2007 年 11 月
}
int main(void)
{
DDRA=0XFF;
PORTA=0XFF;
PORTB=0XFF;
PORTC=0XFF;
PORTD=0XFF;
MCUCSR &=~(1<<ISC2);
//OUT
//HIGH LEVEL
//PULL UP
//falling edge interrupt
GIFR=(1<<INTF2);
//写 1 清除标志位,在使能中断前最好先把对应的标
志位清除,以免误触发
GICR=(1<<INT2);
//使能 INT2 外部中断
SEI();
//使能全局中断
while(1);
}
/**********************************************************************
function: ext2 interrupt service function
**********************************************************************/
#pragma interrupt_handler int2pro: iv_INT2
void int2pro(void) //INT2 中断服务程序
{
PORTA^=(1<<LED_PIN);
delay_nms(300);
//取反
}
实验二十一
红外解码实验
实验目的:学习 AVR 单片机与红外接口的方法
实验准备:请把红外知识学习一下
实验要求:把 84 数码管与液晶跳线打开左侧,把遥控跳线打到左侧
实验现象:按下遥控对应键,则数码管显示对应的码值
AVR—GCC 版本程序如下
/*
Title:
AVR-GCC irdecode
Author: dushibiao
Date:
2007 10 21
Purpose: decode irfraed gray
Frequency: internal 8M
Software: AVR-GCC to compile
169
AVR 单片机学习开发板教程
dushibiao
2007 年 11 月
Hardware: AVR mega16 BOARD
Connect: [email protected]
*/
#include <avr/io.h>
#include <avr/signal.h>
#include <avr/interrupt.h>
#include <avr/delay.h>
#include "shumaguan.h"
#define
IR
2
//PB2,irfrade receive line
volatile unsigned char flag,code;
//0.9ms
void delay0_9ms(void)
{
unsigned char j;
for(j=15;j>0;j--)
_delay_us(60) ;
}
//4.5ms
void delay4_5ms(void)
{
unsigned char i;
for(i=10;i>0;i--)
_delay_us(53);
_delay_ms(4);
}
//100ms
void delay100ms(void)
{
unsigned char i;
for(i=5;i>0;i--)
_delay_ms(20);
}
int main(void)
{
PORTA=0XFF;
DDRA=0XFF;
PORTB=0XFF;
DDRB=0X03;
//OUT
//PB0,PB1
OUT
170
AVR 单片机学习开发板教程
PORTC=0XFF;
DDRD=0XFF;
MCUCSR &=~(1<<ISC2);
GIFR=(1<<INTF2);
志位清除,以免误触发
GICR=(1<<INT2);
sei();
while(1)
{
if(flag==1)
{
display(code);
}
else
{
displayerror();
}
}
dushibiao
2007 年 11 月
//falling edge interrupt
//写 1 清除标志位,在使能中断前最好先把对应的标
//使能 INT2 外部中断
//使能全局中断
}
SIGNAL(SIG_INTERRUPT2) //INT2 中断服务程序
{
unsigned char i,j,k,dm=0,aa[2];
for(k=0;k<10;k++)
//重复 10 次,目的是检测在 9 毫秒内如果出现高电平就
退出解码程序
{
//aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
delay0_9ms();
//延时 0.9 毫秒
if ((PINB&(1<<IR))==(1<<IR))
{k=10;break;}
//延时 0.9 毫秒后判断 PB2 脚是否出现高电平如果有就退出解
码程序
else if(k==9)
//重复 10 次?
{
//bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
while((PINB&(1<<IR))==0);
//等待高电平避开 9 毫秒低电平引导脉冲
delay4_5ms();
//延时 4.5 毫秒避开 4.5 毫秒的结果码
/********************************************
//26 位的用户码,前 13 位数据码,后 13 位数据反码
*********************************************/
for(j=1;j<=26;j++)//每组数据为 26 位
{
while((PINB&(1<<IR))==0); //
171
AVR 单片机学习开发板教程
dushibiao
2007 年 11 月
delay0_9ms();
if((PINB&(1<<IR))==(1<<IR))
{
_delay_ms(1);
}
}
/***************************************************
//16 位的用户码,前 8 位数据码,后 8 位数据反码
****************************************************/
for(i=0;i<2;i++)
{
for(j=1;j<=8;j++)//每组数据为 8 位
{
while((PINB&(1<<IR))==0); //
delay0_9ms();
if((PINB&(1<<IR))==(1<<IR))
{
_delay_ms(1);
dm=dm|0x80;
if(j<8) dm=dm>>1;
//数据"1",右移一个"1",一共 7 次
}
else if(j<8) dm=dm>>1;
//数据"0",一共 7 次
}
aa[i]=dm;
dm=0;
}
code=aa[0];
flag=1;
/***************************************************
//显示部分
****************************************************/
}
}
//bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
//aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
}
ICC--AVR 版本程序如下
172
AVR 单片机学习开发板教程
dushibiao
2007 年 11 月
/*
Title:
icc-avr irdecode
Author: dushibiao
Date:
2007 10 19
Purpose:
decode irfrade gray and display it
Frequency: internal 8M
needed
Software: icc-avr to compile
needed
Hardware: AVR mega16 BOARD
Connect: [email protected]
*/
#include <iom16v.h>
#include <macros.h>
#include "shumaguan.h"
#define
IR
2
//PB2,irfrade receive line
volatile unsigned char flag,code;
void delay0_9ms(void)
{
delay_nus(510);
}
//4.5ms
void delay4_5ms(void)
{
delay_nms(4);
delay_nus(320);
}
void main(void)
{
PORTA=0XFF;
DDRA=0XFF;
PORTB=0XFF;
DDRB=0X03;
PORTC=0XFF;
DDRD=0XFF;
MCUCSR &=~(1<<ISC2);
GIFR=(1<<INTF2);
志位清除,以免误触发
//OUT
//PB0,PB1
OUT
//falling edge interrupt
//写 1 清除标志位,在使能中断前最好先把对应的标
173
AVR 单片机学习开发板教程
GICR=(1<<INT2);
SEI();
flag=0;
while(1)
{
if(flag==1)
{
display(code);
}
else
{
displayerror();
}
dushibiao
2007 年 11 月
//使能 INT2 外部中断
//使能全局中断
}
}
/**********************************************************************
function: ext2 interrupt service function
**********************************************************************/
#pragma interrupt_handler int2pro: iv_INT2
void int2pro(void) //INT2 中断服务程序
{
unsigned char i,j,k,dm=0,aa[2];
for(k=0;k<10;k++)
//重复 10 次,目的是检测在 9 毫秒内如果出现高电平就
退出解码程序
{
//aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
delay0_9ms();
//延时 0.9 毫秒
if ((PINB&(1<<IR))==(1<<IR))
{k=10;break;}
//延时 0.9 毫秒后判断 PB2 脚是否出现高电平如果有就退出解
码程序
else if(k==9)
//重复 10 次?
{
//bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
while((PINB&(1<<IR))==0);
//等待高电平避开 9 毫秒低电平引导脉冲
delay4_5ms();
//延时 4.5 毫秒避开 4.5 毫秒的结果码
/********************************************
//26 位的用户码,前 13 位数据码,后 13 位数据反码
*********************************************/
for(j=1;j<=26;j++)//每组数据为 26 位
{
while((PINB&(1<<IR))==0); //
174
AVR 单片机学习开发板教程
delay0_9ms();
if((PINB&(1<<IR))==(1<<IR))
{
delay_nms(1);
//
dm=dm|0x2000000;
25 个"0",转化二进制即为"1000000"
//
if(j<26) dm=dm>>1;
次
}
// else if(j<26) dm=dm>>1;
}
//bb=dm;
//dm=0;
dushibiao
2007 年 11 月
//因为是 26 位,一个"1",后面
//数据"1",右移一个"1",一共 25
//数据"0",一共 25 次
/***************************************************
//16 位的用户码,前 8 位数据码,后 8 位数据反码
****************************************************/
for(i=0;i<2;i++)
{
for(j=1;j<=8;j++)//每组数据为 8 位
{
while((PINB&(1<<IR))==0); //
delay0_9ms();
if((PINB&(1<<IR))==(1<<IR))
{
delay_nms(1);
dm=dm|0x80;
if(j<8) dm=dm>>1;
//数据"1",右移一个"1",一共 7 次
}
else if(j<8) dm=dm>>1;
//数据"0",一共 7 次
}
aa[i]=dm;
dm=0;
}
if(aa[0]=~aa[1])
{
code=aa[0];
flag=1;
}
}
175
AVR 单片机学习开发板教程
dushibiao
2007 年 11 月
}
}
实验二十二
红外遥控电脑实验
实验目的:学习 AVR 单片机与红外接口的方法
实验准备:请把红外知识学习一下,把上位机所用软件 Gird 熟悉一下,光盘中有说明,请
将此部分仔细阅读一下。
实验要求:把遥控跳线打到左侧
实验现象:根据 Gird 的设置你可以遥控你的电脑,比如开大声音,调低声音。
AVR—GCC 版本程序如下
/*
Title:
AVR-GCC irremote
Author: dushibiao
Date:
2007 10 21
Purpose:
control your pc use a ir remotter
Frequency: internal 8M
Software: AVR-GCC to compile
Hardware: AVR mega16 BOARD
Connect: [email protected]
*/
#include <avr/io.h>
#include <avr/signal.h>
#include <avr/interrupt.h>
#include <avr/delay.h>
#include "shumaguan.h"
//管脚定义
#define PIN_RXD
#define PIN_TXD
0
1
//常量定义
#define BAUDRATE
//#define F_CPU
9600
800000
#define
IR
2
//PD0
//PD1
RXD
TXD
//波特率
//这个已经在 makefile 里面定义了
//PB2,irfrade receive line
void put_c(unsigned char c) //发送采用查询方式
{
while( !(UCSRA & (1<<UDRE)) );
//wait until the uart is empty
UDR=c;
//write data to uart
}
176
AVR 单片机学习开发板教程
dushibiao
2007 年 11 月
volatile unsigned char flag,code;
//0.9ms
void delay0_9ms(void)
{
unsigned char j;
for(j=15;j>0;j--)
_delay_us(60) ;
}
//4.5ms
void delay4_5ms(void)
{
unsigned char i;
for(i=10;i>0;i--)
_delay_us(53);
_delay_ms(4);
}
//100ms
void delay100ms(void)
{
unsigned char i;
for(i=5;i>0;i--)
_delay_ms(20);
}
void init_USART(void)//USART 初始化
{
//USART 9600 8, n,1 PC 上位机软件(超级终端等)也要设成同样的设置才能通讯
UCSRC = (1<<URSEL) | 0x06;
//异步,8 位数据,无奇偶校验,一个停止位,无倍速
/*
UBRRH 与 UCSRC 共用 I/O 地址。因此访问该地址时需注意以下问题。
写访问
当在该地址执行写访问时, USART 寄存器选择位(URSEL)控制被写入的寄存器。
若 URSEL 为 0,对 UBRRH 值更新;若 URSEL 为 1,对 UCSRC 设置更新
读访问
对 UBRRH 或 UCSRC 寄存器的读访问则较为复杂。但在大多数应用中,基本不需要
读这些寄存器
177
AVR 单片机学习开发板教程
dushibiao
2007 年 11 月
没有 UBRR 这个 16 位寄存器,因为 UBRRL(0x09)/UBRRH(0x20)的地址不连续,而且
UBRRH 跟 UCSRC 共用地址
*/
//U2X=0 时的公式计算
UBRRL= (F_CPU/BAUDRATE/16-1)%256;
UBRRH= (F_CPU/BAUDRATE/16-1)/256;
//U2X=1 时的公式计算
//UBRRL= (F_CPU/BAUDRATE/8-1)%256;
//UBRRH= (F_CPU/BAUDRATE/8-1)/256;
//也可根据数据手册的[波特率设置的例子]查得
//UBRRL = 0x2F; //set baud rate lo
//UBRRH = 0x00; //set baud rate hi
UCSRA = 0x00;
//无倍速
UCSRB = (1<<RXCIE)|(1<<RXEN)|(1<<TXEN);
//使能接收中断,使能接收,使能发送
}
int main(void)
{
PORTA=0XFF;
DDRA=0XFF;
PORTB=0XFF;
DDRB=0X03;
PORTC=0XFF;
DDRD =(1<<PIN_TXD);
init_USART();
MCUCSR &=~(1<<ISC2);
GIFR=(1<<INTF2);
志位清除,以免误触发
GICR=(1<<INT2);
sei();
while(1)
{
if(flag==1)
{
display(code);
}
else
{
displayerror();
}
}
//OUT
//PB0,PB1
OUT
//falling edge interrupt
//写 1 清除标志位,在使能中断前最好先把对应的标
//使能 INT2 外部中断
//使能全局中断
178
AVR 单片机学习开发板教程
dushibiao
2007 年 11 月
}
SIGNAL(SIG_INTERRUPT2) //INT2 中断服务程序
{
unsigned char i,j,k,dm=0,aa[2];
for(k=0;k<10;k++)
//重复 10 次,目的是检测在 9 毫秒内如果出现高电平就
退出解码程序
{
//aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
delay0_9ms();
//延时 0.9 毫秒
if ((PINB&(1<<IR))==(1<<IR))
{k=10;break;}
//延时 0.9 毫秒后判断 PB2 脚是否出现高电平如果有就退出解
码程序
else if(k==9)
//重复 10 次?
{
//bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
while((PINB&(1<<IR))==0);
//等待高电平避开 9 毫秒低电平引导脉冲
delay4_5ms();
//延时 4.5 毫秒避开 4.5 毫秒的结果码
/********************************************
//26 位的用户码,前 13 位数据码,后 13 位数据反码
*********************************************/
for(j=1;j<=26;j++)//每组数据为 26 位
{
while((PINB&(1<<IR))==0); //
delay0_9ms();
if((PINB&(1<<IR))==(1<<IR))
{
_delay_ms(1);
}
}
/***************************************************
//16 位的用户码,前 8 位数据码,后 8 位数据反码
****************************************************/
for(i=0;i<2;i++)
{
for(j=1;j<=8;j++)//每组数据为 8 位
{
while((PINB&(1<<IR))==0); //
179
AVR 单片机学习开发板教程
dushibiao
delay0_9ms();
if((PINB&(1<<IR))==(1<<IR))
{
_delay_ms(1);
dm=dm|0x80;
if(j<8) dm=dm>>1;
}
else if(j<8) dm=dm>>1;
2007 年 11 月
//数据"1",右移一个"1",一共 7 次
//数据"0",一共 7 次
}
aa[i]=dm;
dm=0;
}
code=aa[0];
flag=1;
put_c(code);
/***************************************************
//显示部分
****************************************************/
}
//bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
//aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
}
}
ICC--AVR 版本程序如下
/*
Title:
icc-avr irremote
Author: dushibiao
Date:
2007 10 19
Purpose:
control your pc use a ir remoter
Frequency: internal 8M
needed
Software: icc-avr to compile
needed
Hardware: AVR mega16 BOARD
Connect: [email protected]
*/
#include <iom16v.h>
#include <macros.h>
#include "shumaguan.h"
#define IR
2
#define PIN_RXD
0
//PB2,irfrade receive line
//PD0
RXD
180
AVR 单片机学习开发板教程
#define PIN_TXD
#define BAUDRATE
#define F_CPU
dushibiao
2007 年 11 月
1
//PD1
TXD
9600 //baudrate
8000000 //the frequency of the global clock
volatile unsigned char flag,code;
void delay0_9ms(void)
{
delay_nus(510);
}
//4.5ms
void delay4_5ms(void)
{
delay_nms(4);
delay_nus(320);
}
/*--------------------------------------------------------------------------function
send an unsigned char to the uart
----------------------------------------------------------------------------*/
void put_c(unsigned char c) //发送采用查询方式
{
while( !(UCSRA & (1<<UDRE)) );
//wait until the uart is empty
UDR=c;
//write data to uart
}
/*--------------------------------------------------------------------------fuction
initialize the uart unit
----------------------------------------------------------------------------*/
void init_USART(void)//USART 初始化
{
//USART 9600 8, n,1 PC 上位机软件(超级终端等)也要设成同样的设置才能通讯
UCSRC = (1<<URSEL) | 0x06;
//异步,8 位数据,无奇偶校验,一个停止位,无倍速
/*
UBRRH 与 UCSRC 共用 I/O 地址。因此访问该地址时需注意以下问题。
写访问
当在该地址执行写访问时, USART 寄存器选择位(URSEL)控制被写入的寄存器。
若 URSEL 为 0,对 UBRRH 值更新;若 URSEL 为 1,对 UCSRC 设置更新
读访问
对 UBRRH 或 UCSRC 寄存器的读访问则较为复杂。但在大多数应用中,基本不需要
181
AVR 单片机学习开发板教程
dushibiao
2007 年 11 月
读这些寄存器
没有 UBRR 这个 16 位寄存器,因为 UBRRL(0x09)/UBRRH(0x20)的地址不连续,而且
UBRRH 跟 UCSRC 共用地址
*/
//U2X=0 时的公式计算
UBRRL= (F_CPU/BAUDRATE/16-1)%256;
UBRRH= (F_CPU/BAUDRATE/16-1)/256;
//U2X=1 时的公式计算
//UBRRL= (F_CPU/BAUDRATE/8-1)%256;
//UBRRH= (F_CPU/BAUDRATE/8-1)/256;
//也可根据数据手册的[波特率设置的例子]查得
//UBRRL = 0x2F; //set baud rate lo
//UBRRH = 0x00; //set baud rate hi
UCSRA = 0x00;
//无倍速
UCSRB = (1<<RXEN)|(1<<TXEN);
//使能接收,使能发送
}
void main(void)
{
PORTA=0XFF;
DDRA=0XFF;
PORTB=0XFF;
DDRB=0X03;
PORTC=0XFF;
DDRD =(1<<PIN_TXD);
init_USART();
MCUCSR &=~(1<<ISC2);
GIFR=(1<<INTF2);
志位清除,以免误触发
GICR=(1<<INT2);
SEI();
flag=0;
while(1)
{
if(flag==1)
{
display(code);
}
else
{
//OUT
//PB0,PB1
OUT
//TXD 为输出
//falling edge interrupt
//写 1 清除标志位,在使能中断前最好先把对应的标
//使能 INT2 外部中断
//使能全局中断
182
AVR 单片机学习开发板教程
dushibiao
2007 年 11 月
displayerror();
}
}
}
/**********************************************************************
function: ext2 interrupt service function
**********************************************************************/
#pragma interrupt_handler int2pro: iv_INT2
void int2pro(void) //INT2 中断服务程序
{
unsigned char i,j,k,dm=0,aa[2];
for(k=0;k<10;k++)
//重复 10 次,目的是检测在 9 毫秒内如果出现高电平就
退出解码程序
{
//aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
delay0_9ms();
//延时 0.9 毫秒
if ((PINB&(1<<IR))==(1<<IR))
{k=10;break;}
//延时 0.9 毫秒后判断 PB2 脚是否出现高电平如果有就退出解
码程序
else if(k==9)
//重复 10 次?
{
//bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
while((PINB&(1<<IR))==0);
//等待高电平避开 9 毫秒低电平引导脉冲
delay4_5ms();
//延时 4.5 毫秒避开 4.5 毫秒的结果码
/********************************************
//26 位的用户码,前 13 位数据码,后 13 位数据反码
*********************************************/
for(j=1;j<=26;j++)//每组数据为 26 位
{
while((PINB&(1<<IR))==0); //
delay0_9ms();
if((PINB&(1<<IR))==(1<<IR))
{
delay_nms(1);
}
}
/***************************************************
//16 位的用户码,前 8 位数据码,后 8 位数据反码
****************************************************/
183
AVR 单片机学习开发板教程
dushibiao
2007 年 11 月
for(i=0;i<2;i++)
{
for(j=1;j<=8;j++)//每组数据为 8 位
{
while((PINB&(1<<IR))==0); //
delay0_9ms();
if((PINB&(1<<IR))==(1<<IR))
{
delay_nms(1);
dm=dm|0x80;
if(j<8) dm=dm>>1;
//数据"1",右移一个"1",一共 7 次
}
else if(j<8) dm=dm>>1;
//数据"0",一共 7 次
}
aa[i]=dm;
dm=0;
}
if(aa[0]=~aa[1])
{
code=aa[0];
flag=1;
put_c(code);
}
}
}
}
实验二十三
ds18b20 温度计实验
实验目的:学习 AVR 单片机与 ds18b20 的方法
实验准备:请把 ds18b2 知识学习一下
实验要求:把 84 数码管与液晶跳线打到左侧
实验现象:数码管中显示当前的温度值
实验二十四
键盘解码实验
实验目的:学习 AVR 单片机与键盘的连接方法
实验准备:请把 PS2 协议知识学习一下
实验要求:把 84 数码管与液晶跳线打到左侧,本实验中用到串口终端,波特率为 9600
实验现象:数码管中显示当前的键盘的 PS2 值,串口终端中显示中你按下的字母
AVR—GCC 版本程序如下
184
AVR 单片机学习开发板教程
dushibiao
2007 年 11 月
/*
Title:
avr--gcc ps2 test
Author: dushibiao
Date:
2007 10 25
Purpose: decode ps2 keyboard code
Frequency: internal 8M
Software: avr--gcc
Hardware: AVR mega16 BOARD
Connect: [email protected]
*/
#include <avr/io.h>
#include <avr/delay.h>
#include <avr/signal.h>
#include <avr/interrupt.h>
#include "shumaguan.h"
#include "scancodes.h"
#define PORT_KB PORTD
#define PIN_KB PIND
#define PIN_DIR DDRD
#define CLOCK 3
#define DATAPIN 2
//管脚定义
#define PIN_RXD
#define PIN_TXD
0
1
//常量定义
#define BAUDRATE
//#define F_CPU
9600 //baudrate
8000000 //the frequency of the global clock
//PD0
//PD1
RXD
TXD
unsigned char bitcount;
volatile unsigned char origialcode=0;
void intinitial(void);
void portinitial(void); //initialize ports
void init_USART(void);
unsigned char decode(unsigned char sc);
/*-----------------------------------------------------------------
185
AVR 单片机学习开发板教程
dushibiao
2007 年 11 月
function: send a char to the uart
para:
the char to be send
-----------------------------------------------------------------*/
void put_c(unsigned char c) //发送采用查询方式
{
while( !(UCSRA & (1<<UDRE)) );
//wait until the uart is empty
UDR=c;
//write data to uart
}
/*----------------------------------------------------------------function: send a string to the uart
para:
ptr---the pointer point to the string
------------------------------------------------------------------*/
void put_s(unsigned char *ptr)
{
while (*ptr)
{
put_c(*ptr++);
}
put_c(0x0D);
put_c(0x0A); //结尾发送回车换行
}
int main(void)
{
portinitial();
intinitial();
init_USART();
put_s("PS2--KEYOARD test");
put_s("dushibiao");
put_s("[email protected]");
put_c(0x0a);
put_c(0x0d);//换行
sei();
//enable interrupt ,this was predefined in MACROS.h
while(1)
{
display(origialcode);
}
}
/*********************************************************************
186
AVR 单片机学习开发板教程
dushibiao
2007 年 11 月
fuction: ports initialize
**********************************************************************/
void portinitial(void)
{
PORTA=0XFF;
DDRA=0XFF;
//OUT
PORTB=0XFF;
DDRB=0X0F;
//high fout bit in , low four bits out
PORTC=0XFF;
//pull up
PORTD=0XFF;
//pull up
}
/*********************************************************************
fuction: ext interrupt initialize
**********************************************************************/
void intinitial(void)
//int1 ,int0 下降沿中断
{
MCUCR=(1<<ISC10);
//falling edge interrup
GICR |=(1<<INT1);
//enable ext0,ext1 interrupt
bitcount = 11;
}
/*********************************************************************
fuction: ext1 interrupt service routine
**********************************************************************/
SIGNAL(SIG_INTERRUPT1)
{
unsigned char i,code=0;
if ((PIN_KB&(1<<DATAPIN))==0)
{
while((PIN_KB&(1<<CLOCK))==0);
for(i=0;i<8;i++)
{
while(PIN_KB&(1<<CLOCK));
code>>=1;
if ((PIN_KB&(1<<DATAPIN))!=0)
code|=0x80;
while((PIN_KB&(1<<CLOCK))==0);
}
while(PIN_KB&(1<<CLOCK));
while((PIN_KB&(1<<CLOCK))==0);
while(PIN_KB&(1<<CLOCK));
if ((PIN_KB&(1<<DATAPIN))!=0)
{
187
AVR 单片机学习开发板教程
dushibiao
2007 年 11 月
origialcode=code;
code=decode(code);
if(code!=0)
put_c(code);
}
}
}
/*------------------------------------------------------------------function: initialize uart
--------------------------------------------------------------------*/
void init_USART(void)//USART 初始化
{
//USART 9600 8, n,1 PC 上位机软件(超级终端等)也要设成同样的设置才能通讯
UCSRC = (1<<URSEL) | 0x06;
//异步,8 位数据,无奇偶校验,一个停止位,无倍速
/*
UBRRH 与 UCSRC 共用 I/O 地址。因此访问该地址时需注意以下问题。
写访问
当在该地址执行写访问时, USART 寄存器选择位(URSEL)控制被写入的寄存器。
若 URSEL 为 0,对 UBRRH 值更新;若 URSEL 为 1,对 UCSRC 设置更新
读访问
对 UBRRH 或 UCSRC 寄存器的读访问则较为复杂。但在大多数应用中,基本不需要
读这些寄存器
没有 UBRR 这个 16 位寄存器,因为 UBRRL(0x09)/UBRRH(0x20)的地址不连续,而且
UBRRH 跟 UCSRC 共用地址
*/
//U2X=0 时的公式计算
UBRRL= (F_CPU/BAUDRATE/16-1)%256;
UBRRH= (F_CPU/BAUDRATE/16-1)/256;
//U2X=1 时的公式计算
//UBRRL= (F_CPU/BAUDRATE/8-1)%256;
//UBRRH= (F_CPU/BAUDRATE/8-1)/256;
//也可根据数据手册的[波特率设置的例子]查得
//UBRRL = 0x2F; //set baud rate lo
//UBRRH = 0x00; //set baud rate hi
UCSRA = 0x00;
//无倍速
UCSRB = (1<<RXCIE)|(1<<RXEN)|(1<<TXEN);
//使能接收中断,使能接收,使能发送
188
AVR 单片机学习开发板教程
dushibiao
2007 年 11 月
}
/*-------------------------------------------------------------------------function: decode the ps2 code to ASCII
para:
sr----ps2 code
return
ASCII code
--------------------------------------------------------------------------*/
unsigned char decode(unsigned char sc)
{
static unsigned char shift=0,up=0,shiftup=0;
unsigned char i;
if (sc==0xf0)
{
up=1;
return 0;
}
if (up==1)
{
up=0;
if ((sc==0x12)|(sc==0x59)) shift=0;
return 0;
}
switch (sc)
{
case 0x12:{
shift=1;
shiftup=1;
}
case 0x59:{
shift=1;
shiftup=1;
}
default:{
if (shift==0)
{
for(i = 0;unshifted[i][0]!=sc && unshifted[i][0]; i++);
if (unshifted[i][0] == sc)
return unshifted[i][1];
}
else
{
for(i = 0;shifted[i][0]!=sc && shifted[i][0]; i++);
if (shifted[i][0] == sc)
{
189
AVR 单片机学习开发板教程
dushibiao
2007 年 11 月
return shifted[i][1];
}
}
}
return 0;
}
}
ICC--AVR 版本程序如下
/*
Title:
icc-avr ps2 test
Author: dushibiao
Date:
2007 10 25
Purpose: decode ps2 keyboard code
Frequency: internal 8M
Software: icc-avr
Hardware: AVR mega16 BOARD
Connect: [email protected]
*/
#include <iom16v.h>
#include "scancodes.h"
#include "shumaguan.h"
#define PORT_KB PORTD
#define PIN_KB PIND
#define PIN_DIR DDRD
#define CLOCK 3
#define DATAPIN 2
//管脚定义
#define PIN_RXD
#define PIN_TXD
0
1
//常量定义
#define BAUDRATE
#define F_CPU
9600 //baudrate
8000000 //the frequency of the global clock
//PD0
//PD1
RXD
TXD
unsigned char bitcount;
volatile unsigned char origialcode=0;
190
AVR 单片机学习开发板教程
dushibiao
2007 年 11 月
void intinitial(void);
void portinitial(void); //initialize ports
void init_USART(void);
unsigned char decode(unsigned char sc);
/*----------------------------------------------------------------function: send a char to the uart
para:
the char to be send
-----------------------------------------------------------------*/
void put_c(unsigned char c) //发送采用查询方式
{
while( !(UCSRA & (1<<UDRE)) );
//wait until the uart is empty
UDR=c;
//write data to uart
}
/*----------------------------------------------------------------function: send a string to the uart
para:
ptr---the pointer point to the string
------------------------------------------------------------------*/
void put_s(unsigned char *ptr)
{
while (*ptr)
{
put_c(*ptr++);
}
put_c(0x0D);
put_c(0x0A); //结尾发送回车换行
}
void
{
main(void)
portinitial();
intinitial();
init_USART();
put_s("PS2--KEYOARD test");
put_s("dushibiao");
put_s("[email protected]");
put_c(0x0a);
put_c(0x0d);//换行
SEI();
//enable interrupt ,this was predefined in MACROS.h
191
AVR 单片机学习开发板教程
dushibiao
2007 年 11 月
while(1)
{
display(origialcode);
}
}
/*********************************************************************
fuction: ports initialize
**********************************************************************/
void portinitial(void)
{
PORTA=0XFF;
DDRA=0XFF;
//OUT
PORTB=0XFF;
DDRB=0X0F;
//high fout bit in , low four bits out
PORTC=0XFF;
//pull up
PORTD=0XFF;
//pull up
}
/*********************************************************************
fuction: ext interrupt initialize
**********************************************************************/
void intinitial(void)
//int1 ,int0 下降沿中断
{
MCUCR=(1<<ISC10);
//falling edge interrup
GICR |=(1<<INT1);
//enable ext0,ext1 interrupt
bitcount = 11;
}
/*********************************************************************
fuction: ext1 interrupt service routine
**********************************************************************/
#pragma interrupt_handler int1pro: iv_INT1
void int1pro(void)
{
unsigned char i,code;
if ((PIN_KB&(1<<DATAPIN))==0)
{
while((PIN_KB&(1<<CLOCK))==0);
for(i=0;i<8;i++)
{
while(PIN_KB&(1<<CLOCK));
code>>=1;
if ((PIN_KB&(1<<DATAPIN))!=0)
192
AVR 单片机学习开发板教程
dushibiao
2007 年 11 月
code|=0x80;
while((PIN_KB&(1<<CLOCK))==0);
}
while(PIN_KB&(1<<CLOCK));
while((PIN_KB&(1<<CLOCK))==0);
while(PIN_KB&(1<<CLOCK));
if ((PIN_KB&(1<<DATAPIN))!=0)
{
origialcode=code;
code=decode(code);
if(code!=0)
put_c(code);
}
}
}
/*------------------------------------------------------------------function: initialize uart
--------------------------------------------------------------------*/
void init_USART(void)//USART 初始化
{
//USART 9600 8, n,1 PC 上位机软件(超级终端等)也要设成同样的设置才能通讯
UCSRC = (1<<URSEL) | 0x06;
//异步,8 位数据,无奇偶校验,一个停止位,无倍速
/*
UBRRH 与 UCSRC 共用 I/O 地址。因此访问该地址时需注意以下问题。
写访问
当在该地址执行写访问时, USART 寄存器选择位(URSEL)控制被写入的寄存器。
若 URSEL 为 0,对 UBRRH 值更新;若 URSEL 为 1,对 UCSRC 设置更新
读访问
对 UBRRH 或 UCSRC 寄存器的读访问则较为复杂。但在大多数应用中,基本不
需要读这些寄存器
没有 UBRR 这个 16 位寄存器,因为 UBRRL(0x09)/UBRRH(0x20)的地址不连续,
而且 UBRRH 跟 UCSRC 共用地址
*/
//U2X=0 时的公式计算
UBRRL= (F_CPU/BAUDRATE/16-1)%256;
UBRRH= (F_CPU/BAUDRATE/16-1)/256;
//U2X=1 时的公式计算
193
AVR 单片机学习开发板教程
dushibiao
2007 年 11 月
//UBRRL= (F_CPU/BAUDRATE/8-1)%256;
//UBRRH= (F_CPU/BAUDRATE/8-1)/256;
//也可根据数据手册的[波特率设置的例子]查得
//UBRRL = 0x2F; //set baud rate lo
//UBRRH = 0x00; //set baud rate hi
UCSRA = 0x00;
//无倍速
UCSRB = (1<<RXCIE)|(1<<RXEN)|(1<<TXEN);
//使能接收中断,使能接收,使能发送
}
/*-------------------------------------------------------------------------function: decode the ps2 code to ASCII
para:
sr----ps2 code
return
ASCII code
--------------------------------------------------------------------------*/
unsigned char decode(unsigned char sc)
{
static unsigned char shift=0,up=0,shiftup=0;
unsigned char i;
if (sc==0xf0)
{
up=1;
return 0;
}
if (up==1)
{
up=0;
if ((sc==0x12)|(sc==0x59)) shift=0;
return 0;
}
switch (sc)
{
case 0x12:{
shift=1;
shiftup=1;
}
case 0x59:{
shift=1;
shiftup=1;
}
default:{
if (shift==0)
{
for(i = 0;unshifted[i][0]!=sc && unshifted[i][0]; i++);
194
AVR 单片机学习开发板教程
dushibiao
2007 年 11 月
if (unshifted[i][0] == sc)
return unshifted[i][1];
}
else
{
for(i = 0;shifted[i][0]!=sc && shifted[i][0]; i++);
if (shifted[i][0] == sc)
{
return shifted[i][1];
}
}
}
return 0;
}
}
第六章
实验二十五
综合性实验
串口软件控件控制 LED 发光管
实验目的:学习单片机与上位机软件的通信方式
实验准备:请把串口模块知识学习一下
实验要求:把 LED 跳线打到左侧,本实验中用到串口终端,波特率为 9600
实验现象:软件中点中对应的 LED,开发板上的对应 LED 点亮
AVR—GCC 版本程序如下
/*
Title:
AVR-GCC uartled
Author: dushibiao
Date:
2007 10 27
Purpose: learn how to use uart
Frequency: Ext 8M
195
AVR 单片机学习开发板教程
dushibiao
2007 年 11 月
needed
Software: AVR-GCC to compile
needed
Hardware: AVR mega16 BOARD
Connect: [email protected]
*/
#include <avr/io.h>
#include <avr/delay.h>
#include <avr/signal.h>
#include <avr/interrupt.h>
//常量定义
#define BAUDRATE
//#define F_CPU
9600
800000
//管脚定义
#define PIN_RXD
#define PIN_TXD
#define NULL
#define READ
#define WRITE
0
//PD0
1 //PD1
0x00
0X01
0X02
//波特率
//这个已经在 makefile 里面定义了
RXD
TXD
volatile struct COMUNICATION
{
unsigned char flag;
unsigned char data;
}communication;
void init_USART(void);
void put_c(unsigned char c) //发送采用查询方式
{
while( !(UCSRA & (1<<UDRE)) );
//wait until the uart is empty
UDR=c;
//write data to uart
}
int main(void)
{
PORTA=0XFF;
196
AVR 单片机学习开发板教程
DDRA=0XFF;
PORTB=0XFF;
PORTC=0XFF;
PORTD=0XFF;
DDRD|=(1<<PIN_TXD);
init_USART();
sei();
while(1)
{
dushibiao
2007 年 11 月
//PULL-UP
//PULL-UP
//TXD--PIN
OUT
if(communication.flag==READ)
{
communication.flag=NULL;
DDRA=0X00;
_delay_us(20);
put_c(WRITE);
put_c(PINA);
}
else if(communication.flag==WRITE)
{
communication.flag=NULL;
DDRA=0XFF;
_delay_us(20);
PORTA=communication.data;
}
}
}
/*---------------------------------------------------------------------fuction: initialize uart unit
----------------------------------------------------------------------*/
void init_USART(void)//USART 初始化
{
//USART 9600 8, n,1 PC 上位机软件(超级终端等)也要设成同样的设置才能通讯
UCSRC = (1<<URSEL) | 0x06;
//异步,8 位数据,无奇偶校验,一个停止位,无倍速
/*
UBRRH 与 UCSRC 共用 I/O 地址。因此访问该地址时需注意以下问题。
写访问
当在该地址执行写访问时, USART 寄存器选择位(URSEL)控制被写入的寄存器。
若 URSEL 为 0,对 UBRRH 值更新;若 URSEL 为 1,对 UCSRC 设置更新
197
AVR 单片机学习开发板教程
dushibiao
2007 年 11 月
读访问
对 UBRRH 或 UCSRC 寄存器的读访问则较为复杂。但在大多数应用中,基本不需要
读这些寄存器
没有 UBRR 这个 16 位寄存器,因为 UBRRL(0x09)/UBRRH(0x20)的地址不连续,而且
UBRRH 跟 UCSRC 共用地址
*/
//U2X=0 时的公式计算
UBRRL= (F_CPU/BAUDRATE/16-1)%256;
UBRRH= (F_CPU/BAUDRATE/16-1)/256;
//U2X=1 时的公式计算
//UBRRL= (F_CPU/BAUDRATE/8-1)%256;
//UBRRH= (F_CPU/BAUDRATE/8-1)/256;
//也可根据数据手册的[波特率设置的例子]查得
//UBRRL = 0x2F; //set baud rate lo
//UBRRH = 0x00; //set baud rate hi
UCSRA = 0x00;
//无倍速
UCSRB = (1<<RXCIE)|(1<<RXEN)|(1<<TXEN);
//使能接收中断,使能接收,使能发送
}
SIGNAL(SIG_USART_RECV) //串口接收中断服务程序
{
communication.flag=UDR;
while(!(UCSRA&(1<<RXC)));
//wait for data t
communication.data=UDR;
}
实验二十六
串口软件读取拨码管开关
实验目的:学习单片机与上位机软件的通信方式
实验准备:请把串口模块知识学习一下
实验要求:把 165 跳线打到左侧,本实验中用到串口终端,波特率为 9600
实验现象:上位机软件实时读取拨码管状态
198
AVR 单片机学习开发板教程
dushibiao
2007 年 11 月
/*
Title:
AVR-GCC uartkey
Author: dushibiao
Date:
2007 11 1
Purpose: display digtial data in a software
Frequency: internal 8M
needed
Software: AVR-GCC to compile
needed
Hardware: AVR mega16 BOARD
Connect: [email protected]
*/
#include <avr/io.h>
#include <avr/delay.h>
#include <avr/signal.h>
#include <avr/interrupt.h>
//常量定义
#define BAUDRATE
//#define F_CPU
9600
800000
//管脚定义
#define PIN_RXD
#define PIN_TXD
#define SCK 7
#define MISO 6
#define PL
4
#define NULL
#define READ
#define WRITE
0
//PD0
1 //PD1
//PB7
//PB6
//PB4
0x00
0X01
0X02
//波特率
//这个已经在 makefile 里面定义了
RXD
TXD
199
AVR 单片机学习开发板教程
dushibiao
2007 年 11 月
void spiinit(void);
volatile struct COMUNICATION
{
unsigned char flag;
unsigned char data;
}communication;
void init_USART(void);
void put_c(unsigned char c) //发送采用查询方式
{
while( !(UCSRA & (1<<UDRE)) );
//wait until the uart is empty
UDR=c;
//write data to uart
}
int main(void)
{
PORTA=0XFF;
DDRA=0XFF;
PORTB=0XFF;
DDRB=(1<<SCK)|(1<<PL);
PORTC=0XFF;
PORTD=0XFF;
DDRD|=(1<<PIN_TXD);
init_USART();
spiinit();
sei();
while(1)
{
if(communication.flag==READ)
{
if(communication.data==0xfe)
{
communication.flag=NULL;
PORTB&=~(1<<PL);
_delay_us(10);
PORTB|=(1<<PL);
SPDR=0XAA;
while(!(SPSR&(1<<SPIF)));
//PULL-UP
//PB7,PB4 OUT
//PULL-UP
//TXD--PIN
OUT
//wait until spi receive succeed
200
AVR 单片机学习开发板教程
dushibiao
PORTA=~SPDR;
put_c(READ);
put_c(SPDR);
}
//light led
2007 年 11 月
to the corresponding bit
}
}
}
/*---------------------------------------------------------------------fuction: initialize uart unit
----------------------------------------------------------------------*/
void init_USART(void)//USART 初始化
{
//USART 9600 8, n,1 PC 上位机软件(超级终端等)也要设成同样的设置才能通讯
UCSRC = (1<<URSEL) | 0x06;
//异步,8 位数据,无奇偶校验,一个停止位,无倍速
/*
UBRRH 与 UCSRC 共用 I/O 地址。因此访问该地址时需注意以下问题。
写访问
当在该地址执行写访问时, USART 寄存器选择位(URSEL)控制被写入的寄存器。
若 URSEL 为 0,对 UBRRH 值更新;若 URSEL 为 1,对 UCSRC 设置更新
读访问
对 UBRRH 或 UCSRC 寄存器的读访问则较为复杂。但在大多数应用中,基本不需要
读这些寄存器
没有 UBRR 这个 16 位寄存器,因为 UBRRL(0x09)/UBRRH(0x20)的地址不连续,而且
UBRRH 跟 UCSRC 共用地址
*/
//U2X=0 时的公式计算
UBRRL= (F_CPU/BAUDRATE/16-1)%256;
UBRRH= (F_CPU/BAUDRATE/16-1)/256;
//U2X=1 时的公式计算
//UBRRL= (F_CPU/BAUDRATE/8-1)%256;
//UBRRH= (F_CPU/BAUDRATE/8-1)/256;
//也可根据数据手册的[波特率设置的例子]查得
//UBRRL = 0x2F; //set baud rate lo
//UBRRH = 0x00; //set baud rate hi
UCSRA = 0x00;
//无倍速
UCSRB = (1<<RXCIE)|(1<<RXEN)|(1<<TXEN);
201
AVR 单片机学习开发板教程
2007 年 11 月
dushibiao
//使能接收中断,使能接收,使能发送
}
SIGNAL(SIG_USART_RECV) //串口接收中断服务程序
{
communication.flag=UDR;
while(!(UCSRA&(1<<RXC)));
//wait for data t
communication.data=UDR;
}
/*---------------------------------------------------------------fuction: initialize spi unit-----Master Mode
------------------------------------------------------------------*/
void spiinit(void)
{
SPCR=(1<<SPE)|(1<<MSTR)|(1<<SPR1)|(1<<SPR0);
//enable spi,Master Mode
//Msb
first,rising
edge
sample,prescaler 128
/*------------------------------------------------------------SPCR
7
SPIE---------SPI Interrupt Enable 1---enable,0---disable
6
SPE----------SPI Enable
1---enable,0---disable
5
DORD---------Data Order
1---LSB first, 0---MSB first
4
MSTR---------Mastr/Slave Select
1---Master Mode,0----Slave Mode
3
CPOL---------Clock Polarity
CPOL
Leading Edge
Trailing Edge
0
rising
falling
1
falling
rising
2
CPHA---------Clock Phase
CPHA
Leading Edge
Trailing Edge
0
sample
setup
1
setup
sample
1:0
SPR1:0
SPI Clock Rate Select 1 and 0
SPI2X
SPR1
SPR0
SCK Frequency
0
0
0
f(osc)/4
0
0
1
f(osc)/16
0
1
0
f(osc)/64
0
1
1
f(osc)/128
1
0
0
f(osc)/2
1
0
1
f(osc)/8
1
1
0
f(osc)/32
1
1
1
f(osc)/64
--------------------------------------------------------------*/
}
202
AVR 单片机学习开发板教程
实验二十七
dushibiao
2007 年 11 月
串口软件读取外部电压值
实验目的:学习单片机与上位机软件的通信方式
实验准备:请把串口模块及 AD 转换知识学习一下
实验要求:把 AD 跳线打到左侧,本实验中用到串口终端,波特率为 9600,将 LED 用语 84
数码管与液晶选择跳线打到右侧
实验现象:上位机软件实时读取外部通道 0 电压值
/*
Title:
AVR-GCC uartad
Author: dushibiao
Date:
2007 11 1
Purpose: collect ad value from mcu to a computer
Frequency: internal 8M
needed
Software: AVR-GCC to compile
needed
Hardware: AVR mega16 BOARD
Connect: [email protected]
*/
#include <avr/io.h>
#include <avr/delay.h>
#include <avr/signal.h>
#include <avr/interrupt.h>
//常量定义
#define BAUDRATE
//#define F_CPU
9600
800000
//波特率
//这个已经在 makefile 里面定义了
//管脚定义
#define PIN_RXD
0
//PD0
#define PIN_TXD
1 //PD1
#define NULL
0x00
#define READ
0X01
#define WRITE
0X02
volatile struct COMUNICATION
{
unsigned char flag;
unsigned char data;
}communication;
RXD
TXD
203
AVR 单片机学习开发板教程
dushibiao
2007 年 11 月
void init_USART(void);
void put_c(unsigned char c) //发送采用查询方式
{
while( !(UCSRA & (1<<UDRE)) );
//wait until the uart is empty
UDR=c;
//write data to uart
}
int main(void)
{
PORTA=0XFF;
PORTB=0XFF;
PORTC=0XFF;
PORTD=0XFF;
DDRD|=(1<<PIN_TXD);
init_USART();
sei();
while(1)
{
//PULL-UP
//PULL-UP
//TXD--PIN
OUT
if(communication.flag==READ)
{
if(communication.data==0xfe)
{
unsigned char highvalue,lowvalue;
communication.flag=NULL;
ADMUX=(1<<REFS0);
//AVCC 基准电压,通道 0,10bits ad value
/*---------------------------------------------------------------ADMUX
7,6
REFS1:0--------Rrference Selection Bits
00-------------AREF,Internal Vref truned off
01-------------AVCC with external capacior at AREF pin
10-------------Reserved
11-------------internal 2.56V Voltage Reference with external
capacitior at AREF pin
5
ADELAR---------ADC Left Adjust Result
1--------------Left Adjust-----8 bits resolution
0--------------Right Adjust----10 bits resolution
4:0
MUX4:0---------Analog Channel and Gain Selection Bits
00000----------ADC0
00001----------ADC1
204
AVR 单片机学习开发板教程
dushibiao
2007 年 11 月
||||||||||||||||||||||||||||||||||||
00111----------ADC7
|||||||||||||||||||||||||||||||||||||
----------------------------------------------------------------*/
ADCSRA=(1<<ADEN)|(1<<ADSC)|(1<<ADPS2)|(1<<ADPS1)|(1<<ADPS0);
//AD 使能,单次转换,128 分频
/*-------------------------------------------------------------------ADCSRA
7
ADEN-----------ADC Enable
6
ADSC-----------ADC Start Comversion
5
ADATE----------ADC Auto Trigger Enable
4
ADIF-----------ADC Interrupt Flag
3
ADIE-----------ADC Interrupt Enable
2:0
ADPS2:0--------ADC Prescaler Select Bits
000------------2
001------------2
010------------4
011------------8
100------------16
101------------32
110------------64
111------------128
---------------------------------------------------------------------*/
while(!(ADCSRA & (1 << ADIF))); /*等待*/
lowvalue=ADCL;
//first read ADCL
highvalue=ADCH;
//second read ADCH
put_c(READ);
put_c(highvalue);
put_c(lowvalue);
}
}
}
}
/*---------------------------------------------------------------------fuction: initialize uart unit
----------------------------------------------------------------------*/
void init_USART(void)//USART 初始化
{
//USART 9600 8, n,1 PC 上位机软件(超级终端等)也要设成同样的设置才能通讯
UCSRC = (1<<URSEL) | 0x06;
205
AVR 单片机学习开发板教程
dushibiao
2007 年 11 月
//异步,8 位数据,无奇偶校验,一个停止位,无倍速
/*
UBRRH 与 UCSRC 共用 I/O 地址。因此访问该地址时需注意以下问题。
写访问
当在该地址执行写访问时, USART 寄存器选择位(URSEL)控制被写入的寄存器。
若 URSEL 为 0,对 UBRRH 值更新;若 URSEL 为 1,对 UCSRC 设置更新
读访问
对 UBRRH 或 UCSRC 寄存器的读访问则较为复杂。但在大多数应用中,基本不需要
读这些寄存器
没有 UBRR 这个 16 位寄存器,因为 UBRRL(0x09)/UBRRH(0x20)的地址不连续,而且
UBRRH 跟 UCSRC 共用地址
*/
//U2X=0 时的公式计算
UBRRL= (F_CPU/BAUDRATE/16-1)%256;
UBRRH= (F_CPU/BAUDRATE/16-1)/256;
//U2X=1 时的公式计算
//UBRRL= (F_CPU/BAUDRATE/8-1)%256;
//UBRRH= (F_CPU/BAUDRATE/8-1)/256;
//也可根据数据手册的[波特率设置的例子]查得
//UBRRL = 0x2F; //set baud rate lo
//UBRRH = 0x00; //set baud rate hi
UCSRA = 0x00;
//无倍速
UCSRB = (1<<RXCIE)|(1<<RXEN)|(1<<TXEN);
//使能接收中断,使能接收,使能发送
}
SIGNAL(SIG_USART_RECV) //串口接收中断服务程序
{
communication.flag=UDR;
while(!(UCSRA&(1<<RXC)));
//wait for data t
communication.data=UDR;
}
206
Was this manual useful for you? yes no
Thank you for your participation!

* Your assessment is very important for improving the work of artificial intelligence, which forms the content of this project

Download PDF

advertisement