6.3 两个Mega8 间的TWI 通信
两个Mega8 用TWI 总线通信,主要为了说明Mega8 在TWI 从模式下工作的编程方法。
一. 测试电路
两片Mega8 间的连接如图 6-3 所示。

图6-3 电路两个MEGA8 间的电路连接图
Master Mega8 的PD2 引脚接有一个按键,当键被按下时起动一次的TWI 写操作,Slave Mega8 的RXD、TXD 引脚通过电平转换后接到PC 机串行口,用PrintMonitor 监测其打印输出的信息。
二. 程序设计
1.主模式单片机程序
主模式程序用查询方式检测并等待按键,当键被按下时向TWI 口写0~9 。
/*
文件名:master.c
两个Mega8 间的TWI 通信实验主模式单片机程序
内部4MHz 振荡器
芯艺 2004-09-02 ---------- 2004-09-03
*/
#include
#include
#include
#define uint unsigned int
#define uchar unsigned char
#define WAITPRINTDEBUG DelayMs(100) //为从模式单片机打印调试信息而延时
#define KEY 0X04
#define FREQ 4
#define TWI_ADDRESS 0X32
void DelayMs(uint ms)
{
uint i;
for(i=0;i_delay_loop_2(FREQ *250);
}
/*************主模式TWI 操作部分*************开始**************/
//总线上起动停止条件
void twi_stop(void)
{
TWCR = _BV(TWINT) | _BV(TWSTO) | _BV(TWEN);
}
//总线上起动开始条件
void twi_start(void)
{
uchar trycount=0;
TWCR = _BV(TWINT) | _BV(TWSTA) | _BV(TWEN);
while ((TWCR & _BV(TWINT)) == 0) ;
return TW_STATUS;
}
//写一字节
void twi_writebyte(uchar c)
{
TWDR = c;
TWCR = _BV(TWINT) | _BV(TWEN);
while ((TWCR & _BV(TWINT)) == 0);
return TW_STATUS;
}
//读一字节 ack: true 时发ACK,false 时发NACK
uchar twi_readbyte(uchar *c ,uchar ack)
{
uchar tmp=_BV(TWINT)|_BV(TWEN);
if(ack)
tmp|=_BV(TWEA);
TWCR=tmp;
while ((TWCR & _BV(TWINT)) == 0) ;
*c=TWDR;
return TW_STATUS;
}
/*************主模式IIC 操作部分*************结束**************/
//检测按键
uchar WaitKeyDown(void)
{
uchar key;
while(1)
{
key=PIND & KEY;
if( key!=KEY)
{
DelayMs(30);
key=PIND & KEY;
if(key!=KEY)
break;
}
DelayMs(1);
}
while((PIND & KEY)!=KEY)
DelayMs(10);
return key;
}
int main(void)
{
uchar i;
//便能SCL、SDA 引脚内部上拉电阻
DDRC=0;
PORTC=0X30;
//
DDRD=0;
PORTD=0;
TWBR=73;//波特率
while(1)
{
WaitKeyDown();
twi_start();
WAITPRINTDEBUG;
twi_writebyte(TWI_ADDRESS|TW_WRITE);
WAITPRINTDEBUG;
for(i=0;i<10;i++)
{
twi_writebyte(i);
WAITPRINTDEBUG;
}
twi_stop();
}
}
2.从模式单片机程序
从模式程序用查询方式等待TWI 端口中断标志,在TWI 通信的不同阶段从UART 打印出
对应测试信息。
/*
文件名:slave.c
两个Mega8 间的TWI 通信实验从模式单片机程序
外部4MHz 晶振
芯艺 2004-09-02 --- 2004-09-03
*/
#include
#include
#include
#define uint unsigned int
#define uchar unsigned char
#define TWI_ADDRESS 0X32
//标准I/O 输出函数
int usart_putchar(char c)
{
if(c=='\n')
usart_putchar('\r');
loop_until_bit_is_set(UCSRA,UDRE);
UDR=c;
return 0;
}
//初始化
void IoInit(void)
{
//使能SCL、SDA 引脚内部上拉电阻
DDRC=0;
PORTC=0X30;
//串行口初始化
UCSRB=_BV(RXEN)|_BV(TXEN);/*(1<UBRRL=25; //9600 baud 6MHz:38 4MHz:25
//UART 用于标准I/O 输入输出
fdevopen(usart_putchar,0,0);
//TWI 接口初始化,从器件模式
TWAR=TWI_ADDRESS | _BV(TWGCE);
TWCR=_BV(TWEA) | _BV(TWEN);
}
int main(void)
{
uchar i,j=0;
IoInit();
while(1)
{
while ((TWCR & _BV(TWINT)) == 0);
i=TW_STATUS;
switch(i)
{
case TW_SR_SLA_ACK:
printf("START\nSLA+W\n");
break;
case TW_SR_DATA_ACK:
if(j==0)
printf("收到:%d",TWDR);
else
printf(" %d",TWDR);
j++;
break;
case TW_SR_STOP:
printf(";\nSTOP\n\n");
j=0;
break;
default:
printf("error:%x",(int)i);
break;
}
TWCR=_BV(TWEA) | _BV(TWEN)|_BV(TWINT); //清除TWINT 位
}
}
三. 测试结果
从模式程序输出结果如图6-4 所示,当主模式单片的键被按下后从单片机先检测到START 条件,然后收到本机SLA+W,再接收1~9 的数据,最后检测到STOP 条件。

图6-4 从模式程序打印输出结果
第七章 BootLoader 功能应用
7.1 BootLoader 功能介绍
BootLoader 提供我们通常所说的IAP(In Applicaion Program)功能。多数Mega 系列单片机具有片内引导程序自编程功能(BootLoader)。mcu 通过运行一个常驻FLASH 的BootLoader 程序,利用任何可用的数据接口读取代码后写入自身FLASH存储器中 ,实现自编程目的。
下面用ATMEGA8 来说明此功能
ATMEGA8 片内具有8K 的FLASH 程序存储器,BootLoader 功能将其分为 应用程序区(Application section)和引导加载区(Boot loader section), 通过设置熔丝位BOOTSZ0 和BOOTSZ1 可将应用区和引导区的大小分别设置成6K/2K 、7K/1K 、7K512B/512B 和7K768B/256B。详细请参考ATMEGA8 数据手册。
另外,ATMEGA8 还有一个熔丝位BOOTRST 用于设置复位向量,当BOOTRST 未被编程时器件的复位从应用程序区首地址(既0)开始执行,当BOOTRST 被编程时器件的复位从引导区首地址开始执行。
使用BootLoader ,首先应该根据BootLoader 程序的大小设置好BOOTSZ0 和BOOTSZ1熔丝位,然后编程BOOTRST 熔丝位使单片机的复位从引导区开始执行,之后要把BootLoader 程序定位并写入到引导区(首地址取决于熔丝位ROOTSZ0 和BOOTSZ1 的编程状态)。以上过程需要使用ISP 或并行编程方式来实现。
当单片机上电复位后BootLoader 程序开始执行,它通过USART、TWI 或其它方式从计算机或其它数据源读应用区代码并写入到应用区。事实上 BootLoader 程序有能力读写整个FLASH 存储器,包括BootLoader 程序所在的引导区本身,只是写自身所在的引导区的应用较少见。
7.2 avr-libc 对BootLoader 的支持
avr-libc 提供一组C 程序接口API 来支持BootLoader 功能。
包含:
#include
在boot.h 中这些API 均为预定义宏,以下为其主要几个宏。
boot_page_erase ( address )
擦除FLASH 指定页
其中address 是以字节为单位的FLASH 地址
boot_page_fill ( address, data )
填充BootLoader 缓冲页,address 为以字节为单位的缓冲页地址(对mega8 :0~64),而data 是长度为两个字节的字数据,因此调用前address 的增量应为2。 此时data 的高字节写入到高地址,低字节写入到低地址。
boot_page_write ( address )
boot_page_write 执行一次的SPM 指令,将缓冲页数据写入到FLASH 指定页。
boot_rww_enable ( )
RWW 区读使能
根据自编程的同时是否允许读FLASH 存储器,FLASH 存储器可分为两种类型:
可同时读写区( RWW Read-While-Write ) 和 非同时读写区( NRWW Not Read-While-Write)。对于MEGA8 RWW 为前6K 字节 NRWW 为后2K 字节。
引导加载程序对RWW 区编程时mcu 仍可以从NRWW 区读取指令并执行,而对NRWW 区编程时mcu 处于挂起暂停状态。
在对RWW 区自编程(页写入或页擦除)时,由硬件锁定RWW 区 , RWW 区的读操作被禁止,在对RWW 区的编程结束后应当调用boot_rww_enable() 使RWW 区开放。
7.3 BootLoader 应用实例
本节介绍一种BootLoader 的一个简单应用实例 - LuckyProg M8BL。 BootLoader 程序通过UART 与计算机通信,执行读、写和执行FLASH 应用区的操作。
一.硬件:
如图7-1 ,MEGA8 的UART 端口通过MAX232 的电平转换后与计算机RS-232 串行通信口连接。

图7-1 LuckyProg M8BL 测试电路
MEGA8 熔丝位设置图如图7-2 所示,:引导加载区分配2048 个字节,BOOTRST 被编程(复位后从引导区开始执行),时钟源选择了外部晶振。

图7-2 Mega8 熔丝位设置
二.引导加载程序
引导加载程序不使用中断,查询方式读写UART,响应UART 传来的读当前页、写当前页、设置当前页地址、运行应用区代码和BootLoader 程序检测等命令。每读或写一页当前页地址增一。
“运行应用区代码”命令将mcu 引导到应用程序区的首地址,在没有特殊情况下只能用硬件复位方式将mcu 重新引导到引导加载区后才能执行下一次的程序加载操作。
引导加载程序清单:
/*
LuckyProg M8BL 引导加载程序V1.0
文 件 名 : mboot.c
器 件:ATMEGA8
时 钟:4MHz
作 者:芯 艺
编 译:WinAVR-20040720
*/
#include
#include
#include
#define uchar unsigned char
#define uint unsigned int
#define FREQ 4
#define UART_ACK 0XAA
#define PAGE_SIZE 64 //按字节
uint g_wPageIndex=0;
uchar g_aPageTemp[PAGE_SIZE];
void (*reset)(void)=0x0000;
void uart_putc(uchar c)
{
while( !(UCSRA & (1<UDR=c;
}
uchar uart_getc(void)
{
while( !(UCSRA & (1<return UDR;
}
void WritePage(void)
{
uchar i;
// 接收当前页面数据
for(i=0;ig_aPageTemp[i]=uart_getc();
// 页擦除操作
boot_page_erase(g_wPageIndex<<6);
while(boot_rww_busy())
boot_rww_enable();
// 填充缓冲页
for(i = 0; i < PAGE_SIZE; i += 2)
boot_page_fill((unsigned long)i,*((uint *)(g_aPageTemp +i)));
// 页写入操作
boot_page_write(g_wPageIndex<<6);
while(boot_rww_busy())
boot_rww_enable();
g_wPageIndex++;
}
void ReadPage(void)
{
uchar i;
for(i=0;iuart_putc(pgm_read_byte(i+(g_wPageIndex*PAGE_SIZE)));
g_wPageIndex++;
}
int main(void)
{
uchar tmp;
//uart 初始化
UBRRH=0;
UBRRL=25;//9600 baud 6MHz:38 4MHz:25
UCSRB=(1<while(1) //main loop
{
tmp=uart_getc();//recv command
switch(tmp)
{
case 0xB0://设置页地址
g_wPageIndex=uart_getc();
uart_putc(g_wPageIndex);
break;
case 0xBF://运行用户程序
reset();
break;
case 0xAF://写一页
WritePage();
uart_putc(UART_ACK);//应答
break;
case 0xA0://读一页
ReadPage();
break;
case UART_ACK://回应检测命令
uart_putc(UART_ACK);
break;
default:
break;
} //switch
} //main loop
} //main
编译和链接:
用Mfile 生成一个makefile ,设置好以下变量
mcu = atmega8
TARGET = mboot
之后要做一个额外的修改,那就是将程序入口定位到引导区首地址,这是通过指定 .text 段链接地址来实现的。
在makefile 中找到如下代码
# Linker flags.
# -Wl,...: tell GCC to pass this to linker.
# -Map: create map file
# --cref: add cross reference to map file
LDFLAGS = -Wl,-Map=$(TARGET).map,--cref
LDFLAGS += $(EXTMEMOPTS)
LDFLAGS += $(PRINTF_LIB) $(SCANF_LIB) $(MATH_LIB)
并在下边插入如下一行:
LDFLAGS += -Wl,--section-start=.text=0x1800
在命令行,mboot.c 和makefile 所在目录键入Make 命令, 生成引导加载程序 mboot.hex ,将它通过编程器或下载线写入到mega8。
我使用WinAVR-20040720 编译后生成代码大小为512 字节,这样引导区可缩小到512字节处。
三.上位机程序
上位机程序是由Visual C++ 6.0 编写 ,在Windows XP 上运行。运行界面如图7-3:

图7-3 LuckyProg Mega8 BootLoader V1.0 操作界面
“打开”和 “重载”用于打开程序文件,目前只支持二进制(.bin)格式。
“写入”命令将打开的有效代码分页后顺序的向BootLoader 程序发送“写一页”命令。
“校验”命令通过发送 “读一页”命令的方式将打开的代码与mega8 Flash 内容比较。
“运行”对应BootLoader 程序的“运行应用区代码” 命令。
上位机程序的编写属于WINDOWS 应用程序编程,这里不再详细描述。
要注意的是,LuckyProg Mega8 BootLoader V1.0 默认使用COM1 端口,如果要使用其它口需改动源代码。
请到http://bitfu.zj.com 下载LuckyProg Mega8 BootLoader V1.0 的可执行文件或VC 源代码。
单片机教程,五系列(55讲)电子书全集下载论坛精选:
单片机教程,MCS51单片机从零开始 第一讲:初识单片机
http://bbs.huazhoucn.com/Topic.aspx?id=2539
单片机教程,MCS51单片机从零开始 第二讲:MCS-51单片机简述
http://bbs.huazhoucn.com/Topic.aspx?id=2540
单片机教程,MCS51单片机从零开始 第三讲:单片机相关常用名词解释
http://bbs.huazhoucn.com/Topic.aspx?id=2541
单片机教程,MCS51单片机从零开始 第四讲:计算机中数的表示及运算
http://bbs.huazhoucn.com/Topic.aspx?id=2542
单片机教程,MCS51单片机从零开始 第五讲:常用逻辑电路
http://bbs.huazhoucn.com/Topic.aspx?id=2544
单片机教程,MCS51单片机从零开始 第六讲:51单片机的结构及其组成
http://bbs.huazhoucn.com/Topic.aspx?id=2545
单片机教程,MCS51单片机从零开始 第七讲:51单片机的引脚
http://bbs.huazhoucn.com/Topic.aspx?id=2546
单片机教程,MCS51单片机从零开始 第八讲:8051单片机I/O引脚工作原理
http://bbs.huazhoucn.com/Topic.aspx?id=2547
单片机教程,MCS51单片机从零开始 第九讲:8051单片机的存储器结构
http://bbs.huazhoucn.com/Topic.aspx?id=2548
单片机教程,MCS51单片机从零开始 第十讲:编码及译码器工作原理
http://bbs.huazhoucn.com/Topic.aspx?id=2549
单片机教程,MCS51单片机从零开始 第十一讲:存储器的存储原理
http://bbs.huazhoucn.com/Topic.aspx?id=2550
单片机教程,MCS51单片机从零开始 第十二讲:51单片机的特殊功能寄存器
http://bbs.huazhoucn.com/Topic.aspx?id=2551
单片机教程,MCS51单片机从零开始 第十三讲:51单片机CPU的内部结构
http://bbs.huazhoucn.com/Topic.aspx?id=2552
单片机教程,MCS51单片机从零开始 第十四讲:定时器/计数器的基本结构及工作原理
http://bbs.huazhoucn.com/Topic.aspx?id=2553
单片机教程,MCS51单片机从零开始 第十五讲:51单片机的中断系统
http://bbs.huazhoucn.com/Topic.aspx?id=2554
单片机教程,MCS51单片机从零开始 第十六讲:51单片机的复位
http://bbs.huazhoucn.com/Topic.aspx?id=2555
单片机教程,MCS51单片机从零开始 第十七讲:51单片机执行指令的过程
http://bbs.huazhoucn.com/Topic.aspx?id=2556
单片机教程,MCS51单片机从零开始 第十八讲:51单片机的延时及时序分析
http://bbs.huazhoucn.com/Topic.aspx?id=2557
单片机教程,MCS51单片机从零开始 第十九讲:汇编语言基础
http://bbs.huazhoucn.com/Topic.aspx?id=2558
单片机教程,MCS51单片机从零开始 第二十讲:汇编语言及汇编过程
http://bbs.huazhoucn.com/Topic.aspx?id=2556
单片机教程,MCS51单片机从零开始 第二十一讲:汇编程序的基本结构
http://bbs.huazhoucn.com/Topic.aspx?id=2560
单片机教程,MCS51单片机从零开始 第二十二讲:51单片机的寻址方式
http://bbs.huazhoucn.com/Topic.aspx?id=2561
单片机教程,MCS51单片机从零开始 第二十三讲:数据传送类指令分析
http://bbs.huazhoucn.com/Topic.aspx?id=2562
单片机教程,MCS51单片机从零开始 第二十四讲:算术运算类指令分析
http://bbs.huazhoucn.com/Topic.aspx?id=2563
单片机教程,MCS51单片机从零开始 第二十五讲:逻辑运算及移位指令分析
http://bbs.huazhoucn.com/Topic.aspx?id=2564
单片机教程,MCS51单片机从零开始 第二十六讲:控制转移类指令分析
http://bbs.huazhoucn.com/Topic.aspx?id=2565
单片机教程,MCS51单片机从零开始 第二十七讲:布尔变量操作指令分析
http://bbs.huazhoucn.com/Topic.aspx?id=2566
单片机教程,MCS51单片机从零开始 第二十八讲:伪指令分析
http://bbs.huazhoucn.com/Topic.aspx?id=2567