学习笔记:用stm32f103c8t6开发板和MPU6050模块读取角度在0.96寸oled屏幕上显示的代码解释

main.c代码解释

/* USER CODE BEGIN Header */
/**
  ******************************************************************************
  * @file           : main.c
  * @brief          : Main program body
  ******************************************************************************
  * @attention
  *
  * Copyright (c) 2024 STMicroelectronics.
  * All rights reserved.
  *
  * This software is licensed under terms that can be found in the LICENSE file
  * in the root directory of this software component.
  * If no LICENSE file comes with this software, it is provided AS-IS.
  *
  ******************************************************************************
  */
/* USER CODE END Header */
/* Includes ------------------------------------------------------------------*/
// 包含主头文件,通常包含项目的全局定义和配置
#include "main.h"
// 包含I2C通信相关的头文件,用于初始化和操作I2C外设
#include "i2c.h"
// 包含GPIO(通用输入输出)相关的头文件,用于初始化和操作GPIO引脚
#include "gpio.h"

/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
// 包含OLED显示屏相关的头文件,用于操作OLED屏幕显示信息
#include "oled.h"
// 包含I2C通信相关的自定义头文件,可能是对标准I2C库的封装或扩展
#include "IIC.h"
// 包含MPU传感器驱动库的头文件,用于初始化和操作MPU传感器
#include "inv_mpu.h"
// 包含MPU传感器数字运动处理器(DMP)相关的头文件,用于处理传感器数据
#include "inv_mpu_dmp_motion_driver.h"
// 包含MPU6050传感器的头文件,用于初始化和读取MPU6050的数据
#include "mpu6050.h"
// 包含标准输入输出库的头文件,用于使用printf等函数进行调试输出
#include "stdio.h"
/* USER CODE END Includes */

/* Private typedef -----------------------------------------------------------*/
/* USER CODE BEGIN PTD */
// 这里可以定义私有类型,目前为空
/* USER CODE END PTD */

/* Private define ------------------------------------------------------------*/
/* USER CODE BEGIN PD */
// 这里可以定义私有宏,目前为空
/* USER CODE END PD */

/* Private macro -------------------------------------------------------------*/
/* USER CODE BEGIN PM */
// 这里可以定义私有宏,目前为空
/* USER CODE END PM */

/* Private variables ---------------------------------------------------------*/

/* USER CODE BEGIN PV */
// 定义三个浮点型变量,用于存储俯仰角(pitch)、横滚角(roll)和偏航角(yaw)
float pitch,roll,yaw;
// 定义一个长度为20的无符号8位整型数组,用于存储要显示在OLED屏幕上的信息
uint8_t display_buf[20];
/* USER CODE END PV */

/* Private function prototypes -----------------------------------------------*/
// 声明系统时钟配置函数,该函数用于配置系统的时钟源和时钟频率
void SystemClock_Config(void);
/* USER CODE BEGIN PFP */
// 这里可以声明私有函数原型,目前为空
/* USER CODE END PFP */

/* Private user code ---------------------------------------------------------*/
/* USER CODE BEGIN 0 */
// 这里可以添加用户自定义的代码,目前为空
/* USER CODE END 0 */

/**
  * @brief  The application entry point.
  * @retval int
  */
int main(void)
{
  /* USER CODE BEGIN 1 */
  // 这里可以添加在系统初始化之前需要执行的代码,目前为空
  /* USER CODE END 1 */

  /* MCU Configuration--------------------------------------------------------*/

  // 复位所有外设,初始化Flash接口和SysTick定时器
  HAL_Init();

  /* USER CODE BEGIN Init */
  // 这里可以添加自定义的初始化代码,目前为空
  /* USER CODE END Init */

  // 调用系统时钟配置函数,配置系统的时钟源和时钟频率
  SystemClock_Config();

  /* USER CODE BEGIN SysInit */
  // 这里可以添加系统初始化相关的代码,目前为空
  /* USER CODE END SysInit */

  // 初始化GPIO(通用输入输出)外设
  MX_GPIO_Init();
  // 初始化I2C1外设,用于与外部设备进行I2C通信
  MX_I2C1_Init();
  /* USER CODE BEGIN 2 */
  // 初始化OLED显示屏
  OLED_Init();
  // 清除OLED显示屏上的内容
  OLED_Clear();
  // 初始化MPU6050传感器
  MPU_Init();
  // 初始化MPU6050传感器的数字运动处理器(DMP)
  mpu_dmp_init();
  // 在OLED显示屏的指定位置显示字符串 "Init Sucess",字体大小为16
  OLED_ShowString(0,00,"Init Sucess",16);
  /* USER CODE END 2 */

  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
  // 进入无限循环,程序将在此循环中不断执行
  while (1)
  {
    // 延时10毫秒,避免频繁读取传感器数据
    HAL_Delay(10);
    // 从MPU6050传感器的DMP中获取俯仰角、横滚角和偏航角数据
    mpu_dmp_get_data(&pitch,&roll,&yaw);
    // 将俯仰角数据格式化为字符串,存储到display_buf数组中
    sprintf((char *)display_buf,"pitch:%.2f   ",pitch);
    // 在OLED显示屏的指定位置显示俯仰角信息
    OLED_ShowString(0,2,display_buf,16);
    // 将横滚角数据格式化为字符串,存储到display_buf数组中
    sprintf((char *)display_buf,"roll:%.2f   ",roll);
    // 在OLED显示屏的指定位置显示横滚角信息
    OLED_ShowString(0,4,display_buf,16);
    // 将偏航角数据格式化为字符串,存储到display_buf数组中
    sprintf((char *)display_buf,"yaw:%.2f   ",yaw);
    // 在OLED显示屏的指定位置显示偏航角信息
    OLED_ShowString(0,6,display_buf,16);

    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */
    // 这里可以添加循环中需要执行的其他代码,目前为空
    /* USER CODE END 3 */
  }
  /* USER CODE END 3 */
}

/**
  * @brief System Clock Configuration
  * @retval None
  */
void SystemClock_Config(void)
{
  // 定义一个RCC_OscInitTypeDef类型的结构体变量,用于配置振荡器
  RCC_OscInitTypeDef RCC_OscInitStruct = {0};
  // 定义一个RCC_ClkInitTypeDef类型的结构体变量,用于配置时钟
  RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};

  /** Initializes the RCC Oscillators according to the specified parameters
  * in the RCC_OscInitTypeDef structure.
  */
  // 设置振荡器类型为外部高速时钟(HSE)
  RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE;
  // 使能外部高速时钟(HSE)
  RCC_OscInitStruct.HSEState = RCC_HSE_ON;
  // 设置HSE预分频值为1
  RCC_OscInitStruct.HSEPredivValue = RCC_HSE_PREDIV_DIV1;
  // 使能内部高速时钟(HSI)
  RCC_OscInitStruct.HSIState = RCC_HSI_ON;
  // 使能PLL(锁相环)
  RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
  // 设置PLL的时钟源为外部高速时钟(HSE)
  RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE;
  // 设置PLL的乘法因子为9
  RCC_OscInitStruct.PLL.PLLMUL = RCC_PLL_MUL9;
  // 调用HAL_RCC_OscConfig函数配置振荡器,如果配置失败则调用Error_Handler函数处理错误
  if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
  {
    Error_Handler();
  }

  /** Initializes the CPU, AHB and APB buses clocks
  */
  // 设置需要配置的时钟类型,包括HCLK、SYSCLK、PCLK1和PCLK2
  RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK
                              |RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2;
  // 设置系统时钟源为PLLCLK
  RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
  // 设置AHB总线时钟分频系数为1
  RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
  // 设置APB1总线时钟分频系数为2
  RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV2;
  // 设置APB2总线时钟分频系数为1
  RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1;

  // 调用HAL_RCC_ClockConfig函数配置时钟,如果配置失败则调用Error_Handler函数处理错误
  if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_2) != HAL_OK)
  {
    Error_Handler();
  }
}

/* USER CODE BEGIN 4 */
// 这里可以添加自定义的代码,目前为空
/* USER CODE END 4 */

/**
  * @brief  This function is executed in case of error occurrence.
  * @retval None
  */
void Error_Handler(void)
{
  /* USER CODE BEGIN Error_Handler_Debug */
  // 这里可以添加自定义的错误处理代码,目前为空
  // 禁用全局中断
  __disable_irq();
  // 进入无限循环,程序将在此处停滞
  while (1)
  {
  }
  /* USER CODE END Error_Handler_Debug */
}

#ifdef  USE_FULL_ASSERT
/**
  * @brief  Reports the name of the source file and the source line number
  *         where the assert_param error has occurred.
  * @param  file: pointer to the source file name
  * @param  line: assert_param error line source number
  * @retval None
  */
void assert_failed(uint8_t *file, uint32_t line)
{
  /* USER CODE BEGIN 6 */
  // 这里可以添加自定义的断言失败处理代码,目前为空
  /* User can add his own implementation to report the file name and line number,
     ex: printf("Wrong parameters value: file %s on line %d\r\n", file, line) */
  /* USER CODE END 6 */
}
#endif /* USE_FULL_ASSERT */
```

上述注释详细解释了代码的每一部分功能,希望能帮助你更好地理解代码。 

mpu6050.c代码解释

#include "stm32f1xx_hal.h"
#include "mpu6050.h"

// 初始化MPU6050
// 返回值:0,成功
//       其他,错误代码
uint8_t MPU_Init(void)
{
    uint8_t res;

    MPU_IIC_Init();// 初始化IIC总线,用于与MPU6050进行通信
    MPU_Write_Byte(MPU_PWR_MGMT1_REG,0X80);  // 复位MPU6050,通过向电源管理寄存器1写入特定值实现
    delay_ms(100);                           // 等待100毫秒,确保复位操作完成
    MPU_Write_Byte(MPU_PWR_MGMT1_REG,0X00);  // 唤醒MPU6050,设置电源管理寄存器1
    MPU_Set_Gyro_Fsr(3);                    // 设置陀螺仪传感器量程为±2000dps
    MPU_Set_Accel_Fsr(0);                   // 设置加速度传感器量程为±2g
    MPU_Set_Rate(200);                      // 设置采样率为200Hz
    MPU_Write_Byte(MPU_INT_EN_REG,0X00);    // 关闭所有中断,通过向中断使能寄存器写入0实现
    MPU_Write_Byte(MPU_USER_CTRL_REG,0X00);  // 关闭I2C主模式,设置用户控制寄存器
    MPU_Write_Byte(MPU_FIFO_EN_REG,0X00);   // 关闭FIFO,通过向FIFO使能寄存器写入0实现
    MPU_Write_Byte(MPU_INTBP_CFG_REG,0X80); // 设置INT引脚低电平有效,配置中断引脚配置寄存器
    res=MPU_Read_Byte(MPU_DEVICE_ID_REG);    // 读取MPU6050的设备ID
    if(res==MPU_ADDR)// 如果读取到的设备ID与预期的MPU6050地址相符
    {
        MPU_Write_Byte(MPU_PWR_MGMT1_REG,0X01);  // 设置时钟选择为PLL X轴作为参考,配置电源管理寄存器1
        MPU_Write_Byte(MPU_PWR_MGMT2_REG,0X00);  // 使加速度计和陀螺仪都工作,配置电源管理寄存器2
        MPU_Set_Rate(100);                      // 重新设置采样率为100Hz
    } else return 1;                           // 如果设备ID不匹配,返回1表示初始化失败
    return 0;                                  // 初始化成功,返回0
}

// 设置MPU6050陀螺仪传感器满量程范围
// fsr:0, ±250dps;1, ±500dps;2, ±1000dps;3, ±2000dps
// 返回值:0,设置成功
//       其他,设置失败
uint8_t MPU_Set_Gyro_Fsr(uint8_t fsr)
{
    return MPU_Write_Byte(MPU_GYRO_CFG_REG,fsr<<3);// 通过向陀螺仪配置寄存器写入相应的值来设置满量程范围
}

// 设置MPU6050加速度传感器满量程范围
// fsr:0, ±2g;1, ±4g;2, ±8g;3, ±16g
// 返回值:0,设置成功
//       其他,设置失败
uint8_t MPU_Set_Accel_Fsr(uint8_t fsr)
{
    return MPU_Write_Byte(MPU_ACCEL_CFG_REG,fsr<<3);// 通过向加速度计配置寄存器写入相应的值来设置满量程范围
}

// 设置MPU6050的数字低通滤波器
// lpf:数字低通滤波频率(Hz)
// 返回值:0,设置成功
//       其他,设置失败
uint8_t MPU_Set_LPF(uint16_t lpf)
{
    uint8_t data=0;
    if(lpf>=188)data=1;  // 根据输入的滤波频率确定要写入寄存器的值
    else if(lpf>=98)data=2;
    else if(lpf>=42)data=3;
    else if(lpf>=20)data=4;
    else if(lpf>=10)data=5;
    else data=6;
    return MPU_Write_Byte(MPU_CFG_REG,data);// 向配置寄存器写入确定的值以设置低通滤波器
}

// 设置MPU6050的采样率(假定Fs=1KHz)
// rate:4~1000(Hz)
// 返回值:0,设置成功
//       其他,设置失败
uint8_t MPU_Set_Rate(uint16_t rate)
{
    uint8_t data;
    if(rate>1000)rate=1000;  // 限制采样率最大值为1000Hz
    if(rate<4)rate=4;        // 限制采样率最小值为4Hz
    data=1000/rate-1;        // 根据采样率计算要写入寄存器的值
    data=MPU_Write_Byte(MPU_SAMPLE_RATE_REG,data);  // 向采样率寄存器写入计算得到的值
    return MPU_Set_LPF(rate/2);  // 自动设置LPF为采样率的一半,并返回设置结果
}

// 得到温度值
// 返回值:温度值(放大了100倍)
short MPU_Get_Temperature(void)
{
    uint8_t buf[2];  // 用于存储读取到的温度数据的数组
    short raw;       // 用于存储原始温度数据
    float temp;      // 用于存储计算后的温度值

    MPU_Read_Len(MPU_ADDR,MPU_TEMP_OUTH_REG,2,buf);  // 从MPU6050读取温度数据的高字节和低字节
    raw=((uint16_t)buf[0]<<8)|buf[1];  // 组合高低字节得到原始温度数据
    temp=36.53+((double)raw)/340;      // 根据公式计算实际温度值
    return temp*100;;                   // 返回放大100倍后的温度值
}

// 得到陀螺仪值(原始值)
// gx,gy,gz:陀螺仪x,y,z轴的原始读数(带符号)
// 返回值:0,成功
//       其他,错误代码
uint8_t MPU_Get_Gyroscope(short *gx,short *gy,short *gz)
{
    uint8_t buf[6],res;  // 用于存储读取到的陀螺仪数据的数组和操作结果变量

    res=MPU_Read_Len(MPU_ADDR,MPU_GYRO_XOUTH_REG,6,buf);  // 从MPU6050读取陀螺仪数据
    if(res==0)
    {
        *gx=((uint16_t)buf[0]<<8)|buf[1];  // 组合高低字节得到x轴陀螺仪数据
        *gy=((uint16_t)buf[2]<<8)|buf[3];  // 组合高低字节得到y轴陀螺仪数据
        *gz=((uint16_t)buf[4]<<8)|buf[5];  // 组合高低字节得到z轴陀螺仪数据
    }
    return res;;  // 返回操作结果
}

// 得到加速度值(原始值)
// ax,ay,az:加速度计x,y,z轴的原始读数(带符号)
// 返回值:0,成功
//       其他,错误代码
uint8_t MPU_Get_Accelerometer(short *ax,short *ay,short *az)
{
    uint8_t buf[6],res;  // 用于存储读取到的加速度计数据的数组和操作结果变量

    res=MPU_Read_Len(MPU_ADDR,MPU_ACCEL_XOUTH_REG,6,buf);  // 从MPU6050读取加速度计数据
    if(res==0)
    {
        *ax=((uint16_t)buf[0]<<8)|buf[1];  // 组合高低字节得到x轴加速度计数据
        *ay=((uint16_t)buf[2]<<8)|buf[3];  // 组合高低字节得到y轴加速度计数据
        *az=((uint16_t)buf[4]<<8)|buf[5];  // 组合高低字节得到z轴加速度计数据
    }
    return res;;  // 返回操作结果
}

// IIC连续写
// addr:设备地址
// reg:寄存器地址
// len:写入长度
// buf:数据区
// 返回值:0,正常
//       其他,错误代码
uint8_t MPU_Write_Len(uint8_t addr,uint8_t reg,uint8_t len,uint8_t *buf)
{
    uint8_t i;

    MPU_IIC_Start();  // 启动I2C通信
    MPU_IIC_Send_Byte((addr<<1)|0);// 发送设备地址和写命令
    if(MPU_IIC_Wait_Ack())  // 等待设备响应
    {
        MPU_IIC_Stop();  // 如果设备无响应,停止I2C通信
        return 1;
    }
    MPU_IIC_Send_Byte(reg);  // 发送要写入的寄存器地址
    MPU_IIC_Wait_Ack();      // 等待设备响应
    for(i=0; i<len; i++)
    {
        MPU_IIC_Send_Byte(buf[i]);  // 发送数据
        if(MPU_IIC_Wait_Ack())      // 等待设备响应
        {
            MPU_IIC_Stop();
            return 1;
        }
    }
    MPU_IIC_Stop();  // 停止I2C通信
    return 0;
}

// IIC连续读
// addr:设备地址
// reg:要读取的寄存器地址
// len:要读取的长度
// buf:读取到的数据存储区
// 返回值:0,正常
//       其他,错误代码
uint8_t MPU_Read_Len(uint8_t addr,uint8_t reg,uint8_t len,uint8_t *buf)
{
    MPU_IIC_Start();  // 启动I2C通信
    MPU_IIC_Send_Byte((addr<<1)|0);// 发送设备地址和写命令
    if(MPU_IIC_Wait_Ack())  // 等待设备响应
    {
        MPU_IIC_Stop();  // 如果设备无响应,停止I2C通信
        return 1;
    }
    MPU_IIC_Send_Byte(reg);  // 发送要读取的寄存器地址
    MPU_IIC_Wait_Ack();      // 等待设备响应
    MPU_IIC_Start();  // 重新启动I2C通信,准备读数据
    MPU_IIC_Send_Byte((addr<<1)|1);// 发送设备地址和读命令
    MPU_IIC_Wait_Ack();      // 等待设备响应
    while(len)
    {
        if(len==1)*buf=MPU_IIC_Read_Byte(0);// 读取数据,发送nACK
        else *buf=MPU_IIC_Read_Byte(1);     // 读取数据,发送ACK
        len--;
        buf++;
    }
    MPU_IIC_Stop();  // 停止I2C通信
    return 0;
}

// IIC写一个字节
// reg:寄存器地址
// data:数据
// 返回值:0,正常
//       其他,错误代码
uint8_t MPU_Write_Byte(uint8_t reg,uint8_t data)
{
    MPU_IIC_Start();  // 启动I2C通信
    MPU_IIC_Send_Byte((MPU_ADDR<<1)|0);// 发送设备地址和写命令
    if(MPU_IIC_Wait_Ack())  // 等待设备响应
    {
        MPU_IIC_Stop();  // 如果设备无响应,停止I2C通信
        return 1;
    }
    MPU_IIC_Send_Byte(reg);  // 发送要写入的寄存器地址
    MPU_IIC_Wait_Ack();      // 等待设备响应
    MPU_IIC_Send_Byte(data);// 发送数据
    if(MPU_IIC_Wait_Ack())  // 等待设备响应
    {
        MPU_IIC_Stop();
        return 1;
    }
    MPU_IIC_Stop();  // 停止I2C通信
    return 0;
}

// IIC读一个字节
// reg:寄存器地址
// 返回值:读到的数据
uint8_t MPU_Read_Byte(uint8_t reg)
{
    uint8_t res;

    MPU_IIC_Start();  // 启动I2C通信
    MPU_IIC_Send_Byte((MPU_ADDR<<1)|0);// 发送设备地址和写命令
    MPU_IIC_Wait_Ack();      // 等待设备响应
    MPU_IIC_Send_Byte(reg);  // 发送要读取的寄存器地址
    MPU_IIC_Wait_Ack();      // 等待设备响应
    MPU_IIC_Start();  // 重新启动I2C通信,准备读数据
    MPU_IIC_Send_Byte((MPU_ADDR<<1)|1);// 发送设备地址和读命令
    MPU_IIC_Wait_Ack();      // 等待设备响应
    res=MPU_IIC_Read_Byte(0);// 读取数据,发送nACK
    MPU_IIC_Stop();          // 停止I2C通信
    return res;
}

IIC.c代码解释

#include "stm32f1xx_hal.h"
#include "IIC.h"

/* 定义IIC总线连接的GPIO端口, 用户只需要修改下面4行代码即可任意改变SCL和SDA的引脚 */
#define GPIO_PORT_IIC     GPIOB                       /* GPIO端口 */
#define RCC_IIC_ENABLE    __HAL_RCC_GPIOB_CLK_ENABLE()       /* GPIO端口时钟 */
#define IIC_SCL_PIN       GPIO_PIN_4                  /* 连接到SCL时钟线的GPIO */
#define IIC_SDA_PIN       GPIO_PIN_3                  /* 连接到SDA数据线的GPIO */

/* 定义读写SCL和SDA的宏,已增加代码的可移植性和可读写性 */
#if 1	/* 条件编译: 1 选择GPIO的库函数实现IO读写 */
#define IIC_SCL_1()  HAL_GPIO_WritePin(GPIO_PORT_IIC, IIC_SCL_PIN, GPIO_PIN_SET)		/* SCL = 1 */
#define IIC_SCL_0()  HAL_GPIO_WritePin(GPIO_PORT_IIC, IIC_SCL_PIN, GPIO_PIN_RESET)		/* SCL = 0 */

#define IIC_SDA_1()  HAL_GPIO_WritePin(GPIO_PORT_IIC, IIC_SDA_PIN, GPIO_PIN_SET)		/* SDA = 1 */
#define IIC_SDA_0()  HAL_GPIO_WritePin(GPIO_PORT_IIC, IIC_SDA_PIN, GPIO_PIN_RESET)		/* SDA = 0 */

#define IIC_SDA_READ()  HAL_GPIO_ReadPin(GPIO_PORT_IIC, IIC_SDA_PIN)	/* 读SDA口线状态 */
#else	/* 这个分支选择直接寄存器操作实现IO读写 */
/*!!注意: 如下写法,在IAR最高级别优化时,会被编译器错误优化 */
#define IIC_SCL_1()  GPIO_PORT_IIC->BSRR = IIC_SCL_PIN				/* SCL = 1 */
#define IIC_SCL_0()  GPIO_PORT_IIC->BRR = IIC_SCL_PIN				/* SCL = 0 */

#define IIC_SDA_1()  GPIO_PORT_IIC->BSRR = IIC_SDA_PIN				/* SDA = 1 */
#define IIC_SDA_0()  GPIO_PORT_IIC->BRR = IIC_SDA_PIN				/* SDA = 0 */

#define IIC_SDA_READ()  ((GPIO_PORT_IIC->IDR & IIC_SDA_PIN) != 0)	/* 读SDA口线状态 */
#endif

void IIC_GPIO_Init(void);

/*
*********************************************************************************************************
*	函数 名: IIC_Delay
*	功能说明: IIC总线位延迟,最快400KHz
*	形    参: 无
*	返 回 值: 无
*********************************************************************************************************
*/
static void IIC_Delay(void)
{
    uint8_t i;

    /*!!
        下面的时间是通过逻辑分析仪测试得到的。
        CPU主频72MHz时,在内部Flash运行, MDK工程不优化
        循环次数为10时,SCL频率 = 205KHz
        循环次数为7时,SCL频率 = 347KHz, SCL高电平时间1.5us,SCL低电平时间2.87us
        循环次数为5时,SCL频率 = 421KHz, SCL高电平时间1.25us,SCL低电平时间2.375us

    IAR工程编译效率高,不能设置为7
    */
    for (i = 0; i < 10; i++);
}

/*
*********************************************************************************************************
*	函数 名: IIC_Start
*	功能说明: CPU发起IIC总线启动信号
*	形    参: 无
*	返 回 值: 无
*********************************************************************************************************
*/
void IIC_Start(void)
{
    /* 当SCL高电平时,SDA出现一个下跳沿表示IIC总线启动信号 */
    IIC_SDA_1();
    IIC_SCL_1();
    IIC_Delay();
    IIC_SDA_0();
    IIC_Delay();
    IIC_SCL_0();
    IIC_Delay();
}

/*
*********************************************************************************************************
*	函数 名: IIC_Stop
*	功能说明: CPU发起IIC总线停止信号
*	形    参: 无
*	返 回 值: 无
*********************************************************************************************************
*/
void IIC_Stop(void)
{
    /* 当SCL高电平时,SDA出现一个上跳沿表示IIC总线停止信号 */
    IIC_SDA_0();
    IIC_SCL_1();
    IIC_Delay();
    IIC_SDA_1();
}

/*
*********************************************************************************************************
*	函数 名: IIC_SendByte
*	功能说明: CPU向IIC总线设备发送8bit数据
*	形    参: _ucByte : 等待发送的字节
*	返 回 值: 无
*********************************************************************************************************
*/
void IIC_Send_Byte(uint8_t _ucByte)
{
    uint8_t i;

    /* 先发送字节的高位bit7 */
    for (i = 0; i < 8; i++)
    {
        if (_ucByte & 0x80)
        {
            IIC_SDA_1();
        }
        else
        {
            IIC_SDA_0();
        }
        IIC_Delay();
        IIC_SCL_1();
        IIC_Delay();
        IIC_SCL_0();
        if (i == 7)
        {
            IIC_SDA_1(); // 释放总线
        }
        _ucByte <<= 1;	/* 左移一个bit */
        IIC_Delay();
    }
}

/*
*********************************************************************************************************
*	函数 名: IIC_ReadByte
*	功能说明: CPU从IIC总线设备读取8bit数据
*	形    参: 无
*	返 回 值: 读到的数据
*********************************************************************************************************
*/
uint8_t IIC_Read_Byte(uint8_t ack)
{
    uint8_t i;
    uint8_t value;

    /* 读到的第1个bit为数据的bit7 */
    value = 0;
    for (i = 0; i < 8; i++)
    {
        value <<= 1;
        IIC_SCL_1();
        IIC_Delay();
        if (IIC_SDA_READ())
        {
            value++;
        }
        IIC_SCL_0();
        IIC_Delay();
    }
    if(ack==0)
        IIC_NAck();
    else
        IIC_Ack();
    return value;
}

/*
*********************************************************************************************************
*	函数 名: IIC_WaitAck
*	功能说明: CPU产生一个时钟,并读取设备的ACK响应信号
*	形    参: 无
*	返 回 值: 返回0表示正确响应,1表示无设备响应
*********************************************************************************************************
*/
uint8_t IIC_Wait_Ack(void)
{
    uint8_t re;

    IIC_SDA_1();	/* CPU释放SDA总线 */
    IIC_Delay();
    IIC_SCL_1();	/* CPU驱动SCL = 1, 此时设备会返回ACK响应 */
    IIC_Delay();
    if (IIC_SDA_READ())	/* CPU读取SDA口线状态 */
    {
        re = 1;
    }
    else
    {
        re = 0;
    }
    IIC_SCL_0();
    IIC_Delay();
    return re;
}

/*
*********************************************************************************************************
*	函数 名: IIC_Ack
*	功能说明: CPU产生一个ACK信号
*	形    参: 无
*	返 回 值: 无
*********************************************************************************************************
*/
void IIC_Ack(void)
{
    IIC_SDA_0();	/* CPU驱动SDA = 0 */
    IIC_Delay();
    IIC_SCL_1();	/* CPU产生1个时钟 */
    IIC_Delay();
    IIC_SCL_0();
    IIC_Delay();
    IIC_SDA_1();	/* CPU释放SDA总线 */
}

/*
*********************************************************************************************************
*	函数 名: IIC_NAck
*	功能说明: CPU产生1个NACK信号
*	形    参: 无
*	返 回 值: 无
*********************************************************************************************************
*/
void IIC_NAck(void)
{
    IIC_SDA_1();	/* CPU驱动SDA = 1 */
    IIC_Delay();
    IIC_SCL_1();	/* CPU产生1个时钟 */
    IIC_Delay();
    IIC_SCL_0();
    IIC_Delay();
}

/*
*********************************************************************************************************
*	函数 名: IIC_GPIO_Config
*	功能说明: 配置IIC总线的GPIO,采用模拟IO的方式实现
*	形    参: 无
*	返 回 值: 无
*********************************************************************************************************
*/
void IIC_GPIO_Init(void)
{
    GPIO_InitTypeDef GPIO_InitStructure;

    RCC_IIC_ENABLE;	/* 打开GPIO时钟 */

    GPIO_InitStructure.Pin = IIC_SCL_PIN | IIC_SDA_PIN;
    GPIO_InitStructure.Speed = GPIO_SPEED_FREQ_HIGH;
    GPIO_InitStructure.Mode = GPIO_MODE_OUTPUT_OD;  	/* 开漏输出 */
    HAL_GPIO_Init(GPIO_PORT_IIC, &GPIO_InitStructure);

    /* 给一个停止信号, 复位IIC总线上的所有设备到待机模式 */
    IIC_Stop();
}

/*
*********************************************************************************************************
*	函数 名: IIC_CheckDevice
*	功能说明: 检测IIC总线设备,CPU向发送设备地址,然后读取设备响应来判断该设备是否存在
*	形    参: _Address: 设备的IIC总线地址
*	返 回 值: 返回值 0 表示正确, 返回1表示未检测到
*********************************************************************************************************
*/
uint8_t IIC_CheckDevice(uint8_t _Address)
{
    uint8_t ucAck;

    IIC_GPIO_Init();		/* 配置GPIO */

    IIC_Start();		/* 发送启动信号 */

    /* 发送设备地址+读写控制bit(0 = w, 1 = r) bit7 先传 */
    IIC_Send_Byte(_Address|IIC_WR);
    ucAck = IIC_Wait_Ack();	/* 检查设备的ACK响应 */

    IIC_Stop();			/* 发送停止信号 */

    return ucAck;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

DIY机器人工房

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值