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;
}