目标:1 学习IIC协议理论
2 通过IIC协议实现与0.96屏幕通讯和与MPU6050通讯
由于不想帖子拉的太长,所以MPU6050,EEPROM和OLED屏幕的详细使用会单独开两个帖子,这一个帖子主要还是写IIC的软件和硬件通讯,写完之后链接会放在下面的。
0.96 OLED 模拟IIC:
MPU6050:
EEPROM;
如果不想看的可以直接使用git把我的代码下载出来,里面工程挺全的,后期会慢慢的补注释之类的
如果不会使用git快速下载可以选择直接下载压缩包或者去看看git的使用
git的使用(下载及上传_gitcode怎么下载文件_是小刘不是刘的博客-CSDN博客
目录
一、理论部分
1 IIC通讯的
什么是IIC,一种通讯协议,只用两根线就能实现两个设备之间的通讯,一个SCL时钟线,一个SDA数据线,可以一主多从,多主多从。
主机享有绝对的控制权,通讯方式为同步半双工,并且会有应答位,若没有应答就重新传输。
这些可以看后面再了解。
2 硬件电路
IIC的设备没有采用直接的推挽输出,因为SDA既要输入又要输出。所以如果协调不当一个输入一个输出就会导致短路,这里采用了一种场效应管的电路设计方式。
我的模电和电基也忘完了= =后面再补详细的把。
反正场效应管这里就是0v导通-5v截至所以这个电路采用了开漏输出的模式,只有负数和0,如果有人要使用SDA或者SCL线就将这两个线拉低,这样就防止了短路,并且加了两个弱上拉电阻,可以将电路拉成所谓的高电平,当电路不控制其输出0v的时候,两个弱拉电阻就会将其拉回去。(我也不知道这里这样说准不准确,如果有错后面会改)
3 IIC的几种信号状态
1)起始信号、终止信号
起始条件,再SCL为高电平的期间,SDA从高电平切换到低电平
停止条件。SCL为高电平期间,SDA从低切换到高电平
2)发送字节、接收字节
都是以主机的视角描述,IIC下主机享有完全的主动权
发送字节,再SCL为低的期间内,SDA线放上数据,然后释放SCL就能够发送数据位,从机就能接收到数据了
接收数据
SCL低电平时,从机将数据放入SDA,释放SCL线,主机就会读取数据
3)应答信号
发送应答:判断主机是否接收到了数据,再刚刚的接收数据后面加上一个发送应答,发送0位收到,如果为1就是没有收到。(主机是否收到数据
接收应答:在发送一个数据后,判断从机是否接收到了,收到了从机就将SDA拉低(从i是否收到
上面就是协议规定的信号状态,然后就是IIC协议的组成了
4 指定地址写
对于指定的设备,在指定的地址下,写入数据
在协议中一般都需要一个设备号,然后是设备地址,最后是数据 IIC协议也不例外
每一个IIC设备都有自己不同的IIC设备号,这个号码一般为7位,前4位固定,后3位中有可变为(有些时候在一个环境下需要多台同类型设备)这个设备和还要+上一位读写位,来确定用户是想读还是写 (可以看出电平0为写入
第二个部分是地址号 是用户需要写的地址
第三个就是用户需要写入的数据,记得每次发送完一个字节加上一个应答位
5 当前地址读
读取当前指针指向的地址下的数据,但是由于在写入完成后地址指针会自动+1,所以假设我们上次写入的数据在0x01在写入完成后这个指针会跳到0x02,这就导致了我们读取不到我们写入的数据,所以这个模式我们并不经常使用
还是一样的,先发送设备号+rw模式(1为读
然后从机发送数据,主机读取数据(这个数据我们是不能选择地址的
6 指定地址读写
这个就有用多了是吧- -想读哪里读哪里,但是时序也就比较复杂了,起始也就是把写和读组合了一下
首先第一部分和前面的写是一样的,但是少了一部分,就是后面发送的数据,这里只有起始+地址但是没有写进去东西,所以指针也就还是停留在那没有自动后移,就是直接停在了0x19这里
然后后半段,有个SR,这个就是重复起始信号,上一张图最后的sr就是这张图的,这样裁剪让大家好连贯看图,在这重发一个起始位后,我们重复刚刚的当前地址读操作就好,重新发送设备号和读模式,然后直接接收读回来的数据,最后加上停止位,如果你想多字节,不要重复以上步骤,你可以直接先不发停止位,这个后面写代码可以测试。
二 代码部分
1 软件模拟IIC
接下来就是写代码了,我们这是模拟IIC所以我们先初始化两根引脚线,就是常用配置,这里使用了宏定义方便后期修改,初始化两个引脚为开漏输出,这个前面也讲过为什么(控制他时为0,释放他时被外部若上拉拉为高电平1)
#define GPIO_PORT_SCL GPIOB
#define GPIO_PORT_SDA GPIOB
#define GPIO_PIN_SCL GPIO_Pin_10
#define GPIO_PIN_SDA GPIO_Pin_11
void Myiic_GPIO_Init()
{
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode=GPIO_Mode_Out_OD;
GPIO_InitStructure.GPIO_Pin=GPIO_PIN_SCL | GPIO_PIN_SDA;
GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;
GPIO_Init(GPIO_PORT_SCL,&GPIO_InitStructure);
GPIO_SetBits(GPIO_PORT_SCL,GPIO_PIN_SCL);
GPIO_SetBits(GPIO_PORT_SDA,GPIO_PIN_SDA);
}
然后我们写三个函数:读引脚电平 输出高电平 输出低电平,这里是两个写法,一个是直接使用了带参宏,一个是直接函数输入,两个其实都一样,看个人习惯,这里记得将输入强转为BitAction ,这是一个枚举量,里面不是0就是1,可以方便后期的输出,等下就能看见效果。这个writeBit函数前面没有使用过,其实就是一个改变信号线电平的函数,最后那个参数就是setbit和resetbit。
#define OLED_W_SCL(x) GPIO_WriteBit(GPIOB,GPIO_Pin_10,(BitAction)(x))
#define OLED_W_SDA(x) GPIO_WriteBit(GPIOB,GPIO_Pin_11,(BitAction)(x))
void MyI2c_W_SCL(uint8_t Bitvalue)
{
GPIO_WriteBit(GPIO_PORT_SCL,GPIO_PIN_SCL,(BitAction)Bitvalue);
delay_us(10);
}
void MyI2c_W_SDA(uint8_t Bitvalue)
{
GPIO_WriteBit(GPIO_PORT_SDA,GPIO_PIN_SDA,(BitAction)Bitvalue);
delay_us(10);
}
void MyI2c_R_SDA(void)
{
u8 BitValue;
BitValue=GPIO_ReadInputDataBit(GPIO_PORT_SDA,GPIO_Pin_11);
delay_us(10);
return BitValue;
}
然后就可以开始写我们刚刚的几个部分
1 起始信号
首先是起始信号根据图示来,在SCL高的时候,拉低SDA即可,这里代码我们先将SCL和SDA都拉高,虽然初始化都是拉高了的,但是有一个信号是重复起始信号,这里防止出错直接先拉高。
void Myi2c_Start(void)
{
MyI2c_W_SDA(1);
MyI2c_W_SCL(1);
MyI2c_W_SDA(0);
MyI2c_W_SCL(0);
}
2 停止信号
先拉低两根线,之后先释放SCK,再释放SDA
void Myi2c_End(void)
{
MyI2c_W_SCL(0);
MyI2c_W_SDA(0);
MyI2c_W_SCL(1);
MyI2c_W_SDA(1);
}
3 发送一个字节
传入一个字节,因为这个时序是按高位先行,所以我们先想办法发送高位
代码,拿要发送的字节与上0x80取出高位,然后依次右移0x80即可,这样就能取出一整个字节,在放上数据后,就可以让主机释放时钟线了,这里SDA的传入虽然是0x80 0x40这类值,但是在上面标红的地方有说到我们这里进行了强制类型转换,然后这个类型是个枚举类型,不是0就是1.
void Myi2c_SendByte(uint8_t Byte)
{
uint8_t i;
for(i=0;i<8;i++)
{
MyI2c_W_SDA(Byte & (0x80 >>i));
MyI2c_W_SCL(1);
MyI2c_W_SCL(0);
}
}
4 接收一个字节
和接收一样,在SCL低电平的时候,SDA可以改变数据,但是要记得这个数据是从机发来的,我们只需要给他发送接收命令就好,并不需要管从机设备是如何发送过来的。
直接释放SDA线让从机能够放数据,然后释放SCL保存数据到Byte,如果读的电平为1,则保存数据到Byte,这里使用按位或操作,SDA为1时,进入到if判断,让Byte的最高位为1,如果没进入这个判断,那么那一位自然就是0了。
uint8_t Myi2c_ReadByte()
{
uint8_t i;
uint8_t Byte=0x00;
MyI2c_W_SDA(1); //释放SDA让从机有SDA控制权
for(i=0;i<8;i++)
{
MyI2c_W_SCL(1);
if(MyI2c_R_SDA()==1)
{
Byte |= (0x80>>i);
}
MyI2c_W_SCL(0);
}
}
5 发送应答信号
其实和发送一个数据差不多,就是接收完数据后发送一个位给从机,表示应答。
这里我们和发送数据一样,把数据放到SDA,然后释放scl再控制scl即可
uint8_t Myi2c_SendAck(uint8_t ACK)
{
MyI2c_W_SDA(ACK);
MyI2c_W_SCL(1);
MyI2c_W_SCL(0);
}
6 接收应答
主机在发送完一个字节后,从机反馈自己是不是有收到数据,等于和刚刚的主机接收数据一样
还是和刚刚一样,先释放掉SDA让从机有控制权,然后直接释放SCL接收数据就好
uint8_t Myi2c_ReadACK(void)
{
uint8_t ACK;
MyI2c_W_SDA(1); //释放SDA让从机有SDA控制权
MyI2c_W_SCL(1);
ACK = MyI2c_R_SDA();
MyI2c_W_SCL(0);
return ACK;
}
7 主函数测试\
测试是否能够跑通(直接拿主机呼叫设备号,看看设备是否会有ACK信号
这里我直接先拿了一个板载的EEPROM测试芯片是AT24C02
因为我板载的就有一个这个,现在先拿他测一下我们的IIC能不能跑先去翻手册和原理图
芯片手册上说前四位固定组成,后三位可以根据电平更改,接高为高,接低为低,板载的三根线都直接接地,然后就是最后一位为读写位,1为读 0为写,如果有匹配的设备则设备返回0
这里引脚为PB6 和PB7 我们把初始化引脚改一下测试一下
主函数
int main(void)
{
u8 ACKBit=0;
/*初始化USART 配置模式为 115200 8-N-1,中断接收*/
USART_Config();
delay_init();
//PWM_LEDInit();
Myiic_GPIO_Init();
//发送设备号测试是否有应答信号
Myi2c_Start();
Myi2c_SendByte(0xA2);
ACKBit=Myi2c_ReadACK();
Myi2c_End();
printf("%d\r\n",ACKBit);
while(1)
{
}
}
测试打印,第一次发送0xA0 第二次发送了0xA2 测试结果如下 ,是成功了的
然后我们开始写OLED的驱动
由于不想帖子拉的太长,所以EEPROM和OLED屏幕的详细使用会单独开两个帖子。