atmega16应用之TWI(IIC) PCF8574T转接LCD1602

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;
}

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值