S1-04 OLED和MPU6050

使用U8G2操作OLED

什么是U8G2

u8g2是一个基于monochrome lcd的嵌入式图形库。它可以通过多种接口与mcu交互,支持i2c、spi和并行模式等多种接口,以及若干种字体和绘图函数,可用于创建各种ui界面、菜单、文本等。其具有轻量级,灵活性高,支持多种格式的字体及占用空间小等优点,因此在很多嵌入式产品中得到了广泛的应用。

OLED12860

OLED12864是一种工业级的点阵屏,分辨率是128×64像素,通常使用IIC、串口、SPI接口进行控制。它具有高对比度、宽视角和低功耗等特点,使其在诸如机器人、测量仪器、移动终端等领域得到了广泛应用。它可以显示图像、文字和图形等信息,具有丰富的控制选项和灵活的使用方式,是一款非常实用的显示设备。

IIC通讯

IIC通讯协议(inter-integrated circuit)也叫I2C或I²C协议,是一种串行通信协议。使用该协议可以将一个主设备(即控制器)和多个从设备(即被控制器)连接到一个总线上进行数据传输。IIC通讯协议以同步方式传输数据,最常用的是两根线:SCL(时钟线)和SDA(数据线)。SCL由主设备输出,控制整个传输过程的时序;SDA则用来传输数据。在IIC通讯中,每个从设备都有一个唯一的地址,通过地址来识别从设备并进行通信。通信过程中还包括了读写操作、应答检测等基本动作。IIC通讯协议具有传输速率较慢、可靠性强、接口简单等特点,广泛应用于嵌入式系统、数字信号处理器、LCD驱动器等领域。

ESP32-S3利用GPIO矩阵可以将IIC的SCL和SDA映射到任意引脚上,也就意味着,我们所有的IIC都将采用软件IIC(有部分ESP32和其他芯片中采用的是硬件IIC,有固定的引脚,软IIC和硬IIC通讯原理是一样的,在ESP32系列芯片中不存在快慢问题)。

例程中,我们使用16号引脚充当SCL线,17号引脚充当SDA线,VCC连接开发板的VCC5或者VCC3.3(不同OLED电压不同),GND与开发板GND相连。

原理图

需要补全

我们选购的这款OLED的驱动芯片是SSD1306,大小是128×64,采用软I2C协议,所以屏幕初始化代码是:

U8G2_SSD1306_128X64_NONAME_F_SW_I2C u8g2(U8G2_R0, /*clock=*/SCL, /*data=*/SDA, /*reset=*/U8X8_PIN_NONE);

我中第一个参数是屏幕的旋转方向,第二个参数是SCL引脚编号,第三个参数是SDA引脚编号。
如果使用ESP32其他带有硬件IIC的开发板,可以使用。
我们用的是SH1106模块

U8G2_SSD1306_128X64_NONAME_F_FW_I2C u8g2(U8G2_R0, /*reset=*/U8X8_PIN_NONE);

初始化代码只需要 u8g2.begin(); 一行即可,在模拟器上运行较慢,但在实体开发板中速度非常快。

如果想在模拟器使用U8G2,则需要再模拟器 Library Manager中添加U8G2的类库。

代码共享位置:https://wokwi.com/projects/362501497983009793

#include <U8g2lib.h>
#include <Wire.h>
#define SCL 16
#define SDA 17
U8G2_SSD1306_128X64_NONAME_F_SW_I2C u8g2(U8G2_R0, /*clock=*/SCL, /*data=*/SDA, /*reset=*/U8X8_PIN_NONE);
// LED显示任务
void led_task(void *param_t){
  for(;;){
    u8g2.clearBuffer();         // 清理缓冲区
    u8g2.setFont(u8g2_font_ncenB08_tr); // 设置字体
    u8g2.drawStr(30, 10, "Hello ESP32");  // 打印文字
    u8g2.drawRFrame(5,20,118,44,5);
    u8g2.sendBuffer();          // 显示内容
    delay(100);
  }
}
void setup() {
  Serial.begin(115200);
  Serial.println("Hello, ESP32-S3!");
  u8g2.begin();
  printf("初始化完毕\n");
  xTaskCreate(led_task, "LED-DISP", 1024*4, NULL, 1, NULL);
  // xTaskCreatePinnedToCore(led_task, "LED-DISP", 1024*4, NULL, 1, NULL, 1);
}
void loop() {
  delay(100);
}

U8G2_SSD1306_128X64_NONAME_F_SW_I2C 是用软件IIC方式设置SSD1306芯片OLED,如果使用其他芯片或者是硬件IIC,可以选择其他初始化方式,具体可以参照 U8G的相关文档 U8G2官方文档

u8g2.begin(); 用于初始化U8G2,在初始化过程中需要等待一段时间(实际硬件中不会太长,模拟器性能不行,所以要等很长时间),在初始化未完成之前不要对U8G2进行任何操作。

u8g2.setFont(u8g2_font_ncenB08_tr); 用于设置字体,例程中把设置字体放在大循环中了,其实完全没有必要,设置一次即可,常用的字体如下:

  • u8g2_font_unifont_t_chinese2:一种支持 UTF-8 编码的中文、日文、韩文等字符集的字体。
  • u8g2_font_timB08_tf:8x8 像素的 Times New Roman 标准字体。
  • u8g2_font_ncenB08_tf:8x8 像素的 New Century Schoolbook 标准字体。
  • u8g2_font_profont10_tf:10x6 像素的 ProFont 字体。
  • u8g2_font_6x10_mf:6x10 像素的固定宽度字体。
  • u8g2_font_7x14_mr:7x14 像素的 Monospace Regular 字体。

u8g2.clearBuffer(); 用于清空缓冲区,这里不会对硬件进行操作,缓冲区只是放在内存中的一小块区域,缓冲区的内容最后要通过 u8g2.sendBuffer(); 方式发送到硬件中,这里才对硬件进行操作。
其他的绘制方法可以参考U8G2 API文档
OLED上使用U8g2中文库的指南

在多线程中使用U8G2

代码共享位置:https://wokwi.com/projects/362501497983009793

#include <U8g2lib.h>
#include <Wire.h>
#define SCL 16
#define SDA 17
U8G2_SSD1306_128X64_NONAME_F_SW_I2C u8g2(U8G2_R0, /*clock=*/SCL, /*data=*/SDA, /*reset=*/U8X8_PIN_NONE);
// LED显示任务
void led_task(void *param_t){
  for(;;){
    u8g2.clearBuffer();         // 清理缓冲区
    u8g2.setFont(u8g2_font_ncenB08_tr); // 设置字体
    u8g2.drawStr(30, 10, "Hello ESP32");  // 打印文字
    u8g2.drawRFrame(5,20,118,44,5);
    u8g2.sendBuffer();          // 显示内容
    delay(100);
  }
}
void setup() {
  Serial.begin(115200);
  Serial.println("Hello, ESP32-S3!");
  u8g2.begin();
  printf("初始化完毕\n");
  // xTaskCreate(led_task, "LED-DISP", 1024*4, NULL, 1, NULL);
  xTaskCreatePinnedToCore(led_task, "LED-DISP", 1024*4, NULL, 1, NULL, 1);
}
void loop() {
  delay(100);
}

在单任务中使用U8G2对OLE进行操作时候,需要注意的是 u8g2.begin 要和使用的其他函数在同意核心中执行。
在ESP32-S3的Arduino环境中,SuperLoop 默认是在核心1中启动的,但如果我们通过 xTaskCreate 创建任务,那新创建的任务运行在哪个CPU是随机的(或是由系统机制决定的),这样做有一半的几率可能会导致OLED无法显示。
所以我们正确的做法是使用 xTaskCreatePinnedToCore 指定OLED的任务必须在核心1上运行。

多任务同时操作OLED

涉及到多任务同时操作OLED(或其他设备)时,一定要对这个设备的所有操作加锁。
但更好的做法是只有一个线程来控制设备,其他线程如果需要使用设备时,通过消息队列、邮件等方式间接操作设备,这个后续的课程中我们会讲到。

MPU6050

MPU6050 是一款集成了三轴陀螺仪和三轴加速度计的数字运动处理芯片,由英国飞思卡尔公司研制。它能够通过测量物体在空间中的角度和加速度,实现姿态检测、运动追踪、手势识别等功能。同时,MPU6050 还集成了数模转换器(ADC)和数字信号处理器(DSP),能够将模拟信号转换为数字信号,并进行滤波、数据处理等操作,便于嵌入式系统与其通信。
MPU6050 功能强大,精度高,常被用于无人机、智能手环、智能车、电子游戏、运动健康等领域,是一款十分优秀的传感器芯片。
替换产品还有个经度更高的MPU9250,MPU9250 是一款集成了三轴陀螺仪、三轴加速度计和三轴磁力计于一体的九轴传感器芯片,由英伟达子公司 InvenSense 公司生产。与 MPU6050 相比,MPU9250 采用更先进的 MEMS 技术和信号处理算法,能够实现更高精度的姿态检测和运动跟踪,并且支持更多的数据输出格式和接口类型(如 SPI、I2C和UART等)。此外,MPU9250 还支持外部磁力计和增量式编码器等传感器的接入,为一些特定应用场景提供了更强的扩展性和可靠性。
如果我们需要用到指南针的场景,可以选择MPU9250。
本利中,我们只讲解MPU6050的使用方式,关于MPU9250的应用,大家可以参考MPU9250项目,其中有Arduino和ESP32操作的详细案例。

MPU6050接入方式

我们采用的MPU6050使用IIC接口接入到ESP32上,本次例程中我们使用的是MPU6050的模块,是已经封装好的,但在实际开发中我们用到模块的几率比较小(除非是为了偷懒),正常如果不出意外的话,肯定是要将6050的芯片集成到我们自己画的主板上。
MPU6050的电路图非常简单, 除了自身以外还有一些旁路电容和上拉电阻,对于第三方芯片或模块的使用,我们不用背电路图,在参数文档中全都有,只要照抄就行了,以后我们所有应用到第三方芯片和模块,都去找Datasheet或者是技术参考手册,其中百分百会有原理图,直接腾挪过来即可,这是硬件工程师最正确的做法。切忌:自己装逼只会带来灾难。

插入MPU6050的电路图

对于MPU6050的模块应用就比较简单了,SCL和SDL接入单片机的任意引脚,VCC接入3.3V(数据文档中有,不是所有的期间都是3.3V的),GND共线,因为SCL和SDL属于数据引脚,我们不用三极管做隔离。
插入MPU6050的电路图

代码共享位置:https://wokwi.com/projects/362759032395062273

#include <Adafruit_MPU6050.h>
#include <Adafruit_Sensor.h>
#include <Wire.h>
#define SCL 16
#define SDA 17
Adafruit_MPU6050 mpu;   // 声明设备
volatile bool initialized = false;
void setup() {
  Serial.begin(115200);
  Wire.begin(SDA, SCL);   // 初始化I2C总线
  initialized = mpu.begin();
  if (initialized) {      // 初始化陀螺仪
    Serial.println("MPU6050 初始化成功");
    mpu.setAccelerometerRange(MPU6050_RANGE_8_G);     // 设置重力加速度的测量范围为±8G
    mpu.setGyroRange(MPU6050_RANGE_500_DEG);          // 设置陀螺仪的测量范围为±500度/秒
    mpu.setFilterBandwidth(MPU6050_BAND_21_HZ);       // 设置滤波器带宽
  } else {
    Serial.println("MPU6050 初始化失败");
  }
}
void loop() {
  sensors_event_t accel;
  sensors_event_t gyro;
  sensors_event_t temp;
  while(initialized){
    mpu.getEvent(&accel, &gyro, &temp);
    printf("-----===== MPU6050 =====-----\n");
    printf("Temp   : %.2f°C\n",temp.temperature);
    printf("Gyro-X : %.2f°\n",gyro.gyro.x *180 / PI);
    printf("Gyro-Y : %.2f°\n",gyro.gyro.y *180 / PI);
    printf("Gyro-Z : %.2f°\n",gyro.gyro.z *180 / PI);
    printf("Acce-X : %.2f g\n",accel.acceleration.x / 9.81);
    printf("Acce-Y : %.2f g\n",accel.acceleration.y / 9.81);
    printf("Acce-Z : %.2f g\n",accel.acceleration.z / 9.81);
    printf("==============================\n");
    vTaskDelay(pdMS_TO_TICKS(1000));
  }
  while(1){vTaskDelay(pdMS_TO_TICKS(100));}
}

同样的原因,因为ESP32-S3使用的是GPIO矩阵,所以我们任意Pin都可以用作IIC,这里我们依然选择使用16作为SCL,17作为SDL。
MPU6050配置之前,先使用 Wire.begin(SDA, SCL) 对IIC进行初始化,在其他的IIC操作中,如果使用的是Wire操作,都可以使用这个方法对软件IIC进行设置。
IIC总线初始化完毕后,即可通过 mpu.begin() 对MPU6050进行初始化,这一步会调用IIC对MPU6050进行一次通讯,通过返回值确定是否启动成功,如果返回是true表示初始化成功,我们可以继续往下进行。
接下来的三句代码是对陀螺仪的经度进行设置。

mpu.setAccelerometerRange(MPU6050_RANGE_8_G) : 对加速度计的量程范围进行设置,参数可选择如下:

  • MPU6050_RANGE_2G :量程为±2G之间;
  • MPU6050_RANGE_4G :量程为±4G之间;
  • MPU6050_RANGE_8G :量程为±8G之间;
  • MPU6050_RANGE_16G :量程为±16G之间。

mpu.setGyroRange(MPU6050_RANGE_500_DEG) : 对陀螺仪的量程进行设置,表示用户可以测量到的最大偏转角度,单位为弧度/秒,可选参数如下:

  • MPU6050_RANGE_250_DEG,表示量程为±250度每秒;
  • MPU6050_RANGE_500_DEG,表示量程为±500度每秒;
  • MPU6050_RANGE_1000_DEG,表示量程为±1000度每秒;
  • MPU6050_RANGE_2000_DEG,表示量程为±2000度每秒。

mpu.setFilterBandwidth(MPU6050_BAND_21_HZ) : 用来设置低通滤波器的截止频率,
以平滑读数并去除高频噪声,可选参数有:
MPU6050_BAND_260_HZ,表示截止频率为260Hz;
MPU6050_BAND_184_HZ,表示截止频率为184Hz;
MPU6050_BAND_94_HZ,表示截止频率为94Hz;
MPU6050_BAND_44_HZ,表示截止频率为44Hz;
MPU6050_BAND_21_HZ,表示截止频率为21Hz;
MPU6050_BAND_10_HZ,表示截止频率为10Hz;
MPU6050_BAND_5_HZ,表示截止频率为5Hz。

数字低通滤波器的截止频率是指滤波器开始对输入信号进行滤波降频的频率点。在数字低通滤波器中,只有低于截止频率的信号分量可以通过滤波器,而高于截止频率的信号分量则被过滤掉。因此,数字低通滤波器用于去除高频噪声,平滑数据的变化。对于一个数字低通滤波器,其截止频率一般通过设置低通滤波器的模拟升频截止频率来实现。通过一个数字低通滤波器进行信号处理时,需要根据实际的应用需求来选择合适的截止频率。如果数字低通滤波器的截止频率太低,则会导致滤波之后的信号延迟过大,可能会导致时间上的不精确,并且不能及时反映数据的变化。而如果数字低通滤波器的截止频率太高,则不能有效地滤除高频噪声,从而降低滤波效果。因此,在选择数字低通滤波器截止频率时,需要根据具体的应用场景和要求来进行权衡和选择。

数据采集

完成 之后进入大循环,开始不断采集信号。
首先通过 sensors_event_t 声明了一系列的结构体,用于存储MPU6050传回的数据
然后调用 mpu.getEvent(&accel, &gyro, &temp) 将这几个结构体的指针传入,每调用一次getEvent即可获得一次当前的数据。
sensors_event_t 结构体是通用的,每个里面都保存了温度、角速度、加速度三组变量,所以需要通过不同的成员变量取得。
sensors_event_t.temperature :用于获得温度信息,返回一个float型数值,单位为摄氏度(°C)
sensors_event_t.gyro :用于获得XYZ三周的旋转加角速度,也就是我们在不同轴上旋转的数值变化,单位是 弧度/秒,可以通过数学公式:red*180/PI 得到旋转的角度(角度/秒)
sensors_event_t.acceleration :用于获得三个轴的重力加速度,即我们在每个轴上移动的速度,返回的单位为m/s²,除以9.81可得到 以重力加速度g为单位的数据。
实际开发中我们不需要做转换,直接用原始数据操作即可,正现实中为了更直观,我们做了数学转换。

在实际开发中,因为sensors_event_t有些浪费空间,我们可以换成两外三个函数分别获得温度、重力加速度和角速度:
mpu.getTemperature() :没有参数,返回一个float用于表示温度
mpu.getAcceleration(&ax, &ay, &az) :传入三个指针,会得到三轴的重力加速度
mpu.getRotation(&gx, &gy, &gz) :传入三个指针,会得到三轴的角速度

模拟器运行后,我们可以通过点选MPU6050设备打开参数调整开关:
在这里插入图片描述

多任务多IIC设备的综合运用

IIC总线挂在多设备

IIC总线中可以挂在多个设备,主机和设备之间通过地址方式通讯,首先主机发送一个START信号(开始信号),选好传输的开始,紧接着主机会发送设备ID地址(7位或10位)给所有设备,当设备发现地址属于自己的时候,选择激活,等待与主机通讯;之后主机发送读写控制位、数据位、STOP信号(停止信号)释放总线。这样就完成了一次通讯,总线进入静默模式,等待下一次通讯。
由此可见,在IIC总线通讯中,我们可以将很多个设备挂在总线中(理论最多可挂在112个设备),而这些设备进行复杂通讯也仅仅占用了SCL和SDA两个引脚,非常的经济实惠。
在本案例中,我们将MPU6050和OLED共同连接在一个IIC总线中,实现多任务数据通讯。

硬件设计

硬件电路图

软件设计

在本案例中,一共运行三个线程,分别是:

  1. MPU6050数据采集线程,用于对MPU6050硬件进行操作,其中使用IIC通讯获取数据,并放入一个全局通用的数据结构体中;
  2. Print线程,对全局数据结构体内的数据进行控制台输出;
  3. OLED线程,使用IIC与硬件LED进行通讯,同时将全局结构体数据显示在屏幕上。
    其中,MPU6050线程和Print线程都要对全局结构体进行数据操作,为了不引起数据混乱,所以要使用互斥信号量对这个数据结构体进行加锁操作。
    而MPU6050和OLED又同时对IIC总线进行操作,为了防止总线数据混乱,所以需要对IIC总线及一把同步锁,在所有使用到IIC总线的情况下进行互斥操作,而没有用到总线的情况下则不需要加锁。
    最后需要注意,IIC总线的初始化和应用都要在同一核心下进行,否则会引起设备混乱。

代码共享地址:https://wokwi.com/projects/362704315801904129

#include <Adafruit_MPU6050.h>
#include <Adafruit_Sensor.h>
#include <Wire.h>
#include <U8g2lib.h>
#define SCL 16
#define SDA 17
#define DISP_OLED 1
#define DISP_CONSOLE 1
typedef struct{
  // 温度
  float temp;
  // 加速度
  float acce_x;
  float acce_y;
  float acce_z;
  // 姿态
  float gyro_x;
  float gyro_y;
  float gyro_z;
}MPU6050;
MPU6050 mpu6050;
SemaphoreHandle_t xMutexMPU6050 = NULL;   // 同步锁
SemaphoreHandle_t xMutexIIC = NULL;            // IIC设备同步
Adafruit_MPU6050 mpu;   // 声明设备
// 陀螺仪任务
void mpu_task(void* param_t){
  if (xSemaphoreTake(xMutexIIC, 1000) == pdPASS) {
    mpu.setAccelerometerRange(MPU6050_RANGE_8_G);     // 设置重力加速度的测量范围为±8G
    mpu.setGyroRange(MPU6050_RANGE_500_DEG);          // 设置陀螺仪的测量范围为±500度/秒
    mpu.setFilterBandwidth(MPU6050_BAND_21_HZ);       // 设置滤波器带宽
    xSemaphoreGive(xMutexIIC);    // 释放同步锁
  }
 
  sensors_event_t accel;
  sensors_event_t gyro;
  sensors_event_t temp;
 
  for(;;){
    // 循环读取更新陀螺仪数据
    if (xSemaphoreTake(xMutexIIC, 1000) == pdPASS) {
      mpu.getEvent(&accel, &gyro, &temp);             // 获取数据
      xSemaphoreGive(xMutexIIC);    // 释放同步锁
    }
    if (xSemaphoreTake(xMutexMPU6050, 1000) == pdPASS) {
      // printf("获得钥匙,赶紧更新数据...\n");
      // 读出温度
      mpu6050.temp = temp.temperature;
      // 读出加速度
      mpu6050.acce_x = accel.acceleration.x / 9.81; // 得到的是重力, / 9.81得到的是牛
      mpu6050.acce_y = accel.acceleration.y / 9.81;
      mpu6050.acce_z = accel.acceleration.z / 9.81;
      // 读出姿态
      mpu6050.gyro_x = gyro.gyro.x *180 / PI;     // 得到的是弧度,转换为角度
      mpu6050.gyro_y = gyro.gyro.y *180 / PI;
      mpu6050.gyro_z = gyro.gyro.z *180 / PI;
      xSemaphoreGive(xMutexMPU6050);    // 释放同步锁
      // printf("数据更新完毕,把钥匙还回去了!\n");
    }else{
      printf("没人没送回钥匙来,我不等了~~\n");
    }
    vTaskDelay(pdMS_TO_TICKS(100));
  }
}
#if defined(DISP_CONSOLE) && DISP_CONSOLE == 1
// 打印输出线程
void print_task(void *param_t){
  for(;;){
    if (xSemaphoreTake(xMutexMPU6050, 1000) == pdPASS) {
      printf("-----===== MPU6050 =====-----\n");
      printf("Temp   : %.2f°C\n",mpu6050.temp);
      printf("Gyro-X : %.2f°\n",mpu6050.gyro_x);
      printf("Gyro-Y : %.2f°\n",mpu6050.gyro_y);
      printf("Gyro-Z : %.2f°\n",mpu6050.gyro_z);
      printf("Acce-X : %.2f g\n",mpu6050.acce_x);
      printf("Acce-Y : %.2f g\n",mpu6050.acce_y);
      printf("Acce-Z : %.2f g\n",mpu6050.acce_z);
      printf("==============================\n");
      xSemaphoreGive(xMutexMPU6050);
    }
    vTaskDelay(pdMS_TO_TICKS(1000));
  }
}
#endif
#if defined(DISP_OLED) && DISP_OLED == 1
U8G2_SSD1306_128X64_NONAME_F_SW_I2C u8g2(U8G2_R0, /*clock=*/SCL, /*data=*/SDA, /*reset=*/U8X8_PIN_NONE);
// OLED显示线程
void led_task(void *param_t){
  if (xSemaphoreTake(xMutexIIC, 1000) == pdPASS) {
    u8g2.begin();
    // u8g2.setFont(u8g2_font_ncenB08_tr);  // 设置字体
    u8g2.setFont(u8g2_font_7x14_mr);
    xSemaphoreGive(xMutexIIC);       // 释放同步锁
  }
  char buf[24];   // 打印缓冲区
  for(;;){
    u8g2.clearBuffer();         // 清理缓冲区
    if (xSemaphoreTake(xMutexMPU6050, 1000) == pdPASS) {
      sprintf(buf,"Temp : %.2f°C\n",mpu6050.temp);
      u8g2.drawStr(0,10,buf);
      sprintf(buf,"GX:%3.2f° AX:%3.2fg\n",mpu6050.gyro_x, mpu6050.acce_x);
      u8g2.drawStr(0,25,buf);
      sprintf(buf,"GY:%3.2f° AY:%3.2fg\n",mpu6050.gyro_y, mpu6050.acce_y);
      u8g2.drawStr(0,40,buf);
      sprintf(buf,"GZ:%3.2f° AZ:%3.2fg\n",mpu6050.gyro_z, mpu6050.acce_z);
      u8g2.drawStr(0,55,buf);
      xSemaphoreGive(xMutexMPU6050);
    }
    if (xSemaphoreTake(xMutexIIC, 1000) == pdPASS) {
      u8g2.sendBuffer();              // 显示内容
      xSemaphoreGive(xMutexIIC);       // 释放同步锁
    }
    vTaskDelay(pdMS_TO_TICKS(1000));
  }
}
#endif
void setup() {
  Serial.begin(115200);
  xMutexMPU6050 = xSemaphoreCreateMutex();    // 创建数据同步锁
  xMutexIIC = xSemaphoreCreateMutex();           // 创建IIC同步锁
  Wire.begin(SDA, SCL);   // 初始化I2C总线
  if (mpu.begin()) {      // 初始化陀螺仪
    Serial.println("MPU6050 初始化成功");
    xTaskCreatePinnedToCore(mpu_task, "MPU6050", 1024*4, NULL, 1, NULL, 1);  
#if defined(DISP_CONSOLE) && DISP_CONSOLE == 1
    xTaskCreate(print_task, "Print", 1024*4, NULL, 1, NULL);
#endif
#if defined(DISP_OLED) && DISP_OLED == 1
    xTaskCreatePinnedToCore(led_task, "OLED", 1024*4, NULL, 1, NULL,1);
#endif
  } else {
    Serial.println("MPU6050 初始化失败");
  }
}
void loop() {
  delay(10);
}

赠品

/*******************************************************************************
 * 用陀螺仪控制小球移动
 * 本例中使用了OLED12864 和 MPU6050两个组件,但是OLED手里之后恶心的 SH1106
 * 这货跟 MPU6050 或者其他的 IIC 设备不太兼容,同在一个总线容易出问题,
 * 所以大家选择OLED的时候尽量用0.9寸的SSD1306。
 *
 * 还是已恶心的 SH1106为例,必须使用第二条IIC总线,所以这里扩展用到了TwoWire
 * ball_task 线程中负责SH1106的刷新,这个芯片刷新OLED非常慢,大概1帧/秒
 * mpu_task 负责采集陀螺仪的偏转角度,根据偏转角度大小设置小球每帧滑动步长
 ******************************************************************************/
#include <Adafruit_MPU6050.h>
#include <U8g2lib.h>
#include <Wire.h>
#define OLED_SCL 16   // OLED 的IIC_SCL引脚
#define OLED_SDA 17   // OLED 的IIC_SDA引脚
#define MPU_SDA  41   // MPU6050 的IIC_SDA引脚
#define MPU_SCL  42   // MPU6050 的IIC_SCL引脚
#define BALL_SIZE 5   // 球的大小
float x=64 ,y=32;     // 球的中心点坐标
float xa=0, ya=0;     // XY 轴的滑动速度
float xc=0, yc=0;     // 校准后的X轴和Y轴,上电后第一件事就是校准
/*******************************************************************************
 * LED 显示任务
 * 屏幕用的是 SH1106,使用的是IIC_1 ,也就是默认的IIC,
 * 因为在ESP32中使用了GPIO矩阵设置IIC,所有理论上所有的IIC引脚都需要使用Wire.begin
 * 做初始化。OLED的SDC和SCL分别是16和17引脚
 ******************************************************************************/
void ball_task(void *param_t){
  // 初始化U8G2库和OLED
  U8G2_SH1106_128X64_NONAME_F_SW_I2C u8g2(U8G2_R0, /*clock=*/OLED_SCL, /*data=*/OLED_SDA, /*reset=*/U8X8_PIN_NONE);
  Wire.begin(OLED_SDA, OLED_SCL);   // 初始化 IIC 总线
  u8g2.begin();                     // 初始化OLED
  for(;;){
    u8g2.clearBuffer();             // 每帧开始先清空缓冲区
    u8g2.drawFrame(0,0,u8g2.getWidth(), u8g2.getHeight());      // 画一个边框,已确定小球滚动的范围
    x-=xa; y-=ya;                   // 根据偏转角度计算本帧的移动步长
    // 以下四句将小球控制在横轴0~127,纵轴0~63之间,也就是屏幕的大小
    x = x<BALL_SIZE?BALL_SIZE:x;
    x = x>127-BALL_SIZE?127-BALL_SIZE:x;
    y = y<BALL_SIZE?BALL_SIZE:y;
    y = y>63-BALL_SIZE?63-BALL_SIZE:y;
    u8g2.drawDisc((int)x, (int)y, BALL_SIZE, U8G2_DRAW_ALL);    // 画一个实体小球
    u8g2.sendBuffer();                                          // 显示内容,将缓冲区内容发送到OLED,这一步巨慢无比
    delay(1);                                                   // 让出CPU
  }
}
/*******************************************************************************
 * MPU6050 偏转角度获取
 * 为了给恶心的 SH1106 让路,可怜的 MPU6050 只能委屈使用IIC_1
 *
 ******************************************************************************/
void mpu_task(void* param_t){
  // 初始化MPU6050
  Adafruit_MPU6050 mpu;
  TwoWire wire = TwoWire(1);        // 使用第二路IIC,第一路索引为0, 第二路索引为1 ,如果存在第三路,则索引为2
  wire.begin(MPU_SDA,MPU_SCL);      // 使用41和42引脚初始化第二路IIC
 
  // Wire.begin(MPU_SDA, MPU_SCL);
  if (mpu.begin(MPU6050_I2CADDR_DEFAULT,&wire)){      // 使用第二路IIC初始化 MPU6050
  // if (mpu.begin()){
    mpu.setAccelerometerRange(MPU6050_RANGE_8_G);     // 设置重力加速度的测量范围为±8G
    mpu.setGyroRange(MPU6050_RANGE_500_DEG);          // 设置陀螺仪的测量范围为±500度/秒
    mpu.setFilterBandwidth(MPU6050_BAND_21_HZ);       // 设置滤波器带宽
    }else{
      printf("MPU6050 initialization failed!\n");
      vTaskDelete(NULL);
    }
  sensors_event_t accel;
  sensors_event_t gyro;
  sensors_event_t temp;
  /* 先校准
   * 第一次获取MPU6050数据是为了校准用,将开机时候的角度作为默认角度
   * 后面发生角度偏转的时候都已初始角度为参考
   * 因为MPU6050的安装是XY相反的,所以需要对调以下
   */
  mpu.getEvent(&accel, &gyro, &temp);     // 获得偏转角度,加速度和温度
  xc = accel.acceleration.y;
  yc = accel.acceleration.x;
  for(;;){
    /* 开始循环获取角度偏转信息,偏转角度越大小球滑动越快
     * 获得偏转角度后需要和初始化的角度做补偿运算
     */
    mpu.getEvent(&accel, &gyro, &temp);
    xa = accel.acceleration.y - xc;
    ya = accel.acceleration.x - yc;
    delay(100);
  }
}
void setup() {
  Serial.begin(115200);
  Serial.println("Hello, ESP32-S3!");
  xTaskCreate(mpu_task, "MPU6050", 10240, NULL, 1, NULL);
  xTaskCreate(ball_task, "BALL", 10240, NULL, 1, NULL);
  vTaskDelete(NULL);    // 自宫
}
void loop() {
}
发现yaw的值在一定时间后会被锁死(oled屏幕上数字不发生改变),查找原因,查找原因修正 empty.c #include <math.h> // 添加数学库用于fabs函数 #include "board.h" #include "my_key.h" #include "my_time.h" #include "ti_msp_dl_config.h" #include "oled.h" void BUZZY_OFF(void) { DL_GPIO_setPins(BUZZY_PORT, BUZZY_PIN_PIN); } void BUZZY_ON(void) { DL_GPIO_clearPins(BUZZY_PORT, BUZZY_PIN_PIN); } void refresh_oled(void); void key(void); void go_straight(int dis); void go_arc_ccd(hsu_time_t); void go_brc_ccd(hsu_time_t); void turn_in_place(float angle); void sound_light_alert(void); void show_task_now(void); u8 Car_Mode = Diff_Car; int Motor_Left, Motor_Right; // 电机PWM变量 应是Motor的 u8 PID_Send; // 延时调参相关变量 float RC_Velocity = 200, RC_Turn_Velocity, Move_X, Move_Y, Move_Z, PS2_ON_Flag; // 遥控控制的速度 float Velocity_Left, Velocity_Right; // 车轮速度(mm/s) u16 test_num, show_cnt; float Voltage = 0; extern float Yaw; // 声明外部YAW角度变量 int64_t left_encoder = 0, right_encoder = 0; void SysTick_Handler(void) { hsu_time_systick_handler(); } typedef enum { BEGIN, T1, T2, T3, T4 } TaskState; typedef enum { STOP, GO_STRAIGHT, GO_CCD, TURN_IN_PLACE, WAIT_ALERT } DoingWhat; typedef struct __TASK_NAMESPACE { uint8_t is; uint8_t is_running; uint8_t finish; uint8_t sub_finish; uint8_t running_state; // 0: 停止, 1: 运行中 TaskState state; DoingWhat doing_what; float target; float vx; float vz; // 用于复杂任务 uint8_t sub_task_stage; // 子任务阶段 uint8_t lap_count; // 圈数计数 int64_t start_encoder; // 起始编码器值 uint32_t alert_start_time; // 声光提示开始时间 float start_yaw; // 起始YAW角度 float target_yaw_diff; // 目标YAW角度差 hsu_time_t ccd_end_time; } TaskNamespace; void reset_task_namespace(TaskNamespace *t) { t->is_running = 0; t->finish = 0; t->sub_finish = 0; t->state = BEGIN; t->doing_what = STOP; t->vx = 0; t->vz = 0; t->sub_task_stage = 0; t->lap_count = 0; t->start_encoder = left_encoder; t->alert_start_time = 0; t->start_yaw = 0; t->target_yaw_diff = 0; t->running_state = 0; // 明确重置运行状态为0 t->ccd_end_time = 0; t->is = 0; } void next_state(TaskNamespace *t) { TaskState last_state = t->state; reset_task_namespace(t); if (last_state < T4) { t->state = last_state + 1; } } TaskNamespace task_namespace; void show_task_now(void) { //OLED_ShowString(0, 0, "Task Now:"); switch (task_namespace.state) { case BEGIN: OLED_ShowString(1, 10,"0"); break; case T1: OLED_ShowString(1, 10,"1"); break; case T2: OLED_ShowString(1, 10,"2"); break; case T3: OLED_ShowString(1, 10,"3"); break; case T4: OLED_ShowString(1, 10,"4"); break; default: break; } } void main_task(void); int main(void) { // 系统初始化 SYSCFG_DL_init(); // 初始化系统配置 hsu_time_init(); // 时间 // 清除所有外设的中断挂起状态 NVIC_ClearPendingIRQ(ENCODERA_INT_IRQN); // 编码器A中断 NVIC_ClearPendingIRQ(ENCODERB_INT_IRQN); // 编码器B中断 NVIC_ClearPendingIRQ(UART_0_INST_INT_IRQN); // UART0串口中断 // 使能各外设的中断 NVIC_EnableIRQ(ENCODERA_INT_IRQN); // 开启编码器A中断 NVIC_EnableIRQ(ENCODERB_INT_IRQN); // 开启编码器B中断 NVIC_EnableIRQ(UART_0_INST_INT_IRQN); // 开启UART0中断 reset_task_namespace(&task_namespace); task_namespace.state = BEGIN; // 明确设置初始状态 // 定时器ADC相关中断配置 NVIC_ClearPendingIRQ(TIMER_0_INST_INT_IRQN); // 清除定时器0中断挂起 NVIC_EnableIRQ(TIMER_0_INST_INT_IRQN); // 开启定时器0中断 NVIC_EnableIRQ(ADC12_VOLTAGE_INST_INT_IRQN); NVIC_EnableIRQ(ADC12_CCD_INST_INT_IRQN); OLED_Init(); // 初始化OLED显示屏 OLED_ShowString(1, 1, "Task Now:"); OLED_ShowString(2, 1, "state:"); OLED_ShowString(3, 1, "yaw:"); MPU6050_initialize(); DMP_Init(); BUZZY_ON(); // 主循环 // printf("Test delay 500us\n"); // hsu_time_delay_us(500); // printf("Test delay 500us end\n"); uint8_t main_task_timer = hsu_time_timer_create(10, true, main_task); hsu_time_timer_start(main_task_timer); uint8_t refresh_oled_timer = hsu_time_timer_create(5, true, refresh_oled); hsu_time_timer_start(refresh_oled_timer); uint8_t key_timer = hsu_time_timer_create(2, true, key); hsu_time_timer_start(key_timer); while (1) { hsu_time_timer_process(); RD_TSL(); // 读取CCD数据 Find_CCD_Median(); // 计算CCD数据中值 Read_DMP(); show_task_now(); char yaw_str[10]; // 存储格式化后的字符串 snprintf(yaw_str, sizeof(yaw_str), "%.1f", Yaw); // 格式化为带一位小数的字符串 OLED_ShowString(3, 6, yaw_str); // 在指定位置显示YAW值 //DL_GPIO_togglePins(LED_PORT, LED_led_PIN); // printf("L=%lld R=%lld YAW=%.1f\n", left_encoder, right_encoder, Yaw); } } void task_no(void); void task_1(void); void task_2(void); void task_3(void); void task_4(void); void main_task(void) { if (!(task_namespace.is)) return; printf("main task\n"); switch (task_namespace.state) { case BEGIN: task_no(); break; case T1: task_1(); break; case T2: task_2(); break; case T3: task_3(); break; case T4: task_4(); break; default: break; } switch (task_namespace.doing_what) { case STOP: Get_Target_Encoder(0, 0); break; case GO_STRAIGHT: if ((left_encoder * 1.f) < task_namespace.target) { Get_Target_Encoder(0.3, 0); // 提高速度到300mm/s } else { Get_Target_Encoder(0, 0); task_namespace.doing_what = STOP; task_namespace.finish = 1; } break; case GO_CCD: if (task_namespace.ccd_end_time < hsu_time_get_ms()) { Get_Target_Encoder(0, 0); task_namespace.doing_what = STOP; task_namespace.finish = 1; } else { CCD_Mode(); } break; case TURN_IN_PLACE: // 原地转向控制 if (task_namespace.target_yaw_diff != 0) { float current_yaw_diff = Yaw - task_namespace.start_yaw; // 处理角度跨越±180度的情况 if (current_yaw_diff > 180) { current_yaw_diff -= 360; } else if (current_yaw_diff < -180) { current_yaw_diff += 360; } printf("Turn: Start=%.1f Current=%.1f Diff=%.1f Target=%.1f\n", task_namespace.start_yaw, Yaw, current_yaw_diff, task_namespace.target_yaw_diff); // 检查是否达到目标角度 if ((task_namespace.target_yaw_diff > 0 && current_yaw_diff >= task_namespace.target_yaw_diff) || (task_namespace.target_yaw_diff < 0 && current_yaw_diff <= task_namespace.target_yaw_diff)) { Get_Target_Encoder(0, 0); // 停止转向 task_namespace.doing_what = STOP; task_namespace.finish = 1; } else { // 继续转向 float turn_speed = (task_namespace.target_yaw_diff > 0) ? 0.1 : -0.1; Get_Target_Encoder(0, turn_speed); } } break; case WAIT_ALERT: Get_Target_Encoder(0, 0); // 停车 if (hsu_time_get_ms() - task_namespace.alert_start_time > 1000) { // 声光提示1秒 task_namespace.doing_what = STOP; task_namespace.finish = 1; } break; default: break; } } void task_no(void) { return; } // 任务1:A点到B点直线行驶 void task_1(void) { if (!task_namespace.is_running) { task_namespace.is_running = 1; task_namespace.sub_task_stage = 0; task_namespace.finish = 1; return; } if (task_namespace.finish) { switch (task_namespace.sub_task_stage) { case 0: // 开始第一阶段:A到B go_straight(300); break; case 1: // A到B完成,开始B到C DL_GPIO_togglePins(LED_PORT, LED_led_PIN); break; case 2: //任务结束 reset_task_namespace(&task_namespace); task_namespace.running_state = 0; // 重置为停止状态 break; } task_namespace.finish = 0; task_namespace.sub_task_stage++; } return; } // 任务2:A->B->C->D->A循环 void task_2(void) { if (!task_namespace.is_running) { task_namespace.is_running = 1; task_namespace.sub_task_stage = 0; task_namespace.finish = 1; return; } if (task_namespace.finish) { switch (task_namespace.sub_task_stage) { case 0: // 开始第一阶段:A到B go_straight(3300); break; case 1: //DL_GPIO_togglePins(LED_PORT, LED_led_PIN); go_straight(2000); break; case 2: // B到C弧线完成,开始C到D直线 sound_light_alert(); //turn_in_place(-17.0f); go_straight(2980); break; case 3: // C到D完成,开始D到A弧线 sound_light_alert(); go_arc_ccd(3530); break; case 4: // D到A弧线完成,任务结束 sound_light_alert(); reset_task_namespace(&task_namespace); break; } task_namespace.finish = 0; task_namespace.sub_task_stage++; } return; } // 任务3:A->C->B->D->A循环 void task_3(void) { if (!task_namespace.is_running) { task_namespace.is_running = 1; task_namespace.sub_task_stage = 0; task_namespace.finish = 1; return; } if (task_namespace.finish) { switch (task_namespace.sub_task_stage) { case 0: turn_in_place(-31.0f); break; case 1: go_straight(4060); break; case 2: turn_in_place(30.0f); break; case 3: // 开始C到B弧线 sound_light_alert(); //go_straight(40); go_brc_ccd(3550); break; case 4: turn_in_place(36.0f); break; case 5: sound_light_alert(); go_straight(3985); break; case 6: turn_in_place(-40.0f); break; case 7: // 开始C到B弧线 sound_light_alert(); go_arc_ccd(3600); break; case 8: // D到A弧线完成,任务结束 sound_light_alert(); reset_task_namespace(&task_namespace); break; } task_namespace.finish = 0; task_namespace.sub_task_stage++; } return; } // 任务4:重复任务3路径4圈 void task_4(void) { if (!task_namespace.is_running) { task_namespace.is_running = 1; task_namespace.sub_task_stage = 0; task_namespace.finish = 1; return; } if (task_namespace.finish) { switch (task_namespace.sub_task_stage) { case 0: turn_in_place(-30.0f); break; case 1: go_straight(4045); break; case 2: turn_in_place(29.0f); break; case 3: // 开始C到B弧线 sound_light_alert(); //go_straight(40); go_brc_ccd(5000); break; case 4: turn_in_place(34.0f); break; case 5: sound_light_alert(); go_straight(4035); break; case 6: turn_in_place(-33.0f); break; case 7: // 开始C到B弧线 sound_light_alert(); go_arc_ccd(5000); break; case 8: // D到A弧线完成,任务结束 sound_light_alert(); reset_task_namespace(&task_namespace); break; } task_namespace.finish = 0; task_namespace.sub_task_stage++; } } void TIMER_0_INST_IRQHandler(void) { if (DL_TimerA_getPendingInterrupt(TIMER_0_INST)) { if (DL_TIMER_IIDX_ZERO) { Get_Velocity_From_Encoder(Get_Encoder_countA, Get_Encoder_countB); Get_Encoder_countA = Get_Encoder_countB = 0; MotorA.Motor_Pwm = Incremental_PI_Left(MotorA.Current_Encoder, MotorA.Target_Encoder); MotorB.Motor_Pwm = Incremental_PI_Right(MotorB.Current_Encoder, MotorB.Target_Encoder); if (!Flag_Stop) { Set_PWM(-MotorA.Motor_Pwm, -MotorB.Motor_Pwm); } else { Set_PWM(0, 0); } } } } uint32_t gpio_interrup1, gpio_interrup2; int64_t B1, B2, B3, B4; int64_t A1, A2, A3, A4; void GROUP1_IRQHandler(void) { // 获取中断信号 gpio_interrup1 = DL_GPIO_getEnabledInterruptStatus(ENCODERA_PORT, ENCODERA_E1A_PIN | ENCODERA_E1B_PIN); gpio_interrup2 = DL_GPIO_getEnabledInterruptStatus(ENCODERB_PORT, ENCODERB_E2A_PIN | ENCODERB_E2B_PIN); // encoderB if ((gpio_interrup1 & ENCODERA_E1A_PIN) == ENCODERA_E1A_PIN) { if (!DL_GPIO_readPins(ENCODERA_PORT, ENCODERA_E1B_PIN)) { right_encoder--; Get_Encoder_countB--; } else { right_encoder++; Get_Encoder_countB++; } } else if ((gpio_interrup1 & ENCODERA_E1B_PIN) == ENCODERA_E1B_PIN) { if (!DL_GPIO_readPins(ENCODERA_PORT, ENCODERA_E1A_PIN)) { right_encoder++; Get_Encoder_countB++; } else { right_encoder--; Get_Encoder_countB--; } } // encoderA if ((gpio_interrup2 & ENCODERB_E2A_PIN) == ENCODERB_E2A_PIN) { if (!DL_GPIO_readPins(ENCODERB_PORT, ENCODERB_E2B_PIN)) { left_encoder++; Get_Encoder_countA--; } else { left_encoder--; Get_Encoder_countA++; } } else if ((gpio_interrup2 & ENCODERB_E2B_PIN) == ENCODERB_E2B_PIN) { if (!DL_GPIO_readPins(ENCODERB_PORT, ENCODERB_E2A_PIN)) { left_encoder--; Get_Encoder_countA++; } else { left_encoder++; Get_Encoder_countA--; } } DL_GPIO_clearInterruptStatus(ENCODERA_PORT, ENCODERA_E1A_PIN | ENCODERA_E1B_PIN); DL_GPIO_clearInterruptStatus(ENCODERB_PORT, ENCODERB_E2A_PIN | ENCODERB_E2B_PIN); } // 直线行驶函数 void go_straight(int dis) { task_namespace.doing_what = GO_STRAIGHT; task_namespace.target = left_encoder + dis; task_namespace.finish = 0; } // 原地转向函数 void turn_in_place(float angle) { task_namespace.doing_what = TURN_IN_PLACE; task_namespace.start_yaw = Yaw; task_namespace.target_yaw_diff = angle; // 正值右转,负值左转 task_namespace.finish = 0; } // CCD巡线函数(需要外部条件结束) void go_ccd_line(void) { task_namespace.doing_what = GO_CCD; task_namespace.start_encoder = left_encoder; task_namespace.finish = 0; // 设置一个安全的最大距离,防止无限巡线 // 可以根据实际场地调整这个值 static uint32_t ccd_end_time = 0; if (ccd_end_time == 0) { ccd_end_time = hsu_time_get_ms(); } // 如果巡线时间超过10秒或距离超过2000mm,强制结束 if (hsu_time_get_ms() - ccd_end_time > 10000 || (left_encoder * 1.f - task_namespace.start_encoder) > 2000) { task_namespace.finish = 1; ccd_end_time = 0; } } // 弧线CCD巡线函数 void go_arc_ccd(hsu_time_t time) { task_namespace.doing_what = GO_CCD; task_namespace.start_encoder = left_encoder; task_namespace.ccd_end_time = hsu_time_get_ms() + time; } void go_brc_ccd(hsu_time_t time) { task_namespace.doing_what = GO_CCD; task_namespace.start_encoder = right_encoder; task_namespace.ccd_end_time = hsu_time_get_ms() + time; } // 声光提示函数 void sound_light_alert(void) { DL_GPIO_togglePins(LED_PORT, LED_led_PIN); DL_GPIO_setPins(BUZZY_PORT, BUZZY_PIN_PIN); uint32_t start_time = hsu_time_get_ms(); while (hsu_time_get_ms() - start_time < 1000) { // 空循环等待1秒 } //hsu_time_delay_ms(200); DL_GPIO_togglePins(LED_PORT, LED_led_PIN); DL_GPIO_clearPins(BUZZY_PORT, BUZZY_PIN_PIN); } // callback void refresh_oled(void) { show_task_now(); OLED_ShowString(2, 1, "state:"); if (task_namespace.running_state) { OLED_ShowString(2, 7, "1"); // 运行中 } else { OLED_ShowString(2, 7, "0"); // 停止 } } uint32_t key_get_tick_ms(void) { return hsu_time_get_ms(); } void key(void) { key_event_t event = key_scan(); //uint8_t key_value = key_read_pin(); // 获取按键状态 //S1 switch (event) { case KEY_EVENT_SINGLE_CLICK: next_state(&task_namespace); break; case KEY_EVENT_DOUBLE_CLICK: task_namespace.is = 1; task_namespace.running_state = 1; task_namespace.is_running = 0; // 重置任务运行标志 break; } } MPU6050.c #include "MPU6050.h" #include <stdio.h> #include "inv_mpu.h" // #include "IOI2C.h" // #include "usart.h" #define PRINT_ACCEL (0x01) #define PRINT_GYRO (0x02) #define PRINT_QUAT (0x04) #define ACCEL_ON (0x01) #define GYRO_ON (0x02) #define MOTION (0) #define NO_MOTION (1) #define DEFAULT_MPU_HZ (200) #define FLASH_SIZE (512) #define FLASH_MEM_START ((void *)0x1800) #define q30 1073741824.0f short gyro[3], accel[3], sensors; float Roll, Pitch, Yaw; float q0 = 1.0f, q1 = 0.0f, q2 = 0.0f, q3 = 0.0f; static signed char gyro_orientation[9] = {-1, 0, 0, 0, -1, 0, 0, 0, 1}; Imu_t mpu6050 = {0}; Imu_t RegOri_mpu6050 = {0}; // iic转接 #include "bsp_siic.h" static pIICInterface_t siic = &User_sIICDev; uint8_t IICwriteBits(uint8_t addr, uint8_t reg, uint8_t bitStart, uint8_t length, uint8_t data) { uint8_t b; if (siic->read_reg(addr << 1, reg, &b, 1, 200) == IIC_OK) { uint8_t mask = (0xFF << (bitStart + 1)) | (0xFF >> ((8 - bitStart) + length - 1)); data <<= (8 - length); data >>= (7 - bitStart); b &= mask; b |= data; return siic->write_reg(addr << 1, reg, &b, 1, 200); } return 1; } uint8_t IICwriteBit(uint8_t dev, uint8_t reg, uint8_t bitNum, uint8_t data) { uint8_t b; siic->read_reg(dev << 1, reg, &b, 1, 200); b = (data != 0) ? (b | (1 << bitNum)) : (b & ~(1 << bitNum)); return siic->write_reg(dev << 1, reg, &b, 1, 200); } uint8_t IICreadBytes(uint8_t dev, uint8_t reg, uint8_t length, uint8_t *data) { return siic->read_reg(dev << 1, reg, data, length, 200); } int i2cRead(uint8_t addr, uint8_t reg, uint8_t len, uint8_t *buf) { return siic->read_reg(addr << 1, reg, buf, len, 200); } unsigned char I2C_ReadOneByte(unsigned char I2C_Addr, unsigned char addr) { uint8_t b = 0; siic->read_reg(I2C_Addr << 1, addr, &b, 1, 200); return b; } static unsigned short inv_row_2_scale(const signed char *row) { unsigned short b; if (row[0] > 0) b = 0; else if (row[0] < 0) b = 4; else if (row[1] > 0) b = 1; else if (row[1] < 0) b = 5; else if (row[2] > 0) b = 2; else if (row[2] < 0) b = 6; else b = 7; // error return b; } static unsigned short inv_orientation_matrix_to_scalar(const signed char *mtx) { unsigned short scalar; scalar = inv_row_2_scale(mtx); scalar |= inv_row_2_scale(mtx + 3) << 3; scalar |= inv_row_2_scale(mtx + 6) << 6; return scalar; } static void run_self_test(void) { int result; long gyro[3], accel[3]; result = mpu_run_self_test(gyro, accel); if (result == 0x7) { /* Test passed. We can trust the gyro data here, so let's push it down * to the DMP. */ float sens; unsigned short accel_sens; mpu_get_gyro_sens(&sens); gyro[0] = (long)(gyro[0] * sens); gyro[1] = (long)(gyro[1] * sens); gyro[2] = (long)(gyro[2] * sens); dmp_set_gyro_bias(gyro); mpu_get_accel_sens(&accel_sens); accel[0] *= accel_sens; accel[1] *= accel_sens; accel[2] *= accel_sens; dmp_set_accel_bias(accel); // printf("setting bias succesfully ......\r\n"); } } uint8_t buffer[14]; int16_t MPU6050_FIFO[6][11]; int16_t Gx_offset = 0, Gy_offset = 0, Gz_offset = 0; /************************************************************************** Function: The new ADC data is updated to FIFO array for filtering Input : ax,ay,az:x,y, z-axis acceleration data;gx,gy,gz:x. Y, z-axis angular acceleration data Output : none 函数功能:将新的ADC数据更新到 FIFO数组,进行滤波处理 入口参数:ax,ay,az:x,y,z轴加速度数据;gx,gy,gz:x,y,z轴角加速度数据 返回 值:无 **************************************************************************/ void MPU6050_newValues(int16_t ax, int16_t ay, int16_t az, int16_t gx, int16_t gy, int16_t gz) { unsigned char i; int32_t sum = 0; for (i = 1; i < 10; i++) { // FIFO 操作 MPU6050_FIFO[0][i - 1] = MPU6050_FIFO[0][i]; MPU6050_FIFO[1][i - 1] = MPU6050_FIFO[1][i]; MPU6050_FIFO[2][i - 1] = MPU6050_FIFO[2][i]; MPU6050_FIFO[3][i - 1] = MPU6050_FIFO[3][i]; MPU6050_FIFO[4][i - 1] = MPU6050_FIFO[4][i]; MPU6050_FIFO[5][i - 1] = MPU6050_FIFO[5][i]; } MPU6050_FIFO[0][9] = ax; // 将新的数据放置到 数据的最后面 MPU6050_FIFO[1][9] = ay; MPU6050_FIFO[2][9] = az; MPU6050_FIFO[3][9] = gx; MPU6050_FIFO[4][9] = gy; MPU6050_FIFO[5][9] = gz; sum = 0; for (i = 0; i < 10; i++) { // 求当前数组的合,再取平均值 sum += MPU6050_FIFO[0][i]; } MPU6050_FIFO[0][10] = sum / 10; sum = 0; for (i = 0; i < 10; i++) { sum += MPU6050_FIFO[1][i]; } MPU6050_FIFO[1][10] = sum / 10; sum = 0; for (i = 0; i < 10; i++) { sum += MPU6050_FIFO[2][i]; } MPU6050_FIFO[2][10] = sum / 10; sum = 0; for (i = 0; i < 10; i++) { sum += MPU6050_FIFO[3][i]; } MPU6050_FIFO[3][10] = sum / 10; sum = 0; for (i = 0; i < 10; i++) { sum += MPU6050_FIFO[4][i]; } MPU6050_FIFO[4][10] = sum / 10; sum = 0; for (i = 0; i < 10; i++) { sum += MPU6050_FIFO[5][i]; } MPU6050_FIFO[5][10] = sum / 10; } /************************************************************************** Function: Setting the clock source of mpu6050 Input : source:Clock source number Output : none 函数功能:设置 MPU6050 的时钟源 入口参数:source:时钟源编号 返回 值:无 * CLK_SEL | Clock Source * --------+-------------------------------------- * 0 | Internal oscillator * 1 | PLL with X Gyro reference * 2 | PLL with Y Gyro reference * 3 | PLL with Z Gyro reference * 4 | PLL with external 32.768kHz reference * 5 | PLL with external 19.2MHz reference * 6 | Reserved * 7 | Stops the clock and keeps the timing generator in reset **************************************************************************/ void MPU6050_setClockSource(uint8_t source) { IICwriteBits(devAddr, MPU6050_RA_PWR_MGMT_1, MPU6050_PWR1_CLKSEL_BIT, MPU6050_PWR1_CLKSEL_LENGTH, source); } /** Set full-scale gyroscope range. * @param range New full-scale gyroscope range value * @see getFullScaleRange() * @see MPU6050_GYRO_FS_250 * @see MPU6050_RA_GYRO_CONFIG * @see MPU6050_GCONFIG_FS_SEL_BIT * @see MPU6050_GCONFIG_FS_SEL_LENGTH */ void MPU6050_setFullScaleGyroRange(uint8_t range) { IICwriteBits(devAddr, MPU6050_RA_GYRO_CONFIG, MPU6050_GCONFIG_FS_SEL_BIT, MPU6050_GCONFIG_FS_SEL_LENGTH, range); } /************************************************************************** Function: Setting the maximum range of mpu6050 accelerometer Input : range:Acceleration maximum range number Output : none 函数功能:设置 MPU6050 加速度计的最大量程 入口参数:range:加速度最大量程编号 返回 值:无 **************************************************************************/ // #define MPU6050_ACCEL_FS_2 0x00 //===最大量程+-2G // #define MPU6050_ACCEL_FS_4 0x01 //===最大量程+-4G // #define MPU6050_ACCEL_FS_8 0x02 //===最大量程+-8G // #define MPU6050_ACCEL_FS_16 0x03 //===最大量程+-16G void MPU6050_setFullScaleAccelRange(uint8_t range) { IICwriteBits(devAddr, MPU6050_RA_ACCEL_CONFIG, MPU6050_ACONFIG_AFS_SEL_BIT, MPU6050_ACONFIG_AFS_SEL_LENGTH, range); } /************************************************************************** Function: Set mpu6050 to sleep mode or not Input : enable:1,sleep;0,work; Output : none 函数功能:设置 MPU6050 是否进入睡眠模式 入口参数:enable:1,睡觉;0,工作; 返回 值:无 **************************************************************************/ void MPU6050_setSleepEnabled(uint8_t enabled) { IICwriteBit(devAddr, MPU6050_RA_PWR_MGMT_1, MPU6050_PWR1_SLEEP_BIT, enabled); } /************************************************************************** Function: Read identity Input : none Output : 0x68 函数功能:读取 MPU6050 WHO_AM_I 标识 入口参数:无 返回 值:0x68 **************************************************************************/ uint8_t MPU6050_getDeviceID(void) { IICreadBytes(devAddr, MPU6050_RA_WHO_AM_I, 1, buffer); return buffer[0]; } /************************************************************************** Function: Check whether mpu6050 is connected Input : none Output : 1:Connected;0:Not connected 函数功能:检测MPU6050 是否已经连接 入口参数:无 返回 值:1:已连接;0:未连接 **************************************************************************/ uint8_t MPU6050_testConnection(void) { if (MPU6050_getDeviceID() == 0x68) // 0b01101000; return 1; else return 0; } /************************************************************************** Function: Setting whether mpu6050 is the host of aux I2C cable Input : enable:1,yes;0;not Output : none 函数功能:设置 MPU6050 是否为AUX I2C线的主机 入口参数:enable:1,是;0:否 返回 值:无 **************************************************************************/ void MPU6050_setI2CMasterModeEnabled(uint8_t enabled) { IICwriteBit(devAddr, MPU6050_RA_USER_CTRL, MPU6050_USERCTRL_I2C_MST_EN_BIT, enabled); } /************************************************************************** Function: Setting whether mpu6050 is the host of aux I2C cable Input : enable:1,yes;0;not Output : none 函数功能:设置 MPU6050 是否为AUX I2C线的主机 入口参数:enable:1,是;0:否 返回 值:无 **************************************************************************/ void MPU6050_setI2CBypassEnabled(uint8_t enabled) { IICwriteBit(devAddr, MPU6050_RA_INT_PIN_CFG, MPU6050_INTCFG_I2C_BYPASS_EN_BIT, enabled); } /************************************************************************** Function: initialization Mpu6050 to enter the available state Input : none Output : none 函数功能:初始化 MPU6050 以进入可用状态 入口参数:无 返回 值:无 **************************************************************************/ void MPU6050_initialize(void) { // 未识别陀螺仪,复位 if (MPU6050_getDeviceID() != 0x68) DL_SYSCTL_resetDevice(DL_SYSCTL_RESET_POR); MPU6050_setClockSource(MPU6050_CLOCK_PLL_YGYRO); // 设置时钟 MPU6050_setFullScaleGyroRange(MPU6050_GYRO_FS_2000); // 陀螺仪量程设置 MPU6050_setFullScaleAccelRange(MPU6050_ACCEL_FS_2); // 加速度度最大量程 +-2G MPU6050_setSleepEnabled(0); // 进入工作状态 MPU6050_setI2CMasterModeEnabled(0); // 不让MPU6050 控制AUXI2C MPU6050_setI2CBypassEnabled(0); // 主控制器的I2C与 MPU6050的AUXI2C 直通关闭 } /************************************************************************** Function: Initialization of DMP in mpu6050 Input : none Output : none 函数功能:MPU6050内置DMP的初始化 入口参数:无 返回 值:无 **************************************************************************/ void DMP_Init(void) { uint8_t resetflag = 0; uint8_t temp[1] = {0}; i2cRead(0x68, 0x75, 1, temp); printf("mpu_set_sensor complete ......\r\n"); if (temp[0] != 0x68) DL_SYSCTL_resetDevice(DL_SYSCTL_RESET_POR); if (!mpu_init()) { if (!mpu_set_sensors(INV_XYZ_GYRO | INV_XYZ_ACCEL)) printf("mpu_set_sensor complete ......\r\n"); else resetflag = 1; if (!mpu_configure_fifo(INV_XYZ_GYRO | INV_XYZ_ACCEL)) printf("mpu_configure_fifo complete ......\r\n"); else resetflag = 1; if (!mpu_set_sample_rate(DEFAULT_MPU_HZ)) printf("mpu_set_sample_rate complete ......\r\n"); else resetflag = 1; if (!dmp_load_motion_driver_firmware()) printf("dmp_load_motion_driver_firmware complete ......\r\n"); else resetflag = 1; if (!dmp_set_orientation(inv_orientation_matrix_to_scalar(gyro_orientation))) printf("dmp_set_orientation complete ......\r\n"); else resetflag = 1; if (!dmp_enable_feature(DMP_FEATURE_6X_LP_QUAT | DMP_FEATURE_TAP | DMP_FEATURE_ANDROID_ORIENT | DMP_FEATURE_SEND_RAW_ACCEL | DMP_FEATURE_SEND_CAL_GYRO | DMP_FEATURE_GYRO_CAL)) printf("dmp_enable_feature complete ......\r\n"); else resetflag = 1; if (!dmp_set_fifo_rate(DEFAULT_MPU_HZ)) printf("dmp_set_fifo_rate complete ......\r\n"); else resetflag = 1; run_self_test(); if (!mpu_set_dmp_state(1)) printf("mpu_set_dmp_state complete ......\r\n"); } else { DL_SYSCTL_resetDevice(DL_SYSCTL_RESET_POR); } if (resetflag) { mpu6050_i2c_sda_unlock(); DL_SYSCTL_resetDevice(DL_SYSCTL_RESET_POR); } } /************************************************************************** Function: Read the attitude information of DMP in mpu6050 Input : none Output : none 函数功能:读取MPU6050内置DMP的姿态信息 入口参数:无 返回 值:无 **************************************************************************/ void Read_DMP(void) { unsigned long sensor_timestamp; unsigned char more; long quat[4]; dmp_read_fifo(gyro, accel, quat, &sensor_timestamp, &sensors, &more); // 读取DMP数据 if (sensors & INV_WXYZ_QUAT) { q0 = quat[0] / q30; q1 = quat[1] / q30; q2 = quat[2] / q30; q3 = quat[3] / q30; // 四元数 Roll = asin(-2 * q1 * q3 + 2 * q0 * q2) * 57.3; // 计算出横滚角 Pitch = atan2(2 * q2 * q3 + 2 * q0 * q1, -2 * q1 * q1 - 2 * q2 * q2 + 1) * 57.3; // 计算出俯仰角 Yaw = atan2(2 * (q1 * q2 + q0 * q3), q0 * q0 + q1 * q1 - q2 * q2 - q3 * q3) * 57.3; // 计算出偏航角 } } /************************************************************************** Function: Read mpu6050 built-in temperature sensor data Input : none Output : Centigrade temperature 函数功能:读取MPU6050内置温度传感器数据 入口参数:无 返回 值:摄氏温度 **************************************************************************/ int Read_Temperature(void) { float Temp; Temp = (I2C_ReadOneByte(devAddr, MPU6050_RA_TEMP_OUT_H) << 8) + I2C_ReadOneByte(devAddr, MPU6050_RA_TEMP_OUT_L); if (Temp > 32768) Temp -= 65536; // 数据类型转换 Temp = (36.53 + Temp / 340) * 10; // 温度放大十倍存放 return (int)Temp; } //------------------End of File----------------------------
最新发布
07-22
int main(void) 部分中,发现 while (1)部分的代码未触发(未进入循环),查找原因修正 empty.c #include <math.h> // 添加数学库用于fabs函数 #include "board.h" #include "oled.h" #include "my_key.h" #include "my_time.h" #include "ti_msp_dl_config.h" void BUZZY_OFF(void) { DL_GPIO_setPins(BUZZY_PORT, BUZZY_PIN_PIN); } void BUZZY_ON(void) { DL_GPIO_clearPins(BUZZY_PORT, BUZZY_PIN_PIN); } void refresh_oled(void); void key(void); void go_straight(int dis); void go_arc_ccd(hsu_time_t); void go_brc_ccd(hsu_time_t); void turn_in_place(float angle); void sound_light_alert(void); void show_task_now(void); u8 Car_Mode = Diff_Car; int Motor_Left, Motor_Right; // 电机PWM变量 应是Motor的 u8 PID_Send; // 延时调参相关变量 float RC_Velocity = 200, RC_Turn_Velocity, Move_X, Move_Y, Move_Z, PS2_ON_Flag; // 遥控控制的速度 float Velocity_Left, Velocity_Right; // 车轮速度(mm/s) u16 test_num, show_cnt; float Voltage = 0; extern float Yaw; // 声明外部YAW角度变量 int64_t left_encoder = 0, right_encoder = 0; void SysTick_Handler(void) { hsu_time_systick_handler(); } typedef enum { BEGIN, T1, T2, T3, T4 } TaskState; typedef enum { STOP, GO_STRAIGHT, GO_CCD, TURN_IN_PLACE, WAIT_ALERT } DoingWhat; typedef struct __TASK_NAMESPACE { uint8_t is; uint8_t is_running; uint8_t finish; uint8_t sub_finish; TaskState state; DoingWhat doing_what; float target; float vx; float vz; // 用于复杂任务 uint8_t sub_task_stage; // 子任务阶段 uint8_t lap_count; // 圈数计数 int64_t start_encoder; // 起始编码器值 uint32_t alert_start_time; // 声光提示开始时间 float start_yaw; // 起始YAW角度 float target_yaw_diff; // 目标YAW角度差 hsu_time_t ccd_end_time; } TaskNamespace; void reset_task_namespace(TaskNamespace *t) { t->is_running = 0; t->finish = 0; t->sub_finish = 0; t->state = BEGIN; t->doing_what = STOP; t->vx = 0; t->vz = 0; t->sub_task_stage = 0; t->lap_count = 0; t->start_encoder = left_encoder; t->alert_start_time = 0; t->start_yaw = 0; t->target_yaw_diff = 0; t->ccd_end_time = 0; t->is = 0; } void next_state(TaskNamespace *t) { TaskState last_state = t->state; reset_task_namespace(t); if (last_state < T4) { t->state = last_state + 1; } } TaskNamespace task_namespace; void show_task_now(void) { //OLED_ShowString(0, 0, "Task Now:"); switch (task_namespace.state) { case BEGIN: OLED_ShowString(1, 2,"0"); DL_GPIO_togglePins(LED_PORT, LED_led_PIN); break; case T1: OLED_ShowString(1, 2,"1"); break; case T2: OLED_ShowString(1, 2,"2"); break; case T3: OLED_ShowString(1, 2,"3"); break; case T4: OLED_ShowString(1, 2,"4"); break; default: break; } } void main_task(void); int main(void) { // 系统初始化 SYSCFG_DL_init(); // 初始化系统配置 hsu_time_init(); // 时间 // 清除所有外设的中断挂起状态 NVIC_ClearPendingIRQ(ENCODERA_INT_IRQN); // 编码器A中断 NVIC_ClearPendingIRQ(ENCODERB_INT_IRQN); // 编码器B中断 NVIC_ClearPendingIRQ(UART_0_INST_INT_IRQN); // UART0串口中断 // 使能各外设的中断 NVIC_EnableIRQ(ENCODERA_INT_IRQN); // 开启编码器A中断 NVIC_EnableIRQ(ENCODERB_INT_IRQN); // 开启编码器B中断 NVIC_EnableIRQ(UART_0_INST_INT_IRQN); // 开启UART0中断 // 定时器ADC相关中断配置 NVIC_ClearPendingIRQ(TIMER_0_INST_INT_IRQN); // 清除定时器0中断挂起 NVIC_EnableIRQ(TIMER_0_INST_INT_IRQN); // 开启定时器0中断 NVIC_EnableIRQ(ADC12_VOLTAGE_INST_INT_IRQN); NVIC_EnableIRQ(ADC12_CCD_INST_INT_IRQN); OLED_Init(); // 初始化OLED显示屏 OLED_ShowString(1, 1, "Task Now:"); OLED_ShowString(2, 1, "Set:"); OLED_ShowString(3, 1, "yaw:"); DL_GPIO_togglePins(LED_PORT, LED_led_PIN); MPU6050_initialize(); DMP_Init(); // 主循环 // printf("Test delay 500us\n"); // hsu_time_delay_us(500); // printf("Test delay 500us end\n"); uint8_t main_task_timer = hsu_time_timer_create(10, true, main_task); hsu_time_timer_start(main_task_timer); uint8_t refresh_oled_timer = hsu_time_timer_create(5, true, refresh_oled); hsu_time_timer_start(refresh_oled_timer); uint8_t key_timer = hsu_time_timer_create(2, true, key); hsu_time_timer_start(key_timer); while (1) { hsu_time_timer_process(); RD_TSL(); // 读取CCD数据 Find_CCD_Median(); // 计算CCD数据中值 Read_DMP(); show_task_now(); if(DL_GPIO_readPins(KEY_S1_PORT,KEY_S1_PIN)) { delay_ms1(10); if(DL_GPIO_readPins(KEY_S1_PORT,KEY_S1_PIN)) { while(DL_GPIO_readPins(KEY_S1_PORT,KEY_S1_PIN)); DL_GPIO_togglePins(LED_PORT, LED_led_PIN); } } if(!DL_GPIO_readPins(KEY_S2_PORT,KEY_S2_PIN)) { delay_ms1(10); if(!DL_GPIO_readPins(KEY_S2_PORT,KEY_S2_PIN)) { while(!DL_GPIO_readPins(KEY_S2_PORT,KEY_S2_PIN)); DL_GPIO_togglePins(LED_PORT, LED_led_PIN); } } delay_ms1(100); // printf("L=%lld R=%lld YAW=%.1f\n", left_encoder, right_encoder, Yaw); } } void task_no(void); void task_1(void); void task_2(void); void task_3(void); void task_4(void); void main_task(void) { if (!(task_namespace.is)) return; printf("main task\n"); switch (task_namespace.state) { case BEGIN: task_no(); break; case T1: task_1(); break; case T2: task_2(); break; case T3: task_3(); break; case T4: task_4(); break; default: break; } switch (task_namespace.doing_what) { case STOP: Get_Target_Encoder(0, 0); break; case GO_STRAIGHT: if ((left_encoder * 1.f) < task_namespace.target) { Get_Target_Encoder(0.6, 0); // 提高速度到600mm/s } else { Get_Target_Encoder(0, 0); task_namespace.doing_what = STOP; task_namespace.finish = 1; } break; case GO_CCD: if (task_namespace.ccd_end_time < hsu_time_get_ms()) { Get_Target_Encoder(0, 0); task_namespace.doing_what = STOP; task_namespace.finish = 1; } else { CCD_Mode(); } break; case TURN_IN_PLACE: // 原地转向控制 if (task_namespace.target_yaw_diff != 0) { float current_yaw_diff = Yaw - task_namespace.start_yaw; // 处理角度跨越±180度的情况 if (current_yaw_diff > 180) { current_yaw_diff -= 360; } else if (current_yaw_diff < -180) { current_yaw_diff += 360; } printf("Turn: Start=%.1f Current=%.1f Diff=%.1f Target=%.1f\n", task_namespace.start_yaw, Yaw, current_yaw_diff, task_namespace.target_yaw_diff); // 检查是否达到目标角度 if ((task_namespace.target_yaw_diff > 0 && current_yaw_diff >= task_namespace.target_yaw_diff) || (task_namespace.target_yaw_diff < 0 && current_yaw_diff <= task_namespace.target_yaw_diff)) { Get_Target_Encoder(0, 0); // 停止转向 task_namespace.doing_what = STOP; task_namespace.finish = 1; } else { // 继续转向 float turn_speed = (task_namespace.target_yaw_diff > 0) ? 0.1 : -0.1; Get_Target_Encoder(0, turn_speed); } } break; case WAIT_ALERT: Get_Target_Encoder(0, 0); // 停车 if (hsu_time_get_ms() - task_namespace.alert_start_time > 1000) { // 声光提示1秒 task_namespace.doing_what = STOP; task_namespace.finish = 1; } break; default: break; } } void task_no(void) { return; } // 任务1:A点到B点直线行驶 void task_1(void) { if (!task_namespace.is_running) { task_namespace.is_running = 1; go_straight(3300); return; } if (task_namespace.finish) { sound_light_alert(); // 声光提示 reset_task_namespace(&task_namespace); } return; } // 任务2:A->B->C->D->A循环 void task_2(void) { if (!task_namespace.is_running) { task_namespace.is_running = 1; task_namespace.sub_task_stage = 0; task_namespace.finish = 1; return; } if (task_namespace.finish) { switch (task_namespace.sub_task_stage) { case 0: // 开始第一阶段:A到B go_straight(3300); break; case 1: // A到B完成,开始B到C弧线 sound_light_alert(); go_arc_ccd(3550); break; case 2: // B到C弧线完成,开始C到D直线 sound_light_alert(); //turn_in_place(-17.0f); go_straight(2980); break; case 3: // C到D完成,开始D到A弧线 sound_light_alert(); go_arc_ccd(3530); break; case 4: // D到A弧线完成,任务结束 sound_light_alert(); reset_task_namespace(&task_namespace); break; } task_namespace.finish = 0; task_namespace.sub_task_stage++; } return; } // 任务3:A->C->B->D->A循环 void task_3(void) { if (!task_namespace.is_running) { task_namespace.is_running = 1; task_namespace.sub_task_stage = 0; task_namespace.finish = 1; return; } if (task_namespace.finish) { switch (task_namespace.sub_task_stage) { case 0: turn_in_place(-31.0f); break; case 1: go_straight(4060); break; case 2: turn_in_place(30.0f); break; case 3: // 开始C到B弧线 sound_light_alert(); //go_straight(40); go_brc_ccd(3550); break; case 4: turn_in_place(36.0f); break; case 5: sound_light_alert(); go_straight(3985); break; case 6: turn_in_place(-40.0f); break; case 7: // 开始C到B弧线 sound_light_alert(); go_arc_ccd(3600); break; case 8: // D到A弧线完成,任务结束 sound_light_alert(); reset_task_namespace(&task_namespace); break; } task_namespace.finish = 0; task_namespace.sub_task_stage++; } return; } // 任务4:重复任务3路径4圈 void task_4(void) { if (!task_namespace.is_running) { task_namespace.is_running = 1; task_namespace.sub_task_stage = 0; task_namespace.finish = 1; return; } if (task_namespace.finish) { switch (task_namespace.sub_task_stage) { case 0: turn_in_place(-30.0f); break; case 1: go_straight(4045); break; case 2: turn_in_place(29.0f); break; case 3: // 开始C到B弧线 sound_light_alert(); //go_straight(40); go_brc_ccd(5000); break; case 4: turn_in_place(34.0f); break; case 5: sound_light_alert(); go_straight(4035); break; case 6: turn_in_place(-33.0f); break; case 7: // 开始C到B弧线 sound_light_alert(); go_arc_ccd(5000); break; case 8: // D到A弧线完成,任务结束 sound_light_alert(); reset_task_namespace(&task_namespace); break; } task_namespace.finish = 0; task_namespace.sub_task_stage++; } } void TIMER_0_INST_IRQHandler(void) { if (DL_TimerA_getPendingInterrupt(TIMER_0_INST)) { if (DL_TIMER_IIDX_ZERO) { Get_Velocity_From_Encoder(Get_Encoder_countA, Get_Encoder_countB); Get_Encoder_countA = Get_Encoder_countB = 0; MotorA.Motor_Pwm = Incremental_PI_Left(MotorA.Current_Encoder, MotorA.Target_Encoder); MotorB.Motor_Pwm = Incremental_PI_Right(MotorB.Current_Encoder, MotorB.Target_Encoder); if (!Flag_Stop) { Set_PWM(-MotorA.Motor_Pwm, -MotorB.Motor_Pwm); } else { Set_PWM(0, 0); } } } } uint32_t gpio_interrup1, gpio_interrup2; int64_t B1, B2, B3, B4; int64_t A1, A2, A3, A4; void GROUP1_IRQHandler(void) { // 获取中断信号 gpio_interrup1 = DL_GPIO_getEnabledInterruptStatus(ENCODERA_PORT, ENCODERA_E1A_PIN | ENCODERA_E1B_PIN); gpio_interrup2 = DL_GPIO_getEnabledInterruptStatus(ENCODERB_PORT, ENCODERB_E2A_PIN | ENCODERB_E2B_PIN); // encoderB if ((gpio_interrup1 & ENCODERA_E1A_PIN) == ENCODERA_E1A_PIN) { if (!DL_GPIO_readPins(ENCODERA_PORT, ENCODERA_E1B_PIN)) { right_encoder--; Get_Encoder_countB--; } else { right_encoder++; Get_Encoder_countB++; } } else if ((gpio_interrup1 & ENCODERA_E1B_PIN) == ENCODERA_E1B_PIN) { if (!DL_GPIO_readPins(ENCODERA_PORT, ENCODERA_E1A_PIN)) { right_encoder++; Get_Encoder_countB++; } else { right_encoder--; Get_Encoder_countB--; } } // encoderA if ((gpio_interrup2 & ENCODERB_E2A_PIN) == ENCODERB_E2A_PIN) { if (!DL_GPIO_readPins(ENCODERB_PORT, ENCODERB_E2B_PIN)) { left_encoder++; Get_Encoder_countA--; } else { left_encoder--; Get_Encoder_countA++; } } else if ((gpio_interrup2 & ENCODERB_E2B_PIN) == ENCODERB_E2B_PIN) { if (!DL_GPIO_readPins(ENCODERB_PORT, ENCODERB_E2A_PIN)) { left_encoder--; Get_Encoder_countA++; } else { left_encoder++; Get_Encoder_countA--; } } DL_GPIO_clearInterruptStatus(ENCODERA_PORT, ENCODERA_E1A_PIN | ENCODERA_E1B_PIN); DL_GPIO_clearInterruptStatus(ENCODERB_PORT, ENCODERB_E2A_PIN | ENCODERB_E2B_PIN); } // 直线行驶函数 void go_straight(int dis) { task_namespace.doing_what = GO_STRAIGHT; task_namespace.target = left_encoder + dis; task_namespace.finish = 0; } // 原地转向函数 void turn_in_place(float angle) { task_namespace.doing_what = TURN_IN_PLACE; task_namespace.start_yaw = Yaw; task_namespace.target_yaw_diff = angle; // 正值右转,负值左转 task_namespace.finish = 0; } // CCD巡线函数(需要外部条件结束) void go_ccd_line(void) { task_namespace.doing_what = GO_CCD; task_namespace.start_encoder = left_encoder; task_namespace.finish = 0; // 设置一个安全的最大距离,防止无限巡线 // 可以根据实际场地调整这个值 static uint32_t ccd_end_time = 0; if (ccd_end_time == 0) { ccd_end_time = hsu_time_get_ms(); } // 如果巡线时间超过10秒或距离超过2000mm,强制结束 if (hsu_time_get_ms() - ccd_end_time > 10000 || (left_encoder * 1.f - task_namespace.start_encoder) > 2000) { task_namespace.finish = 1; ccd_end_time = 0; } } // 弧线CCD巡线函数 void go_arc_ccd(hsu_time_t time) { task_namespace.doing_what = GO_CCD; task_namespace.start_encoder = left_encoder; task_namespace.ccd_end_time = hsu_time_get_ms() + time; } void go_brc_ccd(hsu_time_t time) { task_namespace.doing_what = GO_CCD; task_namespace.start_encoder = right_encoder; task_namespace.ccd_end_time = hsu_time_get_ms() + time; } // 声光提示函数 void sound_light_alert(void) { LED_ON(); DL_GPIO_setPins(BUZZY_PORT, BUZZY_PIN_PIN); uint32_t start_time = hsu_time_get_ms(); while (hsu_time_get_ms() - start_time < 1000) { // 空循环等待1秒 } //hsu_time_delay_ms(200); DL_GPIO_clearPins(BUZZY_PORT, BUZZY_PIN_PIN); LED_OFF(); } // callback void refresh_oled(void) { show_task_now(); } uint32_t key_get_tick_ms(void) { return hsu_time_get_ms(); } void key(void) { key_event_t event = key_scan(); switch (event) { case KEY_EVENT_SINGLE_CLICK: next_state(&task_namespace); break; case KEY_EVENT_DOUBLE_CLICK: task_namespace.is = 1; break; } }
07-20
keil编写,C语言,板子是M0G3507,根据现象问题情况更改代码: 按键S1双击后可以正常让“state”由0转变为1,显示值“state”从“0”变为“1”应该代表程序开始运行。但是对应的此时TASK情况的任务程序无法启动运行(例如在“TASK NOW:1”的时候应该对应直行“TASK1”的程序部分),但是“TASK1”程序未启动。 #include <math.h> // 添加数学库用于fabs函数 #include "board.h" #include "my_key.h" #include "my_time.h" #include "ti_msp_dl_config.h" #include "oled.h" void BUZZY_OFF(void) { DL_GPIO_setPins(BUZZY_PORT, BUZZY_PIN_PIN); } void BUZZY_ON(void) { DL_GPIO_clearPins(BUZZY_PORT, BUZZY_PIN_PIN); } void refresh_oled(void); void key(void); void go_straight(int dis); void go_arc_ccd(hsu_time_t); void go_brc_ccd(hsu_time_t); void turn_in_place(float angle); void sound_light_alert(void); void show_task_now(void); u8 Car_Mode = Diff_Car; int Motor_Left, Motor_Right; // 电机PWM变量 应是Motor的 u8 PID_Send; // 延时调参相关变量 float RC_Velocity = 200, RC_Turn_Velocity, Move_X, Move_Y, Move_Z, PS2_ON_Flag; // 遥控控制的速度 float Velocity_Left, Velocity_Right; // 车轮速度(mm/s) u16 test_num, show_cnt; float Voltage = 0; extern float Yaw; // 声明外部YAW角度变量 int64_t left_encoder = 0, right_encoder = 0; void SysTick_Handler(void) { hsu_time_systick_handler(); } typedef enum { BEGIN, T1, T2, T3, T4 } TaskState; typedef enum { STOP, GO_STRAIGHT, GO_CCD, TURN_IN_PLACE, WAIT_ALERT } DoingWhat; typedef struct __TASK_NAMESPACE { uint8_t is; uint8_t is_running; uint8_t finish; uint8_t sub_finish; uint8_t running_state; // 0: 停止, 1: 运行中 TaskState state; DoingWhat doing_what; float target; float vx; float vz; // 用于复杂任务 uint8_t sub_task_stage; // 子任务阶段 uint8_t lap_count; // 圈数计数 int64_t start_encoder; // 起始编码器值 uint32_t alert_start_time; // 声光提示开始时间 float start_yaw; // 起始YAW角度 float target_yaw_diff; // 目标YAW角度差 hsu_time_t ccd_end_time; } TaskNamespace; void reset_task_namespace(TaskNamespace *t) { t->is_running = 0; t->finish = 0; t->sub_finish = 0; t->state = BEGIN; t->doing_what = STOP; t->vx = 0; t->vz = 0; t->sub_task_stage = 0; t->lap_count = 0; t->start_encoder = left_encoder; t->alert_start_time = 0; t->start_yaw = 0; t->target_yaw_diff = 0; t->ccd_end_time = 0; t->is = 0; } void next_state(TaskNamespace *t) { TaskState last_state = t->state; reset_task_namespace(t); if (last_state < T4) { t->state = last_state + 1; } } TaskNamespace task_namespace; void show_task_now(void) { //OLED_ShowString(0, 0, "Task Now:"); switch (task_namespace.state) { case BEGIN: OLED_ShowString(1, 10,"0"); break; case T1: OLED_ShowString(1, 10,"1"); break; case T2: OLED_ShowString(1, 10,"2"); break; case T3: OLED_ShowString(1, 10,"3"); break; case T4: OLED_ShowString(1, 10,"4"); break; default: break; } } void main_task(void); int main(void) { // 系统初始化 SYSCFG_DL_init(); // 初始化系统配置 hsu_time_init(); // 时间 // 清除所有外设的中断挂起状态 NVIC_ClearPendingIRQ(ENCODERA_INT_IRQN); // 编码器A中断 NVIC_ClearPendingIRQ(ENCODERB_INT_IRQN); // 编码器B中断 NVIC_ClearPendingIRQ(UART_0_INST_INT_IRQN); // UART0串口中断 // 使能各外设的中断 NVIC_EnableIRQ(ENCODERA_INT_IRQN); // 开启编码器A中断 NVIC_EnableIRQ(ENCODERB_INT_IRQN); // 开启编码器B中断 NVIC_EnableIRQ(UART_0_INST_INT_IRQN); // 开启UART0中断 reset_task_namespace(&task_namespace); task_namespace.state = BEGIN; // 明确设置初始状态 // 定时器ADC相关中断配置 NVIC_ClearPendingIRQ(TIMER_0_INST_INT_IRQN); // 清除定时器0中断挂起 NVIC_EnableIRQ(TIMER_0_INST_INT_IRQN); // 开启定时器0中断 NVIC_EnableIRQ(ADC12_VOLTAGE_INST_INT_IRQN); NVIC_EnableIRQ(ADC12_CCD_INST_INT_IRQN); OLED_Init(); // 初始化OLED显示屏 OLED_ShowString(1, 1, "Task Now:"); OLED_ShowString(2, 1, "state:"); OLED_ShowString(3, 1, "yaw:"); //MPU6050_initialize(); //DMP_Init(); BUZZY_ON(); // 主循环 // printf("Test delay 500us\n"); // hsu_time_delay_us(500); // printf("Test delay 500us end\n"); uint8_t main_task_timer = hsu_time_timer_create(10, true, main_task); hsu_time_timer_start(main_task_timer); uint8_t refresh_oled_timer = hsu_time_timer_create(5, true, refresh_oled); hsu_time_timer_start(refresh_oled_timer); uint8_t key_timer = hsu_time_timer_create(2, true, key); hsu_time_timer_start(key_timer); while (1) { hsu_time_timer_process(); RD_TSL(); // 读取CCD数据 Find_CCD_Median(); // 计算CCD数据中值 Read_DMP(); show_task_now(); //DL_GPIO_togglePins(LED_PORT, LED_led_PIN); // printf("L=%lld R=%lld YAW=%.1f\n", left_encoder, right_encoder, Yaw); } } void task_no(void); void task_1(void); void task_2(void); void task_3(void); void task_4(void); void main_task(void) { if (!(task_namespace.is)) return; printf("main task\n"); switch (task_namespace.state) { case BEGIN: task_no(); break; case T1: task_1(); break; case T2: task_2(); break; case T3: task_3(); break; case T4: task_4(); break; default: break; } switch (task_namespace.doing_what) { case STOP: Get_Target_Encoder(0, 0); break; case GO_STRAIGHT: if ((left_encoder * 1.f) < task_namespace.target) { Get_Target_Encoder(0.6, 0); // 提高速度到600mm/s } else { Get_Target_Encoder(0, 0); task_namespace.doing_what = STOP; task_namespace.finish = 1; } break; case GO_CCD: if (task_namespace.ccd_end_time < hsu_time_get_ms()) { Get_Target_Encoder(0, 0); task_namespace.doing_what = STOP; task_namespace.finish = 1; } else { CCD_Mode(); } break; case TURN_IN_PLACE: // 原地转向控制 if (task_namespace.target_yaw_diff != 0) { float current_yaw_diff = Yaw - task_namespace.start_yaw; // 处理角度跨越±180度的情况 if (current_yaw_diff > 180) { current_yaw_diff -= 360; } else if (current_yaw_diff < -180) { current_yaw_diff += 360; } printf("Turn: Start=%.1f Current=%.1f Diff=%.1f Target=%.1f\n", task_namespace.start_yaw, Yaw, current_yaw_diff, task_namespace.target_yaw_diff); // 检查是否达到目标角度 if ((task_namespace.target_yaw_diff > 0 && current_yaw_diff >= task_namespace.target_yaw_diff) || (task_namespace.target_yaw_diff < 0 && current_yaw_diff <= task_namespace.target_yaw_diff)) { Get_Target_Encoder(0, 0); // 停止转向 task_namespace.doing_what = STOP; task_namespace.finish = 1; } else { // 继续转向 float turn_speed = (task_namespace.target_yaw_diff > 0) ? 0.1 : -0.1; Get_Target_Encoder(0, turn_speed); } } break; case WAIT_ALERT: Get_Target_Encoder(0, 0); // 停车 if (hsu_time_get_ms() - task_namespace.alert_start_time > 1000) { // 声光提示1秒 task_namespace.doing_what = STOP; task_namespace.finish = 1; } break; default: break; } } void task_no(void) { return; } // 任务1:A点到B点直线行驶 void task_1(void) { if (!task_namespace.is_running) { task_namespace.is_running = 1; //task_namespace.running_state = 1; // 设置为运行状态 go_straight(3300); return; } if (task_namespace.finish) { sound_light_alert(); // 声光提示 reset_task_namespace(&task_namespace); task_namespace.running_state = 0; // 重置为停止状态 } return; } // 任务2:A->B->C->D->A循环 void task_2(void) { if (!task_namespace.is_running) { task_namespace.is_running = 1; task_namespace.sub_task_stage = 0; task_namespace.finish = 1; return; } if (task_namespace.finish) { switch (task_namespace.sub_task_stage) { case 0: // 开始第一阶段:A到B go_straight(3300); break; case 1: // A到B完成,开始B到C弧线 sound_light_alert(); go_arc_ccd(3550); break; case 2: // B到C弧线完成,开始C到D直线 sound_light_alert(); //turn_in_place(-17.0f); go_straight(2980); break; case 3: // C到D完成,开始D到A弧线 sound_light_alert(); go_arc_ccd(3530); break; case 4: // D到A弧线完成,任务结束 sound_light_alert(); reset_task_namespace(&task_namespace); break; } task_namespace.finish = 0; task_namespace.sub_task_stage++; } return; } // 任务3:A->C->B->D->A循环 void task_3(void) { if (!task_namespace.is_running) { task_namespace.is_running = 1; task_namespace.sub_task_stage = 0; task_namespace.finish = 1; return; } if (task_namespace.finish) { switch (task_namespace.sub_task_stage) { case 0: turn_in_place(-31.0f); break; case 1: go_straight(4060); break; case 2: turn_in_place(30.0f); break; case 3: // 开始C到B弧线 sound_light_alert(); //go_straight(40); go_brc_ccd(3550); break; case 4: turn_in_place(36.0f); break; case 5: sound_light_alert(); go_straight(3985); break; case 6: turn_in_place(-40.0f); break; case 7: // 开始C到B弧线 sound_light_alert(); go_arc_ccd(3600); break; case 8: // D到A弧线完成,任务结束 sound_light_alert(); reset_task_namespace(&task_namespace); break; } task_namespace.finish = 0; task_namespace.sub_task_stage++; } return; } // 任务4:重复任务3路径4圈 void task_4(void) { if (!task_namespace.is_running) { task_namespace.is_running = 1; task_namespace.sub_task_stage = 0; task_namespace.finish = 1; return; } if (task_namespace.finish) { switch (task_namespace.sub_task_stage) { case 0: turn_in_place(-30.0f); break; case 1: go_straight(4045); break; case 2: turn_in_place(29.0f); break; case 3: // 开始C到B弧线 sound_light_alert(); //go_straight(40); go_brc_ccd(5000); break; case 4: turn_in_place(34.0f); break; case 5: sound_light_alert(); go_straight(4035); break; case 6: turn_in_place(-33.0f); break; case 7: // 开始C到B弧线 sound_light_alert(); go_arc_ccd(5000); break; case 8: // D到A弧线完成,任务结束 sound_light_alert(); reset_task_namespace(&task_namespace); break; } task_namespace.finish = 0; task_namespace.sub_task_stage++; } } void TIMER_0_INST_IRQHandler(void) { if (DL_TimerA_getPendingInterrupt(TIMER_0_INST)) { if (DL_TIMER_IIDX_ZERO) { Get_Velocity_From_Encoder(Get_Encoder_countA, Get_Encoder_countB); Get_Encoder_countA = Get_Encoder_countB = 0; MotorA.Motor_Pwm = Incremental_PI_Left(MotorA.Current_Encoder, MotorA.Target_Encoder); MotorB.Motor_Pwm = Incremental_PI_Right(MotorB.Current_Encoder, MotorB.Target_Encoder); if (!Flag_Stop) { Set_PWM(-MotorA.Motor_Pwm, -MotorB.Motor_Pwm); } else { Set_PWM(0, 0); } } } } uint32_t gpio_interrup1, gpio_interrup2; int64_t B1, B2, B3, B4; int64_t A1, A2, A3, A4; void GROUP1_IRQHandler(void) { // 获取中断信号 gpio_interrup1 = DL_GPIO_getEnabledInterruptStatus(ENCODERA_PORT, ENCODERA_E1A_PIN | ENCODERA_E1B_PIN); gpio_interrup2 = DL_GPIO_getEnabledInterruptStatus(ENCODERB_PORT, ENCODERB_E2A_PIN | ENCODERB_E2B_PIN); // encoderB if ((gpio_interrup1 & ENCODERA_E1A_PIN) == ENCODERA_E1A_PIN) { if (!DL_GPIO_readPins(ENCODERA_PORT, ENCODERA_E1B_PIN)) { right_encoder--; Get_Encoder_countB--; } else { right_encoder++; Get_Encoder_countB++; } } else if ((gpio_interrup1 & ENCODERA_E1B_PIN) == ENCODERA_E1B_PIN) { if (!DL_GPIO_readPins(ENCODERA_PORT, ENCODERA_E1A_PIN)) { right_encoder++; Get_Encoder_countB++; } else { right_encoder--; Get_Encoder_countB--; } } // encoderA if ((gpio_interrup2 & ENCODERB_E2A_PIN) == ENCODERB_E2A_PIN) { if (!DL_GPIO_readPins(ENCODERB_PORT, ENCODERB_E2B_PIN)) { left_encoder++; Get_Encoder_countA--; } else { left_encoder--; Get_Encoder_countA++; } } else if ((gpio_interrup2 & ENCODERB_E2B_PIN) == ENCODERB_E2B_PIN) { if (!DL_GPIO_readPins(ENCODERB_PORT, ENCODERB_E2A_PIN)) { left_encoder--; Get_Encoder_countA++; } else { left_encoder++; Get_Encoder_countA--; } } DL_GPIO_clearInterruptStatus(ENCODERA_PORT, ENCODERA_E1A_PIN | ENCODERA_E1B_PIN); DL_GPIO_clearInterruptStatus(ENCODERB_PORT, ENCODERB_E2A_PIN | ENCODERB_E2B_PIN); } // 直线行驶函数 void go_straight(int dis) { task_namespace.doing_what = GO_STRAIGHT; task_namespace.target = left_encoder + dis; task_namespace.finish = 0; } // 原地转向函数 void turn_in_place(float angle) { task_namespace.doing_what = TURN_IN_PLACE; task_namespace.start_yaw = Yaw; task_namespace.target_yaw_diff = angle; // 正值右转,负值左转 task_namespace.finish = 0; } // CCD巡线函数(需要外部条件结束) void go_ccd_line(void) { task_namespace.doing_what = GO_CCD; task_namespace.start_encoder = left_encoder; task_namespace.finish = 0; // 设置一个安全的最大距离,防止无限巡线 // 可以根据实际场地调整这个值 static uint32_t ccd_end_time = 0; if (ccd_end_time == 0) { ccd_end_time = hsu_time_get_ms(); } // 如果巡线时间超过10秒或距离超过2000mm,强制结束 if (hsu_time_get_ms() - ccd_end_time > 10000 || (left_encoder * 1.f - task_namespace.start_encoder) > 2000) { task_namespace.finish = 1; ccd_end_time = 0; } } // 弧线CCD巡线函数 void go_arc_ccd(hsu_time_t time) { task_namespace.doing_what = GO_CCD; task_namespace.start_encoder = left_encoder; task_namespace.ccd_end_time = hsu_time_get_ms() + time; } void go_brc_ccd(hsu_time_t time) { task_namespace.doing_what = GO_CCD; task_namespace.start_encoder = right_encoder; task_namespace.ccd_end_time = hsu_time_get_ms() + time; } // 声光提示函数 void sound_light_alert(void) { DL_GPIO_togglePins(LED_PORT, LED_led_PIN); DL_GPIO_setPins(BUZZY_PORT, BUZZY_PIN_PIN); uint32_t start_time = hsu_time_get_ms(); while (hsu_time_get_ms() - start_time < 1000) { // 空循环等待1秒 } //hsu_time_delay_ms(200); DL_GPIO_togglePins(LED_PORT, LED_led_PIN); DL_GPIO_clearPins(BUZZY_PORT, BUZZY_PIN_PIN); } // callback void refresh_oled(void) { show_task_now(); OLED_ShowString(2, 1, "state:"); if (task_namespace.running_state) { OLED_ShowString(2, 7, "1"); // 运行中 } else { OLED_ShowString(2, 7, "0"); // 停止 } } uint32_t key_get_tick_ms(void) { return hsu_time_get_ms(); } void key(void) { key_event_t event = key_scan(); uint8_t key_value = key_read_pin(); // 获取按键状态 //S1 switch (event) { case KEY_EVENT_SINGLE_CLICK: next_state(&task_namespace); break; case KEY_EVENT_DOUBLE_CLICK: task_namespace.is = 1; task_namespace.running_state = 1; break; } } //S2 /*if(DL_GPIO_readPins(KEY_S2_PORT,KEY_S2_PIN)) { delay_ms(10); //°´&frac14;üÑÓʱÏû¶¶ if(DL_GPIO_readPins(KEY_S2_PORT,KEY_S2_PIN)) { while(DL_GPIO_readPins(KEY_S2_PORT,KEY_S2_PIN)); // S2按下时启动当前任务 task_namespace.is = 1; // 设置状态为1(运行中) task_namespace.running_state = 1; } }*/
07-21
{ "version": "1.0.0", "name": "Cleaning_Robot_Path_Planning", "description": "清洁机器人清扫路径规划系统完整原理图", "author": "DoubaoAssistant", "date": "2025-07-07", "revision": "1.1", "components": [ { "id": "U1", "name": "STM32F103ZET6", "position": { "x": 100, "y": 100 }, "rotation": 0, "library": "cleaning_robot_lib", "pins": [ { "name": "VCC_3.3V", "position": { "x": 10, "y": 10 } }, { "name": "GND", "position": { "x": 10, "y": 20 } }, { "name": "PA0", "position": { "x": 30, "y": 10 }, "function": "超声波传感器1 Trig" }, { "name": "PA1", "position": { "x": 30, "y": 20 }, "function": "超声波传感器1 Echo" }, { "name": "PA2", "position": { "x": 30, "y": 30 }, "function": "超声波传感器2 Trig" }, { "name": "PA3", "position": { "x": 30, "y": 40 }, "function": "超声波传感器2 Echo" }, { "name": "PB0", "position": { "x": 40, "y": 10 }, "function": "红外传感器1" }, { "name": "PB1", "position": { "x": 40, "y": 20 }, "function": "红外传感器2" }, { "name": "PB2", "position": { "x": 40, "y": 30 }, "function": "红外传感器3" }, { "name": "PB3", "position": { "x": 40, "y": 40 }, "function": "红外传感器4" }, { "name": "SPI1_MOSI", "position": { "x": 50, "y": 10 }, "function": "NRF24L01" }, { "name": "SPI1_MISO", "position": { "x": 50, "y": 20 }, "function": "NRF24L01" }, { "name": "SPI1_SCK", "position": { "x": 50, "y": 30 }, "function": "NRF24L01" }, { "name": "SPI1_NSS", "position": { "x": 50, "y": 40 }, "function": "NRF24L01" }, { "name": "TIM1_CH1", "position": { "x": 60, "y": 10 }, "function": "左电机PWM" }, { "name": "TIM1_CH2", "position": { "x": 60, "y": 20 }, "function": "右电机PWM" }, { "name": "TIM2_CH1", "position": { "x": 60, "y": 30 }, "function": "滚刷PWM" }, { "name": "I2C1_SDA", "position": { "x": 70, "y": 10 }, "function": "MPU6050" }, { "name": "I2C1_SCL", "position": { "x": 70, "y": 20 }, "function": "MPU6050" }, { "name": "USART1_TX", "position": { "x": 70, "y": 30 }, "function": "调试串口" }, { "name": "USART1_RX", "position": { "x": 70, "y": 40 }, "function": "调试串口" } ] }, { "id": "S1", "name": "HC_SR04", "position": { "x": 200, "y": 80 }, "rotation": 0, "library": "cleaning_robot_lib", "pins": [ { "name": "VCC", "position": { "x": 10, "y": 10 } }, { "name": "GND", "position": { "x": 20, "y": 10 } }, { "name": "Trig", "position": { "x": 30, "y": 10 } }, { "name": "Echo", "position": { "x": 40, "y": 10 } } ] }, { "id": "S2", "name": "HC_SR04", "position": { "x": 200, "y": 120 }, "rotation": 0, "library": "cleaning_robot_lib", "pins": [ { "name": "VCC", "position": { "x": 10, "y": 10 } }, { "name": "GND", "position": { "x": 20, "y": 10 } }, { "name": "Trig", "position": { "x": 30, "y": 10 } }, { "name": "Echo", "position": { "x": 40, "y": 10 } } ] }, { "id": "IR1", "name": "IR_SENSOR", "position": { "x": 250, "y": 70 }, "rotation": 0, "library": "cleaning_robot_lib", "pins": [ { "name": "VCC", "position": { "x": 10, "y": 10 } }, { "name": "GND", "position": { "x": 20, "y": 10 } }, { "name": "OUT", "position": { "x": 15, "y": 5 } } ] }, { "id": "IR2", "name": "IR_SENSOR", "position": { "x": 250, "y": 90 }, "rotation": 0, "library": "cleaning_robot_lib", "pins": [ { "name": "VCC", "position": { "x": 10, "y": 10 } }, { "name": "GND", "position": { "x": 20, "y": 10 } }, { "name": "OUT", "position": { "x": 15, "y": 5 } } ] }, { "id": "IR3", "name": "IR_SENSOR", "position": { "x": 250, "y": 110 }, "rotation": 0, "library": "cleaning_robot_lib", "pins": [ { "name": "VCC", "position": { "x": 10, "y": 10 } }, { "name": "GND", "position": { "x": 20, "y": 10 } }, { "name": "OUT", "position": { "x": 15, "y": 5 } } ] }, { "id": "IR4", "name": "IR_SENSOR", "position": { "x": 250, "y": 130 }, "rotation": 0, "library": "cleaning_robot_lib", "pins": [ { "name": "VCC", "position": { "x": 10, "y": 10 } }, { "name": "GND", "position": { "x": 20, "y": 10 } }, { "name": "OUT", "position": { "x": 15, "y": 5 } } ] }, { "id": "U2", "name": "TB6612FNG", "position": { "x": 300, "y": 100 }, "rotation": 0, "library": "cleaning_robot_lib", "pins": [ { "name": "VM", "position": { "x": 10, "y": 10 }, "function": "电机电源(12V)" }, { "name": "VCC", "position": { "x": 10, "y": 20 }, "function": "逻辑电源(3.3V)" }, { "name": "GND", "position": { "x": 10, "y": 30 } }, { "name": "AIN1", "position": { "x": 25, "y": 10 }, "function": "左电机方向1" }, { "name": "AIN2", "position": { "x": 25, "y": 20 }, "function": "左电机方向2" }, { "name": "BIN1", "position": { "x": 25, "y": 30 }, "function": "右电机方向1" }, { "name": "BIN2", "position": { "x": 25, "y": 40 }, "function": "右电机方向2" }, { "name": "PWMA", "position": { "x": 40, "y": 10 }, "function": "左电机PWM" }, { "name": "PWMB", "position": { "x": 40, "y": 20 }, "function": "右电机PWM" }, { "name": "STBY", "position": { "x": 40, "y": 30 }, "function": "待机控制" }, { "name": "AO1", "position": { "x": 60, "y": 10 }, "function": "左电机输出1" }, { "name": "AO2", "position": { "x": 60, "y": 20 }, "function": "左电机输出2" }, { "name": "BO1", "position": { "x": 60, "y": 30 }, "function": "右电机输出1" }, { "name": "BO2", "position": { "x": 60, "y": 40 }, "function": "右电机输出2" } ] }, { "id": "U3", "name": "DRV8833", "position": { "x": 300, "y": 150 }, "rotation": 0, "library": "cleaning_robot_lib", "pins": [ { "name": "VM", "position": { "x": 10, "y": 10 }, "function": "电机电源(12V)" }, { "name": "VCC", "position": { "x": 10, "y": 20 }, "function": "逻辑电源(3.3V)" }, { "name": "GND", "position": { "x": 10, "y": 30 } }, { "name": "IN1", "position": { "x": 25, "y": 10 }, "function": "滚刷方向1" }, { "name": "IN2", "position": { "x": 25, "y": 20 }, "function": "滚刷方向2" }, { "name": "ENA", "position": { "x": 25, "y": 30 }, "function": "滚刷PWM" }, { "name": "OUT1", "position": { "x": 45, "y": 10 }, "function": "滚刷输出1" }, { "name": "OUT2", "position": { "x": 45, "y": 20 }, "function": "滚刷输出2" } ] }, { "id": "U4", "name": "MPU6050", "position": { "x": 350, "y": 100 }, "rotation": 0, "library": "cleaning_robot_lib", "pins": [ { "name": "VCC", "position": { "x": 10, "y": 10 } }, { "name": "GND", "position": { "x": 20, "y": 10 } }, { "name": "SDA", "position": { "x": 10, "y": 15 } }, { "name": "SCL", "position": { "x": 20, "y": 15 } }, { "name": "INT", "position": { "x": 10, "y": 20 }, "function": "中断输出" } ] }, { "id": "U5", "name": "NRF24L01", "position": { "x": 350, "y": 150 }, "rotation": 0, "library": "cleaning_robot_lib", "pins": [ { "name": "VCC", "position": { "x": 10, "y": 10 } }, { "name": "GND", "position": { "x": 20, "y": 10 } }, { "name": "CE", "position": { "x": 10, "y": 15 }, "function": "芯片使能" }, { "name": "CSN", "position": { "x": 20, "y": 15 }, "function": "SPI片选" }, { "name": "SCK", "position": { "x": 30, "y": 15 }, "function": "SPI时钟" }, { "name": "MOSI", "position": { "x": 40, "y": 15 }, "function": "SPI主出从入" }, { "name": "MISO", "position": { "x": 10, "y": 20 }, "function": "SPI主入从出" }, { "name": "IRQ", "position": { "x": 20, "y": 20 }, "function": "中断输出" } ] }, { "id": "U6", "name": "POWER_MANAGER", "position": { "x": 150, "y": 50 }, "rotation": 0, "library": "cleaning_robot_lib", "pins": [ { "name": "VBAT", "position": { "x": 10, "y": 10 }, "function": "电池输入(18650锂电池组)" }, { "name": "GND", "position": { "x": 10, "y": 20 } }, { "name": "5V_OUT", "position": { "x": 10, "y": 30 }, "function": "5V输出" }, { "name": "3V3_OUT", "position": { "x": 10, "y": 40 }, "function": "3.3V输出" }, { "name": "CHG", "position": { "x": 30, "y": 10 }, "function": "充电指示" }, { "name": "PWR_EN", "position": { "x": 30, "y": 20 }, "function": "电源使能" } ] }, { "id": "U7", "name": "BATTERY_PROTECTION", "position": { "x": 100, "y": 50 }, "rotation": 0, "library": "cleaning_robot_lib", "pins": [ { "name": "BATT+", "position": { "x": 10, "y": 10 }, "function": "电池正极" }, { "name": "BATT-", "position": { "x": 10, "y": 20 }, "function": "电池负极" }, { "name": "OUT+", "position": { "x": 30, "y": 10 }, "function": "输出正极" }, { "name": "OUT-", "position": { "x": 30, "y": 20 }, "function": "输出负极" } ] } ], "nets": [ { "name": "3V3", "connections": [ { "component": "U1", "pin": "VCC_3.3V" }, { "component": "U4", "pin": "VCC" }, { "component": "U5", "pin": "VCC" }, { "component": "U2", "pin": "VCC" }, { "component": "U3", "pin": "VCC" }, { "component": "U6", "pin": "3V3_OUT" } ] }, { "name": "GND", "connections": [ { "component": "U1", "pin": "GND" }, { "component": "S1", "pin": "GND" }, { "component": "S2", "pin": "GND" }, { "component": "IR1", "pin": "GND" }, { "component": "IR2", "pin": "GND" }, { "component": "IR3", "pin": "GND" }, { "component": "IR4", "pin": "GND" }, { "component": "U2", "pin": "GND" }, { "component": "U3", "pin": "GND" }, { "component": "U4", "pin": "GND" }, { "component": "U5", "pin": "GND" }, { "component": "U6", "pin": "GND" }, { "component": "U7", "pin": "OUT-" } ] }, { "name": "5V", "connections": [ { "component": "S1", "pin": "VCC" }, { "component": "S2", "pin": "VCC" }, { "component": "IR1", "pin": "VCC" }, { "component": "IR2", "pin": "VCC" }, { "component": "IR3", "pin": "VCC" }, { "component": "IR4", "pin": "VCC" }, { "component": "U6", "pin": "5V_OUT" } ] }, { "name": "VBAT", "connections": [ { "component": "U7", "pin": "BATT+" }, { "component": "U6", "pin": "VBAT" } ] }, { "name": "USART1_TX", "connections": [ { "component": "U1", "pin": "USART1_TX" } ] }, { "name": "USART1_RX", "connections": [ { "component": "U1", "pin": "USART1_RX" } ] }, { "name": "ULTRASONIC1_TRIG", "connections": [ { "component": "U1", "pin": "PA0" }, { "component": "S1", "pin": "Trig" } ] }, { "name": "ULTRASONIC1_ECHO", "connections": [ { "component": "U1", "pin": "PA1" }, { "component": "S1", "pin": "Echo" } ] }, { "name": "ULTRASONIC2_TRIG", "connections": [ { "component": "U1", "pin": "PA2" }, { "component": "S2", "pin": "Trig" } ] }, { "name": "ULTRASONIC2_ECHO", "connections": [ { "component": "U1", "pin": "PA3" }, { "component": "S2", "pin": "Echo" } ] }, { "name": "IR_SENSOR1", "connections": [ { "component": "U1", "pin": "PB0" }, { "component": "IR1", "pin": "OUT" } ] }, { "name": "IR_SENSOR2", "connections": [ { "component": "U1", "pin": "PB1" }, { "component": "IR2", "pin": "OUT" } ] }, { "name": "IR_SENSOR3", "connections": [ { "component": "U1", "pin": "PB2" }, { "component": "IR3", "pin": "OUT" } ] }, { "name": "IR_SENSOR4", "connections": [ { "component": "U1", "pin": "PB3" }, { "component": "IR4", "pin": "OUT" } ] }, { "name": "LEFT_MOTOR_PWM", "connections": [ { "component": "U1", "pin": "TIM1_CH1" }, { "component": "U2", "pin": "PWMA" } ] }, { "name": "RIGHT_MOTOR_PWM", "connections": [ { "component": "U1", "pin": "TIM1_CH2" }, { "component": "U2", "pin": "PWMB" } ] }, { "name": "LEFT_MOTOR_DIR1", "connections": [ { "component": "U1", "pin": "PA4" }, { "component": "U2", "pin": "AIN1" } ] }, { "name": "LEFT_MOTOR_DIR2", "connections": [ { "component": "U1", "pin": "PA5" }, { "component": "U2", "pin": "AIN2" } ] }, { "name": "RIGHT_MOTOR_DIR1", "connections": [ { "component": "U1", "pin": "PA6" }, { "component": "U2", "pin": "BIN1" } ] }, { "name": "RIGHT_MOTOR_DIR2", "connections": [ { "component": "U1", "pin": "PA7" }, { "component": "U2", "pin": "BIN2" } ] }, { "name": "BRUSH_MOTOR_PWM", "connections": [ { "component": "U1", "pin": "TIM2_CH1" }, { "component": "U3", "pin": "ENA" } ] }, { "name": "BRUSH_MOTOR_DIR1", "connections": [ { "component": "U1", "pin": "PB4" }, { "component": "U3", "pin": "IN1" } ] }, { "name": "BRUSH_MOTOR_DIR2", "connections": [ { "component": "U1", "pin": "PB5" }, { "component": "U3", "pin": "IN2" } ] }, { "name": "I2C1_SDA", "connections": [ { "component": "U1", "pin": "I2C1_SDA" }, { "component": "U4", "pin": "SDA" } ] }, { "name": "I2C1_SCL", "connections": [ { "component": "U1", "pin": "I2C1_SCL" }, { "component": "U4", "pin": "SCL" } ] }, { "name": "NRF_CE", "connections": [ { "component": "U1", "pin": "PB6" }, { "component": "U5", "pin": "CE" } ] }, { "name": "NRF_CSN", "connections": [ { "component": "U1", "pin": "SPI1_NSS" }, { "component": "U5", "pin": "CSN" } ] }, { "name": "SPI1_SCK", "connections": [ { "component": "U1", "pin": "SPI1_SCK" }, { "component": "U5", "pin": "SCK" } ] }, { "name": "SPI1_MOSI", "connections": [ { "component": "U1", "pin": "SPI1_MOSI" }, { "component": "U5", "pin": "MOSI" } ] }, { "name": "SPI1_MISO", "connections": [ { "component": "U1", "pin": "SPI1_MISO" }, { "component": "U5", "pin": "MISO" } ] }, { "name": "MOTOR_POWER", "connections": [ { "component": "U6", "pin": "VBAT" }, { "component": "U2", "pin": "VM" }, { "component": "U3", "pin": "VM" } ] }, { "name": "POWER_ENABLE", "connections": [ { "component": "U1", "pin": "PC13" }, { "component": "U6", "pin": "PWR_EN" } ] } ] }
07-09
<think>首先,用户的问题是关于清洁机器人清扫路径规划系统的完整原理图及其硬件组件连接方式。具体来说,用户提到了STM32F103ZET6微控制器,所以我需要基于这个来回答。 回顾系统级指令: - 所有行内数学表达式必须使用$...$格式。 - 独立公式必须使用$$...$$格式并单独成段。 - LaTeX语法正确。 - 使用中文回答。 - 生成相关问题。 - 在回答中引用的段落末尾自然地添加引用标识,例如[^1]。 用户可见层: - 回答结构清晰,帮助用户逐步解决问题。 - 尽量保证回答真实可靠。 参考站内引用: - 引用[1]: STM32F103ZET6与超声波模块的距离测量程序实现。 - 引用[2]: 基于STM32F103ZET6设计的遥控小车(GPS+温湿度+手机APP)。 - 引用[3]: HCSR04超声波模块工作原理。 这些引用与用户的问题相关,因为清洁机器人可能涉及超声波传感器用于避障或距离测量。用户的需求是获取原理图硬件连接方式。 我的回答结构: 1. **介绍系统**:简要解释清洁机器人路径规划系统的基本原理。 2. **硬件组件**:列出主要硬件组件,包括STM32F103ZET6微控制器。 3. **原理图**:描述完整的原理图,包括连接方式。 4. **连接方式**:详细说明组件如何连接到微控制器。 5. **相关问题**:生成相关问题。 确保回答真实可靠:基于通用知识,因为我没有具体的原理图文件。我会描述一个典型的基于STM32F103ZET6的清洁机器人系统。 关键组件: - **微控制器**:STM32F103ZET6(核心)。 - **传感器**:超声波传感器(如HCSR04用于避障)、红外传感器、碰撞传感器、编码器(用于轮子速度测量)。 - **执行器**:直流电机(用于驱动轮子)、电机驱动器(如L298N)。 - **其他**:电源管理、Wi-Fi或蓝牙模块(用于远程控制)、显示屏(可选)。 - **路径规划相关**:可能涉及GPS模块(从引用[2]),但清洁机器人通常使用室内定位如IMU或编码器。 原理图描述: - 使用方框图或文字描述连接。 - 例如:STM32F103ZET6连接到各种传感器驱动器。 连接方式: - **GPIO引脚**:用于数字输入/输出。 - **ADC引脚**:用于模拟传感器。 - **定时器**:用于PWM控制电机。 - **串口**:用于通信模块。 引用相关引用: - 引用[1]:超声波模块连接。 - 引用[3]:超声波工作原理。 - 引用[2]:GPS温湿度传感器(可能用于环境监测)。 在回答中自然地添加引用标识。 生成相关问题: - 在末尾添加"§§相关问题§§"部分。 回答语言:中文。 逐步回答: 1. **介绍清洁机器人路径规划系统**:解释路径规划如何工作。 2. **硬件组件列表**:列出主要组件。 3. **原理图概述**:描述整体框图。 4. **详细连接方式**:针对每个组件说明如何连接到STM32。 5. **总结**。 6. **相关问题**。</think>### 清洁机器人路径规划系统原理图及硬件组件连接方式 在本回答中,我将基于STM32F103ZET6微控制器,详细解释清洁机器人清扫路径规划系统的完整原理图硬件组件连接方式。该系统通过传感器采集环境数据,微控制器执行路径规划算法(如A*算法或Dijkstra算法),并驱动电机实现高效清扫。路径规划的核心是优化清扫路径以覆盖所有区域,同时避开障碍物。原理图设计需考虑电源管理、传感器接口、执行器控制等模块。以下内容基于通用嵌入式系统设计原则参考引用资料,确保真实可靠。 #### 1. **系统整体原理图概述** 清洁机器人路径规划系统的原理图采用模块化设计,以STM32F103ZET6为核心,通过GPIO、ADC、定时器等接口连接各类传感器执行器。整体框图如下: ``` +-------------------+ +-------------------+ +-------------------+ | 传感器模块 | | STM32F103ZET6 | | 执行器模块 | | (超声波、红外等) |<----->| (微控制器) |<----->| (电机、驱动器等) | +-------------------+ +-------------------+ +-------------------+ ^ | ^ | v | +-------------------+ +-------------------+ +-------------------+ | 环境感知 | | 路径规划算法 | | 电源管理模块 | | (如避障检测) | | (运行在MCU) | | (电池、稳压器) | +-------------------+ +-------------------+ +-------------------+ ``` - **关键点**: - STM32F103ZET6作为主控单元,处理传感器数据、运行路径规划算法(例如,使用贪心算法优化路径:$ \min \sum_{i=1}^{n} d_i $,其中 $ d_i $ 表示路径段距离),并输出控制信号。 - 传感器模块提供环境数据,用于实时路径调整。 - 执行器模块驱动轮子移动,实现清扫动作。 - 电源模块确保系统稳定供电,通常使用锂电池DC-DC稳压器。 - 原理图设计需符合低功耗、高可靠性要求,完整原理图建议使用EDA工具(如KiCad或Altium Designer)绘制,但核心连接逻辑可通过文字描述[^1]。 #### 2. **主要硬件组件列表** 清洁机器人路径规划系统包含以下核心硬件组件: - **微控制器**:STM32F103ZET6(基于ARM Cortex-M3内核,144引脚LQFP封装),负责数据处理控制逻辑。 - **传感器模块**: - 超声波传感器(如HCSR04):用于距离测量避障,检测范围2-400cm。 - 红外传感器:用于边缘检测悬崖避免。 - 碰撞传感器(机械开关):用于碰撞检测。 - 编码器(安装在轮子上):用于速度反馈位置估算。 - GPS模块(可选,用于室外定位):如引用[2]中的集成方案。 - 温湿度传感器(如DHT11):用于环境监测(可选)。 - **执行器模块**: - 直流电机(2个,用于驱动轮子):额定电压6-12V。 - 电机驱动器(如L298N):控制电机方向速度。 - 清扫电机(用于刷子或吸尘器)。 - **通信模块**:Wi-Fi或蓝牙模块(如ESP8266),用于远程监控或APP控制(参考引用[2])。 - **电源模块**:锂电池(7.4V)、稳压电路(如LM2596,输出3.3V5V)。 - **其他组件**:OLED显示屏(用于状态显示)、蜂鸣器(报警)、LED指示灯。 #### 3. **硬件组件连接方式详解** 以下是基于STM32F103ZET6的详细连接方式,每个组件通过特定引脚连接到微控制器。连接原理图需遵循以下规则: - **供电连接**:所有组件共享电源总线,STM32的VDD引脚连接3.3V稳压输出,电机驱动器连接5V或更高电压。 - **信号连接**:使用GPIO、ADC、定时器等接口,确保电平匹配(如超声波模块输出5V信号,需通过分压电阻或电平转换器连接到STM32的3.3V引脚)。 - **接地**:所有组件GND引脚连接到公共地线。 具体连接步骤: 1. **STM32F103ZET6基础连接**: - VDD(引脚7, 29等)连接3.3V稳压输出。 - GND(引脚8, 50等)连接公共地。 - 晶振(8MHz外部晶振连接引脚56,用于系统时钟)。 - BOOT0(引脚44)接地,确保正常启动模式。 2. **传感器模块连接**: - **超声波传感器(HCSR04)**: - Trig引脚(触发)连接STM32的GPIO(如PA0),用于发送脉冲。 - Echo引脚(回波)连接STM32的GPIO(如PA1),用于接收信号,通过定时器测量时间差计算距离(距离公式:$ \text{距离} = \frac{\text{声速} \times \text{时间差}}{2} $,其中声速约340m/s)[^3]。 - VCC连接5V电源,GND接地。 - **红外传感器**: - 输出引脚连接STM32的ADC引脚(如PA2),用于模拟信号读取(检测反射强度)。 - 参考引用[1],避障逻辑可基于多传感器融合。 - **编码器**: - A相B相信号分别连接STM32的定时器输入引脚(如TIM2_CH1/PB8TIM2_CH2/PB9),用于速度反馈。 - **GPS模块(可选)**: - TX引脚连接STM32的USART_RX(如PD9),用于接收位置数据(NMEA协议)。 - 参考引用[2],适用于室外路径规划。 3. **执行器模块连接**: - **电机驱动器(L298N)**: - IN1IN2连接STM32的GPIO(如PC0PC1),控制电机1方向。 - IN3IN4连接GPIO(如PC2PC3),控制电机2方向。 - ENAENB连接STM32的PWM输出引脚(如TIM3_CH1/PA6TIM3_CH2/PA7),用于速度调节(PWM占空比公式:$ \text{占空比} = \frac{\text{脉冲宽度}}{\text{周期}} \times 100\% $)。 - 驱动器VCC连接7.4V锂电池,GND接地。 - **清扫电机**:通过继电器或MOSFET连接到STM32的GPIO,由高电平触发。 4. **通信辅助模块连接**: - **Wi-Fi模块(ESP8266)**: - TX引脚连接STM32的USART_TX(如PD5),RX连接USART_RX(如PD6),用于数据传输(如上报清扫路径数据到APP)。 - 参考引用[2],实现手机APP远程控制。 - **OLED显示屏**:I2C接口连接STM32的I2C引脚(如PB6/SCLPB7/SDA)。 - **电源管理**:锂电池通过LM2596稳压器输出5V3.3V,STM32的VBAT引脚(引脚3)连接备份电池。 #### 4. **路径规划系统工作流程** - **初始化**:STM32启动后,初始化所有外设(定时器用于超声波测距,PWM用于电机控制)。 - **数据采集**:传感器实时读取环境数据(如超声波检测障碍物距离)。 - **算法执行**:微控制器运行路径规划算法(如基于栅格地图的A*算法),优化清扫路径。例如,路径成本函数可表示为:$$ \text{cost} = g(n) + h(n) $$,其中 $ g(n) $ 是起始点到当前点距离, $ h(n) $ 是启发式估计到终点距离。 - **控制输出**:根据算法结果,调整PWM信号驱动电机,实现转向或调速。 - **通信反馈**:通过串口输出数据到显示屏或APP(如引用[1]中的串口通信实现)。 #### 5. **注意事项** - **抗干扰设计**:在原理图中添加滤波电容(如0.1μF)到每个电源引脚。 - **安全保护**:使用保险丝或过流保护电路。 - **测试建议**:先用开发板(如STM32F103ZET6最小系统板)验证连接,再设计PCB。 - 完整原理图可参考开源项目或EDA库,确保符合实际应用需求[^1][^2]。 通过以上设计,清洁机器人能高效执行清扫任务,同时适应复杂环境。如果您需要具体EDA文件或代码示例,请提供更多细节。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值