25/1/17 嵌入式笔记 STM32F103

embedded/2025/1/18 20:48:39/

#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;// 唤醒 MPU6050HAL_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();  // 初始化 I2Cint16_t AccX, AccY, AccZ, GyroX, GyroY, GyroZ;MPU6050_Init(&hi2c1);  // 初始化 MPU6050while (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;// 唤醒 MPU6050HAL_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();  // 初始化 I2Cint16_t AccX, AccY, AccZ, GyroX, GyroY, GyroZ;MPU6050_Init(&hi2c1);  // 初始化 MPU6050while (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 = 1HAL_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++) {// 设置 MOSIHAL_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// 读取 MISOreceivedData <<= 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 = 1return receivedData;
}
  • SPI 是全双工通信协议,每次传输一个字节时,主设备会同时发送和接收数据。

  • 通过逐位操作模拟 SPI 的时钟和数据传输。

  • 选择从设备:拉低 SS 引脚,选择从设备。

  • 逐位发送和接收

    • 设置 MOSI 引脚的值(根据数据的最高位)。

    • 产生时钟上升沿,从设备在此时采样 MOSI 数据。

    • 读取 MISO 引脚的值,从设备在此时发送数据。

    • 产生时钟下降沿,完成一位数据的传输。

  • 取消选择从设备:拉高 SS 引脚,结束通信。

主函数中使用
int main(void) {HAL_Init();SystemClock_Config();SPI_Init();  // 初始化 SPIuint8_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;              // 软件控制 SShspi.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_256; // 波特率分频hspi.Init.FirstBit = SPI_FIRSTBIT_MSB;     // 高位先传输hspi.Init.TIMode = SPI_TIMODE_DISABLE;     // 禁用 TI 模式hspi.Init.CRCCalculation = SPI_CRCCALCULATION_DISABLE; // 禁用 CRChspi.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();  // 初始化 SPIuint8_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}
}

http://www.ppmy.cn/embedded/155037.html

相关文章

ubuntu20.04 docker安装

Ubuntu | Docker DocsPost-installation steps | Docker Docs # 创建目录 sudo mkdir -p /etc/docker # 写入配置文件 sudo tee /etc/docker/daemon.json <<-EOF { "registry-mirrors": [ "https://docker-0.unsee.tech", &qu…

下载文件,浏览器阻止不安全下载

背景&#xff1a; 在项目开发中&#xff0c;遇到需要下载文件的情况&#xff0c;文件类型可能是图片、excell表、pdf、zip等文件类型&#xff0c;但浏览器会阻止不安全的下载链接。 效果展示&#xff1a; 下载文件的两种方式&#xff1a; 一、根据接口的相对url&#xff0c;拼…

某讯一面,感觉问Redis的难度不是很大

前不久&#xff0c;有位朋友去某讯面试&#xff0c;他说被问到了很多关于 Redis 的问题&#xff0c;比如为什么用 Redis 作为 MySQL 的缓存&#xff1f;Redis 中大量 key 集中过期怎么办&#xff1f;如何保证缓存和数据库数据的一致性&#xff1f;我将它们整理出来&#xff0c;…

HBase深度历险

作者&#xff1a;京东物流 于建飞 简介 HBase 的全称是 Hadoop Database&#xff0c;是一个分布式的&#xff0c;可扩展&#xff0c;面向列簇的数据库&#xff0c;是一个通过大量廉价的机器解决海量数据的高速存储和读取的分布式数据库解决方案。本文会像剥洋葱一样&#xff0c…

如何使用Python将长图片分隔为若干张小图片

如何使用Python将长图片分隔为若干张小图片 1. Python需求的任务2. Python代码的实现3. 代码修改的位置4. 运行结果5. 注意事项6. 其他文章链接快来试试吧&#x1f60a; 1. Python需求的任务 _ 使用Python将长图片分隔为若干张小图片 我有如下的一张长图片 想要将其分割为若…

C#里await Task.Run死锁的分析与解决

一段如下的代码,它是必然死锁的: private void button1_Click(object sender, EventArgs e){Task<string> res = GetResAsync();textBox1.Text = res.Result;}private async Task<string> GetResAsync(){string t = await Task.Run(() => {Thread.Sleep(10…

前缀和 (一维 二维)

前缀和作用&#xff1a; 快速求出原数组中一段数组的和 思路 1.预处理前缀和数组 2.用公式求区间和 公式&#xff1a; 二维前缀和&#xff1a; s [ i ] [ j ] s[ i - 1 ] [ j ] s[ i ] [ j - 1 ] - s [ i - 1 ] [ j - 1]; 题型 一维 二维 题解 一维 #include <iost…

当前目录不是一个git仓库/远程仓库已经有了一些你本地没有的更改

目录 问题1&#xff1a;问题2&#xff1a;解决1解决2 问题1&#xff1a; fatal: not a git repository (or any parent up to mount point /) Stopping at filesystem boundary (GIT_DISCOVERY_ACROSS_FILESYSTEM not set). # 初始化 Git 仓库 git init需要到本地目录下先添加…