#include "stm32f10x.h" // Device header
#include "Delay.h"
#include "OLED.h"
#include "Serial.h"
uint8_t RxData; //定义用于接收串口数据的变量
int main(void)
{
/*模块初始化*/
OLED_Init(); //OLED初始化
/*显示静态字符串*/
OLED_ShowString(1, 1, "RxData:");
/*串口初始化*/
Serial_Init(); //串口初始化
while (1)
{
if (Serial_GetRxFlag() == 1) //检查串口接收数据的标志位
{
RxData = Serial_GetRxData(); //获取串口接收的数据
Serial_SendByte(RxData); //串口将收到的数据回传回去,用于测试
OLED_ShowHexNum(1, 8, RxData, 2); //显示串口接收的数据
}
}
}
I2C读取mpu6050
定义 MPU6050 地址和寄存器
#define MPU6050_ADDR 0x68 << 1 // I2C 地址左移一位(HAL 库要求)
#define PWR_MGMT_1 0x6B // 电源管理寄存器
#define ACCEL_XOUT_H 0x3B // 加速度计 X 轴高字节
初始化 MPU6050
void MPU6050_Init(I2C_HandleTypeDef *hi2c) {
uint8_t data = 0;
// 唤醒 MPU6050
HAL_I2C_Mem_Write(hi2c, MPU6050_ADDR, PWR_MGMT_1, 1, &data, 1, 100);
}
读取 MPU6050 数据
void MPU6050_Read(I2C_HandleTypeDef *hi2c, int16_t *AccX, int16_t *AccY, int16_t *AccZ, int16_t *GyroX, int16_t *GyroY, int16_t *GyroZ) {
uint8_t buffer[14];
// 从 0x3B 开始读取 14 字节数据
HAL_I2C_Mem_Read(hi2c, MPU6050_ADDR, ACCEL_XOUT_H, 1, buffer, 14, 100);
// 解析数据
*AccX = (int16_t)(buffer[0] << 8 | buffer[1]);
*AccY = (int16_t)(buffer[2] << 8 | buffer[3]);
*AccZ = (int16_t)(buffer[4] << 8 | buffer[5]);
*GyroX = (int16_t)(buffer[8] << 8 | buffer[9]);
*GyroY = (int16_t)(buffer[10] << 8 | buffer[11]);
*GyroZ = (int16_t)(buffer[12] << 8 | buffer[13]);
}
主函数中使用
int main(void) {
HAL_Init();
SystemClock_Config();
MX_I2C1_Init(); // 初始化 I2C
int16_t AccX, AccY, AccZ, GyroX, GyroY, GyroZ;
MPU6050_Init(&hi2c1); // 初始化 MPU6050
while (1) {
MPU6050_Read(&hi2c1, &AccX, &AccY, &AccZ, &GyroX, &GyroY, &GyroZ);
// 打印数据(通过串口或调试工具)
printf("AccX = %d, AccY = %d, AccZ = %d\n", AccX, AccY, AccZ);
printf("GyroX = %d, GyroY = %d, GyroZ = %d\n", GyroX, GyroY, GyroZ);
HAL_Delay(500); // 延时 500ms
}
}
与软件模拟 I2C 相比,硬件 I2C 依赖于 STM32 内置的 I2C 外设,能够提供更高的性能和更低的 CPU 占用率。
硬件 I2C 的基本原理
STM32 的硬件 I2C 外设负责处理 I2C 协议的底层细节,包括:
-
生成起始条件(Start Condition)和停止条件(Stop Condition)。
-
发送和接收数据字节。
-
处理 ACK/NACK 信号。
-
支持多主模式和仲裁。
硬件 I2C 的代码实现
定义 MPU6050 地址和寄存器
#define MPU6050_ADDR 0x68 << 1 // I2C 地址左移一位(HAL 库要求)
#define PWR_MGMT_1 0x6B // 电源管理寄存器
#define ACCEL_XOUT_H 0x3B // 加速度计 X 轴高字节
作用:
定义 MPU6050 的 I2C 地址和关键寄存器的地址。
方便后续代码中使用这些常量。
为什么需要:
I2C 设备通过地址进行寻址,MPU6050 的默认地址是
0x68
。HAL 库要求 I2C 地址左移一位(即
0x68 << 1
),因为 I2C 协议中地址的最低一位表示读/写操作(0 表示写,1 表示读)。寄存器地址用于访问 MPU6050 的特定功能(如加速度计数据、电源管理等)。
初始化 MPU6050
void MPU6050_Init(I2C_HandleTypeDef *hi2c) {
uint8_t data = 0;
// 唤醒 MPU6050
HAL_I2C_Mem_Write(hi2c, MPU6050_ADDR, PWR_MGMT_1, 1, &data, 1, 100);
}
作用:
- 初始化 MPU6050,将其从睡眠模式唤醒。
为什么需要:
MPU6050 默认处于睡眠模式,需要通过写
PWR_MGMT_1
寄存器来唤醒。唤醒后才能读取传感器数据。
读取 MPU6050 数据
void MPU6050_Read(I2C_HandleTypeDef *hi2c, int16_t *AccX, int16_t *AccY, int16_t *AccZ, int16_t *GyroX, int16_t *GyroY, int16_t *GyroZ) {
uint8_t buffer[14];
// 从 0x3B 开始读取 14 字节数据
HAL_I2C_Mem_Read(hi2c, MPU6050_ADDR, ACCEL_XOUT_H, 1, buffer, 14, 100);
// 解析数据
*AccX = (int16_t)(buffer[0] << 8 | buffer[1]);
*AccY = (int16_t)(buffer[2] << 8 | buffer[3]);
*AccZ = (int16_t)(buffer[4] << 8 | buffer[5]);
*GyroX = (int16_t)(buffer[8] << 8 | buffer[9]);
*GyroY = (int16_t)(buffer[10] << 8 | buffer[11]);
*GyroZ = (int16_t)(buffer[12] << 8 | buffer[13]);
}
作用:
- 从 MPU6050 读取加速度计和陀螺仪的原始数据。
为什么需要:
MPU6050 的加速度计和陀螺仪数据存储在特定的寄存器中,需要通过 I2C 读取。
读取的数据是原始值,需要解析后才能使用。
数据解析:
加速度计和陀螺仪的数据是 16 位有符号整数,分为高字节和低字节。
通过
(buffer[0] << 8 | buffer[1])
将两个字节组合成一个 16 位整数。
主函数中使用
int main(void) {
HAL_Init();
SystemClock_Config();
MX_I2C1_Init(); // 初始化 I2C
int16_t AccX, AccY, AccZ, GyroX, GyroY, GyroZ;
MPU6050_Init(&hi2c1); // 初始化 MPU6050
while (1) {
MPU6050_Read(&hi2c1, &AccX, &AccY, &AccZ, &GyroX, &GyroY, &GyroZ);
// 打印数据(通过串口或调试工具)
printf("AccX = %d, AccY = %d, AccZ = %d\n", AccX, AccY, AccZ);
printf("GyroX = %d, GyroY = %d, GyroZ = %d\n", GyroX, GyroY, GyroZ);
HAL_Delay(500); // 延时 500ms
}
}
作用:
- 主程序逻辑,初始化硬件并循环读取 MPU6050 数据。
为什么需要:
初始化系统时钟、I2C 外设和 MPU6050。
循环读取传感器数据并打印。
开漏输出模式的基本原理
在开漏输出模式下:
-
GPIO 引脚只能输出低电平或高阻态(高阻抗状态,相当于断开)。
-
输出低电平时,引脚内部连接到地(GND)。
-
输出高电平时,引脚处于高阻态,需要外部上拉电阻将引脚拉高到高电平。
SPI通信协议
SPI 的基本特点
-
全双工通信:可以同时发送和接收数据。
-
高速传输:通常比 I2C 和 UART 更快,速度可达几十 Mbps。
-
主从架构:一个主设备(Master)可以连接多个从设备(Slave)。
-
硬件简单:通常只需要 4 根信号线。
软件 SPI 的基本原理
软件 SPI 的核心是通过 GPIO 引脚模拟以下 SPI 协议的关键部分:
-
时钟信号(SCLK):由主设备产生,用于同步数据传输。
-
数据信号(MOSI 和 MISO):主设备通过 MOSI 发送数据,从设备通过 MISO 返回数据。
-
从设备选择信号(SS):主设备通过拉低 SS 引脚选择特定的从设备。
软件 SPI 的实现步骤
定义 GPIO 引脚
假设使用以下 GPIO 引脚:
-
SCLK:PA5
-
MOSI:PA6
-
MISO:PA7
-
SS:PA4
#define SCLK_PIN GPIO_PIN_5
#define SCLK_PORT GPIOA
#define MOSI_PIN GPIO_PIN_6
#define MOSI_PORT GPIOA
#define MISO_PIN GPIO_PIN_7
#define MISO_PORT GPIOA
#define SS_PIN GPIO_PIN_4
#define SS_PORT GPIOA -
SCLK_PIN
和SCLK_PORT
:时钟信号引脚。 -
MOSI_PIN
和MOSI_PORT
:主设备发送数据引脚。 -
MISO_PIN
和MISO_PORT
:主设备接收数据引脚。 -
SS_PIN
和SS_PORT
:从设备选择引脚。
初始化 GPIO
配置 SCLK、MOSI、MISO 和 SS 引脚为推挽输出模式(SS 和 SCLK)或输入模式(MISO)。
void SPI_Init(void) {
// 使能 GPIO 时钟
__HAL_RCC_GPIOA_CLK_ENABLE();
// 配置 SCLK 和 MOSI 为推挽输出模式
GPIO_InitTypeDef GPIO_InitStruct = {0};
GPIO_InitStruct.Pin = SCLK_PIN | MOSI_PIN | SS_PIN;
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP; // 推挽输出
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
HAL_GPIO_Init(SCLK_PORT, &GPIO_InitStruct);
// 配置 MISO 为输入模式
GPIO_InitStruct.Pin = MISO_PIN;
GPIO_InitStruct.Mode = GPIO_MODE_INPUT; // 输入模式
HAL_GPIO_Init(MISO_PORT, &GPIO_InitStruct);
// 初始状态:SS 高电平,SCLK 低电平
HAL_GPIO_WritePin(SS_PORT, SS_PIN, GPIO_PIN_SET); // SS = 1
HAL_GPIO_WritePin(SCLK_PORT, SCLK_PIN, GPIO_PIN_RESET); // SCLK = 0
}
-
SPI 通信需要正确的 GPIO 配置:
-
SCLK 和 MOSI 是输出引脚,用于主设备发送时钟和数据。
-
MISO 是输入引脚,用于主设备接收数据。
-
SS 是输出引脚,用于选择从设备。
-
-
初始状态设置(SS 高电平,SCLK 低电平)是为了确保 SPI 总线处于空闲状态。
发送和接收一个字节
uint8_t SPI_TransmitReceive(uint8_t data) {
uint8_t receivedData = 0;
// 选择从设备
HAL_GPIO_WritePin(SS_PORT, SS_PIN, GPIO_PIN_RESET); // SS = 0
// 逐位发送和接收数据
for (int i = 0; i < 8; i++) {
// 设置 MOSI
HAL_GPIO_WritePin(MOSI_PORT, MOSI_PIN, (data & 0x80) ? GPIO_PIN_SET : GPIO_PIN_RESET);
data <<= 1;
// 产生时钟上升沿
HAL_GPIO_WritePin(SCLK_PORT, SCLK_PIN, GPIO_PIN_SET); // SCLK = 1
// 读取 MISO
receivedData <<= 1;
if (HAL_GPIO_ReadPin(MISO_PORT, MISO_PIN) == GPIO_PIN_SET) {
receivedData |= 0x01;
}
// 产生时钟下降沿
HAL_GPIO_WritePin(SCLK_PORT, SCLK_PIN, GPIO_PIN_RESET); // SCLK = 0
}
// 取消选择从设备
HAL_GPIO_WritePin(SS_PORT, SS_PIN, GPIO_PIN_SET); // SS = 1
return receivedData;
}
-
SPI 是全双工通信协议,每次传输一个字节时,主设备会同时发送和接收数据。
-
通过逐位操作模拟 SPI 的时钟和数据传输。
-
选择从设备:拉低 SS 引脚,选择从设备。
-
逐位发送和接收:
-
设置 MOSI 引脚的值(根据数据的最高位)。
-
产生时钟上升沿,从设备在此时采样 MOSI 数据。
-
读取 MISO 引脚的值,从设备在此时发送数据。
-
产生时钟下降沿,完成一位数据的传输。
-
-
取消选择从设备:拉高 SS 引脚,结束通信。
主函数中使用
int main(void) {
HAL_Init();
SystemClock_Config();
SPI_Init(); // 初始化 SPI
uint8_t txData[] = {0x01, 0x02, 0x03}; // 要发送的数据
uint8_t rxData[3]; // 接收数据的缓冲区
while (1) {
// 发送和接收数据
for (int i = 0; i < 3; i++) {
rxData[i] = SPI_TransmitReceive(txData[i]);
}
// 打印接收到的数据(通过串口或调试工具)
printf("Received: %02X %02X %02X\n", rxData[0], rxData[1], rxData[2]);
HAL_Delay(500); // 延时 500ms
}
}
硬件 SPI 是使用微控制器内置的 SPI 外设来实现 SPI 通信的方式。与软件 SPI 相比,硬件 SPI 具有更高的性能和更低的 CPU 占用率。
硬件 SPI 的基本原理
硬件 SPI 依赖于微控制器内置的 SPI 外设,自动处理 SPI 协议的底层细节,包括:
-
生成时钟信号(SCLK)。
-
发送和接收数据(MOSI 和 MISO)。
-
处理从设备选择信号(SS)。
硬件 SPI 的代码实现
-
定义 SPI 句柄,用于管理 SPI 外设的配置和状态。
SPI_HandleTypeDef hspi;
-
HAL 库使用句柄来管理外设的实例和配置。
-
通过句柄可以方便地调用 HAL 库的 SPI 函数。
初始化 SPI
void SPI_Init(void) {
hspi.Instance = SPI1;
hspi.Init.Mode = SPI_MODE_MASTER; // 主模式
hspi.Init.Direction = SPI_DIRECTION_2LINES; // 全双工
hspi.Init.DataSize = SPI_DATASIZE_8BIT; // 数据大小为 8 位
hspi.Init.CLKPolarity = SPI_POLARITY_LOW; // 时钟极性
hspi.Init.CLKPhase = SPI_PHASE_1EDGE; // 时钟相位
hspi.Init.NSS = SPI_NSS_SOFT; // 软件控制 SS
hspi.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_256; // 波特率分频
hspi.Init.FirstBit = SPI_FIRSTBIT_MSB; // 高位先传输
hspi.Init.TIMode = SPI_TIMODE_DISABLE; // 禁用 TI 模式
hspi.Init.CRCCalculation = SPI_CRCCALCULATION_DISABLE; // 禁用 CRC
hspi.Init.CRCPolynomial = 10; // CRC 多项式
HAL_SPI_Init(&hspi); // 初始化 SPI
}
- 配置 SPI 外设的工作模式、数据格式、时钟极性和相位等参数。
-
SPI 外设需要正确的配置才能正常工作。
-
通过初始化函数设置 SPI 的工作模式和参数。
发送和接收数据
void SPI_TransmitReceive(uint8_t *txData, uint8_t *rxData, uint16_t size) {
HAL_SPI_TransmitReceive(&hspi, txData, rxData, size, 100); // 发送和接收数据
}
- 发送数据并接收从设备的响应。
-
SPI 是全双工通信协议,主设备在发送数据的同时会接收数据。
-
通过 HAL 库函数实现数据的发送和接收。
主函数中使用
int main(void) {
HAL_Init();
SystemClock_Config();
SPI_Init(); // 初始化 SPI
uint8_t txData[] = {0x01, 0x02, 0x03}; // 要发送的数据
uint8_t rxData[3]; // 接收数据的缓冲区
while (1) {
// 选择从设备
HAL_GPIO_WritePin(GPIOA, GPIO_PIN_4, GPIO_PIN_RESET); // 拉低 SS
// 发送和接收数据
SPI_TransmitReceive(txData, rxData, 3);
// 取消选择从设备
HAL_GPIO_WritePin(GPIOA, GPIO_PIN_4, GPIO_PIN_SET); // 拉高 SS
// 打印接收到的数据(通过串口或调试工具)
printf("Received: %02X %02X %02X\n", rxData[0], rxData[1], rxData[2]);
HAL_Delay(500); // 延时 500ms
}
}