TWI
介绍TWI其实也是在介绍IIC,因为他们是同一协议,只是名字不一样而已,我也是看m16的接口看到有SDA、SCL这两个接口,查找文档才发现有TWI这个的,这大大方便了我写IIC协议控制LCD1602,因为之前我也写了软件模拟IIC协议,觉的太麻烦了,如果你的单片机没有SDA和SCL接口,说明没有硬件IIC协议,那么你可以写软件模拟IIC协议,别人已经写的很好了,我就不写了,因为太麻烦了,我懒得写-_-
先来介绍下TWI
介绍什么的太麻烦了,我不想写了,所以大家看技术文档吧,没有技术文档可以和我要,剩下的我直奔重点
寄存器介绍
TWI比特率寄存器-TWBR
通过配置比特率寄存器,设置传输数据的速率,工作在主机模式下,才需要配置
TWI控制寄存器-TWCR
我们只需要配置控制的寄存器就行,读取状态的就不用配置了,看自己需要的功能置1相应的位,常用的就是配置 TWEA: 使能TWI 应答 ,TWEN: TWI使能, TWIE: 使能TWI 中断 ,其他的一般不用
TWI状态寄存器-TWSR
这个就看TWPS1和TWPS0配置分频器就行,相信大家学过定时器就知道分频器了
数据寄存器就不需要多介绍了,因为这只是放置数据的,知道读取数据是读这个寄存器就行了
TWI(从机) 地址寄存器- TWAR
如果你是设置单片机为从机,就需要配置地址,以便主机能根据地址找到这个设备,但我们写IIC协议控制LCD1602,就不需要了,因为单片机设置的是主机
代码
寄存器介绍完了,怎么去写START,STOP,其实根本就不用我们抄心,人家技术文档都写好了,步骤都写好了,其中TWINT,TWSTA等这些的值是啥,新手可能一脸懵逼,其实就是宏定义的数字,看前面的寄存器,有对应位的名字,例如TWCR寄存器,TWINT就是第7位,当然,TWINT在编译器里已经预定义好了,直接写名字就行
先使能TWI,初始化下
//TWI初始化函数
void twi_init(void)
{
TWCR = 0x00; //禁止TWI
TWBR = 50; //设置比特率9600
//TWAR = 0x4E; //设置从机地址
TWSR|= 0x01; //设置分频因子
TWCR = 0x45; //启动TWI
SEI();
}
这个流程其实很简单
发送START,
发送地址SLA+W(SLA是7位地址,W是写,所以写成Address<<1|0 )
发送数据DATA
#define IIC_Start() TWCR = (1<<TWINT)|(1<<TWSTA)|(1<<TWEN) ///发出Start信号
#define IIC_Stop() TWCR = (1<<TWINT)|(1<<TWEN)|(1<<TWSTO) ///发出Stop信号
#define IIC_Wait() while (!(TWCR & (1<<TWINT)))
/*I2C总线单字节写入*/
uchar TWI_Write( /*uchar addr,*/ char data)
{
TWBR = 50; //设定波特率
///START启动
IIC_Start();
IIC_Wait(); //等待Start完成
if( (TWSR & 0xf8) != TW_START ) //Start未发出,错误退出
return 0;
/*发送设备地址*/
TWDR = IIC_Address<<1|0; //芯片地址 0x3F ,赋值给数据寄存器 TWDR ,等待发送
TWCR = (1 << TWINT) | (1 << TWEN); //对控制寄存器TWCR的 TWINT 位软件写1进行清零,然后 使能TWI硬件接口 ,让TWI进行工作,发送 TWDR寄存器 中的数据
IIC_Wait(); //等待数据发送完毕 TWINT重新置位
if ((TWSR & 0xF8) != TW_MT_SLA_ACK )//&& (TWSR & 0xF8) != TW_MT_SLA_NACK)
return 0; //检测到TWINT位置位,比对TWSR寄存器内的状态量 , 如果正确则向下进行数据传输,错误返回 0
/*data 写入数据*/
TWDR = data; //将要写入的数据 ,赋值给数据寄存器 TWDR ,等待发送
TWCR = (1 << TWINT) | (1 << TWEN); //对控制寄存器TWCR的 TWINT 位软件写1进行清零,然后 使能TWI硬件接口 ,让TWI进行工作,发送 TWDR寄存器 中的数据
IIC_Wait(); //等待数据发送完毕 TWINT重新置位
if ((TWSR & 0xF8) != TW_MT_DATA_ACK)// && (TWSR & 0xF8) != TW_MT_DATA_NACK)
return 0; //检测到TWINT位置位,比对TWSR寄存器内的状态量 , 如果正确则向下进行数据传输,错误返回 0
/*stop 停止*/
IIC_Stop(); //数据传输完成,发送STOP信号,释放对总线的控制
return 1; //写入数据成功 ,返回1 ,用来判断是否成功写入数据
}
上面的应答标识符是需要根据下面的状态码来判断是否介绍到应答ACK了,有地址应答和数据应答,注意区分
#define IIC_Address 0x3f //PCF8574T地址
///主机发送模式时各状态字的后续动作
#define TW_START 0x08 //开始信号已发出
#define TW_REP_START 0x10 //重复开始信号已发出
#define TW_MT_SLA_ACK 0x18 //写字节已发出并受到ACK信号
#define TW_MT_SLA_NACK 0x20 //写字节已发出并受到NACK信号
#define TW_MT_DATA_ACK 0x28 //数据已发出并受到ACK 信号
#define TW_MT_DATA_NACK 0x30 //数据已发出并受到NACK 信号
#define TW_MT_ARB_LOST 0x38 //丢失总线控制权
///主机接收模式时各状态字的后续动作
#define TW_MR_ARB_LOST 0x38 //丢失总线控制权,未收到应答信号
#define TW_MR_SLA_ACK 0x40 //读命令已发出并受到ACK
#define TW_MR_SLA_NACK 0x48 //读命令已发出并受到NACK
#define TW_MR_DATA_ACK 0x50 //数据已收到,ACK已发出
#define TW_MR_DATA_NACK 0x58 //数据已收到,NACK已发出
发送模式已经介绍完了,IIC协议准备好了,那么就可以写LCD1602的写命令和写数据了
发送的data中,第0位是RS接口,第1位RW接口,第2位是E接口,第4~7位是D4-D5接口,其实如果你写过3+4线接LCD1602就是应该知道,PCF8574T模块转接LCD1602就是用的3+4接线法。
写命令其实就是RS=0,RW=0,然后再测试D0-D7,根据LCD1602的命令集,直接去查每个命令执行什么功能,我就不过多写了。
//*************写命令****************************
void LCD_write_command(unsigned char command)
{
delay_nus(16);
LCD_data&=~(1<<(1-1));//RS=0;指令寄存器
LCD_data&=~(1<<(2-1));//RW=0;
//LCD_data&=~(1<<(4-1));
TWI_Write(LCD_data);
LCD_data&=0X0f; //清高四位
LCD_data|=command & 0xf0; //写高四位
TWI_Write(LCD_data);
Enable_LCD_write();
command=command<<4; //低四位移到高四位
LCD_data&=0x0f; //清高四位
LCD_data|=command&0xf0; //写低四位
TWI_Write(LCD_data);
Enable_LCD_write();
}
写命令和写数据函数非常像,如果你看错就会写成一样的
知道哪不同了吗?对,就这是RS=1,其他的都一样
//*************写数据****************************
void LCD_write_data(unsigned char value)
{
delay_nus(16);
LCD_data|=(1<<(1-1));//RS=1;数据寄存器
LCD_data&=~(1<<(2-1));//RW=0;
TWI_Write(LCD_data);
LCD_data&=0X0f; //清高四位
LCD_data|=value&0xf0; //写高四位
TWI_Write(LCD_data);
Enable_LCD_write();
value=value<<4; //低四位移到高四位
LCD_data&=0x0f; //清高四位
LCD_data|=value&0xf0; //写低四位
TWI_Write(LCD_data);
Enable_LCD_write();
}
液晶使能比较简单,就不介绍了
//********************液晶屏使能*********************
uchar LCD_data;
void Enable_LCD_write()
{
LCD_data|=(1<<(3-1));//E=1;
TWI_Write(LCD_data);
delay_nus(25);
LCD_data&=~(1<<(3-1));//E=0;
TWI_Write(LCD_data);
}
最后贴一下总的代码,其实有些代码是测试用的,看着用吧
/*
*/
#include "iom16v.h"
#include <macros.h>
typedef unsigned char uchar;
typedef unsigned int uint;
#define IIC_Address 0x3f //PCF8574T地址
///主机发送模式时各状态字的后续动作
#define TW_START 0x08 //开始信号已发出
#define TW_REP_START 0x10 //重复开始信号已发出
#define TW_MT_SLA_ACK 0x18 //写字节已发出并受到ACK信号
#define TW_MT_SLA_NACK 0x20 //写字节已发出并受到NACK信号
#define TW_MT_DATA_ACK 0x28 //数据已发出并受到ACK 信号
#define TW_MT_DATA_NACK 0x30 //数据已发出并受到NACK 信号
#define TW_MT_ARB_LOST 0x38 //丢失总线控制权
///主机接收模式时各状态字的后续动作
#define TW_MR_ARB_LOST 0x38 //丢失总线控制权,未收到应答信号
#define TW_MR_SLA_ACK 0x40 //读命令已发出并受到ACK
#define TW_MR_SLA_NACK 0x48 //读命令已发出并受到NACK
#define TW_MR_DATA_ACK 0x50 //数据已收到,ACK已发出
#define TW_MR_DATA_NACK 0x58 //数据已收到,NACK已发出
#define IIC_Start() TWCR = (1<<TWINT)|(1<<TWSTA)|(1<<TWEN) ///发出Start信号
#define IIC_Stop() TWCR = (1<<TWINT)|(1<<TWEN)|(1<<TWSTO) ///发出Stop信号
#define IIC_Wait() while (!(TWCR & (1<<TWINT))) ///等待Start信号发出
/*I2C总线单字节写入*/
uchar TWI_Write( /*uchar addr,*/ char data)
{
TWBR = 50; //设定波特率
///START启动
IIC_Start();
IIC_Wait(); //等待Start完成
if( (TWSR & 0xf8) != TW_START ) //Start未发出,错误退出
return 0;
TWDR = IIC_Address<<1|0; //芯片地址 0x3F ,赋值给数据寄存器 TWDR ,等待发送
TWCR = (1 << TWINT) | (1 << TWEN); //对控制寄存器TWCR的 TWINT 位软件写1进行清零,然后 使能TWI硬件接口 ,让TWI进行工作,发送 TWDR寄存器 中的数据
IIC_Wait(); //等待数据发送完毕 TWINT重新置位
if ((TWSR & 0xF8) != TW_MT_SLA_ACK )//&& (TWSR & 0xF8) != TW_MT_SLA_NACK)
return 0; //检测到TWINT位置位,比对TWSR寄存器内的状态量 , 如果正确则向下进行数据传输,错误返回 0
// /*addr 操作地址*/
// TWDR = addr; //将写入数据的绝对地址 ,赋值给数据寄存器 TWDR ,等待发送
// TWCR = (1 << TWINT) | (1 << TWEN); //对控制寄存器TWCR的 TWINT 位软件写1进行清零,然后 使能TWI硬件接口 ,让TWI进行工作,发送 TWDR寄存器 中的数据
// IIC_Wait(); //等待数据发送完毕 TWINT重新置位
// if ((TWSR & 0xF8) != 0x28)
// return 0; //检测到TWINT位置位,比对TWSR寄存器内的状态量 , 如果正确则向下进行数据传输,错误返回 0
/*data 写入数据*/
TWDR = data; //将要写入的数据 ,赋值给数据寄存器 TWDR ,等待发送
TWCR = (1 << TWINT) | (1 << TWEN); //对控制寄存器TWCR的 TWINT 位软件写1进行清零,然后 使能TWI硬件接口 ,让TWI进行工作,发送 TWDR寄存器 中的数据
IIC_Wait(); //等待数据发送完毕 TWINT重新置位
if ((TWSR & 0xF8) != TW_MT_DATA_ACK)// && (TWSR & 0xF8) != TW_MT_DATA_NACK)
return 0; //检测到TWINT位置位,比对TWSR寄存器内的状态量 , 如果正确则向下进行数据传输,错误返回 0
/*stop 停止*/
IIC_Stop(); //数据传输完成,发送STOP信号,释放对总线的控制
return 1; //写入数据成功 ,返回1 ,用来判断是否成功写入数据
}
/*I2C总线单字节读取*/
uchar TWI_Read(uchar addr)
{
uchar Receive_Byte ;
TWBR = 2; //设定波特率
///*start 启动*///
IIC_Start(); //硬件发送START信号,并且清零TWINT位,使能硬件TWI,使TWI开始工作
IIC_Wait(); //等待 发送START完成 TWINT位置位
if ((TWSR & 0xF8) != TW_START)
return 0; //检测到TWINT位置位,比对TWSR寄存器内的状态量,如果正确则向下进行数据传输,错误返回 0
/*SLA_W 芯片地址*/
TWDR = IIC_Address; //芯片地址 0x3F ,赋值给数据寄存器 TWDR ,等待发送
TWCR = (1 << TWINT) | (1 << TWEN); //对控制寄存器TWCR的 TWINT 位软件写1进行清零,然后 使能TWI硬件接口 ,让TWI进行工作,发送 TWDR寄存器 中的数据
IIC_Wait(); //等待数据发送完毕 TWINT重新置位
if ((TWSR & 0xF8) != TW_MT_SLA_ACK)
return 0; //检测到TWINT位置位,比对TWSR寄存器内的状态量 , 如果正确则向下进行数据传输,错误返回 0
///*addr 操作地址*/
TWDR = addr; //将写入数据的绝对地址 ,赋值给数据寄存器 TWDR ,等待发送
TWCR = (1 << TWINT) | (1 << TWEN); //对控制寄存器TWCR的 TWINT 位软件写1进行清零,然后 使能TWI硬件接口 ,让TWI进行工作,发送 TWDR寄存器 中的数据
IIC_Wait(); //等待数据发送完毕 TWINT重新置位
if ((TWSR & 0xF8) != TW_MT_DATA_ACK)
return 0; //检测到TWINT位置位,比对TWSR寄存器内的状态量 , 如果正确则向下进行数据传输,错误返回 0
///*restart 重启动*/
IIC_Start(); //硬件发送 RESTART 信号,并且清零TWINT位,使能硬件TWI,使TWI开始工作
IIC_Wait(); //等待数据发送完毕 TWINT重新置位
if ((TWSR & 0xF8) != TW_REP_START)
return 0; //检测到TWINT位置位,比对TWSR寄存器内的状态量 , 如果正确则向下进行数据传输,错误返回 0
///*SLA_R 芯片地址*/
TWDR = 0xA1; //芯片地址 0xA0 并注明是读取操作(最后一位为 1 ),赋值给数据寄存器 TWDR ,等待发送
TWCR = (1 << TWINT) | (1 << TWEN); //对控制寄存器TWCR的 TWINT 位软件写1进行清零,然后 使能TWI硬件接口 ,让TWI进行工作,发送 TWDR寄存器 中的数据
IIC_Wait(); //等待数据发送完毕 TWINT重新置位
if ((TWSR & 0xF8) != TW_MR_SLA_ACK )
return 0; //检测到TWINT位置位,比对TWSR寄存器内的状态量 , 如果正确则向下进行数据传输,错误返回 0
/*读取数据*/
TWCR = (1 << TWINT) | (1 << TWEN); //对控制寄存器TWCR的 TWINT 位软件写1进行清零,然后 使能TWI硬件接口 ,让TWI进行工作,发送 TWDR寄存器 中的数据
IIC_Wait(); //等待数据发送完毕 TWINT重新置位
if ((TWSR & 0xF8) != TW_MR_DATA_NACK )
return 0; //检测到TWINT位置位,比对TWSR寄存器内的状态量 , 如果正确则向下进行数据传输,错误返回 0
Receive_Byte = TWDR; //读取到的数据放到局部变量里
///*stop 停止*/
IIC_Stop(); //数据传输完成,发送STOP信号,释放对总线的控制
return Receive_Byte; //将读取到的数据作为函数的输出
}
void delay_nus(int n)
{
while(n--)
NOP();
}
void delay_nms(int n)
{
while(n--)
delay_nus(1000);
}
//********************液晶屏使能*********************
uchar LCD_data;
void Enable_LCD_write()
{
LCD_data|=(1<<(3-1));//E=1;
TWI_Write(LCD_data);
delay_nus(25);
LCD_data&=~(1<<(3-1));//E=0;
TWI_Write(LCD_data);
}
//*************写命令****************************
void LCD_write_command(unsigned char command)
{
delay_nus(16);
LCD_data&=~(1<<(1-1));//RS=0;指令寄存器
LCD_data&=~(1<<(2-1));//RW=0;
//LCD_data&=~(1<<(4-1));
TWI_Write(LCD_data);
LCD_data&=0X0f; //清高四位
LCD_data|=command & 0xf0; //写高四位
TWI_Write(LCD_data);
Enable_LCD_write();
command=command<<4; //低四位移到高四位
LCD_data&=0x0f; //清高四位
LCD_data|=command&0xf0; //写低四位
TWI_Write(LCD_data);
Enable_LCD_write();
}
//*************写数据****************************
void LCD_write_data(unsigned char value)
{
delay_nus(16);
LCD_data|=(1<<(1-1));//RS=1;数据寄存器
LCD_data&=~(1<<(2-1));//RW=0;
TWI_Write(LCD_data);
LCD_data&=0X0f; //清高四位
LCD_data|=value&0xf0; //写高四位
TWI_Write(LCD_data);
Enable_LCD_write();
value=value<<4; //低四位移到高四位
LCD_data&=0x0f; //清高四位
LCD_data|=value&0xf0; //写低四位
TWI_Write(LCD_data);
Enable_LCD_write();
}
//**********************设置显示位置*********************************
void set_position(unsigned char x,unsigned char y)
{
unsigned char position;
if (y == 0)
position = 0x80 + x;
else
position = 0xc0 + x;
LCD_write_command(position);
}
//**********************显示字符串*****************************
void display_string(unsigned char x,unsigned char y,unsigned char *s)
{
set_position(x,y);
while (*s)
{
LCD_write_data(*s);
s++;
}
}
//*************液晶初始化****************************
void LCD_init(void)
{
// LCD_write_data(0x08);
// LCD_write_command(0x0F);
// LCD_write_command(0x28);//数据4位
// LCD_write_data(0x0C);
// LCD_write_data(0x08);
// LCD_write_command(0x28);
// LCD_write_command(0x01);//光标清0
// LCD_write_command(0x0C);//开显示、无光标、不闪烁
// LCD_write_command(0x06);//光标加一、整屏不移动
// LCD_write_command(0x28);
// delay_nus(40);
// LCD_write_command(0x28);
// delay_nus(40);
// Enable_LCD_write();
// delay_nus(40);
// LCD_write_command(0x28); //4位显示!!!!!!!!!!!!!!!!!!
// LCD_write_command(0x0c); //显示开
// LCD_write_command(0x01); //清屏
// delay_nms(2);
LCD_write_command(0x33);
delay_nus(50) ;
// LCD_write_command(0x28);
// delay_nus(50) ;
LCD_write_command(0x0C);
delay_nus(50) ;
// LCD_write_command(0x06);
// delay_nus(50) ;
// LCD_write_command(0x01);
// delay_nus(50);
}
//TWI初始化函数
void twi_init(void)
{
TWCR = 0x00; //禁止TWI
TWBR = 50; //设置比特率
//TWAR = 0x4E; //设置从机地址
TWSR|= 0x01; //设置分频因子
TWCR = 0x45; //启动TWI
SEI();
}
int main(void)
{
MCUCSR |= 0x80;
MCUCSR |= 0x80;
DDRC |= (1<<4);
PORTC |= (1<<4);
twi_init();
LCD_init();
//
LCD_write_command(0x80);//第一行数据
//TWI_Write('A');
// TWI_Write('B');
// TWI_Write('C');
// TWI_Write('D');
// TWI_Write('E');
// TWI_Write('F');
// TWI_Write('G');
// display_string(4,0,"LiaoMi"); //显示一段文字
// display_string(4,1,"2016.7.8");//显示一段文字
return 0;
}