当前位置: 首页 > news >正文

stm32week5

stm32学习

二.外设

14.串口发送数据包

数据包的定义:
HEX数据包(以0xFF为包头,0xFE为包尾,实际上可自定义):

  1. 固定包长,含包头包尾
  2. 可变包长,含包头包尾

对于数据中不会出现包头包尾的数据可以用可变包长的,用固定包长的可以在一定程度上避免数据中出现的与包头包尾重复的数据产生的影响
文本数据包(以’@‘为包头,’/r/n’(换行)为包尾,实际上可自定义):

  1. 固定包长,含包头包尾
  2. 可变包长,含包头包尾

HEX数据包接收的状态有限机:

图片消失了

文本数据包接收的状态有限机与上述类似

HEX接发串口数据包的代码:

#include "stm32f10x.h"                  // Device header
#include <stdio.h>
#include <stdarg.h>

uint8_t Serial_TxPacket[4];    //定义发送数据包数组,数据包格式:FF 01 02 03 04 FE
uint8_t Serial_RxPacket[4];    //定义接收数据包数组
uint8_t Serial_RxFlag;     //定义接收数据包标志位

/**
  * 函    数:串口初始化
  * 参    数:无
  * 返 回 值:无
  */
void Serial_Init(void)
{
 /*开启时钟*/
 RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE); //开启USART1的时钟
 RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); //开启GPIOA的时钟
 
 /*GPIO初始化*/
 GPIO_InitTypeDef GPIO_InitStructure;
 GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
 GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;
 GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
 GPIO_Init(GPIOA, &GPIO_InitStructure);     //将PA9引脚初始化为复用推挽输出
 
 GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
 GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;
 GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
 GPIO_Init(GPIOA, &GPIO_InitStructure);     //将PA10引脚初始化为上拉输入
 
 /*USART初始化*/
 USART_InitTypeDef USART_InitStructure;     //定义结构体变量
 USART_InitStructure.USART_BaudRate = 9600;    //波特率
 USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None; //硬件流控制,不需要
 USART_InitStructure.USART_Mode = USART_Mode_Tx | USART_Mode_Rx; //模式,发送模式和接收模式均选择
 USART_InitStructure.USART_Parity = USART_Parity_No;  //奇偶校验,不需要
 USART_InitStructure.USART_StopBits = USART_StopBits_1; //停止位,选择1位
 USART_InitStructure.USART_WordLength = USART_WordLength_8b;  //字长,选择8位
 USART_Init(USART1, &USART_InitStructure);    //将结构体变量交给USART_Init,配置USART1
 
 /*中断输出配置*/
 USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);   //开启串口接收数据的中断
 
 /*NVIC中断分组*/
 NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);   //配置NVIC为分组2
 
 /*NVIC配置*/
 NVIC_InitTypeDef NVIC_InitStructure;     //定义结构体变量
 NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn;  //选择配置NVIC的USART1线
 NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;   //指定NVIC线路使能
 NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;  //指定NVIC线路的抢占优先级为1
 NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;  //指定NVIC线路的响应优先级为1
 NVIC_Init(&NVIC_InitStructure);       //将结构体变量交给NVIC_Init,配置NVIC外设
 
 /*USART使能*/
 USART_Cmd(USART1, ENABLE);        //使能USART1,串口开始运行
}

/**
  * 函    数:串口发送一个字节
  * 参    数:Byte 要发送的一个字节
  * 返 回 值:无
  */
void Serial_SendByte(uint8_t Byte)
{
 USART_SendData(USART1, Byte);  //将字节数据写入数据寄存器,写入后USART自动生成时序波形
 while (USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET); //等待发送完成
 /*下次写入数据寄存器会自动清除发送完成标志位,故此循环后,无需清除标志位*/
}

/**
  * 函    数:串口发送一个数组
  * 参    数:Array 要发送数组的首地址
  * 参    数:Length 要发送数组的长度
  * 返 回 值:无
  */
void Serial_SendArray(uint8_t *Array, uint16_t Length)
{
 uint16_t i;
 for (i = 0; i < Length; i ++)  //遍历数组
 {
  Serial_SendByte(Array[i]);  //依次调用Serial_SendByte发送每个字节数据
 }
}

/**
  * 函    数:串口发送一个字符串
  * 参    数:String 要发送字符串的首地址
  * 返 回 值:无
  */
void Serial_SendString(char *String)
{
 uint8_t i;
 for (i = 0; String[i] != '\0'; i ++)//遍历字符数组(字符串),遇到字符串结束标志位后停止
 {
  Serial_SendByte(String[i]);  //依次调用Serial_SendByte发送每个字节数据
 }
}

/**
  * 函    数:次方函数(内部使用)
  * 返 回 值:返回值等于X的Y次方
  */
uint32_t Serial_Pow(uint32_t X, uint32_t Y)
{
 uint32_t Result = 1; //设置结果初值为1
 while (Y --)   //执行Y次
 {
  Result *= X;  //将X累乘到结果
 }
 return Result;
}

/**
  * 函    数:串口发送数字
  * 参    数:Number 要发送的数字,范围:0~4294967295
  * 参    数:Length 要发送数字的长度,范围:0~10
  * 返 回 值:无
  */
void Serial_SendNumber(uint32_t Number, uint8_t Length)
{
 uint8_t i;
 for (i = 0; i < Length; i ++)  //根据数字长度遍历数字的每一位
 {
  Serial_SendByte(Number / Serial_Pow(10, Length - i - 1) % 10 + '0'); //依次调用Serial_SendByte发送每位数字
 }
}

/**
  * 函    数:使用printf需要重定向的底层函数
  * 参    数:保持原始格式即可,无需变动
  * 返 回 值:保持原始格式即可,无需变动
  */
int fputc(int ch, FILE *f)
{
 Serial_SendByte(ch);   //将printf的底层重定向到自己的发送字节函数
 return ch;
}

/**
  * 函    数:自己封装的prinf函数
  * 参    数:format 格式化字符串
  * 参    数:... 可变的参数列表
  * 返 回 值:无
  */
void Serial_Printf(char *format, ...)
{
 char String[100];    //定义字符数组
 va_list arg;     //定义可变参数列表数据类型的变量arg
 va_start(arg, format);   //从format开始,接收参数列表到arg变量
 vsprintf(String, format, arg); //使用vsprintf打印格式化字符串和参数列表到字符数组中
 va_end(arg);     //结束变量arg
 Serial_SendString(String);  //串口发送字符数组(字符串)
}

/**
  * 函    数:串口发送数据包
  * 参    数:无
  * 返 回 值:无
  * 说    明:调用此函数后,Serial_TxPacket数组的内容将加上包头(FF)包尾(FE)后,作为数据包发送出去
  */
void Serial_SendPacket(void)
{
 Serial_SendByte(0xFF);
 Serial_SendArray(Serial_TxPacket, 4);
 Serial_SendByte(0xFE);
}

/**
  * 函    数:获取串口接收数据包标志位
  * 参    数:无
  * 返 回 值:串口接收数据包标志位,范围:0~1,接收到数据包后,标志位置1,读取后标志位自动清零
  */
uint8_t Serial_GetRxFlag(void)
{
 if (Serial_RxFlag == 1)   //如果标志位为1
 {
  Serial_RxFlag = 0;
  return 1;     //则返回1,并自动清零标志位
 }
 return 0;      //如果标志位为0,则返回0
}

/**
  * 函    数:USART1中断函数
  * 参    数:无
  * 返 回 值:无
  * 注意事项:此函数为中断函数,无需调用,中断触发后自动执行
  *           函数名为预留的指定名称,可以从启动文件复制
  *           请确保函数名正确,不能有任何差异,否则中断函数将不能进入
  */
void USART1_IRQHandler(void)
{
 static uint8_t RxState = 0;  //定义表示当前状态机状态的静态变量
 static uint8_t pRxPacket = 0; //定义表示当前接收数据位置的静态变量
 if (USART_GetITStatus(USART1, USART_IT_RXNE) == SET)  //判断是否是USART1的接收事件触发的中断
 {
  uint8_t RxData = USART_ReceiveData(USART1);    //读取数据寄存器,存放在接收的数据变量
  
  /*使用状态机的思路,依次处理数据包的不同部分*/
  
  /*当前状态为0,接收数据包包头*/
  if (RxState == 0)
  {
   if (RxData == 0xFF)   //如果数据确实是包头
   {
    RxState = 1;   //置下一个状态
    pRxPacket = 0;   //数据包的位置归零
   }
  }
  /*当前状态为1,接收数据包数据*/
  else if (RxState == 1)
  {
   Serial_RxPacket[pRxPacket] = RxData; //将数据存入数据包数组的指定位置
   pRxPacket ++;    //数据包的位置自增
   if (pRxPacket >= 4)   //如果收够4个数据
   {
    RxState = 2;   //置下一个状态
   }
  }
  /*当前状态为2,接收数据包包尾*/
  else if (RxState == 2)
  {
   if (RxData == 0xFE)   //如果数据确实是包尾部
   {
    RxState = 0;   //状态归0
    Serial_RxFlag = 1;  //接收数据包标志位置1,成功接收一个数据包
   }
  }
  
  USART_ClearITPendingBit(USART1, USART_IT_RXNE);  //清除标志位
 }
}

主程序代码:

#include "stm32f10x.h"                  // Device header
#include "Delay.h"
#include "OLED.h"
#include "Serial.h"
#include "Key.h"

uint8_t KeyNum;   //定义用于接收按键键码的变量

int main(void)
{
 /*模块初始化*/
 OLED_Init();  //OLED初始化
 Key_Init();   //按键初始化
 Serial_Init();  //串口初始化
 
 /*显示静态字符串*/
 OLED_ShowString(1, 1, "TxPacket");
 OLED_ShowString(3, 1, "RxPacket");
 
 /*设置发送数据包数组的初始值,用于测试*/
 Serial_TxPacket[0] = 0x01;
 Serial_TxPacket[1] = 0x02;
 Serial_TxPacket[2] = 0x03;
 Serial_TxPacket[3] = 0x04;
 
 while (1)
 {
  KeyNum = Key_GetNum();   //获取按键键码
  if (KeyNum == 1)    //按键1按下
  {
   Serial_TxPacket[0] ++;  //测试数据自增
   Serial_TxPacket[1] ++;
   Serial_TxPacket[2] ++;
   Serial_TxPacket[3] ++;
   
   Serial_SendPacket();  //串口发送数据包Serial_TxPacket
   
   OLED_ShowHexNum(2, 1, Serial_TxPacket[0], 2); //显示发送的数据包
   OLED_ShowHexNum(2, 4, Serial_TxPacket[1], 2);
   OLED_ShowHexNum(2, 7, Serial_TxPacket[2], 2);
   OLED_ShowHexNum(2, 10, Serial_TxPacket[3], 2);
  }
  
  if (Serial_GetRxFlag() == 1) //如果接收到数据包
  {
   OLED_ShowHexNum(4, 1, Serial_RxPacket[0], 2); //显示接收的数据包
   OLED_ShowHexNum(4, 4, Serial_RxPacket[1], 2);
   OLED_ShowHexNum(4, 7, Serial_RxPacket[2], 2);
   OLED_ShowHexNum(4, 10, Serial_RxPacket[3], 2);
  }
 }
}

发送文本数据包的代码类似

三.通信

1.FlyMcu和ST-link Utility

FlyMcu的作用是串口下载
流程:需要在keil中勾选创建HEX文件,编译一遍,在FlyMcu中选择产生的HEX文件,然后将配置boot引脚的跳线帽改成1/0,按下复位键

图片消失了

原理就是先存储到系统存储器中,再由板子内部的代码将代码转存到Flash中

图片消失了

此外,FlyMcu可以读stm32的Flash,存储为.bin文件,.bin文件是没有地址信息的原始数据文件,也可以设置读保护(还有很多功能)

ST-link Utility:连接后可以直接读取stm32的FLash中的数据,也可以将.bin文件或者HEX文件导入到stm32中

2.I2C通信协议

I2C总线(Inter IC BUS,俗称I方C)是一种通用数据总线
两根通信线:SCL(Serial Clock)、SDA(Serial Data)
同步、半双工
带数据应答
支持总线挂载多设备(一主多从:一个主设备、多个从设备,多主多从)
一主多从模型(多主多从模型复杂):

图片消失了

SCLK就是SCL,所有I2C设备的SCL连在一起,SDA连在一起
为了防止主设备输出1、从设备输出0导致短路的情况
设备的SCL和SDA均要配置成开漏输出模式
SCL和SDA各添加一个上拉电阻,阻值一般为4.7 k ω k\omega

I2C时序基本单元:
起始条件:SCL高电平期间,SDA从高电平切换到低电平
终止条件:SCL高电平期间,SDA从低电平切换到高电平

图片消失了

发送一个字节:SCL低电平期间,主机将数据位依次放到SDA线上(高位先行),然后释放SCL,从机将在高电平期间读取数据位,所以SCL高电平期间SDA不允许有数据变化,依次循环上述过程8次,即可发送一个字节

图片消失了

接收一个字节:SCL低电平期间,主机将数据位依次放到SDA线上(高位先行),然后释放SCL,主机将在SCL高电平期间读取数据位,所以SCL高电平期间SDA不允许有数据变化,依次循环上述过程8次,即可发送一个字节(主机接收前,需要释放SDA)

发送应答:主机在接收完一个字节之后,在下一个时钟发送一位数据,数据0表示应答,数据1表示非应答
接收应答:主机在发送完一个字节之后,在下一个时钟接收一位数据,判断从机是否应答,数据0表示应答,数据1表示非应答(主机在接收之前,需要释放SDA)

图片消失了

指定地址写:
对于指定设备,在指定地址下,写入指定数据

图片消失了

数据组成:起始条件+从设备的7位地址+1位读写位+1位应答位+要写入的地址+1位应答位+发送的数据+1位应答位+结束条件

当前地址读:
对于指定设备,在当前地址指针指示的地址下,读取从机数据

图片消失了

数据组成:起始条件+从设备的7位地址+1位读写位+1位应答位+读取的数据+1位应答位+结束条件
从设备的寄存器的地址是按线性排列的,读取一位数据后,指针会自动跳到下一位地址,所以可以连续读取数据

指定地址读:
对于指定设备,在指定地址下,读取从机数据

图片消失了

是指定地址写和当前地址读的组合

3.MPU6050

MPU6050是一个6轴(x,y,z的加速度和角速度)姿态传感器,通过数据融合,可进一步得到姿态角,常应用于平衡车、飞行器等
3轴加速度计:测量X、Y、Z轴的加速度
3轴陀螺仪传感器:测量X、Y、Z轴的角速度

MPU6050参数:
16位ADC采集传感器的模拟信号,量化范围:-32768~32767
加速度计满量程选择: ± 2 、 ± 4 、 ± 8 、 ± 16 ( g ) \pm2、\pm4、\pm8、\pm16(g) ±2±4±8±16(g)
陀螺仪满量程选择: ± 250 、 ± 500 、 ± 1000 、 ± 2000 ( o / s e c ) \pm250、\pm500、\pm1000、\pm2000(^o/sec) ±250±500±1000±2000(o/sec)
满量程越大,精度越小,反之越大
有可配置的数字低通滤波器
有可配置的时钟源
可配置的采样分频
I2C从机地址:1101000(AD0=0),1101001(AD0=1),AD0是MPU6050的引脚,专门用于设置从机地址的最后一位
发送从机地址时,最后一位是读写位,所以整体地址要左移一格,也就是0xD0

硬件电路:

图片消失了
引脚功能
VCC、GND电源
SCL、SDAI2C通信引脚
XCL、XDA主机I2C通信引脚
AD0从机最低位
INT中断信号输出

MPU6050框图:

图片消失了

4.软件I2C读写MPU6050

代码:

#include "stm32f10x.h"                  // Device header
#include "Delay.h"
#include "OLED.h"
#include "MPU6050.h"

uint8_t ID;        //定义用于存放ID号的变量
int16_t AX, AY, AZ, GX, GY, GZ;   //定义用于存放各个数据的变量

int main(void)
{
 /*模块初始化*/
 OLED_Init();  //OLED初始化
 MPU6050_Init();  //MPU6050初始化
 
 /*显示ID号*/
 OLED_ShowString(1, 1, "ID:");  //显示静态字符串
 ID = MPU6050_GetID();    //获取MPU6050的ID号
 OLED_ShowHexNum(1, 4, ID, 2);  //OLED显示ID号
 
 while (1)
 {
  MPU6050_GetData(&AX, &AY, &AZ, &GX, &GY, &GZ);  //获取MPU6050的数据
  OLED_ShowSignedNum(2, 1, AX, 5);     //OLED显示数据
  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);
 }
}

MyI2C的代码:

#include "stm32f10x.h"                  // Device header
#include "Delay.h"

/*引脚配置层*/

/**
  * 函    数:I2C写SCL引脚电平
  * 参    数:BitValue 协议层传入的当前需要写入SCL的电平,范围0~1
  * 返 回 值:无
  * 注意事项:此函数需要用户实现内容,当BitValue为0时,需要置SCL为低电平,当BitValue为1时,需要置SCL为高电平
  */
void MyI2C_W_SCL(uint8_t BitValue)
{
 GPIO_WriteBit(GPIOB, GPIO_Pin_10, (BitAction)BitValue);  //根据BitValue,设置SCL引脚的电平
 Delay_us(10);            //延时10us,防止时序频率超过要求
}

/**
  * 函    数:I2C写SDA引脚电平
  * 参    数:BitValue 协议层传入的当前需要写入SDA的电平,范围0~1
  * 返 回 值:无
  * 注意事项:此函数需要用户实现内容,当BitValue为0时,需要置SDA为低电平,当BitValue为1时,需要置SDA为高电平
  */
void MyI2C_W_SDA(uint8_t BitValue)
{
 GPIO_WriteBit(GPIOB, GPIO_Pin_11, (BitAction)BitValue);  //根据BitValue,设置SDA引脚的电平,BitValue要实现非0即1的特性
 Delay_us(10);            //延时10us,防止时序频率超过要求
}

/**
  * 函    数:I2C读SDA引脚电平
  * 参    数:无
  * 返 回 值:协议层需要得到的当前SDA的电平,范围0~1
  * 注意事项:此函数需要用户实现内容,当前SDA为低电平时,返回0,当前SDA为高电平时,返回1
  */
uint8_t MyI2C_R_SDA(void)
{
 uint8_t BitValue;
 BitValue = GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_11);  //读取SDA电平
 Delay_us(10);            //延时10us,防止时序频率超过要求
 return BitValue;           //返回SDA电平
}

/**
  * 函    数:I2C初始化
  * 参    数:无
  * 返 回 值:无
  * 注意事项:此函数需要用户实现内容,实现SCL和SDA引脚的初始化
  */
void MyI2C_Init(void)
{
 /*开启时钟*/
 RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE); //开启GPIOB的时钟
 
 /*GPIO初始化*/
 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);     //将PB10和PB11引脚初始化为开漏输出
 
 /*设置默认电平*/
 GPIO_SetBits(GPIOB, GPIO_Pin_10 | GPIO_Pin_11);   //设置PB10和PB11引脚初始化后默认为高电平(释放总线状态)
}

/*协议层*/

/**
  * 函    数:I2C起始
  * 参    数:无
  * 返 回 值:无
  */
void MyI2C_Start(void)
{
 MyI2C_W_SDA(1);       //释放SDA,确保SDA为高电平
 MyI2C_W_SCL(1);       //释放SCL,确保SCL为高电平
 MyI2C_W_SDA(0);       //在SCL高电平期间,拉低SDA,产生起始信号
 MyI2C_W_SCL(0);       //起始后把SCL也拉低,即为了占用总线,也为了方便总线时序的拼接
}

/**
  * 函    数:I2C终止
  * 参    数:无
  * 返 回 值:无
  */
void MyI2C_Stop(void)
{
 MyI2C_W_SDA(0);       //拉低SDA,确保SDA为低电平
 MyI2C_W_SCL(1);       //释放SCL,使SCL呈现高电平
 MyI2C_W_SDA(1);       //在SCL高电平期间,释放SDA,产生终止信号
}

/**
  * 函    数:I2C发送一个字节
  * 参    数:Byte 要发送的一个字节数据,范围:0x00~0xFF
  * 返 回 值:无
  */
void MyI2C_SendByte(uint8_t Byte)
{
 uint8_t i;
 for (i = 0; i < 8; i ++)    //循环8次,主机依次发送数据的每一位
 {
  /*两个!可以对数据进行两次逻辑取反,作用是把非0值统一转换为1,即:!!(0) = 0,!!(非0) = 1*/
  MyI2C_W_SDA(!!(Byte & (0x80 >> i)));//使用掩码的方式取出Byte的指定一位数据并写入到SDA线
  MyI2C_W_SCL(1);      //释放SCL,从机在SCL高电平期间读取SDA
  MyI2C_W_SCL(0);      //拉低SCL,主机开始发送下一位数据
 }
}

/**
  * 函    数:I2C接收一个字节
  * 参    数:无
  * 返 回 值:接收到的一个字节数据,范围:0x00~0xFF
  */
uint8_t MyI2C_ReceiveByte(void)
{
 uint8_t i, Byte = 0x00;     //定义接收的数据,并赋初值0x00,此处必须赋初值0x00,后面会用到
 MyI2C_W_SDA(1);       //接收前,主机先确保释放SDA,避免干扰从机的数据发送
 for (i = 0; i < 8; i ++)    //循环8次,主机依次接收数据的每一位
 {
  MyI2C_W_SCL(1);      //释放SCL,主机机在SCL高电平期间读取SDA
  if (MyI2C_R_SDA()){Byte |= (0x80 >> i);} //读取SDA数据,并存储到Byte变量
             //当SDA为1时,置变量指定位为1,当SDA为0时,不做处理,指定位为默认的初值0
  MyI2C_W_SCL(0);      //拉低SCL,从机在SCL低电平期间写入SDA
 }
 return Byte;       //返回接收到的一个字节数据
}

/**
  * 函    数:I2C发送应答位
  * 参    数:Byte 要发送的应答位,范围:0~1,0表示应答,1表示非应答
  * 返 回 值:无
  */
void MyI2C_SendAck(uint8_t AckBit)
{
 MyI2C_W_SDA(AckBit);     //主机把应答位数据放到SDA线
 MyI2C_W_SCL(1);       //释放SCL,从机在SCL高电平期间,读取应答位
 MyI2C_W_SCL(0);       //拉低SCL,开始下一个时序模块
}

/**
  * 函    数:I2C接收应答位
  * 参    数:无
  * 返 回 值:接收到的应答位,范围:0~1,0表示应答,1表示非应答
  */
uint8_t MyI2C_ReceiveAck(void)
{
 uint8_t AckBit;       //定义应答位变量
 MyI2C_W_SDA(1);       //接收前,主机先确保释放SDA,避免干扰从机的数据发送
 MyI2C_W_SCL(1);       //释放SCL,主机机在SCL高电平期间读取SDA
 AckBit = MyI2C_R_SDA();     //将应答位存储到变量里
 MyI2C_W_SCL(0);       //拉低SCL,开始下一个时序模块
 return AckBit;       //返回定义应答位变量
}

MPU6050的代码:

#include "stm32f10x.h"                  // Device header
#include "MyI2C.h"
#include "MPU6050Reg.h"

#define MPU6050_ADDRESS  0xD0  //MPU6050的I2C从机地址

/**
  * 函    数:MPU6050写寄存器
  * 参    数:RegAddress 寄存器地址,范围:参考MPU6050手册的寄存器描述
  * 参    数:Data 要写入寄存器的数据,范围:0x00~0xFF
  * 返 回 值:无
  */
void MPU6050_WriteReg(uint8_t RegAddress, uint8_t Data)
{
 MyI2C_Start();      //I2C起始
 MyI2C_SendByte(MPU6050_ADDRESS); //发送从机地址,读写位为0,表示即将写入
 MyI2C_ReceiveAck();     //接收应答
 MyI2C_SendByte(RegAddress);   //发送寄存器地址
 MyI2C_ReceiveAck();     //接收应答
 MyI2C_SendByte(Data);    //发送要写入寄存器的数据
 MyI2C_ReceiveAck();     //接收应答
 MyI2C_Stop();      //I2C终止
}

/**
  * 函    数:MPU6050读寄存器
  * 参    数:RegAddress 寄存器地址,范围:参考MPU6050手册的寄存器描述
  * 返 回 值:读取寄存器的数据,范围:0x00~0xFF
  */
uint8_t MPU6050_ReadReg(uint8_t RegAddress)
{
 uint8_t Data;
 
 MyI2C_Start();      //I2C起始
 MyI2C_SendByte(MPU6050_ADDRESS); //发送从机地址,读写位为0,表示即将写入
 MyI2C_ReceiveAck();     //接收应答
 MyI2C_SendByte(RegAddress);   //发送寄存器地址
 MyI2C_ReceiveAck();     //接收应答
 
 MyI2C_Start();      //I2C重复起始
 MyI2C_SendByte(MPU6050_ADDRESS | 0x01); //发送从机地址,读写位为1,表示即将读取
 MyI2C_ReceiveAck();     //接收应答
 Data = MyI2C_ReceiveByte();   //接收指定寄存器的数据
 MyI2C_SendAck(1);     //发送应答,给从机非应答,终止从机的数据输出
 MyI2C_Stop();      //I2C终止
 
 return Data;
}

/**
  * 函    数:MPU6050初始化
  * 参    数:无
  * 返 回 值:无
  */
void MPU6050_Init(void)
{
 MyI2C_Init();         //先初始化底层的I2C
 
 /*MPU6050寄存器初始化,需要对照MPU6050手册的寄存器描述配置,此处仅配置了部分重要的寄存器*/
 MPU6050_WriteReg(MPU6050_PWR_MGMT_1, 0x01);  //电源管理寄存器1,取消睡眠模式,选择时钟源为X轴陀螺仪
 MPU6050_WriteReg(MPU6050_PWR_MGMT_2, 0x00);  //电源管理寄存器2,保持默认值0,所有轴均不待机
 MPU6050_WriteReg(MPU6050_SMPLRT_DIV, 0x09);  //采样率分频寄存器,配置采样率
 MPU6050_WriteReg(MPU6050_CONFIG, 0x06);   //配置寄存器,配置DLPF
 MPU6050_WriteReg(MPU6050_GYRO_CONFIG, 0x18); //陀螺仪配置寄存器,选择满量程为±2000°/s
 MPU6050_WriteReg(MPU6050_ACCEL_CONFIG, 0x18); //加速度计配置寄存器,选择满量程为±16g
}

/**
  * 函    数:MPU6050获取ID号
  * 参    数:无
  * 返 回 值:MPU6050的ID号
  */
uint8_t MPU6050_GetID(void)
{
 return MPU6050_ReadReg(MPU6050_WHO_AM_I);  //返回WHO_AM_I寄存器的值
}

/**
  * 函    数:MPU6050获取数据
  * 参    数:AccX AccY AccZ 加速度计X、Y、Z轴的数据,使用输出参数的形式返回,范围:-32768~32767
  * 参    数:GyroX GyroY GyroZ 陀螺仪X、Y、Z轴的数据,使用输出参数的形式返回,范围:-32768~32767
  * 返 回 值:无
  */
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;        //定义数据高8位和低8位的变量
 
 DataH = MPU6050_ReadReg(MPU6050_ACCEL_XOUT_H);  //读取加速度计X轴的高8位数据
 DataL = MPU6050_ReadReg(MPU6050_ACCEL_XOUT_L);  //读取加速度计X轴的低8位数据
 *AccX = (DataH << 8) | DataL;      //数据拼接,通过输出参数返回
 
 DataH = MPU6050_ReadReg(MPU6050_ACCEL_YOUT_H);  //读取加速度计Y轴的高8位数据
 DataL = MPU6050_ReadReg(MPU6050_ACCEL_YOUT_L);  //读取加速度计Y轴的低8位数据
 *AccY = (DataH << 8) | DataL;      //数据拼接,通过输出参数返回
 
 DataH = MPU6050_ReadReg(MPU6050_ACCEL_ZOUT_H);  //读取加速度计Z轴的高8位数据
 DataL = MPU6050_ReadReg(MPU6050_ACCEL_ZOUT_L);  //读取加速度计Z轴的低8位数据
 *AccZ = (DataH << 8) | DataL;      //数据拼接,通过输出参数返回
 
 DataH = MPU6050_ReadReg(MPU6050_GYRO_XOUT_H);  //读取陀螺仪X轴的高8位数据
 DataL = MPU6050_ReadReg(MPU6050_GYRO_XOUT_L);  //读取陀螺仪X轴的低8位数据
 *GyroX = (DataH << 8) | DataL;      //数据拼接,通过输出参数返回
 
 DataH = MPU6050_ReadReg(MPU6050_GYRO_YOUT_H);  //读取陀螺仪Y轴的高8位数据
 DataL = MPU6050_ReadReg(MPU6050_GYRO_YOUT_L);  //读取陀螺仪Y轴的低8位数据
 *GyroY = (DataH << 8) | DataL;      //数据拼接,通过输出参数返回
 
 DataH = MPU6050_ReadReg(MPU6050_GYRO_ZOUT_H);  //读取陀螺仪Z轴的高8位数据
 DataL = MPU6050_ReadReg(MPU6050_GYRO_ZOUT_L);  //读取陀螺仪Z轴的低8位数据
 *GyroZ = (DataH << 8) | DataL;      //数据拼接,通过输出参数返回
}

关于寄存器的宏定义:

#ifndef __MPU6050REG_H
#define __MPU6050REG_H

#define MPU6050_SMPLRT_DIV  0x19
#define MPU6050_CONFIG   0x1A
#define MPU6050_GYRO_CONFIG  0x1B
#define MPU6050_ACCEL_CONFIG 0x1C

#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
#define MPU6050_PWR_MGMT_2  0x6C
#define MPU6050_WHO_AM_I  0x75

#endif

5.硬件读取I2C

stm32内部集成了硬件I2C收发电路,可以由硬件自动执行时钟生成、起始终止条件生成、应答位收发、数据收发等功能,减轻CPU的负担
支持多主机模型(在SCL空闲时,所有从机都可以是主机)
支持7位/10位地址
支持DMA
支持不同的通讯速度,标准速度100kHz,快速400kHz
兼容SMBus协议

I2C框图:

图片消失了

I2C基本结构:

图片消失了

主机发送流程图:

图片消失了

主机接收流程图:

图片消失了

相关文章:

  • 细说 Java 集合之 Map
  • 【机器学习chp10】降维——(核化)PCA + MDS + lsomap + 拉普拉斯特征映射 + t-NSE + UMAP
  • 【无标题】词源故事:role与roll的联系,词根horr(恐惧)与hair(毛发)关系
  • Python之参数星号(*)使用笔记
  • js基础二
  • JMeter 实战项目脚本录制最佳实践(含 BadBoy 录制方式)
  • Docker 常用指令手册(学习使用)
  • Skywalking介绍,Skywalking 9.4 安装,SpringBoot集成Skywalking
  • 16.11 LangChain SQL 生成与执行实战:构建安全高效的数据库查询引擎
  • DeepSeek如何快速开发PDF转Word软件
  • 【JAVA面试题】JDK、JRE、JVM 三者区别和联系
  • 商米科技前端工程师(base上海)内推
  • 【HTML—前端快速入门】HTML 基础
  • 什么是最终一致性,它对后端系统的意义是什么
  • 物联网 智慧园区井盖管理办法和功能介绍
  • 大数据学习(53)-Hive与Impala
  • 授权与认证之jwt(四)创建OAuth2 Filter类
  • 3471. 找出最大的几近缺失整数
  • 基于图神经网络的会话推荐经典论文
  • 十四届蓝桥杯JAVA-b组-合并石子
  • 马上评|中药液涉嫌添加安眠药?药品安全儿戏不得
  • 肖钢:一季度证券业金融科技投资强度在金融各子行业中居首
  • 看展 | 黄永玉新作展,感受赤子般的生命力
  • 广西隆林突发山洪,致3人遇难1人失联
  • 光速晋级!2025年多哈世乒赛孙颖莎4比0战胜对手
  • 河南一女子被医院强制带走治疗,官方通报:当值医生停职