IIC特点:
同步串行半双工通信总线
IIC有一个弱上拉电阻,在主机和从机都没有传输数据下拉时,总线会自动上拉
SCL在低电平期间,改变SDA的值来上传数据,方便SCL电平上升时进行数据读取
SCL在高电平期间,不能改变SDA的值,若改变,SDA高到低为起始信号,低到高为终止信号
IIC配置步骤
1.使能SCL和SDA对应时钟 _HAL_RCC_GPIOB_CLK_ENABLE()
2.设置GPIO工作模式 HAL_GPIO_Init()
3.编写基本信号 起始send ack 停止send nak 应答wait ack
4.编写读和写函数 iic_read_byte iic_send_byte
软件驱动外设步骤
1.初始化IIC接口
2.编写写入/读取一个字节数据的函数
3.编写连续读和连续写函数
iic代码
//myiic.c#include "./BSP/IIC/myiic.h"
#include "./SYSTEM/delay/delay.h"//初始化IIC
void iic_init(void)
{GPIO_InitTypeDef gpio_init_struct;IIC_SCL_GPIO_CLK_ENABLE(); /* SCL引脚时钟使能 */IIC_SDA_GPIO_CLK_ENABLE(); /* SDA引脚时钟使能 */gpio_init_struct.Pin = IIC_SCL_GPIO_PIN;gpio_init_struct.Mode = GPIO_MODE_OUTPUT_PP; /* 推挽输出 */gpio_init_struct.Pull = GPIO_PULLUP; /* 上拉 */gpio_init_struct.Speed = GPIO_SPEED_FREQ_VERY_HIGH; /* 快速 */HAL_GPIO_Init(IIC_SCL_GPIO_PORT, &gpio_init_struct);/* SCL */gpio_init_struct.Pin = IIC_SDA_GPIO_PIN; gpio_init_struct.Mode = GPIO_MODE_OUTPUT_OD; /* 开漏输出 */HAL_GPIO_Init(IIC_SDA_GPIO_PORT, &gpio_init_struct);/* SDA *//* SDA引脚模式设置,开漏输出,上拉, 这样就不用再设置IO方向了, 开漏输出的时候(=1), 也可以读取外部信号的高低电平 */iic_stop(); /* 停止总线上所有设备 */
}//IIC延时函数,给芯片反应时间
static void iic_delay(void)
{delay_us(2); /* 2us的延时, 读写速度在250Khz以内 */
}//编写时序,产生IIC起始信号
void iic_start(void)
{IIC_SDA(1);IIC_SCL(1);iic_delay();IIC_SDA(0); /* START信号: 当SCL为高时, SDA从高变成低, 表示起始信号 */iic_delay();IIC_SCL(0); /* 下拉I2C总线,准备发送或接收数据 */iic_delay();
}//编写时序,产生IIC结束信号
void iic_stop(void)
{IIC_SDA(0); /* STOP信号: 当SCL为高时, SDA从低变成高, 表示停止信号 */iic_delay();IIC_SCL(1);iic_delay();IIC_SDA(1); /* 发送I2C总线结束信号 */iic_delay();
}//接收应答信号,1为接收失败,0为接收成功
uint8_t iic_wait_ack(void)
{uint8_t waittime = 0;uint8_t rack = 0;IIC_SDA(1); /* 主机释放SDA线(此时外部器件可以拉低SDA线) */iic_delay();IIC_SCL(1); /* SCL=1, 此时从机可以返回ACK应答信号 */iic_delay();while (IIC_READ_SDA) /* 等待应答 */{waittime++;if (waittime > 250){iic_stop();rack = 1;break;}}IIC_SCL(0); /* SCL=0, 结束ACK检查 */iic_delay();return rack;
}/*** @brief 从机产生ACK应答*/
void iic_ack(void)
{IIC_SDA(0); /* SCL = 1 时 SDA = 0,表示应答 */iic_delay();IIC_SCL(1);iic_delay();IIC_SCL(0); /* 产生下一个时钟 */iic_delay();IIC_SDA(1); /* 释放SDA线 */iic_delay();
}/*** @brief 不产生ACK应答*/
void iic_nack(void)
{IIC_SDA(1); /* SCL = 1 时 SDA = 1,表示不应答 */iic_delay();IIC_SCL(1);iic_delay();IIC_SCL(0); /* 产生下一个时钟 */iic_delay();
}/*** @brief IIC发送一个字节
*/
void iic_send_byte(uint8_t data)
{uint8_t t;for (t = 0; t < 8; t++){IIC_SDA(data & (0x80 >> t)); /* 高位先发送,发完后右移 */iic_delay();IIC_SCL(1);iic_delay();IIC_SCL(0);}IIC_SDA(1); /* 发送完成, 主机释放SDA线 */
}/*** @brief IIC读取一个字节* @param ack: ack=1时,发送ack; ack=0时,发送nack* @retval 接收到的数据*/
uint8_t iic_read_byte(uint8_t ack)
{uint8_t i, receive = 0;IIC_SDA(1);for (i = 0; i < 8; i++ ) /* 接收1个字节数据,循环八次 */{IIC_SCL(1);if(IIC_READ_SDA == 1){receive |= (0x80 >> i);} //SDA为1时,读取当前这位,为0时,不读取因为本身该位为0IIC_SCL(0);}if (!ack){iic_nack(); /* 发送nACK */}else{iic_ack(); /* 发送ACK */}return receive;
}//myiic.h#ifndef __MYIIC_H
#define __MYIIC_H#include "./SYSTEM/sys/sys.h"/******************************************************************************************/
/* 引脚 定义 */#define IIC_SCL_GPIO_PORT GPIOB
#define IIC_SCL_GPIO_PIN GPIO_PIN_8
#define IIC_SCL_GPIO_CLK_ENABLE() do{ __HAL_RCC_GPIOB_CLK_ENABLE(); }while(0) /* PB口时钟使能 */#define IIC_SDA_GPIO_PORT GPIOB
#define IIC_SDA_GPIO_PIN GPIO_PIN_9
#define IIC_SDA_GPIO_CLK_ENABLE() do{ __HAL_RCC_GPIOB_CLK_ENABLE(); }while(0) /* PB口时钟使能 *//******************************************************************************************//* IO操作 */
#define IIC_SCL(x) do{ x ? \HAL_GPIO_WritePin(IIC_SCL_GPIO_PORT, IIC_SCL_GPIO_PIN, GPIO_PIN_SET) : \HAL_GPIO_WritePin(IIC_SCL_GPIO_PORT, IIC_SCL_GPIO_PIN, GPIO_PIN_RESET); \}while(0) /* SCL */#define IIC_SDA(x) do{ x ? \HAL_GPIO_WritePin(IIC_SDA_GPIO_PORT, IIC_SDA_GPIO_PIN, GPIO_PIN_SET) : \HAL_GPIO_WritePin(IIC_SDA_GPIO_PORT, IIC_SDA_GPIO_PIN, GPIO_PIN_RESET); \}while(0) /* SDA */#define IIC_READ_SDA HAL_GPIO_ReadPin(IIC_SDA_GPIO_PORT, IIC_SDA_GPIO_PIN) /* 读取SDA *//* IIC所有操作函数 */
void iic_init(void); /* 初始化IIC的IO口 */
void iic_start(void); /* 发送IIC开始信号 */
void iic_stop(void); /* 发送IIC停止信号 */
void iic_ack(void); /* IIC发送ACK信号 */
void iic_nack(void); /* IIC不发送ACK信号 */
uint8_t iic_wait_ack(void); /* IIC等待ACK信号 */
void iic_send_byte(uint8_t txd);/* IIC发送一个字节 */
uint8_t iic_read_byte(unsigned char ack);/* IIC读取一个字节 */#endif
iic软件驱动MPU6050代码
//MPU6050.c#include "./BSP/IIC/myiic.h"
#include "./SYSTEM/delay/delay.h"
#include "./BSP/MPU6050/MPU6050.h"#define MPU6050_ADDRESS 0xD0void MPU6050_WriteReg(uint8_t RegAddress,uint8_t Data)//向外设写数据
{iic_start(); //开始信号iic_send_byte(MPU6050_ADDRESS);//向该外设地址发送数据iic_wait_ack(); //接收应答iic_send_byte(RegAddress); //发送目标寄存器地址iic_wait_ack();iic_send_byte(Data); //向该地址发送数据,这里是发送一个数据iic_wait_ack();/*for(int i = 0;i<8; i++) //发送多个字节数据{iic_send_byte(Data);iic_wait_ack();}
*/iic_stop(); //终止信号
}uint8_t MPU6050_ReadReg(uint8_t RegAddress) //从外设读取数据
{uint*_t Data;iic_start(); //开始信号iic_send_byte(MPU6050_ADDRESS);//向该外设地址发送数据iic_wait_ack(); //接收应答iic_send_byte(RegAddress); //发送目标寄存器地址iic_wait_ack();iic_start(); //重新起始iic_send_byte(MPU6050_ADDRESS | 0x01) //将最后一位变1,进行从机的读取数据iic_wait_ack();Data = iic_read_byte(); //读取数据iic_nack(); //因为读取一个字节,所以不用产生应答iic_stop();
/*for(int i = 0;i < n;i++) //读取n个字节,就要产生应答{Data = iic_read_byte();iic_ack();}
*/return Data;
}void MPU6050_Init(void)
{iic_init();MPU6050_WriteReg(MPU6050_PWR_MGMT_1,0x01); //解除睡眠,X轴陀螺仪时钟MPU6050_WriteReg(MPU6050_PWR_MGMT_2,0x01); //6个轴都不需要待机MPU6050_WriteReg(MPU6050_SMPLRT_DIV,0x09); //选择十分频的采样率MPU6050_WriteReg(MPU6050_CONFIG,0x06); //滤波参数给最大MPU6050_WriteReg(MPU6050_GYRO_CONFIG,0x18); //陀螺仪最大量程MPU6050_WriteReg(MPU6050_ACCEL_CONFIG,0x18); //加速度计最大量程}void MPU6050_GetData(struct MPU6050_Data* p) //获得芯片记录的各类数据
{uint16_t DataH,DataL;DataH = MPU6050_ReadReg(MPU6050_ACCEL_XOUT_H); //获取加速度x轴数据DataL = MPU6050_ReadReg(MPU6050_ACCEL_XOUT_L);p->AccX = (DataH << 8) | DataL;DataH = MPU6050_ReadReg(MPU6050_ACCEL_YOUT_H);DataL = MPU6050_ReadReg(MPU6050_ACCEL_YOUT_L);p->AccY = (DataH << 8) | DataL;DataH = MPU6050_ReadReg(MPU6050_ACCEL_ZOUT_H);DataL = MPU6050_ReadReg(MPU6050_ACCEL_ZOUT_L);p->AccZ = (DataH << 8) | DataL;DataH = MPU6050_ReadReg(MPU6050_GYRO_XOUT_H); //获取陀螺仪x轴数据DataL = MPU6050_ReadReg(MPU6050_GYRO_XOUT_L);p->GyroX = (DataH << 8) | DataL;DataH = MPU6050_ReadReg(MPU6050_GYRO_YOUT_H);DataL = MPU6050_ReadReg(MPU6050_GYRO_YOUT_L);p->GyroY = (DataH << 8) | DataL;DataH = MPU6050_ReadReg(MPU6050_GYRO_ZOUT_H);DataL = MPU6050_ReadReg(MPU6050_GYRO_ZOUT_L);p->GyroZ = (DataH << 8) | DataL;
}uint8_t MPU6050_GetID(void) //获取芯片ID号
{return MPU6050_ReadReg(MPU6050_WHO_AM_I);
}//MPU6050.h#ifndef __MPU6050_H
#define __MPU6050_H#include "./SYSTEM/sys/sys.h"typedef struct MPU6050_Data{uint16_t AccX; //加速度x轴数据uint16_t AccY;uint16_t AccZ;uint16_t GyroX; //陀螺仪x轴数据uint16_t GyroY;uint16_t GyroZ;
}Data;void MPU6050_WriteReg(uint8_t RegAddress,uint8_t Data);
uint8_t MPU6050_ReadReg(uint8_t RegAddress);
void MPU6050_Init(void);
uint8_t MPU6050_GetID(void);
void MPU6050_GetData(struct MPU6050_Data* p);#define MPU6050_SMPLRT_DIV 0x19 //陀螺仪采样率,典型值:0x07(125Hz)
#define MPU6050_CONFIG 0x1A //低通滤波频率,典型值:0x06(5Hz)
#define MPU6050_GYRO_CONFIG 0x1B //陀螺仪自检及测量范围,典型值:0x18(不自检,2000deg/s)
#define MPU6050_ACCEL_CONFIG 0x1C //加速计自检、测量范围及高通滤波频率,典型值:0x01(不自检,2G,5Hz)
#define MPU6050_ACCEL_XOUT_H 0x3B
#define MPU6050_ACCEL_XOUT_L 0x3C
#define MPU6050_ACCEL_YOUT_H 0x3D
#define MPU6050_ACCEL_YOUT_L 0x3E
#define MPU6050_ACCEL_ZOUT_H 0x3F
#define MPU6050_ACCEL_ZOUT_L 0x40
#define MPU6050_TEMP_OUT_H 0x41
#define MPU6050_TEMP_OUT_L 0x42
#define MPU6050_GYRO_XOUT_H 0x43
#define MPU6050_GYRO_XOUT_L 0x44
#define MPU6050_GYRO_YOUT_H 0x45
#define MPU6050_GYRO_YOUT_L 0x46
#define MPU6050_GYRO_ZOUT_H 0x47
#define MPU6050_GYRO_ZOUT_L 0x48
#define MPU6050_PWR_MGMT_1 0x6B //电源管理,典型值:0x00(正常启用)
#define MPU6050_PWR_MGMT_2 0x6C //电源管理寄存器2
#define MPU6050_WHO_AM_I 0x75 //IIC地址寄存器(默认数值0x68,只读)#endif
main.c
int main(void)
{Data Data1;int ID;OLED_Init(); //显示屏初始化MPU6050_Init(); //MPU6050初始化 OLED_ShowString(1,1,"ID:");ID = MPU6050_GetID(); //获取芯片IDwhile(1){MPU6050_GetData(&Data1);OLED_ShowSignedNum(2,1,Data1.AccX,5); //获取x的加速度值}
}