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时,不读取因为本身该位为0
IIC_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 0xD0
void 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(); //获取芯片ID
while(1)
{
MPU6050_GetData(&Data1);
OLED_ShowSignedNum(2,1,Data1.AccX,5); //获取x的加速度值
}
}