STM32学习25--硬件I2C
一、I2C外设简介
1、STM32内部集成了I2C收发电路,可以硬件自动执行时钟生成、起始终止条件生成、应答位收发、收据收发等功能,减轻CPU负担。
2、支持多主机模型;
3、支持7位/10位地址模式;
4、支持不同的通讯速度,标准速度(100KHz),快速400KHz;
5、支持DMA;
二、硬件引脚与复用
1、定义表
| 外设 | 默认引脚 | 重映射引脚 | 总线 |
| I2C1 |
PB6-SCL PB7-SDA |
PB8-SCL PB9-SDA | APB1 |
| I2C2 |
PB10-SCL PB11-SDA | 无重映射 | APB1 |
注:GPIO硬性要求:必须分配为GPIO_Mode_AF_OD,复用开漏输出,不能用推挽。
I2C总线是线与结构,需要外部4.7k--10kΩ上拉电阻,芯片内部上拉能力不足,不加上拉不稳定。空闲时,SDA、SCL被拉到高电平。
2、关键寄存器
1)I2C_CR1控制寄存器1
PE(bit0):I2C外设使能位,1开启;
START(bit8):写1,硬件产生起始信号;
STOP(bit9):写1,硬件产生停止信号;
ACK(bit10):应答机制,1:接收时发送ACK,0:接收时发送NACK;
2)I2C_CR2控制寄存器2
FREQ[5:0]:APB1时钟频率,单位MHz;
3)I2C_OAR1/OAR2 自身从机地址寄存器
主机模式基本不用,做从机时填写7bit/10bit地址。
4)I2C_DR 数据寄存器
写DR:发送数据;读DR:接受数据。硬件移位寄存器自动完成位收发。DR读写会自动清除部分状态标志。
5)I2C_CCR时钟控制寄存器,计算SCL频率
标准频率:100KHz,快速模式:400KHz。
fscl=fapb1/2*CCR。PCLK1就是APB1的时钟(36MHz)。
100KHz标准模式:CCR=180,36M/(2*180) =100K 。

6)I2C_SR1状态寄存器1(事件+错误标志,只读)
| 标志位 | 含义 | 清除条件 |
| SB(bit0) | EV5:起始信号也发出 | 读SR1,再写DR |
| ADDR(bit1) | EV6:地址匹配应答成功 | 读SR1,再读SR2 |
| BTF(bit2) | EV8_2:字节传输完成,移位寄存器+DR全部空 | 读写DR/读SR1+SR2 |
| RxNE(bit6) | EV7:接收寄存器非空,收到数据 | 读DR |
| TXE(bit7) | EV8:发送寄存器空,可以写新数据 | 写DR |
| AE(bit10) | 应答识别,从机没有ACK | 软件写0清零 |
7)I2C_SR2状态寄存器2(只读,必须配合SR1读取)
MSL(bit0):1:主机模式;0:从机;
BUSY(bit1):总线通信中,0表示总线空闲;
TRA(bit2):1:发送模式,0:接收模式;
三、标准库
I2C初始化结构体详解
uint32_t I2C_ClockSpeed:SCL频率,0~400KHz。
uint16_t I2C_Mode:I2C_Mode_I2C 或者I2C_Mode_SMBus。
uint16_t I2C_DutyCycle:I2C_DutyCycle_2(Duty=0标准模式)或I2C_DutyCycle_16_9(Duty =1快速模式)。
DutyCycle =SCL时钟高电平时间:低电平时间。
I2C_DutyCycle_2是高:低=1:2;标准模式下,无视这个参数,高:低=1:1
I2C_DutyCycle_16_9是:高:低=9:16。
uint16_t I2C_OwnAddress1:本机从地址,主机模式填0。
uint16_t I2C_Ack:I2C_Ack_Enable或I2C_Ack_Disable,接收是否发送ACK。
uint16_t I2C_AcknowledgedAddress:地址模式,I2C_AcknowledgedAddress_7bit或I2C_AcknowledgedAddress_10bit,一般选7位。
库函数I2C_Init()内部自动设置CR2.FREQ,CCR、TRISE,不需要手动写寄存器。

四、事件EV编号
标准库定义、轮询核心。
| 事件宏 | 说明 |
|
EV5: I2C_EVENT_MASTER_MODE_SELECT | 起始条件发送完成,SB置1 |
|
EV6: I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED | 地址发送完成,从机应答,主机发送模式 |
|
EV6: I2C_EVENT_MASTER_RECEIVER_MODE_SELECTED | 地址发送完成,主机接收模式 |
|
EV7: I2C_EVENT_MASTER_BYTE_RECEIVED | RxNE=1,DR收到1字节数据,可以读DR |
|
EV8: I2C_EVENT_MASTER_BYTE_TRANSMITTING | TXE=1,DR空,可以写下一个字节 |
|
EV8_2: I2C_EVENT_MASTER_BYTE_TRANSMITTED | BTF+TXE都置1,移位+DR全部空,适合发STOP |
注:EV8只代表DR寄存器空,移位寄存器还在往外发字节,EV8_2才是整字节全部移位输出完毕,发STOP必须等EV8_2,否则停止信号错乱。
五、代码部分
1、写时序:Start→从机地址W→ACK→寄存器地址→ACK→数据1→ACK→数据2→ACK→STOP;
注:
1)发送完最后一个字节,不能看TXE(EV8)就发送STOP,必须等BTF=1(EV8_2),BTF:数据字节传输完。
2)TXE=1只代表数据寄存器DR空,BTF=1代表DR和移位寄存器全部空,字节真正全部搬运到总线上发完。
3)所以while必须加超时,防止总线卡死程序。
4)GPIO必须复用开漏模式。
参数说明:RegAddr寄存器地址,Data:要写入的值。
#define MPU6050_ADDRESS 0xD0
//超时处理函数
void MPU6050_WaitEvent(I2C_TypeDef* I2Cx, uint32_t I2C_EVENT)
{
uint16_t Timeout =10000;
while(I2C_CheckEvent(I2C2,I2C_EVENT)!=SUCCESS)
{
Timeout--;
if(0 ==Timeout)
{
//超时处理代码,比如报警等
break;
}
}
}
//单字节写
void MPU6050_WriteReq(uint8_t RegAddr,uint8_t Data)
{
I2C_GenerateSTART(I2C2,ENABLE);
MPU6050_WaitEvent(I2C2,I2C_EVENT_MASTER_MODE_SELECT);//EV5
I2C_Send7bitAddress(I2C2,MPU6050_ADDRESS,I2C_Direction_Transmitter);
MPU6050_WaitEvent(I2C2,I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED);//EV6
I2C_SendData(I2C2,RegAddr);//写数据到DR
MPU6050_WaitEvent(I2C2,I2C_EVENT_MASTER_BYTE_TRANSMITTING);//EV8
I2C_SendData(I2C2,Data);
MPU6050_WaitEvent(I2C2,I2C_EVENT_MASTER_BYTE_TRANSMITTED);//EV8_2
I2C_GenerateSTOP(I2C2,ENABLE);
}
多字节写:连续写一串数据,比如连续写多个寄存器。中间字节:只需要等待EV8(TXE=1),就可以填充下一个DR;只有最后一个字节必须等待EV8_2(BTF=1)再发STOP。
2、读时序
写传感器标准流程:写寄存器地址→重复起始→读数据。时序如下:
Start→从机地址→ACK→寄存器地址W→ACK→RepeatStart→从机地址R→ACK→接收N个字节→NACK→STOP。
核心铁律:
1)最后一个字节之前,提前关闭ACK,不能读完数据再关。读完数据之后才关ACK,硬件I2C的第9位应答是提前预配置,硬件在SCL第9个脉冲输出ACK/NACK,等读完DR再改寄存器,第9个脉冲已经发送完毕。
2)多字节:倒数第2字节收到后关闭ACK。
3)单字节:EV6(接收模式选定)之后立刻关闭ACK。
4)每一步必须等待对应事件,不能直接跑代码。
5)所有循环,工程代码必须加延时,防止卡死。
6)如果只能读一次,第2次直接卡死,忘记恢复I2C_AcknowledgeConfig(I2C2,ENABLE);
7)读出的数据全是0或者0xFF,NACK时机错误。
8)和软件I2C本质区别:
软件I2C接收:收到8bit数据后,CPU再决定输出ACK/NACK。
硬件I2C接收:第9位应答的配置,必须在这一字节的SCL第9个时钟到来之前提前设置完毕。
uint8_t MPU6050_ReadReg(uint8_t RegAddress)
{
uint8_t Data;
I2C_GenerateSTART(I2C2,ENABLE);
MPU6050_WaitEvent(I2C2,I2C_EVENT_MASTER_MODE_SELECT);//监测EV5事件是否发生
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);//EV8
//重新开始
I2C_GenerateSTART(I2C2,ENABLE);
MPU6050_WaitEvent(I2C2,I2C_EVENT_MASTER_MODE_SELECT);//EV5
I2C_Send7bitAddress(I2C2,MPU6050_Address,I2C_Direction_Receiver);//函数自动将最低位置1,不用写|0x01
MPU6050_WaitEvent(I2C2,I2C_EVENT_MASTER_RECEIVER_MODE_SELECTED);//EV6
//开始接收数据
I2C_AcknowledgeConfig(I2C2,DISABLE);
I2C_GenerateSTOP(I2C2,ENABLE);//
MPU6050_WaitEvent(I2C2,I2C_EVENT_MASTER_BYTE_RECEIVED);//在接收最后一个字节前,要提前设置非应答ACK和STOP
Data = I2C_ReceiveData(I2C2);//
I2C_AcknowledgeConfig(I2C2,ENABLE); //恢复应答
return Data;
}
完整工程:硬件I2C读取MPU6050参数
MPU6050.c文件
#include "stm32f10x.h" // Device header
#include "MPU6050_Reg.h"
#include "MPU6050.h"
//硬件I2C是非阻塞的,软件I2C包含延时函数,是阻塞式的
#define MPU6050_Address 0xD0
void MPU6050_WaitEvent(I2C_TypeDef* I2Cx, uint32_t I2C_EVENT)
{
uint32_t Timeout =10000;
while(I2C_CheckEvent(I2C2,I2C_EVENT) == !SUCCESS)//监测EV5事件是否发生
{
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);//监测EV5事件是否发生
I2C_Send7bitAddress(I2C2,MPU6050_Address,I2C_Direction_Transmitter);//从机地址
MPU6050_WaitEvent(I2C2,I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED);//EV6
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);//监测EV5事件是否发生
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);//EV8
I2C_GenerateSTART(I2C2,ENABLE);
MPU6050_WaitEvent(I2C2,I2C_EVENT_MASTER_MODE_SELECT);//EV5
I2C_Send7bitAddress(I2C2,MPU6050_Address,I2C_Direction_Receiver);//函数自动将最低位置1,不用写|0x01
MPU6050_WaitEvent(I2C2,I2C_EVENT_MASTER_RECEIVER_MODE_SELECTED);//EV6
//开始接收数据
I2C_AcknowledgeConfig(I2C2,DISABLE);//这4行,用for循环可以实现读取多个字节
I2C_GenerateSTOP(I2C2,ENABLE);//
MPU6050_WaitEvent(I2C2,I2C_EVENT_MASTER_BYTE_RECEIVED);//在接收最后一个字节前,要提前设置非应答ACK和STOP
Data = I2C_ReceiveData(I2C2);//
I2C_AcknowledgeConfig(I2C2,ENABLE); //恢复应答
return Data;
}
void MPU6050_Init()
{
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_Ack=I2C_Ack_Enable;
I2C_InitStructure.I2C_AcknowledgedAddress=I2C_AcknowledgedAddress_7bit;//STM32作为从机时参数
I2C_InitStructure.I2C_ClockSpeed =50000;//50kHz
I2C_InitStructure.I2C_DutyCycle =I2C_DutyCycle_2;//>100kHz时才有用
I2C_InitStructure.I2C_OwnAddress1 =0x00;//STM32作为从机时参数
I2C_Init(I2C2,&I2C_InitStructure);
I2C_Cmd(I2C2,ENABLE);
MPU6050_WriteReg(MPU6050_PWR_MGMT_1,0x01);//解除睡眠模式及时钟选择
MPU6050_WriteReg(MPU6050_PWR_MGMT_2,0x00);//XYZ轴不睡眠
MPU6050_WriteReg(MPU6050_SMPLRT_DIV,0x09);//10分频
MPU6050_WriteReg(MPU6050_CONFIG,0x06);//滤波参数
MPU6050_WriteReg(MPU6050_GYRO_CONFIG,0x18);//陀螺仪最大量程
MPU6050_WriteReg(MPU6050_ACCEL_CONFIG,0x18);//加速度计最大量程
}
MPU6050.h文件
#ifndef __MPU6050_H
#define __MPU6050_H
uint8_t MPU6050_ReadReg(uint8_t RegAddress);
void MPU6050_WriteReg(uint8_t RegAddress,uint8_t Data);
void MPU6050_Init();
void MPU6050_GegData(int16_t *AccX,int16_t *AccY,int16_t *AccZ,
int16_t *GyroX,int16_t *GyroY,int16_t *GyroZ);
uint8_t MPU6050_GetID();
main函数
#include "stm32f10x.h"
#include "Delay.h"
#include "OLED.h"
#include "MPU6050.h"
int16_t AX,AY ,AZ,GX,GY,GZ;
int8_t ID;
int main()
{
OLED_Init();
MPU6050_Init();
OLED_ShowString(1,1,"ID:");
ID =MPU6050_GetID();
OLED_ShowHexNum(1,4,ID,2);
while(1)
{
MPU6050_GegData(&AX,&AY,&AZ,&GX,&GY,&GZ);
OLED_ShowSignedNum(2,1,AX,5);
OLED_ShowSignedNum(3,1,AY,5);
OLED_ShowSignedNum(4,1,AZ,5);
OLED_ShowSignedNum(2,8,GX,5);
OLED_ShowSignedNum(3,8,GY,5);
OLED_ShowSignedNum(4,8,GZ,5);
delay_ms(100);
}
}
本文完!
更多推荐



所有评论(0)