1.IIC简单的介绍
I²C(Inter-Integrated Circuit),即集成电路总线,也被称为I2C或IIC,在中文里有时称为双线串行总线。它是由飞利浦半导体(现在的NXP半导体)在1982年开发的一种简单双向二线制同步串行数据通信总线,主要用于PC的微控制器和其他模块之间的短距离通信,尤其是在嵌入式系统中。
假如IIC通信没有被发明出来,那么微控制器与被控制芯片之间就需要串口连接,假如只有一个被控制芯片,那还好,假如有四个或者很多个被控制芯片呢,串口连接起来就会很复杂了,于是IIC通信协议产生了
I²C总线设计的主要目的是为了简化电路板上的微控制器与被控芯片之间的连接,通过使用仅有的两根双向信号线(一根为数据线SDA,另一根为时钟线SCL)来实现多个设备间的通信。这种方式减少了布线复杂度,使得电路板的设计更加简洁高效。
I²C的工作原理基于主从模式,其中至少有一个设备充当主机,而其他设备作为从机。主机负责启动和终止数据传输,并且控制时钟信号;而从机则响应主机的命令。每个设备都有一个唯一的地址,主机通过这个地址来选择要通信的具体从机。
2.IIC通信的时序
谈到软件模拟任何一个通信协议时,最不可或缺的就是对他的通信协议的讲解,下面我们将根据图片与文字讲述一下IIC通信协议.根据上段所讲的IIC工作基本原理,在STM32中,主机在大部分情况下就是单片机本身了,那么被控制的芯片就是从机了.
在串口通信中我们用发送线和接收线来进行数据的传递,那么在IIC中我们则是利用SCL(时钟线)和SDA(数据线)来进行数据的发送和接收.如下图所示
那么要通过IIC发送一个数据,首先就产生一个起始信号
起始信号:
在空闲状态下,数据线和时钟线必须保持高电平.产生起始信号时SCL时钟线必须是高电平,而在这时SDA(数据线)则在此时完成由高电平到低电平的跳跃,如图
这样就产生了一个起始信号,那么在软件模拟IIC通信时,起始信该怎么写呢,
首先对GPIO口进行初始化,我们要用的是GPIOB 0和 1分别代表SCL(时钟线)和SDA(数据线)
static void OLED_GPIO_Init(void) { GPIO_InitTypeDef GPIO_InitStructure; //定义一个GPIO_InitTypeDef类型的结构体 RCC_APB2PeriphClockCmd( RCC_APB2Periph_GPIOB, ENABLE); //打开GPIOC的外设时钟 GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0 | GPIO_Pin_1; //选择控制的引脚 GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_OD; //设置为通用开漏输出 GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; //设置输出速率为50MHz GPIO_Init(GPIOB,&GPIO_InitStructure); //调用库函数初始化GPIOA SCL_Bits(); //设B0(SCL)为高电平 SDA_Bits(); //设PB1(SDA)为高电平 }
然后就开始写起始信号了
//IIC起始信号 static void IIC_Star() { SCL_Bits(); //时间总线 高电平 SDA_Bits(); //数据总线 高电平 delay_us(1); SDA_Resebits(); //数据总线 低电平 delay_us(1); SCL_Resebits(); //时间总线低电平 方便后面读取数据 delay_us(1); }
下面是是一些宏定义方便理解
#define SCL_Bits() GPIO_SetBits(GPIOB,GPIO_Pin_0) #define SCL_Resebits() GPIO_ResetBits(GPIOB,GPIO_Pin_0) #define SDA_Bits() GPIO_SetBits(GPIOB,GPIO_Pin_1) #define SDA_Resebits() GPIO_ResetBits(GPIOB,GPIO_Pin_1) #define SDA_Read() GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_1)//读取SDA的电平
写一个字节的函数
这样一来,我们就大概看懂了IIC的起始信号时如何产生的,起始信号产生后,接下来我们就需要书写一个写一个字节的函数了,那么在SCL(时钟线)高电平的时候,所以说当我们想传输一个数据是,首先拉低SCL(时钟线),然后让SDA(数据线)为高电平或者低电平,然后再将SCL(时钟线)拉高,这样SDA(数据线)上的电平就不能发生变化,假如此时SDA(数据总线)为高电平,这样就完成了,一个字节'1'的传输,,反之则传输'0',如下图
例如我们发送的是这样的数据,他就表示发送的数据为1010000
下面就是代码部分
static void Write_IIC_Byte(unsigned char IIC_Byte) { unsigned char i; //定义变量,一个字节有八位数据 for(i = 0;i < 8;i++) { SCL_Resebits(); //时钟线置低,为传输数据做准备 delay_us(1); if(IIC_Byte & 0x80)//获取最高位 { SDA_Bits(); //高电平 } else{ SDA_Resebits();//低电平 } IIC_Byte<< = 1; //左移一位,将下一位放到最高位,以便单独提取出来 delay_us(1); SCL_Bits(); //时钟线置高,产生上升沿,把数据发送出去 delay_us(1); } SCL_Resebits(); //时钟线置低 delay_us(1); while(IIC_Wait_Ack()); //从机应答,这个就是接下来讲的 }
从机应答
当我们发送完一个数据后,从机得回答一下我们主机一下,收到,由此产生了从机应答信号,从机应答的时候,根据I2C硬件通信协议,我们也要将SCL(时钟线)拉低,因为要将SDA线拉高(此时我们就是第九个脉冲),只有在SCL低电平时才能改变SDA的电平,然后再将SCL拉高,然后开始读取数据,
//模拟IIC从机的应答信号 static uint8_t IIC_Wait_Ack(void) { uint8_t ack; SCL_Resebits();//拉低SCL delay_us(1); SDA_Bits(); //释放SDA 就是将SDA置1 delay_us(1); SCL_Bits();//拉高SCL 读取SDA上面的数据 if(SDA_Read() ) //读取SDA的输入信号 { ack = 1; } else{ ack = 0; } SCL_Resebits(); //拉低SCL 为下一个时钟周期或数据传输做准备 delay_us(1); return ack; }
终止信号
我们再根据协议写一个终止指令,告诉单机读取结束了,这个就比较简单了,
static void IIC_Stop() { SDA_Resebits(); //数据总线 低电平 delay_us(1); SCL_Bits(); //时间总线 高电平 delay_us(1); SDA_Bits(); //数据总线 高电平 delay_us(1); }
根据IIC协议,我们书写了一个驱动OLED的程序,供大家参考
OLED程序
#include "stm32f10x.h" #include "OLED.h" #include "Systick.h" #include "picture.h" //OLED端口初始化 static void OLED_GPIO_Init(void) { GPIO_InitTypeDef GPIO_InitStructure; //定义一个GPIO_InitTypeDef类型的结构体 RCC_APB2PeriphClockCmd( RCC_APB2Periph_GPIOB, ENABLE); //打开GPIOC的外设时钟 GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0 | GPIO_Pin_1; //选择控制的引脚 GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_OD; //设置为通用开漏输出 GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; //设置输出速率为50MHz GPIO_Init(GPIOB,&GPIO_InitStructure); //调用库函数初始化GPIOA SCL_Bits(); //设B0(SCL)为高电平 SDA_Bits(); //设PB1(SDA)为高电平 } //IIC起始信号 static void IIC_Star() { SCL_Bits(); //时间总线 高电平 SDA_Bits(); //数据总线 高电平 delay_us(1); SDA_Resebits(); //数据总线 低电平 delay_us(1); SCL_Resebits(); //时间总线低电平 方便后面读取数据 delay_us(1); } //IIC停止信号 static void IIC_Stop() { SDA_Resebits(); //数据总线 低电平 delay_us(1); SCL_Bits(); //时间总线 高电平 delay_us(1); SDA_Bits(); //数据总线 高电平 delay_us(1); } //模拟IIC从机的应答信号 static uint8_t IIC_Wait_Ack(void) { uint8_t ack; SCL_Resebits();//拉低SCL delay_us(1); SDA_Bits(); //释放SDA 就是将SDA置1 delay_us(1); SCL_Bits();//拉高SCL 读取SDA上面的数据 if(SDA_Read() ) //读取SDA的输入信号 { ack = 1; } else{ ack = 0; } SCL_Resebits(); //拉低SCL 为下一个时钟周期或数据传输做准备 delay_us(1); return ack; } //写字节 static void Write_IIC_Byte(unsigned char IIC_Byte) { unsigned char i; //定义变量 for(i=0;i<8;i++) //for循环8次 { SCL_Resebits(); //时钟线置低,为传输数据做准备 delay_us(1); if(IIC_Byte & 0x80) //读取最高位 SDA_Bits();//最高位为1 else SDA_Resebits(); //最高位为0 IIC_Byte <<= 1; //数据左移1位 delay_us(1); SCL_Bits(); //时钟线置高,产生上升沿,把数据发送出去 delay_us(1); } SCL_Resebits(); //时钟线置低 delay_us(1); while(IIC_Wait_Ack()); //从机应答 } //写命令 static void Write_IIC_Command(unsigned char IIC_Command) { IIC_Star(); Write_IIC_Byte(0x78);//写入从机地址,SD0 = 0 Write_IIC_Byte(0x00);//写入命令 Write_IIC_Byte(IIC_Command);//数据 IIC_Stop(); //发送停止信号 } //写数据 static void Write_IIC_data(unsigned char IIC_Data) { IIC_Star(); Write_IIC_Byte(0x78); //写入从机地址,SD0 = 0 Write_IIC_Byte(0x40); //写入数据 Write_IIC_Byte(IIC_Data);//数据 IIC_Stop(); //发送停止信号 } // void OLED_WR_Byte(unsigned char dat,unsigned char cmd) { if(cmd) { Write_IIC_data(dat); //写入数据 } else { Write_IIC_Command(dat); //写入命令 } } //设置起始位置 void OLED_Set_Pos(unsigned char x, unsigned char y) { OLED_WR_Byte(0xb0+y,OLED_CMD); OLED_WR_Byte((x & 0x0f) ,OLED_CMD); OLED_WR_Byte(((x & 0xf0) >>4 )| 0x10,OLED_CMD); } //开启 void OLED_Display_On(void) { OLED_WR_Byte(0X8D,OLED_CMD); //设置OLED电荷泵 OLED_WR_Byte(0X14,OLED_CMD); //使能,开 OLED_WR_Byte(0XAF,OLED_CMD); //开显示 } //关闭 void OLED_Display_Off(void) { OLED_WR_Byte(0XAE,OLED_CMD); //关显示 OLED_WR_Byte(0X8D,OLED_CMD); //设置OLED电荷泵 OLED_WR_Byte(0X10,OLED_CMD); //失能,关 } void OLED_Clear(void) { uint8_t i = 0,j = 0; for(j = 0;j<8;j++) { OLED_WR_Byte (0xb0+j,OLED_CMD); //从0~7页依次写入 OLED_WR_Byte (0x00,OLED_CMD); //列低地址 OLED_WR_Byte (0x10,OLED_CMD); //列高地址 for(i = 0;i<128;i++) { OLED_WR_Byte(0,OLED_DATA); } } } void OLED_ShouChar(unsigned char x,unsigned char y,unsigned char chr,uint8_t size) { uint8_t ch = 0,i = 0; ch = chr - 32; if(x>128) { x = 0; y = y+2; } if(size == 16) { OLED_Set_Pos(x,y); //从x,y开始画点 for(i=0;i<8;i++) OLED_WR_Byte(OLED_F8x16[ch][i],OLED_DATA);//找出字符c的数组位数,先把第一列画完 OLED_Set_Pos(x,y+1); //从x,y开始画点 for(i=0;i<8;i++) OLED_WR_Byte(OLED_F8x16[ch][i+8],OLED_DATA);//找出字符c的数组位数,画出第二列,我这个是二维数组 } else{ OLED_Set_Pos(x,y); //从x,y开始画点 for(i=0;i<6;i++) OLED_WR_Byte(OLED_F6x8[ch][i],OLED_DATA);//找出字符c的数组位数,先把第一列画完 } } //显示字符串 void OLED_Show_String(uint8_t x,uint8_t y,uint8_t ch[],uint8_t s) { uint8_t i = 0; while(ch[i]!='\0') { OLED_ShouChar(x,y,ch[i],s); i++; if(s == 16) { x = x+8; } else { x = x+6; } } } //计算平方的函数,为了提取出单个数 uint32_t oled_pow(uint8_t m,uint8_t n) { unsigned int result = 1; while(n--) { result = result* m; } return result; } void oled_Num(uint8_t x,uint8_t y,uint32_t i,uint8_t len,uint8_t size) { uint8_t t = 0,temp = 0; for(i = 0;i < len;i++) { temp = oled_pow(i,len) ; } } //显示数字的 void OLED_ShowNum(unsigned char x,unsigned char y,unsigned int num,unsigned char len,unsigned char size) { unsigned char t,temp; //定义变量 uint8_t enshow = 0; for(t=0;t<len;t++) { temp=(num/oled_pow(10,len-1-t))%10;//取出输入数的每个位,由高到低 if(temp == 0 && enshow == 0) { OLED_ShouChar(x,y,' ',size); continue; } OLED_ShouChar(x,y,temp+'0',size); enshow = 1; if(size == 16) { x = x+8; } else{ x = x+6; } } } //显示中文 void OLED_ShowCHinese(unsigned char x,unsigned char y,unsigned char no) { uint8_t i = 0; OLED_Set_Pos(x,y); for(i = 0;i < 16;i++) { OLED_WR_Byte(Hzk[no*2][i],OLED_DATA); } OLED_Set_Pos(x,y+1); for(i = 0;i < 16;i++) { OLED_WR_Byte(Hzk[no*2 +1][i],OLED_DATA); } } //显示图片的函数,x0,y0是 左下角的 起始点坐标位置 x1,y1是右下角坐标结束位置 void OLED_ShowDraw(uint8_t x0,uint8_t y0,uint8_t x1,uint8_t y1,uint8_t BMP[]) { uint8_t x ,y ; unsigned int i = 0; if(y1%8 == 0) y= y1/8; else y= y1/8 + 1; for(y = y0;y<y1;y++) { //因为x0 y0是左上角 起始位置坐标 OLED_Set_Pos(x0,y); for(x = x0;x<x1;x++) { Write_IIC_data(BMP[i++]); } } } void OLED_Init(void) { OLED_GPIO_Init(); //GPIO口初始化 delay_ms(200); //延迟,由于单片机上电初始化比OLED快,所以必须加上延迟,等待OLED上复位完成 OLED_WR_Byte(0xAE,OLED_CMD); //关闭显示 OLED_WR_Byte(0x00,OLED_CMD); //设置低列地址 OLED_WR_Byte(0x10,OLED_CMD); //设置高列地址 OLED_WR_Byte(0x40,OLED_CMD); //设置起始行地址 OLED_WR_Byte(0xB0,OLED_CMD); //设置页地址 OLED_WR_Byte(0x81,OLED_CMD); // 对比度设置,可设置亮度 OLED_WR_Byte(0xFF,OLED_CMD); // 265 OLED_WR_Byte(0xA1,OLED_CMD); //设置段(SEG)的起始映射地址;column的127地址是SEG0的地址 OLED_WR_Byte(0xA6,OLED_CMD); //正常显示;0xa7逆显示 OLED_WR_Byte(0xA8,OLED_CMD); //设置驱动路数(16~64) OLED_WR_Byte(0x3F,OLED_CMD); //64duty OLED_WR_Byte(0xC8,OLED_CMD); //重映射模式,COM[N-1]~COM0扫描 OLED_WR_Byte(0xD3,OLED_CMD); //设置显示偏移 OLED_WR_Byte(0x00,OLED_CMD); //无偏移 OLED_WR_Byte(0xD5,OLED_CMD); //设置震荡器分频 OLED_WR_Byte(0x80,OLED_CMD); //使用默认值 OLED_WR_Byte(0xD9,OLED_CMD); //设置 Pre-Charge Period OLED_WR_Byte(0xF1,OLED_CMD); //使用官方推荐值 OLED_WR_Byte(0xDA,OLED_CMD); //设置 com pin configuartion OLED_WR_Byte(0x12,OLED_CMD); //使用默认值 OLED_WR_Byte(0xDB,OLED_CMD); //设置 Vcomh,可调节亮度(默认) OLED_WR_Byte(0x40,OLED_CMD); 使用官方推荐值 OLED_WR_Byte(0x8D,OLED_CMD); //设置OLED电荷泵 OLED_WR_Byte(0x14,OLED_CMD); //开显示 OLED_WR_Byte(0xAF,OLED_CMD); //开启OLED面板显示 OLED_Clear(); //清屏 OLED_Set_Pos(0,0); //设置数据写入的起始行、列 }
.h文件
#ifndef __OLED_H #define __OLED_H #define SCL_Bits() GPIO_SetBits(GPIOB,GPIO_Pin_0) #define SCL_Resebits() GPIO_ResetBits(GPIOB,GPIO_Pin_0) #define SDA_Bits() GPIO_SetBits(GPIOB,GPIO_Pin_1) #define SDA_Resebits() GPIO_ResetBits(GPIOB,GPIO_Pin_1) #define SDA_Read() GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_1)//读取SDA的电平 #define IIC_ACK 0 //应答 #define IIC_NO_ACK 1 //不应答 #define OLED_CMD 0 //写命令 #define OLED_DATA 1 //写数据 #define Max_Column 128 //最大列数 #include "stm32f10x.h" void OLED_Init(void); static void OLED_GPIO_Init(void); static void IIC_Star(); //开始信号 static void IIC_Stop(); //停止信号 static uint8_t IIC_Wait_Ack(void);//模拟IIC应答 static void Write_IIC_Byte(unsigned char IIC_Byte); //写字节函数 static void Write_IIC_Command(unsigned char IIC_Command);//写命令 static void Write_IIC_data(unsigned char IIC_Data);//写数据 void OLED_WR_Byte(unsigned char dat,unsigned char cmd); //写命令或者 数据 void OLED_Set_Pos(unsigned char x, unsigned char y);// 设置位置 void OLED_Display_On(void); //开启屏幕 void OLED_Display_Off(void);//关闭屏幕 void OLED_Clear(void); //清屏函数 void OLED_ShouChar(unsigned char x,unsigned char y,unsigned char chr,uint8_t size); //显示字符的函数 void OLED_Show_String(uint8_t x,uint8_t y,uint8_t ch[],uint8_t s);//显示字符串的函数 void OLED_ShowNum(unsigned char x,unsigned char y,unsigned int num,unsigned char len,unsigned char size); //显示数字 void OLED_ShowCHinese(unsigned char x,unsigned char y,unsigned char no); //显示中文 void OLED_ShowDraw(uint8_t x0,uint8_t y0,uint8_t x1,uint8_t y1,uint8_t BMP[]); #endif
main
#include "stm32f10x.h" #include "main.h" #include "LED.h" #include "Bear.h" #include "key.h" #include "Realy.h" #include "usart.h" #include "shake.h" #include "exti.h" #include "time.h" #include "pwm.h" #include "HC-SR04.h" #include "Systick.h" #include "OLED.h" #include "DHT11.h" extern const unsigned char gImage_oled[]; uint8_t GARM[1024]; int main() { OLED_Init(); OLED_Clear(); uint16_t a ; for(a= 0;a<1023;a++) { GARM[a] = 0xff; OLED_WR_Byte(GARM[a],OLED_DATA); //OLED_Set_Pos(0,1); } while(1) { } }