本篇博客重点在于标准库函数的理解与使用,搭建一个框架便于快速开发
目录
前言
I2C协议介绍:【通信协议】I2C总线(一主多从)-CSDN博客
本篇博客学习使用STM32的I2C硬件收发电路生成I2C时序。
I2C外设简介
STM32内部集成了硬件I2C收发电路,可以由硬件自动执行时钟生成、起始终止条件生成、应答位收发、数据收发等功能,减轻CPU的负担。
- 支持多主机功能
- 支持7位/10位地址模式
- 支持不同的通讯速度,标准速度(高达100 kHz),快速(高达400 kHz)
- 支持DMA
- 兼容SMBus协议
STM32F103C8T6 硬件I2C资源:I2C1、I2C2
I2C默认工作于从模式。接口在生成起始条件后自动地从从模式切换到主模式;当产生停止信号时,则从主模式切换到从模式。
IO口初始化
GPIO的其它参数的理解可以阅读下方博客,这里不再赘述。
根据引脚定义表GPIOB的10和11可分别复用在单片机内的I2C2接口的SCL和SDA线。再由I2C协议,则可配置这两个引脚的GPIO模式为复用开漏输出模式。
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_OD;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10 | GPIO_Pin_11;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOB, &GPIO_InitStructure);
I2C接口配置
I2C时钟配置
I2C1、I2C2均在APB1总线上
再由RCC时钟树,需要使能I2C外设对应的时钟
cpp
RCC_APB1PeriphClockCmd(RCC_APB1Periph_I2C2, ENABLE);
I2C初始化
I2C模式
|----------------------|---------|
| I2C_Mode_I2C | I2C模式 |
| I2C_Mode_SMBusDevice | SMBus设备 |
| I2C_Mode_SMBusHost | SMBus主机 |
cpp
I2C_InitStructure.I2C_Mode = I2C_Mode_I2C;
时钟速度
I2C_ClockSpeed 该参数用来设置时钟频率,这个值不能高于 400KHz
|-----------|------|
| 时钟频率(KHz) | 速度模式 |
| 0~100 | 标准 |
| 101~400 | 快速 |
cpp
I2C_InitStructure.I2C_ClockSpeed = 50000;//标准速度
占空比
|--------------------|------------------------------|
| I2C_DutyCycle | 描述 |
| I2C_DutyCycle_16_9 | I2C 快速模式 T_low/T_high = 16/9 |
| I2C_DutyCycle_2 | I2C 快速模式 T_low/T_high = 2 |
注意:该参数只有在I2C 工作在快速模式(时钟工作频率高于 100KHz)下才有意义
小于等于100KHz的标准速度下,占空比为固定的1:1
cpp
I2C_InitStructure.I2C_DutyCycle = I2C_DutyCycle_2;//标准速度配置该参数无意义
单片机地址
I2C_AcknowledgedAddres 定义了应答 7 位地址还是 10 位地址
I2C_OwnAddress1 该参数用来设置单片机第一个自身地址,它可以是一个 7 位地址或者一个 10 位地址
单片机作为从机模式下才需配置,这两个参数,不在本博客学习范围内,就随便配置了
cpp
I2C_InitStructure.I2C_AcknowledgedAddress = I2C_AcknowledgedAddress_7bit;
I2C_InitStructure.I2C_OwnAddress1 = 0x00;
应答位使能
发送数据自带接收应答的过程,接收数据自带发送应答的过程,不需要接收与发送应答位就失能
cpp
I2C_InitStructure.I2C_Ack = I2C_Ack_Enable;
I2C接口使能
I2C接口配置的最后调用即可
cpp
I2C_Cmd(I2C2, ENABLE);
I2C外设配置框架
cpp
RCC_APB1PeriphClockCmd(RCC_APB1Periph_I2C2, ENABLE);
I2C_InitTypeDef I2C_InitStructure;
I2C_InitStructure.I2C_Mode = I2C_Mode_I2C;
I2C_InitStructure.I2C_ClockSpeed = 50000;
I2C_InitStructure.I2C_DutyCycle = I2C_DutyCycle_2;
I2C_InitStructure.I2C_Ack = I2C_Ack_Enable;
I2C_InitStructure.I2C_AcknowledgedAddress = I2C_AcknowledgedAddress_7bit;
I2C_InitStructure.I2C_OwnAddress1 = 0x00;
I2C_Init(I2C2, &I2C_InitStructure);
I2C_Cmd(I2C2, ENABLE);
主机的发送与接收
代码来自江协科技:[10-5] 硬件I2C读写MPU6050_哔哩哔哩_bilibili
cpp
/*while (I2C_CheckEvent(I2Cx, I2C_EVENT) != SUCCESS)
while循环等待硬件置事件发送标志位,但硬件I2C有许多事件,就有许多while循环,容易造成堵塞
*/
//超时退出,防止阻塞主循环其他程序
void MPU6050_WaitEvent(I2C_TypeDef* I2Cx, uint32_t I2C_EVENT)
{
uint32_t Timeout;
Timeout = 10000;
while (I2C_CheckEvent(I2Cx, I2C_EVENT) != SUCCESS)
{
Timeout --;
if (Timeout == 0)
{
break;
}
}
}
主机发送
一定要对着参考手册的寄存器描述看
cpp
I2C_GenerateSTART(I2C2, ENABLE); //使能起始条件生成电路
MPU6050_WaitEvent(I2C2, I2C_EVENT_MASTER_MODE_SELECT);//EV5:等待起始条件发送完成
I2C_Send7bitAddress(I2C2, MPU6050_ADDRESS, I2C_Direction_Transmitter);//告诉MPU6050从机,主机要发送数据,自动接收应答位,可使能应答位有关的中断
MPU6050_WaitEvent(I2C2, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED);//EV6:等待地址发送结束
//
I2C_SendData(I2C2, RegAddress);//发送指定数据指定要写的寄存器地址
MPU6050_WaitEvent(I2C2, I2C_EVENT_MASTER_BYTE_TRANSMITTING);//EV8:移位寄存器数据未发送完,就可以将数据放在数据寄存器等着了,这里等待EV8事件发生即可
//可用for循环这两句发多个字节
I2C_SendData(I2C2, Data);//发送数据
MPU6050_WaitEvent(I2C2, I2C_EVENT_MASTER_BYTE_TRANSMITTED);//EV8_2:等待移位寄存器和数据寄存器都为空,数据完成后再发送起始条件
I2C_GenerateSTOP(I2C2, ENABLE);
主机接收
cpp
uint8_t Data; //存放接收的一个字节
I2C_GenerateSTART(I2C2, ENABLE);
MPU6050_WaitEvent(I2C2, I2C_EVENT_MASTER_MODE_SELECT);
I2C_Send7bitAddress(I2C2, MPU6050_ADDRESS, I2C_Direction_Transmitter);
MPU6050_WaitEvent(I2C2, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED);
I2C_SendData(I2C2, RegAddress);
MPU6050_WaitEvent(I2C2, I2C_EVENT_MASTER_BYTE_TRANSMITTED);//等待数据发送完,再生成起始条件,等待I2C_EVENT_MASTER_BYTE_TRANSMITTING事件也可行
I2C_GenerateSTART(I2C2, ENABLE); ///使能重复起始条件生成电路,改变数据传输方向
MPU6050_WaitEvent(I2C2, I2C_EVENT_MASTER_MODE_SELECT);//EV5:等待重复起始条件发送完成
I2C_Send7bitAddress(I2C2, MPU6050_ADDRESS, I2C_Direction_Receiver);//告诉MPU6050从机,主机要接收数据
MPU6050_WaitEvent(I2C2, I2C_EVENT_MASTER_RECEIVER_MODE_SELECTED);//EV6:等待地址发送结束
//
I2C_AcknowledgeConfig(I2C2, DISABLE);//在从机发完一个字节之后的应答位时序前,失能ACK应答位,只接受一个字节的数据
I2C_GenerateSTOP(I2C2, ENABLE);//根据序列图建议,这里设置STOP请求
MPU6050_WaitEvent(I2C2, I2C_EVENT_MASTER_BYTE_RECEIVED);//EV7:接收数据寄存器非空
Data = I2C_ReceiveData(I2C2);//接收1个字节
//for循环调用,接收多个字节,只需在最后接收最后一个字节之前,失能应答和使能停止生成电路
I2C_AcknowledgeConfig(I2C2, ENABLE);//使能ACK应答位,恢复初始化设置状态
return Data;
I2C例程
mpu6050的c文件
cpp
#include "stm32f10x.h" // Device header
#include "MPU6050_Reg.h"
#define MPU6050_ADDRESS 0xD0
void MPU6050_WaitEvent(I2C_TypeDef* I2Cx, uint32_t I2C_EVENT)
{
uint32_t Timeout;
Timeout = 10000;
while (I2C_CheckEvent(I2Cx, I2C_EVENT) != SUCCESS)
{
Timeout --;
if (Timeout == 0)
{
break;
}
}
}
void MPU6050_WriteReg(uint8_t RegAddress, uint8_t Data)
{
I2C_GenerateSTART(I2C2, ENABLE);
MPU6050_WaitEvent(I2C2, I2C_EVENT_MASTER_MODE_SELECT);
I2C_Send7bitAddress(I2C2, MPU6050_ADDRESS, I2C_Direction_Transmitter);
MPU6050_WaitEvent(I2C2, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED);
I2C_SendData(I2C2, RegAddress);
MPU6050_WaitEvent(I2C2, I2C_EVENT_MASTER_BYTE_TRANSMITTING);
I2C_SendData(I2C2, Data);
MPU6050_WaitEvent(I2C2, I2C_EVENT_MASTER_BYTE_TRANSMITTED);
I2C_GenerateSTOP(I2C2, ENABLE);
}
uint8_t MPU6050_ReadReg(uint8_t RegAddress)
{
uint8_t Data;
I2C_GenerateSTART(I2C2, ENABLE);
MPU6050_WaitEvent(I2C2, I2C_EVENT_MASTER_MODE_SELECT);
I2C_Send7bitAddress(I2C2, MPU6050_ADDRESS, I2C_Direction_Transmitter);
MPU6050_WaitEvent(I2C2, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED);
I2C_SendData(I2C2, RegAddress);
MPU6050_WaitEvent(I2C2, I2C_EVENT_MASTER_BYTE_TRANSMITTED);
I2C_GenerateSTART(I2C2, ENABLE);
MPU6050_WaitEvent(I2C2, I2C_EVENT_MASTER_MODE_SELECT);
I2C_Send7bitAddress(I2C2, MPU6050_ADDRESS, I2C_Direction_Receiver);
MPU6050_WaitEvent(I2C2, I2C_EVENT_MASTER_RECEIVER_MODE_SELECTED);
I2C_AcknowledgeConfig(I2C2, DISABLE);
I2C_GenerateSTOP(I2C2, ENABLE);
MPU6050_WaitEvent(I2C2, I2C_EVENT_MASTER_BYTE_RECEIVED);
Data = I2C_ReceiveData(I2C2);
I2C_AcknowledgeConfig(I2C2, ENABLE);
return Data;
}
void MPU6050_Init(void)
{
RCC_APB1PeriphClockCmd(RCC_APB1Periph_I2C2, ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_OD;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10 | GPIO_Pin_11;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOB, &GPIO_InitStructure);
I2C_InitTypeDef I2C_InitStructure;
I2C_InitStructure.I2C_Mode = I2C_Mode_I2C;
I2C_InitStructure.I2C_ClockSpeed = 50000;
I2C_InitStructure.I2C_DutyCycle = I2C_DutyCycle_2;
I2C_InitStructure.I2C_Ack = I2C_Ack_Enable;
I2C_InitStructure.I2C_AcknowledgedAddress = I2C_AcknowledgedAddress_7bit;
I2C_InitStructure.I2C_OwnAddress1 = 0x00;
I2C_Init(I2C2, &I2C_InitStructure);
I2C_Cmd(I2C2, ENABLE);
MPU6050_WriteReg(MPU6050_PWR_MGMT_1, 0x01);
MPU6050_WriteReg(MPU6050_PWR_MGMT_2, 0x00);
MPU6050_WriteReg(MPU6050_SMPLRT_DIV, 0x09);
MPU6050_WriteReg(MPU6050_CONFIG, 0x06);
MPU6050_WriteReg(MPU6050_GYRO_CONFIG, 0x18);
MPU6050_WriteReg(MPU6050_ACCEL_CONFIG, 0x18);
}
uint8_t MPU6050_GetID(void)
{
return MPU6050_ReadReg(MPU6050_WHO_AM_I);
}
void MPU6050_GetData(int16_t *AccX, int16_t *AccY, int16_t *AccZ,
int16_t *GyroX, int16_t *GyroY, int16_t *GyroZ)
{
uint8_t DataH, DataL;
DataH = MPU6050_ReadReg(MPU6050_ACCEL_XOUT_H);
DataL = MPU6050_ReadReg(MPU6050_ACCEL_XOUT_L);
*AccX = (DataH << 8) | DataL;
DataH = MPU6050_ReadReg(MPU6050_ACCEL_YOUT_H);
DataL = MPU6050_ReadReg(MPU6050_ACCEL_YOUT_L);
*AccY = (DataH << 8) | DataL;
DataH = MPU6050_ReadReg(MPU6050_ACCEL_ZOUT_H);
DataL = MPU6050_ReadReg(MPU6050_ACCEL_ZOUT_L);
*AccZ = (DataH << 8) | DataL;
DataH = MPU6050_ReadReg(MPU6050_GYRO_XOUT_H);
DataL = MPU6050_ReadReg(MPU6050_GYRO_XOUT_L);
*GyroX = (DataH << 8) | DataL;
DataH = MPU6050_ReadReg(MPU6050_GYRO_YOUT_H);
DataL = MPU6050_ReadReg(MPU6050_GYRO_YOUT_L);
*GyroY = (DataH << 8) | DataL;
DataH = MPU6050_ReadReg(MPU6050_GYRO_ZOUT_H);
DataL = MPU6050_ReadReg(MPU6050_GYRO_ZOUT_L);
*GyroZ = (DataH << 8) | DataL;
}