news 2026/5/21 18:58:14

19.STM32串口打印_OLED显示MPU-9250九轴传感器实时数据(使用中断唤醒)

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
19.STM32串口打印_OLED显示MPU-9250九轴传感器实时数据(使用中断唤醒)

通过MPU9250中断唤醒,控制led1亮灭。
注:
1.GPIO中断输入,开始GPIO_Init未配置也能正常使用,原因为芯片上电默认就是浮空输入,刚好外部MPU9250中断配置的是推挽输出,所以可以正常使用
2.如果外部触发中断是推挽输出的,STM32侧中断输入配置模式可以为GPIO_Mode_IPD下拉输入、GPIO_Mode_IPU上拉输入、GPIO_Mode_IN_FLOATING浮空输入均可
3.如果为GPIO_Mode_IPD下拉输入,对方断电悬空时,引脚固定为低电平
4.如果为GPIO_Mode_IPU上拉输入,对方断电悬空时,引脚固定为高电平
5.如果为GPIO_Mode_IN_FLOATING浮空输入,对方断电悬空时,引脚电平随机,容易误触发中断,不推荐使用!!!

一、硬件

参考篇《STM32串口打印以及OLED显示MPU-9250九轴传感器实时数据》

  • MPU9250-INT中断管脚 ---- 接开发板PC4
  • MPU9260-INT配置为推挽输出,高电平有效,用户需要主动读取MPU9250寄存器,INT中断管脚才能被拉低。

二、寄存器配置









寄存器具体配置如下(重要):

void MPU9250_IT_CONFIG_Init(u8 threshold) { Single_Write(ACCEL_ADDRESS, PWR_MGMT_1, 0x00); //解除休眠状态,退出睡眠,MPU9250_Init中也会配置 Single_Write(ACCEL_ADDRESS, USER_CTRL, 0x00); //不使用 FIFO,不使用 DMP(数字运动处理器),后续再看这里配置会不会影响 Single_Write(ACCEL_ADDRESS, ACCEL_CONFIG_2, 0x05); //启用滤波器 Single_Write(ACCEL_ADDRESS, MOT_DETECT_CTRL, 0xC0); // 使能运动唤醒中断 Single_Write(ACCEL_ADDRESS, INT_ENABLE, 0x40); //只使能WOM运动中断,禁用溢出等中断 Single_Write(ACCEL_ADDRESS, INT_PIN_CFG, 0x10); //INT高电平有效,推挽输出,中断引脚电平保持,任意 I2C 读操作即可清除中断 //Single_Write(ACCEL_ADDRESS, INT_PIN_CFG, 0x20); //INT高电平有效,推挽输出,中断引脚电平保持,直至中断状态被清除 if((threshold > 0) && (threshold <= 0xFF)) //1~255 { Single_Write(ACCEL_ADDRESS, WOM_THR, threshold); //0-255对应INT唤醒阈值4mg-1020mg,这里配置由参数决定 } else { Single_Write(ACCEL_ADDRESS, WOM_THR, 0xFA); //0-255对应INT唤醒阈值4mg-1020mg,这里配置为1000mg = 1g } }

三、工程实现

mpu9250.h

#ifndef __MPU9250_H #define __MPU9250_H #include "system.h" //可以使用位带操作 #include "math.h" #include <stdbool.h> //使用true/false //定义MPU9250内部地址 #define ACCEL_ADDRESS 0xD0 //加速度地址 #define GYRO_ADDRESS 0xD0 //陀螺地址 //MPU9250内置磁力计(AK8963)的I2C地址通常是 0x0C(7位地址),写地址应该是 0x18(0x0C << 1)。 #define MAG_ADDRESS 0x18 //磁场地址 #define WHO_AM_I 0x75 //IIC地址寄存器(默认数值0x68,只读) #define PWR_MGMT_1 0x6B //电源管理,典型值:0x00(正常启用) #define SMPLRT_DIV 0x19 //陀螺仪采样率,典型值:0x07(125Hz) #define CONFIG 0x1A //低通滤波频率,典型值:0x06(5Hz) #define GYRO_CONFIG 0x1B //陀螺仪自检及测量范围,典型值:0x18(不自检,+-2000deg/s) #define ACCEL_CONFIG 0x1C //加速计自检、测量范围及高通滤波频率,典型值:0x01(不自检,2G,5Hz) /* 加速度XYZ三轴数据读取寄存器,每轴对应着高8位和低8位 */ #define ACCEL_XOUT_H 0x3B #define ACCEL_XOUT_L 0x3C #define ACCEL_YOUT_H 0x3D #define ACCEL_YOUT_L 0x3E #define ACCEL_ZOUT_H 0x3F #define ACCEL_ZOUT_L 0x40 /* 温度数据读取寄存器 */ #define TEMP_OUT_H 0x41 #define TEMP_OUT_L 0x42 /* 陀螺仪数据读取寄存器 */ #define GYRO_XOUT_H 0x43 #define GYRO_XOUT_L 0x44 #define GYRO_YOUT_H 0x45 #define GYRO_YOUT_L 0x46 #define GYRO_ZOUT_H 0x47 #define GYRO_ZOUT_L 0x48 /* 指南针数据读取寄存器 */ #define MAG_XOUT_L 0x03 #define MAG_XOUT_H 0x04 #define MAG_YOUT_L 0x05 #define MAG_YOUT_H 0x06 #define MAG_ZOUT_L 0x07 #define MAG_ZOUT_H 0x08 /* 中断配置相关,具体说明看C文件 */ //#define PWR_MGMT_1 0x6B //电源管理,典型值:0x00(正常启用) 上面已经定义了 #define ACCEL_CONFIG_2 0x1D #define MOT_DETECT_CTRL 0x69 #define INT_ENABLE 0x38 #define INT_PIN_CFG 0x37 #define USER_CTRL 0x6A #define WOM_THR 0x1F /*==========================更换管脚修改如下即可============================*/ /* IIC_SCL时钟端口、引脚定义 */ #define IIC_SCL_PORT GPIOB #define IIC_SCL_PIN (GPIO_Pin_10) #define IIC_SCL_PORT_RCC RCC_APB2Periph_GPIOB //为了使能端口时钟 /* IIC_SDA时钟端口、引脚定义 */ #define IIC_SDA_PORT GPIOB #define IIC_SDA_PIN (GPIO_Pin_11) #define IIC_SDA_PORT_RCC RCC_APB2Periph_GPIOB #define SCL_H GPIOB->BSRR = GPIO_Pin_10 #define SCL_L GPIOB->BRR = GPIO_Pin_10 #define SDA_H GPIOB->BSRR = GPIO_Pin_11 #define SDA_L GPIOB->BRR = GPIO_Pin_11 #define READ_SDA GPIOB->IDR & GPIO_Pin_11 #define READ_MPU9250_INT PCin(4) /*===========================================================================*/ /* 配置读9轴数据调用: 1.MPU9250_IIC_Init 2.MPU9250_CONFIG_Init 3.调用函数读取数据:READ_MPU9250_Accel/READ_MPU9250_Gyro/READ_MPU9250_Temp/READ_MPU9250_Mag */ /* 配置中断唤醒调用flow: 1.MPU9250_IIC_Init 2.MPU9250_IT_CONFIG_Init 3.MPU9250_TIM4_1MS_Init 4.MPU9250_EXIT_Init */ //COMMON公共使用 void MPU9250_IIC_Init(void); //初始化IIC的IO口 SDL SDA unsigned char Single_Read(unsigned char SlaveAddress, unsigned char REG_Address); //用于读取固定寄存器值,判断模块及I2C通信是否正常 bool Single_Write(unsigned char SlaveAddress, unsigned char REG_Address, unsigned char REG_data); //写寄存器接口 //普通读取9轴数据及温度需要调用 void MPU9250_CONFIG_Init(void); //配置MPU9250 相关寄存器,如量程等,读9轴数据时需要先配置 void READ_MPU9250_Accel(float *pA_x_g, float *pA_y_g, float *pA_z_g); void READ_MPU9250_Gyro(short *pG_x, short *pG_y, short *pG_z); void READ_MPU9250_Temp(float *pT_temp); void READ_MPU9250_Mag(short *pM_x, short *pM_y, short *pM_z); //MPU9250中断测试需要调用 void MPU9250_TIM4_1MS_Init(void); //定时器中断1MS,用于GPIO中断中计时消除抖动 void MPU9250_EXIT_Init(void); //配置GPIO中断 void MPU9250_IT_CONFIG_Init(u8 threshold); //MPU9250相关中断配置 #endif

mup9250.c

#include "mpu9250.h" #include "SysTick.h" #include "led.h" //使用led1位带操作 //不需要外部文件访问 static uint32_t time4_count = 0; static uint32_t last_count = 0; //不需要外部文件访问 static void I2C_delay(void); static void delay5ms(void); static void Delay(vu32 nCount); static void Delayms(vu32 m); static bool I2C_Start(void); static void I2C_Stop(void); static void I2C_Ack(void); static bool I2C_WaitAck(void); static void I2C_NoAck(void); static void I2C_SendByte(u8 SendByte); static unsigned char I2C_RadeByte(void); void I2C_delay(void) { u8 i = 30; //这里可以优化速度,经测试最低到5还能写入 while(i) { i--; } } void delay5ms(void) { int i = 5000; while(i) { i--; } } void Delay(vu32 nCount) { for(; nCount != 0; nCount--); } void Delayms(vu32 m) { u32 i; for(; m != 0; m--) { for(i = 0; i < 50000; i++); } } /* 加速度数据读取,读取数据位有符号浮点,单位:g */ //静态姿态由该函数读取 void READ_MPU9250_Accel(float *pA_x_g, float *pA_y_g, float *pA_z_g) { unsigned char buf[2]; short raw_x, raw_y, raw_z; const float sensitivity = 16384.0; // ±2g量程的灵敏度 buf[0] = Single_Read(ACCEL_ADDRESS, ACCEL_XOUT_L); buf[1] = Single_Read(ACCEL_ADDRESS, ACCEL_XOUT_H); raw_x = (buf[1] << 8) | buf[0]; *pA_x_g = (float)raw_x / sensitivity; buf[0] = Single_Read(ACCEL_ADDRESS, ACCEL_YOUT_L); buf[1] = Single_Read(ACCEL_ADDRESS, ACCEL_YOUT_H); raw_y = (buf[1] << 8) | buf[0]; *pA_y_g = (float)raw_y / sensitivity; buf[0] = Single_Read(ACCEL_ADDRESS, ACCEL_ZOUT_L); buf[1] = Single_Read(ACCEL_ADDRESS, ACCEL_ZOUT_H); raw_z = (buf[1] << 8) | buf[0]; *pA_z_g = (float)raw_z / sensitivity; } //读取动态角速度 //读取的RAW数据是16位有符号整型,范围:-32768 到 +32767 //输出转化为short型(后续也可以改成浮点型),+-2000 void READ_MPU9250_Gyro(short *pG_x, short *pG_y, short *pG_z) { unsigned char buf[2]; buf[0] = Single_Read(GYRO_ADDRESS, GYRO_XOUT_L); buf[1] = Single_Read(GYRO_ADDRESS, GYRO_XOUT_H); *pG_x = (buf[1] << 8) | buf[0]; *pG_x /= 16.4; buf[0] = Single_Read(GYRO_ADDRESS, GYRO_YOUT_L); buf[1] = Single_Read(GYRO_ADDRESS, GYRO_YOUT_H); *pG_y = (buf[1] << 8) | buf[0]; *pG_y /= 16.4; buf[0] = Single_Read(GYRO_ADDRESS, GYRO_ZOUT_L); buf[1] = Single_Read(GYRO_ADDRESS, GYRO_ZOUT_H); *pG_z = (buf[1] << 8) | buf[0]; *pG_z /= 16.4; } void READ_MPU9250_Temp(float *pT_temp) { unsigned char buf[2]; short raw_temp; buf[0] = Single_Read(GYRO_ADDRESS, TEMP_OUT_L); buf[1] = Single_Read(GYRO_ADDRESS, TEMP_OUT_H); raw_temp = (buf[1] << 8) | buf[0]; // 标准公式:温度(°C) = raw_temp / 333.87 + 21 *pT_temp = (float)raw_temp / 333.87f + 21.0f; } //磁轴传感器读取RAW数据,数据未处理,寄存器精度配置为14bit void READ_MPU9250_Mag(short *pM_x, short *pM_y, short *pM_z) { unsigned char buf[2]; //这里的设置和Delayms后续再优化 Single_Write(GYRO_ADDRESS, 0x37, 0x02); //turn on Bypass Mode Delayms(10); Single_Write(MAG_ADDRESS, 0x0A, 0x01); //ADC配置为14位 0.6 μT/LSB -8192 ~ +8191 (配置为16位需要写0x11) Delayms(10); buf[0] = Single_Read(MAG_ADDRESS, MAG_XOUT_L); buf[1] = Single_Read(MAG_ADDRESS, MAG_XOUT_H); *pM_x = (buf[1] << 8) | buf[0]; buf[0] = Single_Read(MAG_ADDRESS, MAG_YOUT_L); buf[1] = Single_Read(MAG_ADDRESS, MAG_YOUT_H); *pM_y = (buf[1] << 8) | buf[0]; buf[0] = Single_Read(MAG_ADDRESS, MAG_ZOUT_L); buf[1] = Single_Read(MAG_ADDRESS, MAG_ZOUT_H); *pM_z = (buf[1] << 8) | buf[0]; } void MPU9250_IIC_Init(void) //初始化IIC的IO口 { GPIO_InitTypeDef GPIO_InitStructure; RCC_APB2PeriphClockCmd(IIC_SCL_PORT_RCC|IIC_SDA_PORT_RCC, ENABLE); //使能端口时钟 GPIO_InitStructure.GPIO_Pin = IIC_SCL_PIN; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_OD; //配置为开漏输出(EEPRM配置为推挽也可以,后面再研究) GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(IIC_SCL_PORT, &GPIO_InitStructure); GPIO_InitStructure.GPIO_Pin = IIC_SDA_PIN; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_OD; //配置为开漏输出(EEPRM配置为推挽也可以,后面再研究) GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(IIC_SDA_PORT, &GPIO_InitStructure); //总线开始为空闲状态 SDA_H; SCL_H; } void MPU9250_CONFIG_Init(void) { delay_ms(10); // Single_Write(GYRO_ADDRESS,PWR_M, 0x80); // // Single_Write(GYRO_ADDRESS,SMPL, 0x07); // // Single_Write(GYRO_ADDRESS,DLPF, 0x1E); //±2000° // Single_Write(GYRO_ADDRESS,INT_C, 0x00 ); // // Single_Write(GYRO_ADDRESS,PWR_M, 0x00); // Single_Write(GYRO_ADDRESS, PWR_MGMT_1, 0x00); //解除休眠状态 Single_Write(GYRO_ADDRESS, SMPLRT_DIV, 0x07); Single_Write(GYRO_ADDRESS, CONFIG, 0x06); Single_Write(GYRO_ADDRESS, GYRO_CONFIG, 0x18); //配置的量程为±2000 dps(度/秒) ±2000 dps:16.4 LSB/(°/s) Single_Write(GYRO_ADDRESS, ACCEL_CONFIG, 0x01); //配置加速度量程为±2g // Single_Write(GYRO_ADDRESS,0x6A,0x00); //close Master Mode } /***************************************************************************/ //1MS产生一次中断 void MPU9250_TIM4_1MS_Init(void) { TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure; NVIC_InitTypeDef NVIC_InitStructure; //TIM4时钟使能 RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM4, ENABLE); //初始化NVIC,这里未配置中断优先级 NVIC_InitStructure.NVIC_IRQChannel = TIM4_IRQn; NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; NVIC_Init(&NVIC_InitStructure); TIM_TimeBaseInitStructure.TIM_Period = 1000-1; //1ms TIM_TimeBaseInitStructure.TIM_Prescaler = 72-1; //72分频 = 1MHz TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up; TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1; TIM_TimeBaseInit(TIM4, &TIM_TimeBaseInitStructure); //开启定时器中断,即使用定时器4的更新中断作为NVIC的中断源,如计数器到达设定自动重装载寄存器值时,触发更新中断 TIM_ITConfig(TIM4, TIM_IT_Update, ENABLE); TIM_Cmd(TIM4, ENABLE); } //定时器中断服务函数:只做一件事,计数器加一 void TIM4_IRQHandler(void) { if(TIM_GetITStatus(TIM4,TIM_IT_Update) == SET) //溢出中断 { time4_count++; } TIM_ClearITPendingBit(TIM4,TIM_IT_Update); //清除中断标志位 } /***************************************************************************/ void MPU9250_EXIT_Init(void) { GPIO_InitTypeDef MY_GPIO_Init; EXTI_InitTypeDef EXTI_InitStructure; NVIC_InitTypeDef NVIC_InitStructure; RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC, ENABLE); //在LED初始化中已经使能过了,重复使能不影响 RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE); //开启GPIO复用功能时钟 MY_GPIO_Init.GPIO_Pin = GPIO_Pin_4; MY_GPIO_Init.GPIO_Mode = GPIO_Mode_IPD; //设置为输入下拉,GPIO_Mode_IN_FLOATING浮空输入也可以,不推荐!!! MY_GPIO_Init.GPIO_Speed = GPIO_Speed_50MHz; /* 1.开始GPIO_Init未配置也能正常使用,原因为芯片上电默认就是浮空输入,刚好外部MPU9250中断配置的是推挽输出,所以可以正常使用 2.如果外部触发中断是推挽输出的,STM32侧中断输入配置模式可以为GPIO_Mode_IPD下拉输入、GPIO_Mode_IPU上拉输入、GPIO_Mode_IN_FLOATING浮空输入均可 3.如果为GPIO_Mode_IPD下拉输入,对方断电悬空时,引脚固定为低电平 4.如果为GPIO_Mode_IPU上拉输入,对方断电悬空时,引脚固定为高电平 5.如果为GPIO_Mode_IN_FLOATING浮空输入,对方断电悬空时,引脚电平随机,容易误触发,不推荐!!! */ GPIO_Init(GPIOC, &MY_GPIO_Init); GPIO_EXTILineConfig(GPIO_PortSourceGPIOC, GPIO_PinSource4); //选择GPIO管脚作为外部中断线路 EXTI_InitStructure.EXTI_Line = EXTI_Line4; EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt; //mode为外部中断 EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Rising; //选择上升边沿触发 EXTI_InitStructure.EXTI_LineCmd = ENABLE; //使能 EXTI_Init(&EXTI_InitStructure); NVIC_InitStructure.NVIC_IRQChannel = EXTI4_IRQn; NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 2; //抢占优先级设为2 NVIC_InitStructure.NVIC_IRQChannelSubPriority = 3; //响应优先级设为3 NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; NVIC_Init(&NVIC_InitStructure); } void EXTI4_IRQHandler(void) { uint32_t now = 0; if(EXTI_GetITStatus(EXTI_Line4) == 1) { now = time4_count; if((now - last_count) < 80) //消除抖动,延时80ms { EXTI_ClearITPendingBit(EXTI_Line4); return; } last_count = now; if(READ_MPU9250_INT == 1) //再次判断中断有没有被拉起来 { printf("1.READ_MPU9250_INT = %d\r\n", READ_MPU9250_INT); Single_Read(ACCEL_ADDRESS, WHO_AM_I); //读MPU9250任意寄存器,即可清除中断 //delay_ms(5); //实际测试不需要延时,只要Single_Read后,INT管脚立即被拉低 printf("2.READ_MPU9250_INT = %d\r\n", READ_MPU9250_INT); led1 = ~led1; } } EXTI_ClearITPendingBit(EXTI_Line4); } void MPU9250_IT_CONFIG_Init(u8 threshold) { Single_Write(ACCEL_ADDRESS, PWR_MGMT_1, 0x00); //解除休眠状态,退出睡眠,MPU9250_Init中也会配置 Single_Write(ACCEL_ADDRESS, USER_CTRL, 0x00); //不使用 FIFO,不使用 DMP(数字运动处理器),后续再看这里配置会不会影响 Single_Write(ACCEL_ADDRESS, ACCEL_CONFIG_2, 0x05); //启用滤波器 Single_Write(ACCEL_ADDRESS, MOT_DETECT_CTRL, 0xC0); // 使能运动唤醒中断 Single_Write(ACCEL_ADDRESS, INT_ENABLE, 0x40); //只使能WOM运动中断,禁用溢出等中断 Single_Write(ACCEL_ADDRESS, INT_PIN_CFG, 0x10); //INT高电平有效,推挽输出,中断引脚电平保持,任意 I2C 读操作即可清除中断 //Single_Write(ACCEL_ADDRESS, INT_PIN_CFG, 0x20); //INT高电平有效,推挽输出,中断引脚电平保持,直至中断状态被清除 if((threshold > 0) && (threshold <= 0xFF)) //1~255 { Single_Write(ACCEL_ADDRESS, WOM_THR, threshold); //0-255对应INT唤醒阈值4mg-1020mg,这里配置由参数决定 } else { Single_Write(ACCEL_ADDRESS, WOM_THR, 0xFA); //0-255对应INT唤醒阈值4mg-1020mg,这里配置为1000mg = 1g } } /******************************************************************************* * Function Name : I2C_Start * Description : Master Start Simulation IIC Communication * Input : None * Output : None * Return : Wheather Start ****************************************************************************** */ bool I2C_Start(void) { SDA_H; SCL_H; I2C_delay(); if(!READ_SDA) { return false; //SDA线为低电平则总线忙,退出 } SDA_L; I2C_delay(); if(READ_SDA) { return false; //SDA线为高电平则总线出错,退出 } SDA_L; I2C_delay(); return true; } /******************************************************************************* * Function Name : I2C_Stop * Description : Master Stop Simulation IIC Communication * Input : None * Output : None * Return : None ****************************************************************************** */ void I2C_Stop(void) { SCL_L; I2C_delay(); SDA_L; I2C_delay(); SCL_H; I2C_delay(); SDA_H; I2C_delay(); } /******************************************************************************* * Function Name : I2C_Ack * Description : Master Send Acknowledge Single * Input : None * Output : None * Return : None ****************************************************************************** */ void I2C_Ack(void) { SCL_L; I2C_delay(); SDA_L; I2C_delay(); SCL_H; I2C_delay(); SCL_L; I2C_delay(); } /******************************************************************************* * Function Name : I2C_NoAck * Description : Master Send No Acknowledge Single * Input : None * Output : None * Return : None ****************************************************************************** */ void I2C_NoAck(void) { SCL_L; I2C_delay(); SDA_H; I2C_delay(); SCL_H; I2C_delay(); SCL_L; I2C_delay(); } /******************************************************************************* * Function Name : I2C_WaitAck * Description : Master Reserive Slave Acknowledge Single * Input : None * Output : None * Return : Wheather Reserive Slave Acknowledge Single ****************************************************************************** */ bool I2C_WaitAck(void) //返回为:=1有ACK,=0无ACK { SCL_L; I2C_delay(); SDA_H; I2C_delay(); SCL_H; I2C_delay(); if(READ_SDA) { SCL_L; I2C_delay(); return false; } SCL_L; I2C_delay(); return true; } /******************************************************************************* * Function Name : I2C_SendByte * Description : Master Send a Byte to Slave * Input : Will Send Date * Output : None * Return : None ****************************************************************************** */ void I2C_SendByte(u8 SendByte) //数据从高位到低位 { u8 i = 8; while(i--) { SCL_L; I2C_delay(); if(SendByte & 0x80) { SDA_H; } else { SDA_L; } SendByte <<= 1; I2C_delay(); SCL_H; I2C_delay(); } SCL_L; } /******************************************************************************* * Function Name : I2C_RadeByte * Description : Master Reserive a Byte From Slave * Input : None * Output : None * Return : Date From Slave ****************************************************************************** */ unsigned char I2C_RadeByte(void) //数据从高位到低位 { u8 i = 8; u8 ReceiveByte = 0; SDA_H; while(i--) { ReceiveByte <<= 1; SCL_L; I2C_delay(); SCL_H; I2C_delay(); if(READ_SDA) { ReceiveByte |= 0x01; } } SCL_L; return ReceiveByte; } //单字节写入 bool Single_Write(unsigned char SlaveAddress, unsigned char REG_Address, unsigned char REG_data) { if(!I2C_Start()) { return false; } I2C_SendByte(SlaveAddress); //发送设备地址+写信号 //I2C_SendByte(((REG_Address & 0x0700) >>7) | SlaveAddress & 0xFFFE);//设置高起始地址+器件地址 if(!I2C_WaitAck()) { I2C_Stop(); return false; } I2C_SendByte(REG_Address); //设置低起始地址 I2C_WaitAck(); I2C_SendByte(REG_data); I2C_WaitAck(); I2C_Stop(); delay5ms(); return true; } //单字节读取 unsigned char Single_Read(unsigned char SlaveAddress, unsigned char REG_Address) { unsigned char REG_data; if(!I2C_Start()) { return false; } I2C_SendByte(SlaveAddress); //I2C_SendByte(((REG_Address & 0x0700) >>7) | REG_Address & 0xFFFE); //设置高起始地址+器件地址 if(!I2C_WaitAck()) { I2C_Stop(); return false; } I2C_SendByte((u8)REG_Address); //设置低起始地址 I2C_WaitAck(); I2C_Start(); I2C_SendByte(SlaveAddress + 1); I2C_WaitAck(); REG_data = I2C_RadeByte(); I2C_NoAck(); I2C_Stop(); return REG_data; }

main.c

#include "system.h" //已经包含了"stm32f10x.h" 以及定义了位带操作的宏,后续只包含该头文件就可以了 #include "SysTick.h" //使用delay_ms和delay_us #include "mpu9250.h" //包含了math.h #include "oled.h" //oled显示 #include "bmp.h" //存储要显示的图片数据 #include "usart.h" //使用printf #include "led.h" //使用led1 //同时只能使能一个 #define TEST_Accel 0 //测试加速度 #define TEST_Gyro_Temp 0 //测试陀螺仪动态角速度和温度 #define TEST_Mag 0 //测试磁场传感器(指南针) #define TEST_INT 1 //测试中断触发 // 直接调用这个函数,返回0-360度指南针效果 float GetCompassAngle(short mx, short my, short mz) { float angle; angle = atan2((float)my, (float)mx) * 180.0 / 3.14159; if(angle < 0) angle += 360; return angle; } int main() { float threshold = 1.0; float data_x = 0, data_y = 0, data_z = 0, data_temp = 0; short gyro_x, gyro_y, gyro_z, mag_x, mag_y, mag_z; float gyro_pitch, gyro_roll; u8 id = 0; SysTick_Init(72); //72为SYSCLK delay_ms延时函数需要使用 NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); //设置中断分组2:2 在使用中断回调函数时都需要调用, 这里虽然没有使用中断,但还是留着 USART1_Init(9600); OLED_Init(); OLED_ColorTurn(0); //0正常显示,1 反色显示 OLED_DisplayTurn(0); //0正常显示 1 屏幕翻转显示 #if (TEST_INT == 1) Led_Init(); MPU9250_IIC_Init(); MPU9250_IT_CONFIG_Init(128); //设置触发阈值128 * 4 = 512mg加速度触发 效果最好 //MPU9250_IT_CONFIG_Init(16); //设置触发阈值16 * 4 = 64mg加速度触发 拍桌子可触发 MPU9250_TIM4_1MS_Init(); MPU9250_EXIT_Init(); #else MPU9250_IIC_Init(); MPU9250_CONFIG_Init(); //测试中断,不需要初始化MPU9250 #endif id = Single_Read(ACCEL_ADDRESS, WHO_AM_I); //读到 0x71 = 通信成功 printf("MPU9250 ID = 0x%02X \r\n", id); if(id != 0x71) { printf("MPU9250 I2C failed!\r\n"); } else { printf("MPU9250 ready.\r\n"); } while(1) { #if TEST_INT //while循环,什么也不干 #endif //1.测试三轴加速度,单位g,量程+-2g //2.俯仰角,横滚角都是通过三轴加速度得到的 #if TEST_Accel READ_MPU9250_Accel(&data_x, &data_y, &data_z); /* 如果需要检测碰撞加速度,把这3行屏蔽即可 */ gyro_pitch = atan2(-data_x, sqrt(data_y*data_y + data_z*data_z)) * 180 / 3.14159; //俯仰角+-90 gyro_roll = atan2(data_y, data_z) * 180 / 3.14159; //横滚角+-180 printf("gyro_pitch=%.2f, gyro_roll=%.2f\r\n", gyro_pitch, gyro_roll); // 检测是否有超过阈值的加速度变化 if(abs(data_x) > threshold || abs(data_y) > threshold || abs(data_z) > threshold) { printf("Knock detected! Accel: X=%.2f, Y=%.2f, Z=%.2f\r\n", data_x, data_y, data_z); // 在OLED上显示敲击提示 OLED_ShowString(0,0,"Knock!",16,1); OLED_Refresh(); delay_ms(200); OLED_Clear(); } else { // 正常显示加速度值 OLED_ShowString(0,0,"Unit(+-2g):g",16,1); OLED_ShowString(0,16,"X-Val:",16,1); OLED_ShowString(0,32,"Y-Val:",16,1); OLED_ShowString(0,48,"Z-Val:",16,1); OLED_ShowFloat(60,16,data_x,1,3,16,1); OLED_ShowFloat(60,32,data_y,1,3,16,1); OLED_ShowFloat(60,48,data_z,1,3,16,1); OLED_Refresh(); } delay_ms(50); // 敲击检测用50ms足够 OLED_Clear(); #endif //1.测试陀螺仪及温度(这里只能测试角速度,不能检测静态姿态!!!) //2.这里温度测试正常(浮点) #if TEST_Gyro_Temp READ_MPU9250_Gyro(&gyro_x, &gyro_y, &gyro_z); //这里读取的是+-2000,但是有静态漂移,实际应用中需要减去静态漂移 READ_MPU9250_Temp(&data_temp); // 串口输出 printf("Gyro/Temp: X=%d Y=%d Z=%d Temp=%f\r\n", gyro_x, gyro_y, gyro_z, data_temp); // OLED显示 OLED_ShowString(0,0,"Temp:",16,1); OLED_ShowString(0,16,"X:",16,1); OLED_ShowString(0,32,"Y:",16,1); OLED_ShowString(0,48,"Z:",16,1); OLED_ShowFloat(60,0,data_temp,2,3,16,1); OLED_ShowIntNum(60,16,gyro_x,5,16,1); OLED_ShowIntNum(60,32,gyro_y,5,16,1); OLED_ShowIntNum(60,48,gyro_z,5,16,1); OLED_Refresh(); delay_ms(200); OLED_Clear(); #endif //测试磁轴传感器,显示指南针效果 #if TEST_Mag READ_MPU9250_Mag(&mag_x, &mag_y, &mag_z); // OLED显示RAW值 OLED_ShowString(0,0,"Mag(s16)",16,1); OLED_ShowString(0,16,"X:",16,1); OLED_ShowString(0,32,"Y:",16,1); OLED_ShowString(0,48,"Z:",16,1); OLED_ShowIntNum(60,16,mag_x,5,16,1); OLED_ShowIntNum(60,32,mag_y,5,16,1); OLED_ShowIntNum(60,48,mag_z,5,16,1); printf("角度: %.0f°\r\n", GetCompassAngle(mag_x, mag_y, mag_z)); //串口显示指南针效果 OLED_Refresh(); delay_ms(200); OLED_Clear(); #endif } }

四、效果演示

开机后,触碰唤醒触发INT前led1灭:

触发后打开led1:

再次触发关闭led1:

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/5/17 10:34:26

避坑指南:在 Windows 上用 Python Bleak 连接 BLE 设备时,你可能会遇到的 3 个典型问题及解决方案

Windows平台Python Bleak库连接BLE设备的三大疑难解析与实战解决方案 当你在Windows系统上尝试用Python的Bleak库连接低功耗蓝牙(BLE)设备时&#xff0c;可能会遇到各种看似简单却令人抓狂的问题。这些问题往往不会出现在基础教程里&#xff0c;却能让一个功能完整的项目陷入停…

作者头像 李华
网站建设 2026/5/17 10:34:04

5分钟掌握Unlock-Music:打破音乐平台格式限制的终极解决方案

5分钟掌握Unlock-Music&#xff1a;打破音乐平台格式限制的终极解决方案 【免费下载链接】unlock-music 在浏览器中解锁加密的音乐文件。原仓库&#xff1a; 1. https://github.com/unlock-music/unlock-music &#xff1b;2. https://git.unlock-music.dev/um/web 项目地址:…

作者头像 李华
网站建设 2026/5/17 10:33:54

KMS智能激活架构设计:Windows与Office批量授权的完整实现方案

KMS智能激活架构设计&#xff1a;Windows与Office批量授权的完整实现方案 【免费下载链接】KMS_VL_ALL_AIO Smart Activation Script 项目地址: https://gitcode.com/gh_mirrors/km/KMS_VL_ALL_AIO KMS_VL_ALL_AIO智能激活脚本为技术用户提供了完整的Windows和Office批量…

作者头像 李华
网站建设 2026/5/17 10:33:38

3分钟免费绕过iPhone激活锁:applera1n工具完整使用教程

3分钟免费绕过iPhone激活锁&#xff1a;applera1n工具完整使用教程 【免费下载链接】applera1n icloud bypass for ios 15-16 项目地址: https://gitcode.com/gh_mirrors/ap/applera1n 当您购买二手iPhone却发现设备被激活锁锁定&#xff1f;忘记Apple ID密码无法正常使…

作者头像 李华
网站建设 2026/5/17 10:28:11

如何永久保存微信聊天记录?WeChatMsg本地备份完整解决方案

如何永久保存微信聊天记录&#xff1f;WeChatMsg本地备份完整解决方案 【免费下载链接】WeChatMsg 提取微信聊天记录&#xff0c;将其导出成HTML、Word、CSV文档永久保存&#xff0c;对聊天记录进行分析生成年度聊天报告 项目地址: https://gitcode.com/GitHub_Trending/we/W…

作者头像 李华