和串口通讯相比,I2C是半双工的,意思就是要么只能发,要么只能收
速度最高10kbps,串口最高达到20kbps
优点是一条总线上可以挂载多个支持I2C协议的设备,每个IC有唯一的地址
所有设备都可以是主机,但是同一时间只能有一个主机
一般有两根信号线,双向数据线SDA和单向时钟线SCL
图一
传输过程一般有三种信号:起始信号、结束信号、应答信号
如图一所示:两条信号线在初始状态下都是通过上拉电阻拉至高电平的,所有需要给总线一些信号才能精确的工作
完成三个信号的配置就可以使用I2C协议来收发数据了
分析时序图:
起始信号是S,是一个下降沿触发
两个时间段的配置:
SCL高电平且SDA高电平持续时间超过4.7us
SCL高电平且SDA低电平持续时间超过4us
在1和2之间转变SDA的电平状态即可给I2C总线发送起始信号,I2C总线进入工作状态
sbit scl = P0^1;
sbit sda = P0^3;
void IIC_Start()
{
scl = 0;//防止屏幕显示雪花
sda = 1;
scl = 1;
_nop_();//一个_nop_()在11.059MHZ的51单片机中差不多为5us
sda = 0;
_nop_();
}
终止信号是P,上升沿触发
两个时间段配置:
SCL高电平且SDA低电平持续时间超过4us
SCL高电平且SDA高电平持续时间超过4.7us
在1和2之间转变SDA的电平状态即可向总线发送终止信号,I2C总线停止工作
void IIC_Stop()
{
scl = 0;//防止屏幕显示雪花
sda = 0;
scl = 1;
_nop_();
sda = 1;
_nop_();
}
发送器每发送一个8bit字节,就会释放数据线,并且由接收器接收应答信号,应答信号为0表示有效应答(ACK);1表示无效应答,既接收数据不成功(NACK)
ACK:
一个时间区间:SCL高电平和SDA低电平持续时间超过4us
NACK:
一个时间区间:SCL高电平和SDA高电平持续时间超过4us
代码流程:
先释放数据线等待,再让SCL处于高电平时检测SDA的电平状态,赋予flag进入应答时间区间,结束以后拉低SCL等待
char IIC_ACK()
{
char flag;
sda = 1;//在时钟脉冲期间释放数据线
_nop_();
scl = 1;
_nop_();//等待检测应答信号
flag = sda;
_nop_();//应答时间区间
scl = 0;
_nop_();
return flag;
}
一次发送8位数据,高位开始发送,所以在读高位数据的时候需要(data & 0x80)按位计算知识点
发送时序分析:
上图只需要注意一点:
SDA转变电平状态的时候,SCL必须处于低电平
为什么?以为起始信号,终止信号和应答信号都是SCL位于高电平时做出的动作,发送数据低电平才能转变的目的时为了防止I2C总线提前退出工作
void IIC_Send_Byte(char dataSend)
{
int i;
for(i = 0;i<8;i++){//一次发送1位,循环8次发送8位
scl = 0;//scl拉低,让sda做好数据准备
sda = dataSend & 0x80;//1000 0000获得dataSend的最高位,给sda
_nop_();//发送数据建立时间
scl = 1;//scl拉高开始发送
_nop_();//数据发送时间
scl = 0;//发送完毕拉低
_nop_();//
dataSend = dataSend << 1;//数据左移一位,让下一个数据变为下一轮的高位
}
}
前面四步完成以后,即可向设备写入指令/数据
写入指令:
如上图:
Slave Address ->从机地址
start
0111 10()()(后面两位为用户自己配置)**
看这一小块,后面两个位代表SA0和R/W#
SA0:1、0均可配置,一般用于区分两个不同设备 第一个括号姑且让它为0
R/W#:0代表配置为写入模式 第二个括号为0
2.设置为写入 0111 1000->0x78
ACK
control byte xx000000
co:“0”表示此后只接数据字节
D/C:“0”代表发送命令;“1”代表发送数据
发送命令:0000 0000 (0x00)
发送数据:0100 0000 (0x40)
ACK
写入数据/命令
ACK
stop
void Oled_Write_Cmd(char dataCmd)
{
// 1. start()
IIC_Start();
//
// 2. 写入从机地址 b0111 1000 0x78
IIC_Send_Byte(0x78);
// 3. ACK
IIC_ACK();
// 4. cotrol byte: (0)(0)000000 写入命令 (0)(1)000000写入数据
IIC_Send_Byte(0x00);
// 5. ACK
IIC_ACK();
//6. 写入指令/数据
IIC_Send_Byte(dataCmd);
//7. ACK
IIC_ACK();
//8. STOP
IIC_Stop();
}
void Oled_Write_Data(char dataData)
{
// 1. start()
IIC_Start();
//
// 2. 写入从机地址 b0111 1000 0x78
IIC_Send_Byte(0x78);
// 3. ACK
IIC_ACK();
// 4. cotrol byte: (0)(0)000000 写入命令 (0)(1)000000写入数据
IIC_Send_Byte(0x40);
// 5. ACK
IIC_ACK();
///6. 写入指令/数据
IIC_Send_Byte(dataData);
//7. ACK
IIC_ACK();
//8. STOP
IIC_Stop();
}
初始化OLED、找位置、写入字符
void Oled_Init(void){
Oled_Write_Cmd(0xAE);//--display off
Oled_Write_Cmd(0x00);//---set low column address
Oled_Write_Cmd(0x10);//---set high column address
Oled_Write_Cmd(0x40);//--set start line address
Oled_Write_Cmd(0xB0);//--set page address
Oled_Write_Cmd(0x81); // contract control
Oled_Write_Cmd(0xFF);//--128
Oled_Write_Cmd(0xA1);//set segment remap
Oled_Write_Cmd(0xA6);//--normal / reverse
Oled_Write_Cmd(0xA8);//--set multiplex ratio(1 to 64)
Oled_Write_Cmd(0x3F);//--1/32 duty
Oled_Write_Cmd(0xC8);//Com scan direction
Oled_Write_Cmd(0xD3);//-set display offset
Oled_Write_Cmd(0x00);//
Oled_Write_Cmd(0xD5);//set osc division
Oled_Write_Cmd(0x80);//
Oled_Write_Cmd(0xD8);//set area color mode off
Oled_Write_Cmd(0x05);//
Oled_Write_Cmd(0xD9);//Set Pre-Charge Period
Oled_Write_Cmd(0xF1);//
Oled_Write_Cmd(0xDA);//set com pin configuartion
Oled_Write_Cmd(0x12);//
Oled_Write_Cmd(0xDB);//set Vcomh
Oled_Write_Cmd(0x30);//
Oled_Write_Cmd(0x8D);//set charge pump enable
Oled_Write_Cmd(0x14);//
Oled_Write_Cmd(0xAF);//--turn on oled panel
}
//定义了这个函数之后直接在main函数中调用即可实现初始化
怎么找?页寻址、水平寻址、垂直寻址
一般采用页寻址
发送cmd:0x20 Oled_Write_Cmd(0x20)
发送cmd:0x02 Oled_Write_Cmd(0x02)
选择到第0页:(一共8列)低八位控制
1011 0000(0xB0)
Oled_Write_Cmd(0xB0)
选择到第几列,输入两次,都是配置低位,一共128列,最低为0x00,0x10;最高位0x0f,0x17
Oled_Write_Cmd(0x00)
Oled_Write_Cmd(0x10)//第0列
配置为页寻址
配置从第几页(下)的第几列(上)开始
写入字符:
例如:
Oled_Write_Data(0x08);
以上在屏幕上显示的东西因为时保存在内存里的,所以都会保留在屏幕上,所以需要一个清屏函数
从page0-page7
每个page从第0列开始
0到127列,依次写0,每写入一次,列地址会自动偏移
代码如下:
void Oled_Clear()
{
unsigned char i,j; //256个数
for(i=0;i<8;i++){
Oled_Write_Cmd(0xB0 + i);//page0--page7
//每个page从0列
Oled_Write_Cmd(0x00);
Oled_Write_Cmd(0x10);
//0到127列,依次写入0,每写入数据,列地址自动偏移
for(j = 0;j<128;j++){
Oled_Write_Data(0);
}
}
}
运用自字模软件:
参数设置:
2.1字体设置:一般采用12
2.2其他选项:纵向取模,字节倒序,保留,任何时候都加0
输入要取模的字母,例如:A
ctr+enter
取模方式:C51
/*-- 文字: A --*/
/*-- 宋体12; 此字体下对应的点阵为:宽x高=8x16 --*/
char A1[8] = {0x00,0x00,0xC0,0x38,0xE0,0x00,0x00,0x00};
char A2[8] = {0x20,0x3C,0x23,0x02,0x02,0x27,0x38,0x20};
//main函数中
int i;
//第0页
Oled_Write_Cmd(0xB0);
Oled_Write_Cmd(0x00);
Oled_Write_Cmd(0x10);
for(i = 0;i < 8;i++){
Oled_Write_Data(A1[i]);
}
//第1页
Oled_Write_Cmd(0xB1);
Oled_Write_Cmd(0x00);
Oled_Write_Cmd(0x10);
for(i = 0;i < 8;i++){
Oled_Write_Data(A2[i]);
}
宽*高=8*16,宽代表列,高代表页,意思是需要8列2页(一页是8)所以需要用两页来分开显示一个字母A
取模步骤同上,注意规格
#include "reg52.h"
#include "intrins.h"
sbit scl = P0^1;
sbit sda = P0^3;
void IIC_Start()
{
scl = 0;
sda = 1;
scl = 1;
_nop_();
sda = 0;
_nop_();
}
void IIC_Stop()
{
scl = 0;
sda = 0;
scl = 1;
_nop_();
sda = 1;
_nop_();
}
char IIC_ACK()
{
char flag;
sda = 1;//就在时钟脉冲9期间释放数据线
_nop_();
scl = 1;
_nop_();
flag = sda;
_nop_();
scl = 0;
_nop_();
return flag;
}
void IIC_Send_Byte(char dataSend)
{
int i;
for(i = 0;i<8;i++){
scl = 0;//scl拉低,让sda做好数据准备
sda = dataSend & 0x80;//1000 0000获得dataSend的最高位,给sda
_nop_();//发送数据建立时间
scl = 1;//scl拉高开始发送
_nop_();//数据发送时间
scl = 0;//发送完毕拉低
_nop_();//
dataSend = dataSend << 1;
}
}
void Oled_Write_Cmd(char dataCmd)
{
// 1. start()
IIC_Start();
//
// 2. 写入从机地址 b0111 1000 0x78
IIC_Send_Byte(0x78);
// 3. ACK
IIC_ACK();
// 4. cotrol byte: (0)(0)000000 写入命令 (0)(1)000000写入数据
IIC_Send_Byte(0x00);
// 5. ACK
IIC_ACK();
//6. 写入指令/数据
IIC_Send_Byte(dataCmd);
//7. ACK
IIC_ACK();
//8. STOP
IIC_Stop();
}
void Oled_Write_Data(char dataData)
{
// 1. start()
IIC_Start();
//
// 2. 写入从机地址 b0111 1000 0x78
IIC_Send_Byte(0x78);
// 3. ACK
IIC_ACK();
// 4. cotrol byte: (0)(0)000000 写入命令 (0)(1)000000写入数据
IIC_Send_Byte(0x40);
// 5. ACK
IIC_ACK();
///6. 写入指令/数据
IIC_Send_Byte(dataData);
//7. ACK
IIC_ACK();
//8. STOP
IIC_Stop();
}
void Oled_Init(void){
Oled_Write_Cmd(0xAE);//--display off
Oled_Write_Cmd(0x00);//---set low column address
Oled_Write_Cmd(0x10);//---set high column address
Oled_Write_Cmd(0x40);//--set start line address
Oled_Write_Cmd(0xB0);//--set page address
Oled_Write_Cmd(0x81); // contract control
Oled_Write_Cmd(0xFF);//--128
Oled_Write_Cmd(0xA1);//set segment remap
Oled_Write_Cmd(0xA6);//--normal / reverse
Oled_Write_Cmd(0xA8);//--set multiplex ratio(1 to 64)
Oled_Write_Cmd(0x3F);//--1/32 duty
Oled_Write_Cmd(0xC8);//Com scan direction
Oled_Write_Cmd(0xD3);//-set display offset
Oled_Write_Cmd(0x00);//
Oled_Write_Cmd(0xD5);//set osc division
Oled_Write_Cmd(0x80);//
Oled_Write_Cmd(0xD8);//set area color mode off
Oled_Write_Cmd(0x05);//
Oled_Write_Cmd(0xD9);//Set Pre-Charge Period
Oled_Write_Cmd(0xF1);//
Oled_Write_Cmd(0xDA);//set com pin configuartion
Oled_Write_Cmd(0x12);//
Oled_Write_Cmd(0xDB);//set Vcomh
Oled_Write_Cmd(0x30);//
Oled_Write_Cmd(0x8D);//set charge pump enable
Oled_Write_Cmd(0x14);//
Oled_Write_Cmd(0xAF);//--turn on oled panel
}
void Oled_Clear()
{
unsigned char i,j; //-128 --- 127
for(i=0;i<8;i++){
Oled_Write_Cmd(0xB0 + i);//page0--page7
//每个page从0列
Oled_Write_Cmd(0x00);
Oled_Write_Cmd(0x10);
//0到127列,依次写入0,每写入数据,列地址自动偏移
for(j = 0;j<128;j++){
Oled_Write_Data(0);
}
}
}
/*-- 文字: 麦 --*/
/*-- 宋体12; 此字体下对应的点阵为:宽x高=16x16 --*/
char m1[16] = {0x00,0x04,0x24,0x24,0x24,0x24,0x24,0xFF,0x24,0x24,0x24,0x24,0x24,0x04,0x00,0x00};
char m2[16] = {0x81,0x91,0x89,0x45,0x4F,0x55,0x25,0x25,0x25,0x55,0x4D,0x45,0x81,0x81,0x81,0x00};
/*-- 文字: 仕 --*/
/*-- 宋体12; 此字体下对应的点阵为:宽x高=16x16 --*/
code char s1[16] = {0x00,0x80,0x60,0xF8,0x27,0x20,0x20,0x20,0x20,0xFF,0x20,0x20,0x20,0x20,0x20,0x00};
code char s2[16] = {0x01,0x00,0x00,0xFF,0x00,0x20,0x20,0x20,0x20,0x3F,0x20,0x20,0x20,0x20,0x00,0x00};
/*-- 文字: 腾 --*/
/*-- 宋体12; 此字体下对应的点阵为:宽x高=16x16 --*/
code char t1[16] = {0x00,0xFE,0x22,0x22,0xFE,0x28,0xA9,0x6E,0x28,0x3F,0x28,0x6C,0xAB,0x28,0x20,0x00};
code char t2[16] = {0x80,0x7F,0x02,0x82,0xFF,0x01,0x20,0x2D,0x29,0x29,0x29,0x4F,0x88,0x79,0x01,0x00};
/*-- 文字: 真 --*/
/*-- 宋体12; 此字体下对应的点阵为:宽x高=16x16 --*/
code char z1[16] = {0x00,0x04,0x04,0xF4,0x54,0x54,0x54,0x5F,0x54,0x54,0x54,0xF4,0x04,0x04,0x00,0x00};
code char z2[16] = {0x10,0x10,0x90,0x5F,0x35,0x15,0x15,0x15,0x15,0x15,0x35,0x5F,0x90,0x10,0x10,0x00};
/*-- 文字: T --*/
/*-- 宋体12; 此字体下对应的点阵为:宽x高=16x16 --*/
code char T_1[16] = {0x18,0x08,0x08,0xF8,0x08,0x08,0x18,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00};
code char T_2[16] = {0x00,0x00,0x20,0x3F,0x20,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00};
/*-- 文字: M --*/
/*-- 宋体12; 此字体下对应的点阵为:宽x高=16x16 --*/
code char M_1[16] = {0x08,0xF8,0xF8,0x00,0xF8,0xF8,0x08,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00};
code char M_2[16] = {0x20,0x3F,0x01,0x3E,0x01,0x3F,0x20,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00};
/*-- 文字: 帅 --*/
/*-- 宋体12; 此字体下对应的点阵为:宽x高=16x16 --*/
code char S_1[16] = {0x00,0xFC,0x00,0x00,0xFF,0x00,0x00,0xF0,0x10,0x10,0xFF,0x10,0x10,0xF0,0x00,0x00};
code char S_2[16] = {0x00,0x87,0x40,0x30,0x0F,0x00,0x00,0x1F,0x00,0x00,0xFF,0x08,0x10,0x0F,0x00,0x00};
void main()
{
unsigned char i;
//1. OLED初始化
Oled_Init();
//2. 选择一个位置
//2.1 确认页寻址模式
Oled_Write_Cmd(0x20);
Oled_Write_Cmd(0x02);
Oled_Clear();
//2.2 选择PAGE0 1011 0000
// 0xB0
Oled_Write_Cmd(0xB2);
Oled_Write_Cmd(0x08);
Oled_Write_Cmd(0x10);
//3. 显示一个点
// Oled_Write_Data(0x08);
// Oled_Write_Data(0x08);
// Oled_Write_Data(0x08);
// Oled_Write_Data(0x08);
// Oled_Write_Data(0x08);
for(i=0;i<16;i++){
Oled_Write_Data(m1[i]);
}
for(i=0;i<16;i++){
Oled_Write_Data(s1[i]);
}
for(i=0;i<16;i++){
Oled_Write_Data(t1[i]);
}
for(i=0;i<16;i++){
Oled_Write_Data(z1[i]);
}
for(i=0;i<16;i++){
Oled_Write_Data(T_1[i]);
}
for(i=0;i<16;i++){
Oled_Write_Data(M_1[i]);
}
for(i=0;i<16;i++){
Oled_Write_Data(S_1[i]);
}
Oled_Write_Cmd(0xB3);
Oled_Write_Cmd(0x08);
Oled_Write_Cmd(0x10);
for(i=0;i<16;i++){
Oled_Write_Data(m2[i]);
}
for(i=0;i<16;i++){
Oled_Write_Data(s2[i]);
}
for(i=0;i<16;i++){
Oled_Write_Data(t2[i]);
}
for(i=0;i<16;i++){
Oled_Write_Data(z2[i]);
}
for(i=0;i<16;i++){
Oled_Write_Data(T_2[i]);
}
for(i=0;i<16;i++){
Oled_Write_Data(M_2[i]);
}
for(i=0;i<16;i++){
Oled_Write_Data(S_2[i]);
}
while(1);
}
待更新...