软件实现I2C:通过手动翻转普通GPIO电平实现通信
代码整体框架:
1、建立I2C通信层的.c和.h模块,编写I2C底层的GPIO初始化和6个时序基本单元(起始、终止、发送一个字节、接收一个字节、发送应答、接收应答
)
2、建立MPU6050的.c和.h模块,基于I2C通信模块实现指定地址读、指定地址写,并且写寄存器对芯片进行配置,读寄存器得到传感器数据
3、在main.c里调用MPU6050模块,初始化、拿到数据、显示数据
【基本驱动代码】
1、完成I2C驱动文件导入操作和编写驱动程序基本代码(参考之前文章)
//将【MyI2C】驱动文件放在【Hardware】文件夹中
//【MyI2C】名称是为了防止与库函数里面的I2C重复
2、在
MyI2C.c中初始化函数
MyI2C_Init
//本代码使用软件I2C,只需要GPIO的读写函数,不需要I2C库函数/*给端口号换个名字————语义明确、方便删改----------------------------------------方法一 宏定义#define SCL_PORT GPIOB#define SCL_PIN GPIO_Pin_10释放SCL:GPIO_SetBits(SCL_PORT,SCL_PIN);----------------------------------------方法二 将GPIO对端口的整个操作函数用宏定义替换#define OLED_W_SCL(x) GPIO_WriteBit(GPIOB,GPIO_Pin_8,(BitAction)(x))#define OLED_W_SDA(x) GPIO_WriteBit(GPIOB,GPIO_Pin_9,(BitAction)(x))可以使用带参数的宏定义(有参宏)----------------------------------------方法三 定义一个函数,对操作端口的库函数进行封装//另外本方法方便添加延时void MyI2C_W_SCL(uint8_t BitValue){GPIO_WriteBit(GPIOB,GPIO_Pin_10,(BitAction)BitValue);Delay_us(10);}调用函数,参数给1或0,就可以释放或拉低SCL----------------------------------------//本例使用【方法三】*///写SCLvoid MyI2C_W_SCL(uint8_t BitValue){GPIO_WriteBit(GPIOB,GPIO_Pin_10,(BitAction)BitValue);Delay_us(10);}//写SDAvoid MyI2C_W_SDA(uint8_t BitValue){GPIO_WriteBit(GPIOB,GPIO_Pin_11,(BitAction)BitValue);Delay_us(10);}//读SDA(读和写不是同一个寄存器)//返回读到SDA线的电平uint8_t MyI2C_R_SDA(void){uint8_t BitValue;BitValue = GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_11);Delay_us(10);return BitValue;}/*PB10和PB11两个端口被初始化为开漏输出模式释放总线,SCL和SDA处于高电平,此时I2C总线处于空闲状态*/void MyI2C_Init(void){//把SCL和SDA初始化为开漏输出模式//当前接线为SCL=PB10,SDA=PB11RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);GPIO_InitTypeDef GPIO_InitStructure;GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_OD;//开漏输出GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10 |GPIO_Pin_11;GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;GPIO_Init(GPIOB,&GPIO_InitStructure);//把SCL和SDA置高电平(初始时为空闲状态)GPIO_SetBits(GPIOB,GPIO_Pin_10 |GPIO_Pin_11);}//起始条件void MyI2C_Start(void){//把SCL和SDA置高电平(初始时为空闲状态)MyI2C_W_SDA(1);MyI2C_W_SCL(1);//先拉低SDA,再拉低SCLMyI2C_W_SDA(0);MyI2C_W_SCL(0);}//终止条件void MyI2C_Stop(void){//拉低SDA(确保之后SDA能产生上升沿)//结束时SCL一定为低电平MyI2C_W_SDA(0);//先拉高SCL,再拉高SDAMyI2C_W_SCL(1);MyI2C_W_SDA(1);//只有【终止条件】SCL高电平结束,其余单元SCL都是低电平结束}//发送一个字节/*SCL低电平时,主机写数据到SDA线上SCL高电平时,从机在SDA线上读取数据字节高位先发送,先放最高位,再放次高位……*/void MyI2C_SendByte(uint8_t Byte){uint8_t i;for(i = 0; i < 8; i ++){//默认SCL为低电平MyI2C_W_SDA(Byte & ( 0x80 >> i) );//主机由高到低写入数据MyI2C_W_SCL(1);//从机在SDA线上读取数据MyI2C_W_SCL(0);//主机写数据到SDA线上}}//接收一个字节/*SCL低电平时,从机写数据到SDA线上SCL高电平时,主机在SDA线上读取数据字节高位先发送,先放最高位,再放次高位……*/uint8_t MyI2C_ReceiveByte(void){uint8_t i, Byte = 0x00;//默认SCL为低电平MyI2C_W_SDA(1);//主机先释放SDA(将SDA控制权赋予从机)for(i = 0; i < 8; i ++){MyI2C_W_SCL(1);//主机在SDA线上读取数据//如果主机读取SDA为1if( MyI2C_R_SDA() == 1){Byte |= ( 0x80 >> i );//主机依次把Byte相应位置1}MyI2C_W_SCL(0);//从机写数据到SDA线上}return Byte;//返回主机接收的Byte}//发送应答/*SCL低电平时,主机写发送应答到SDA线上SCL高电平时,从机在SDA线上读取应答*/void MyI2C_SendAck(uint8_t AckBit){//默认SCL为低电平MyI2C_W_SDA(AckBit);//主机向从机写入发送应答MyI2C_W_SCL(1);//从机在SDA线上读取发送应答MyI2C_W_SCL(0);//进入下一个时序单元}//接收应答/*SCL低电平时,从机写数据到SDA线上SCL高电平时,主机在SDA线上读取数据*/uint8_t MyI2C_ReceiveAck(void){uint8_t AckBit;//默认SCL为低电平MyI2C_W_SDA(1);//主机先释放SDA(将SDA控制权赋予从机,从机在SDA写接收应答)MyI2C_W_SCL(1);//主机在SDA线上读取接收应答AckBit = MyI2C_R_SDA();//主机读取的值赋给接收应答MyI2C_W_SCL(0);//进入下一个时序单元return AckBit;//返回主机接收的AckBit}
3、在MyI2C.h中声明初始化函数
MyI2C_Init及其
时序单元函数
void MyI2C_Init(void);void MyI2C_Start(void);void MyI2C_Stop(void);void MyI2C_SendByte(uint8_t Byte);uint8_t MyI2C_ReceiveByte(void);void MyI2C_SendAck(uint8_t AckBit);uint8_t MyI2C_ReceiveAck(void);
4、在主程序main.c中
#include
"MyI2C
.h
"
#include "MyI2C.h"
5、在主循环中编写程序主体
int main(void){MyI2C_Init();OLED_Init();//测试方法:对照完成【指定地址写】、【指定地址读】完整时序//【指定地址写】//本例用于测试从机是否收到应答//调用Start产生起始条件开始传输MyI2C_Start();//第一个字节时序(必须是发送)——字节内容:从机地址(7位)+读写位(1位)MyI2C_SendByte(0xD0); //1101 000 0 (0代表即将进行写入操作)//【接收应答】uint8_t Ack = MyI2C_ReceiveAck();//拓展:可以返回值变量判断从机是否收到主机发送的数据//调用Stop产生终止条件结束传输MyI2C_Stop();OLED_ShowNum(1,1,Ack,3);while(1){}}
实现功能:上电后初始化I2C和OLED模块,通过软件I2C向从机发送寻址数据【0xD0】,MPU6050收到后返回接收应答【000】并通过OLED显示;此处如果修改寻址数据为不存在的从机,则返回接收应答【001】并通过OLED显示