目录
一 IIC协议
1.概述
IIC全称Inter-Integrated Circuit (集成电路总线)
是由PHILIPS公司在80年代开发的两线式串行总线,用于连接微控制器及其外围设备。IIC属于半双工同步通信方式
特点
简单性和有效性。
由于接口直接在组件之上,因此IIC总线占用的空间非常小,减少了电路板的空间和芯片管脚的数量,降低了互联成本。总线的长度可高达25英尺,并且能够以10Kbps的最大传输速率支持40个组件
多主控(multimastering)
其中任何能够进行发送和接收的设备都可以成为主总线。一个主控能够控制信号的传输和时钟频率。当然,在任何时间点上只能有一个主控。
构成
IIC串行总线一般有两根信号线,一根是双向的数据线SDA,另一根是时钟线SCL,其时钟信号是由主控器件产生。所有接到IIC总线设备上的串行数据SDA都接到总线的SDA上,各设备的时钟线SCL接到总线的SCL上。对于并联在一条总线上的每个IC都有唯一的地址。
2.IIC协议
IIC总线在传输数据的过程中一共有三种类型信号,分别为:开始信号、结束信号和应答信号。
//起始位,停止位,数据位,速度
这些信号中,起始信号是必需的,结束信号和应答信号
起始信号
终止信号
应答信号
发送器每发送一个字节(8个bit),就在时钟脉冲9期间释放数据线,由接收器反馈一个应答信号。
应答信号为低电平时,规定为有效应答位(ACK,简称应答位),表示接收器已经成功地接收了该字节;
应答信号为高电平时,规定为非应答位(NACK),一般表示接收器接收该字节没有成功。
数据发送的时序
3.代码实现
void IIC_START()
{
SCL = 1;
SDA = 1;
_nop_();
SDA = 0;
_nop_();
}
void IIC_END()
{
SCL = 1;
SDA = 0;
_nop_();
SDA = 1;
_nop_();
}
char IIC_ACK()
{
char flag;
SDA = 1;
_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;
SDA = dataSend & 0x80;//1000 0000 获得dataSend最高位给SDA
_nop_();//数据建立时间
SCL = 1;//SCL拉高开始发送
_nop_();//数据发送时间
SCL = 0;//发送完拉低
_nop_();
dataSend <<= 1;
}
}
二 OLED
1.OLED写命令
1. start()
2. 写入从机地址 b0111 1000 0x78
3. ACK
4. cotrol byte: (0)(0)000000 写入命令 (0)(1)000000写入数据
5. ACK
6. 写入指令/数据
7. ACK
8. STOP
void Oled_Write_Cmd(char OLED_cmd)
{
// 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(OLED_cmd);
//7. ACK
IIC_ACK();
//8. STOP
IIC_END();
}
void Oled_Write_Data(char OLED_data)
{
// 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(OLED_data);
//7. ACK
IIC_ACK();
//8. STOP
IIC_END();
}
2.OLED的寻址模式
如何显示一个点?
有三种,分别位页地址模式,水平地址模式和垂直地址模式,可以通过一下表格进行配置内存管理
页地址模式
水平地址模式
垂直地址模式
列地址选择
如果写入0x08(b00001000)会显示什么呢
一个字节负责一个Page的一列显示
初始化代码
//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
}
显示一个点:
(如果雪花可以先往后看)
void main()
{
//Oled初始化
Oled_Init();
//确认页寻址模式
Oled_Write_Cmd(0x20);
Oled_Write_Cmd(0x02);
//选择PAGE0
Oled_Write_Cmd(0xB0);
//显示一个点
Oled_Write_Data(0x08);
while(1);
}
列地址:
如果显示雪花,可能需要清屏一下
清屏并显示一条线(注意这里用char很容易雪花,建议用int),清屏函数往后再看。
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);
}
}
}
void main()
{
char i;
//Oled初始化
Oled_Init();
//确认页寻址模式
Oled_Write_Cmd(0x20);
Oled_Write_Cmd(0x02);
//清屏
Oled_Clear();
//选择PAGE0
Oled_Write_Cmd(0xB0);
//显示一条线
for(i = 0;i<50;i++){
Oled_Write_Data(0x08);
}
//选择PAGE5
Oled_Write_Cmd(0xB5);
//显示一条线
for(i = 0;i<50;i++){
Oled_Write_Data(0x08);
}
while(1);
}
显示了两条线,但是发现第二条线起始位不是屏幕边缘,而是跟着上一条线的屁股的横坐标继续显示,列在递增。
所以配置列,使得其不递增。
在最后一列显示一个点。
//选择PAGE6
Oled_Write_Cmd(0xB6);
//高位7,低位f,即01111111.对应127,最后一列
Oled_Write_Cmd(0x0f);
Oled_Write_Cmd(0x17);
//显示一个点
Oled_Write_Data(0x08);
所以可以使得两条线并行:
//选择PAGE0
Oled_Write_Cmd(0xB0);
//显示一条线
for(i = 0;i<30;i++){
Oled_Write_Data(0x08);
}
//选择PAGE5
Oled_Write_Cmd(0xB5);
//重新从第一列开始
Oled_Write_Cmd(0x00);
Oled_Write_Cmd(0x10);
//显示一条线
for(i = 0;i<30;i++){
Oled_Write_Data(0x08);
}
清屏函数:
unsigned char i,j; char型本身范围小//-128 --- 127
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);
}
}
}
3.显示文字
屏幕本身没字库,可以借助字模软件。
宋体字号12 、其它选项:
输入A,ctrl+enter 取模,选择C51模式,可得:
/*-- 文字: A --*/
/*-- 宋体12; 此字体下对应的点阵为:宽x高=8x16 --*/
0x00,0x00,0xC0,0x38,0xE0,0x00,0x00,0x00,0x20,0x3C,0x23,0x02,0x02,0x27,0x38,0x20,
一个page高度为8,所以把A拆分成上下部分。
/*-- 文字: A --*/
/*-- 宋体12; 此字体下对应的点阵为:宽x高=8x16 --*/
char A1[] = {0x00,0x00,0xC0,0x38,0xE0,0x00,0x00,0x00};
char A1[] = {0x20,0x3C,0x23,0x02,0x02,0x27,0x38,0x20};
显示一个字母A
//显示一个字母A
//选择PAGE0
Oled_Write_Cmd(0xB0);
//从0列开始
Oled_Write_Cmd(0x00);
Oled_Write_Cmd(0x10);
//显示上半部分
for(i = 0;i<8;i++) Oled_Write_Data(A1[i]);
//选择PAGE1
Oled_Write_Cmd(0xB1);
//从0列开始
Oled_Write_Cmd(0x00);
Oled_Write_Cmd(0x10);
//显示下半部分
for(i = 0;i<8;i++) Oled_Write_Data(A2[i]);
显示“鸡你太美”
/*-- 文字: 鸡 --*/
/*-- 宋体12; 此字体下对应的点阵为:宽x高=16x16 --*/
0x08,0x48,0x88,0x08,0xC8,0x38,0x00,0x00,0xFC,0x06,0x15,0x44,0x84,0x7C,0x00,0x00,
0x20,0x10,0x0C,0x03,0x04,0x18,0x00,0x10,0x13,0x12,0x12,0x52,0x92,0x42,0x3E,0x00,/*-- 文字: 你 --*/
/*-- 宋体12; 此字体下对应的点阵为:宽x高=16x16 --*/
0x00,0x80,0x60,0xF8,0x07,0x40,0x20,0x18,0x0F,0x08,0xC8,0x08,0x08,0x28,0x18,0x00,
0x01,0x00,0x00,0xFF,0x00,0x10,0x0C,0x03,0x40,0x80,0x7F,0x00,0x01,0x06,0x18,0x00,/*-- 文字: 太 --*/
/*-- 宋体12; 此字体下对应的点阵为:宽x高=16x16 --*/
0x20,0x20,0x20,0x20,0x20,0x20,0x20,0xFF,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x00,
0x80,0x80,0x40,0x20,0x10,0x0C,0x13,0x60,0x03,0x0C,0x10,0x20,0x40,0x80,0x80,0x00,/*-- 文字: 美 --*/
/*-- 宋体12; 此字体下对应的点阵为:宽x高=16x16 --*/
0x00,0x04,0x24,0x24,0x25,0x26,0x24,0xFC,0x24,0x26,0x25,0x24,0x24,0x04,0x00,0x00,
0x81,0x89,0x89,0x49,0x49,0x29,0x19,0x0F,0x19,0x29,0x49,0x49,0x89,0x89,0x81,0x00,
容量太大,加上code前缀
/*-- 文字: 鸡 --*/
/*-- 宋体12; 此字体下对应的点阵为:宽x高=16x16 --*/
char j1[] = {0x08,0x48,0x88,0x08,0xC8,0x38,0x00,0x00,0xFC,0x06,0x15,0x44,0x84,0x7C,0x00,0x00};
char j2[] = {0x20,0x10,0x0C,0x03,0x04,0x18,0x00,0x10,0x13,0x12,0x12,0x52,0x92,0x42,0x3E,0x00};
/*-- 文字: 你 --*/
/*-- 宋体12; 此字体下对应的点阵为:宽x高=16x16 --*/
char n1[] = {0x00,0x80,0x60,0xF8,0x07,0x40,0x20,0x18,0x0F,0x08,0xC8,0x08,0x08,0x28,0x18,0x00};
char n2[] = {0x01,0x00,0x00,0xFF,0x00,0x10,0x0C,0x03,0x40,0x80,0x7F,0x00,0x01,0x06,0x18,0x00};
/*-- 文字: 太 --*/
/*-- 宋体12; 此字体下对应的点阵为:宽x高=16x16 --*/
code char t1[] = {0x20,0x20,0x20,0x20,0x20,0x20,0x20,0xFF,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x00};
code char t2[] = {0x80,0x80,0x40,0x20,0x10,0x0C,0x13,0x60,0x03,0x0C,0x10,0x20,0x40,0x80,0x80,0x00};
/*-- 文字: 美 --*/
/*-- 宋体12; 此字体下对应的点阵为:宽x高=16x16 --*/
code char m1[] = {0x00,0x04,0x24,0x24,0x25,0x26,0x24,0xFC,0x24,0x26,0x25,0x24,0x24,0x04,0x00,0x00};
code char m2[] = {0x81,0x89,0x89,0x49,0x49,0x29,0x19,0x0F,0x19,0x29,0x49,0x49,0x89,0x89,0x81,0x00};
先显示一个鸡字:
/*-- 文字: 鸡 --*/
/*-- 宋体12; 此字体下对应的点阵为:宽x高=16x16 --*/
char j1[] = {0x08,0x48,0x88,0x08,0xC8,0x38,0x00,0x00,0xFC,0x06,0x15,0x44,0x84,0x7C,0x00,0x00};
char j2[] = {0x20,0x10,0x0C,0x03,0x04,0x18,0x00,0x10,0x13,0x12,0x12,0x52,0x92,0x42,0x3E,0x00};
void main()
{
int i;
//Oled初始化
Oled_Init();
//确认页寻址模式
Oled_Write_Cmd(0x20);
Oled_Write_Cmd(0x02);
//清屏
Oled_Clear();
//显示 鸡
//选择PAGE0
Oled_Write_Cmd(0xB0);
//从0列开始
Oled_Write_Cmd(0x00);
Oled_Write_Cmd(0x10);
//显示上半部分
for(i = 0;i<16;i++) Oled_Write_Data(j1[i]);
//选择PAGE1
Oled_Write_Cmd(0xB1);
//从0列开始
Oled_Write_Cmd(0x00);
Oled_Write_Cmd(0x10);
//显示下半部分
for(i = 0;i<16;i++) Oled_Write_Data(j2[i]);
while(1);
}
一个个打太麻烦,封装一个函数,分别用两个指针数组表示字符串上下部分,函数中用指针数组地址偏移的方式:
*(*(str1+i)+j)
代码:
//创2个指针数组
char* str1[4] = {j1,n1,t1,m1};
char* str2[4] = {j2,n2,t2,m2};
//
void Print_On_Oled(char* str1[],char* str2[]){
unsigned char i,j;
//选择PAGE0
Oled_Write_Cmd(0xB0);
//显示上半
for(i = 0;i<4;i++){
for(j = 0;j<16;j++){
Oled_Write_Data(*(*(str1+i)+j));
}
}
//选择PAGE1
Oled_Write_Cmd(0xB1);
//列数归0
Oled_Write_Cmd(0x00);
Oled_Write_Cmd(0x10);
//显示下半
for(i = 0;i<4;i++){
for(j = 0;j<16;j++){
Oled_Write_Data(*(*(str2+i)+j));
}
}
}
void main()
{
//Oled初始化
Oled_Init();
//确认页寻址模式
Oled_Write_Cmd(0x20);
Oled_Write_Cmd(0x02);
//清屏
Oled_Clear();
//显示字符串
Print_On_Oled(str1,str2);
while(1);
}
效果:
4.显示图片
支持BMP格式图片。
代码逻辑类似清屏函数:
/*-- 调入了一幅图像:C:\Users\26982\Desktop\2.bmp --*/
/*-- 宽度x高度=128x64 --*/
code unsigned char image[] = {
0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
... ...
... ...
... ...
0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF};
void Oled_Image(unsigned char *bmp)
{
unsigned char i;
unsigned int j;
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 = 128 * i;j<(128 * (i+1));j++){
Oled_Write_Data(bmp[j]);
}
}
}