文章目录[隐藏]
软件模拟IIC驱动OLED 附源码
前言
CSDN上有很多的关于模拟IIC驱动的代码,都讲解的特别好。但对于想短时间理解并使用IIC的同学们而言是很枯燥困难的。所以我想由果到因,从写好的代码开始讲解IIC,希望可以帮助大家短时间掌握使用IIC进行驱动。
源代码在最下面给出
1、相关宏定义
//宏定义
#define I2C_GPIO_CLK RCC_AHB1Periph_GPIOB /* GPIO端口时钟 */
#define I2C_GPIO_PORT GPIOB
#define I2C_SCL_PIN GPIO_Pin_6
#define I2C_SDA_PIN GPIO_Pin_5
#define I2C_SCL_H GPIO_SetBits(I2C_GPIO_PORT, I2C_SCL_PIN) //拉高时钟线
#define I2C_SCL_L GPIO_ResetBits(I2C_GPIO_PORT, I2C_SCL_PIN) //拉低时钟线
#define I2C_SDA_H GPIO_SetBits(I2C_GPIO_PORT, I2C_SDA_PIN) //拉高数据线
#define I2C_SDA_L GPIO_ResetBits(I2C_GPIO_PORT, I2C_SDA_PIN) //拉低数据线
#define I2C_SDA_READ GPIO_ReadInputDataBit(I2C_GPIO_PORT, I2C_SDA_PIN) /* 读SDA口线状态 */
这就相关的宏定义,我们进行代码移植的时候需要在这里将相关的底层操作换成对应平台的就可以了。
2、初始化相应GPIO
void I2C_GPIO_Config(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
RCC_AHB1PeriphClockCmd(I2C_GPIO_CLK, ENABLE); /* 打开GPIO时钟 */
GPIO_InitStructure.GPIO_Pin = I2C_SCL_PIN | I2C_SDA_PIN;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT;
GPIO_InitStructure.GPIO_OType = GPIO_OType_OD; /* 开漏输出 */
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL;
GPIO_Init(I2C_GPIO_PORT, &GPIO_InitStructure);
/* 给一个停止信号, 复位I2C总线上的所有设备到待机模式 */
i2c_Stop(); //这是ICC的停止信号
}
各位在移植代码的时候,这一块也需要更换,因为不同平台GPIO的初始化函数是不太一样的,不过目的都只是初始化GPIO,在学习点灯的时候就已经配置过了,相信大家是很熟悉的。
3、起始信号
当 SCL 线是高电平时 SDA 线从高电平向低电平切换,表示通讯的起始。
void i2c_Start(void)
{
/* 当SCL高电平时,SDA出现一个下降沿表示I2C总线启动信号 */
I2C_SDA_H; //拉高数据线
I2C_SCL_H; //拉高时钟线
Delay(); //延时
I2C_SDA_L; //拉低数据线
Delay();
I2C_SCL_L; //拉低时钟线
}
现在我们按照这个函数代码写一遍对应的IIC数据线与时钟线的状态
很明显我们可以发现当时钟线为高电平的时候,数据线存在一个由高到低的跳变。
这个就是IIC协议里面的起始信号。
4、停止信号
当 SCL 是高电平时 SDA 线由低电平向高电平切换,表示通讯的停止。
void i2c_Stop(void)
{
/* 当SCL高电平时,SDA出现一个上升沿表示I2C总线停止信号 */
I2C_SDA_L;
I2C_SCL_H;
Delay();
I2C_SDA_H;
Delay();
I2C_SCL_L;
}
同样我们按照这个函数代码写一遍对应的IIC数据线与时钟线的状态
很明显我们可以发现当时钟线为高电平的时候,数据线存在一个由低到高的跳变。
这个就是IIC协议里面的停止信号。
5、发送一个字节数据
void i2c_SendByte(uint8_t _ucByte)
{
uint8_t i;
for (i = 0; i < 8; i++) //循环发送8个数据,刚好一个字节
{
if (_ucByte & 0x80) //取出待发送字节_ucByte 的第8位
{
I2C_SDA_H; //最高位是1就拉高数据线,是0就拉低数据线
}
else
{
I2C_SDA_L;
}
Delay();
I2C_SCL_H; //拉高时钟线
Delay();
I2C_SCL_L; //拉低时钟线
if (i == 7) //i=7意味着已经发送了8位数据了
{
I2C_SDA_H; // 释放总线
}
_ucByte <<= 1; /左移一个bit,左移之后第7位就变成了第8位,就可以通过_ucByte & 0x80取出来
Delay();
}
}
很明显我们可以发现IIC发送数据有几个特点
1、在SCL为高电平的时候读取SDA的数据
2、在SCL为低电平的时候SDA的数据进行改变
6、读取一个字节的数据
uint8_t i2c_ReadByte(void)
{
uint8_t i;
uint8_t value;
/* 读到第1个bit为数据的bit7 */
value = 0;
for (i = 0; i < 8; i++)
{
value <<= 1; //左移一位
I2C_SCL_H;
Delay();
if (I2C_SDA_READ) //I2C_SDA_READ这一个宏定义,读取并返回数据线的状态
{
value++;
}
I2C_SCL_L;
Delay();
}
return value;
}
在这里我们可以发现IIC读取数据有两个特点
1、在SCL为低电平的时候读取SDA的数据
2、在SCL为高电平的时候SDA的数据进行改变
读取应答ACK信号
使用模拟IIC发送数据的时候,每发送一个字节的数据之后需要释放总线控制,等待从机发送响应信号。通过接收从机返回的响应信号来判断从机是否需要继续接收数据。
从机发送响应信号由从机本身决定与我们要写的代码无关
uint8_t i2c_WaitAck(void)
{
uint8_t re;
I2C_SDA_H;
Delay();
I2C_SCL_H; /* CPU驱动SCL = 1, 此时器件会返回ACK应答 */
Delay();
if (I2C_SDA_READ) /* CPU读取SDA口线状态 */
{
re = 1;
}
else
{
re = 0;
}
I2C_SCL_L;
Delay();
return re;
}
这是一个有返回值的函数,我们依然在SCL为低电平的时候读取此时的SDA值,如果SDA为低电平,就代表从机产生了应答信号。
以上就是我们驱动OLED所需要的模拟IIC函数了,接下来我们通过上面的函数写出发给OLED的数据与命令函数。
向OLED发送命令函数
void WriteCmd(u8 command)
{
i2c_Start(); //产生IIC时序的起始信号
i2c_SendByte(0x78); //发送OLED的地址
i2c_WaitAck();
i2c_SendByte(0x00);//发送OLED命令寄存器地址
i2c_WaitAck();
i2c_SendByte(command);//发送相关指令
i2c_WaitAck();
i2c_Stop(); //结束IIC通讯
}
这段代码里面,OLED地址以及寄存器地址是硬件固定的,对于所有的OLED都是这个,所以大家在移植时不用改。
然后很显然应答函数i2c_WaitAck是有返回值的,但我们并没有在此处检测他的返回值。我个人理解是对于驱动OLED我们是固定要发送三个字节的数据的,所以我们不要检测它的返回值,换而言之,前两个应答信号的返回值一定是0。我通过代码检测其返回值也确实如此。
向OLED发送数据函数
void WriteDat(u8 data)
{
i2c_Start();
i2c_SendByte(0x78);//OLED地址
i2c_WaitAck(); //可能需要检测返回
i2c_SendByte(0x40);//数据寄存器地址
if(!i2c_WaitAck())
i2c_SendByte(data);
i2c_WaitAck();
i2c_Stop();
}
这个代码与发送命令函数基本一样,唯一的区别就是,发送的寄存器地址不同。这也是OLED从机判断检测接收的字节是命令还是数据的方式。
结束
好了,到这里我们就已经用模拟IIC写好的对OLED的驱动。之后我们只需要通过WriteCmd和WriteDat这两个函数对OLED进行命令与数据的发送即可以实现OLED显示了。
IIC代码
链接:https://pan.baidu.com/s/1v72Tp4gSxV-yFcisiMcH6Q
提取码:lel3
版权声明:本文为CSDN博主「云中不是云」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/Edisonbest/article/details/121653483
暂无评论