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

25/1/17 嵌入式笔记 STM32F103

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

http://www.kler.cn/a/508023.html

相关文章:

  • [0242].第4-3章:SpringBoot2核心技术笔记
  • Vue2+OpenLayers实现折线绘制功能(提供Gitee源码)
  • 简历_使用优化的Redis自增ID策略生成分布式环境下全局唯一ID,用于用户上传数据的命名以及多种ID的生成
  • 云手机技术怎么实现的?
  • 闪豆多平台视频批量下载器
  • Docker PG流复制搭建实操
  • 利用.NET版Word处理控件Aspose.Words,使用 C# 在 Word 中创建图表
  • MySQL SQL优化技巧与原理
  • 【HarmonyOS之旅】基于ArkTS开发(二) -> UI开发三
  • 如何实现圆形头像效果
  • 基于TypeScript封装 `axios` 请求工具详解
  • openharmony测试子系统
  • 【React】函数组件底层渲染机制
  • 【2024年华为OD机试】 (B卷,200分)- 二叉树中序遍历(Java JS PythonC/C++)
  • GIFT ICA 下载记录
  • Flink的优化技巧
  • 力扣-数据结构-21【算法学习day.92】
  • 如何选择适合特定项目需求的人工智能学习框架?
  • python-44-嵌入式数据库SQLite和DuckDB
  • SQL-杂记1
  • C++11特性简述
  • 恒生科技OK吗?低频量化日报(明日涨停预测,指数数据,可转债上市价格预测)...
  • 2025.1.15——八、ua注入
  • SpringBoot入门实现简单增删改查
  • 基于springboot的快速集成多数据源的启动器
  • 数据结构-栈队列OJ题