我目前只有一个SG90舵机,你重新来写一下
时间: 2025-08-24 22:56:13 浏览: 4
<think>我们正在构建一个基于STM32和Keil5的仿生鱼项目,使用单个SG90舵机(鱼尾)实现自主驱动和轨迹调整。项目结构将包括PWM控制、舵机驱动、轨迹校正和主程序。由于用户是新手,我们将从Keil5项目创建开始逐步指导。
### 项目步骤
#### 1. 创建Keil5项目
- 打开Keil5,点击Project -> New μVision Project
- 选择项目存储路径,命名为"BionicFish"
- 选择芯片型号:STM32F103C8(或根据你的开发板选择)
- 在Manage Run-Time Environment窗口中,取消所有选项(我们使用标准库开发)
#### 2. 添加标准外设库
- 下载STM32F10x标准外设库(STM32F10x_StdPeriph_Lib_V3.5.0)
- 在项目目录下创建文件夹:`Libraries`,将库中的`CMSIS`和`STM32F10x_StdPeriph_Driver`复制到`Libraries`
- 在Keil5中创建文件夹结构:
```
Project Targets
├── CMSIS
├── StdPeriph_Driver
├── User
└── Startup (包含启动文件)
```
- 添加文件:
- 启动文件:`Libraries\CMSIS\CM3\DeviceSupport\ST\STM32F10x\startup\arm\startup_stm32f10x_md.s` (根据芯片选择,md代表中等容量)
- 用户代码:在`User`文件夹中添加`main.c`, `pwm.c`, `pwm.h`, `servo.c`, `servo.h`, `sensor.c`, `sensor.h`, `sys.c`, `sys.h`
- 标准外设库源文件:将`Libraries\STM32F10x_StdPeriph_Driver\src`中需要的驱动文件(如`stm32f10x_gpio.c`, `stm32f10x_rcc.c`, `stm32f10x_tim.c`)添加到`StdPeriph_Driver`组
- CMSIS文件:添加`Libraries\CMSIS\CM3\DeviceSupport\ST\STM32F10x\system_stm32f10x.c`到`CMSIS`组
#### 3. 项目配置
- 点击魔术棒按钮(Options for Target)
- C/C++选项卡:
- Define: `USE_STDPERIPH_DRIVER, STM32F10X_MD` (根据芯片容量,MD代表中等容量)
- Include Paths: 添加以下路径:
```
\Libraries\CMSIS\CM3\CoreSupport
\Libraries\CMSIS\CM3\DeviceSupport\ST\STM32F10x
\Libraries\STM32F10x_StdPeriph_Driver\inc
\User
```
- Debug选项卡:选择ST-Link Debugger
- Utilities选项卡:取消"Use Debug Driver"的勾选,然后重新勾选(确保ST-Link被选中)
#### 4. 文件内容实现
##### (1) sys.h - 系统头文件
```c
#ifndef __SYS_H
#define __SYS_H
#include "stm32f10x.h"
void SystemInit(void); // 系统初始化声明
#endif
```
##### (2) sys.c - 系统初始化
```c
#include "sys.h"
// 系统时钟初始化(使用72MHz)
void SystemInit(void) {
// 复位时钟配置
RCC_DeInit();
// 使能外部高速晶振
RCC_HSEConfig(RCC_HSE_ON);
while (RCC_GetFlagStatus(RCC_FLAG_HSERDY) == RESET);
// 配置PLL:HSE作为输入,9倍频(8MHz*9=72MHz)
RCC_PLLConfig(RCC_PLLSource_HSE_Div1, RCC_PLLMul_9);
RCC_PLLCmd(ENABLE);
while (RCC_GetFlagStatus(RCC_FLAG_PLLRDY) == RESET);
// 切换系统时钟到PLL
RCC_SYSCLKConfig(RCC_SYSCLKSource_PLLCLK);
while (RCC_GetSYSCLKSource() != 0x08);
// 配置AHB、APB1、APB2分频
RCC_HCLKConfig(RCC_SYSCLK_Div1); // AHB=72MHz
RCC_PCLK1Config(RCC_HCLK_Div2); // APB1=36MHz
RCC_PCLK2Config(RCC_HCLK_Div1); // APB2=72MHz
}
```
##### (3) pwm.h - PWM控制头文件
```c
#ifndef __PWM_H
#define __PWM_H
#include "stm32f10x.h"
// 舵机通道定义
#define FISH_TAIL_SERVO 1 // 鱼尾舵机
// PWM初始化函数
void PWM_Init(TIM_TypeDef* TIMx, uint16_t arr, uint16_t psc);
// 设置舵机角度
void Set_Servo_Angle(TIM_TypeDef* TIMx, uint8_t channel, float angle);
#endif
```
##### (4) pwm.c - PWM控制实现
```c
#include "pwm.h"
#include "stm32f10x_tim.h"
#include "stm32f10x_gpio.h"
#include "stm32f10x_rcc.h"
void PWM_Init(TIM_TypeDef* TIMx, uint16_t arr, uint16_t psc) {
TIM_TimeBaseInitTypeDef TIM_Base;
TIM_OCInitTypeDef TIM_OC;
GPIO_InitTypeDef GPIO_InitStruct;
// 1. 时钟使能(以TIM2为例)
if (TIMx == TIM2) {
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
// PA0(TIM2_CH1) 舵机信号线
GPIO_InitStruct.GPIO_Pin = GPIO_Pin_0;
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF_PP; // 复用推挽输出
GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStruct);
}
// 2. 定时器基础配置
TIM_Base.TIM_Period = arr; // 自动重装载值
TIM_Base.TIM_Prescaler = psc; // 预分频器
TIM_Base.TIM_ClockDivision = 0;
TIM_Base.TIM_CounterMode = TIM_CounterMode_Up;
TIM_TimeBaseInit(TIMx, &TIM_Base);
// 3. PWM模式配置
TIM_OC.TIM_OCMode = TIM_OCMode_PWM1;
TIM_OC.TIM_OutputState = TIM_OutputState_Enable;
TIM_OC.TIM_OCPolarity = TIM_OCPolarity_High;
TIM_OC.TIM_Pulse = 0; // 初始占空比为0
// 通道1配置(鱼尾)
TIM_OC1Init(TIMx, &TIM_OC);
TIM_OC1PreloadConfig(TIMx, TIM_OCPreload_Enable);
// 4. 启动定时器
TIM_Cmd(TIMx, ENABLE);
TIM_CtrlPWMOutputs(TIMx, ENABLE);
}
void Set_Servo_Angle(TIM_TypeDef* TIMx, uint8_t channel, float angle) {
// SG90舵机:0.5ms(0°) ~ 2.5ms(180°)
// 周期20ms(50Hz),计数器时钟1MHz时,计数值20000对应20ms
// 脉宽 = (0.5 + angle/180.0 * 2.0) * 1000 us -> 计数值 = (脉宽/20) * 20000
// 计算:计数值 = (0.5 + 2.0 * angle/180.0) * 1000 * (20000/20000) 简化:
uint32_t pulse = (uint32_t)(500 + (2000 * angle) / 180.0); // 500-2500μs
switch (channel) {
case FISH_TAIL_SERVO:
TIM_SetCompare1(TIMx, pulse); // 设置通道1的脉宽
break;
}
}
```
##### (5) servo.h - 舵机控制头文件
```c
#ifndef __SERVO_H
#define __SERVO_H
// 仿生鱼运动模式
typedef enum {
SWIM_FORWARD, // 前进
CORRECT_LEFT, // 向左校正
CORRECT_RIGHT // 向右校正
} SwimMode;
void Servo_Init(void);
void Fish_Swim(SwimMode mode);
#endif
```
##### (6) servo.c - 舵机控制实现
```c
#include "servo.h"
#include "pwm.h"
#include "delay.h" // 需要实现延时函数
// 舵机初始化
void Servo_Init(void) {
// TIM2: 20ms周期(50Hz), 72MHz/(72*20000) -> 实际使用1MHz计数频率
// 预分频72-1,自动重装载值20000-1
PWM_Init(TIM2, 19999, 71);
}
// 仿生鱼游动函数
void Fish_Swim(SwimMode mode) {
switch (mode) {
case SWIM_FORWARD:
// 鱼尾摆动实现前进
Set_Servo_Angle(TIM2, FISH_TAIL_SERVO, 60); // 左摆
delay_ms(300);
Set_Servo_Angle(TIM2, FISH_TAIL_SERVO, 120); // 右摆
delay_ms(300);
break;
case CORRECT_LEFT:
// 向左校正:鱼尾短暂右偏
Set_Servo_Angle(TIM2, FISH_TAIL_SERVO, 120);
delay_ms(500);
break;
case CORRECT_RIGHT:
// 向右校正:鱼尾短暂左偏
Set_Servo_Angle(TIM2, FISH_TAIL_SERVO, 60);
delay_ms(500);
break;
}
// 恢复中立位置
Set_Servo_Angle(TIM2, FISH_TAIL_SERVO, 90);
}
```
##### (7) delay.h - 延时函数头文件
```c
#ifndef __DELAY_H
#define __DELAY_H
#include "stm32f10x.h"
void delay_init(void);
void delay_us(uint32_t nus);
void delay_ms(uint16_t nms);
#endif
```
##### (8) delay.c - 延时函数实现
```c
#include "delay.h"
static uint32_t fac_us = 0; // us延时倍乘数
static uint32_t fac_ms = 0; // ms延时倍乘数
// 初始化延时函数
void delay_init() {
// 使用SysTick时钟源为72MHz
SysTick_CLKSourceConfig(SysTick_CLKSource_HCLK_Div8); // 9分频,SysTick时钟=72/9=8MHz
fac_us = 8; // 8MHz,1us需要8个周期
fac_ms = (uint32_t)fac_us * 1000; // 1ms需要8000个周期
}
// 延时nus
void delay_us(uint32_t nus) {
uint32_t temp;
SysTick->LOAD = nus * fac_us; // 重装载值
SysTick->VAL = 0x00; // 清空计数器
SysTick->CTRL |= SysTick_CTRL_ENABLE_Msk; // 启动计时
do {
temp = SysTick->CTRL;
} while ((temp & 0x01) && !(temp & (1 << 16))); // 等待时间到
SysTick->CTRL &= ~SysTick_CTRL_ENABLE_Msk; // 关闭计时器
SysTick->VAL = 0x00; // 清空计数器
}
// 延时nms
void delay_ms(uint16_t nms) {
uint32_t temp;
SysTick->LOAD = (uint32_t)nms * fac_ms; // 重装载值
SysTick->VAL = 0x00; // 清空计数器
SysTick->CTRL |= SysTick_CTRL_ENABLE_Msk; // 启动计时
do {
temp = SysTick->CTRL;
} while ((temp & 0x01) && !(temp & (1 << 16))); // 等待时间到
SysTick->CTRL &= ~SysTick_CTRL_ENABLE_Msk; // 关闭计时器
SysTick->VAL = 0x00; // 清空计数器
}
```
##### (9) sensor.h - 传感器头文件
```c
#ifndef __SENSOR_H
#define __SENSOR_H
void Sensor_Init(void);
float Get_Trajectory_Deviation(void); // 获取轨迹偏离角度
#endif
```
##### (10) sensor.c - 传感器实现(轨迹检测)
```c
#include "sensor.h"
// 初始化传感器(实际需要连接陀螺仪等)
void Sensor_Init(void) {
// 此处为伪代码,实际需要初始化I2C/SPI读取陀螺仪数据
}
// 获取轨迹偏离角度(假设有传感器)
float Get_Trajectory_Deviation(void) {
// 伪代码:读取陀螺仪Z轴角度偏差
// 实际应用中,这里需要读取传感器数据并计算
// 返回正值表示向右偏离,负值表示向左偏离
// 示例:返回0表示无偏离
return 0.0f;
}
```
##### (11) main.c - 主程序
```c
#include "stm32f10x.h"
#include "sys.h"
#include "delay.h"
#include "servo.h"
#include "sensor.h"
int main(void) {
// 系统初始化
SystemInit(); // 时钟配置
delay_init(); // 延时初始化
Servo_Init(); // 舵机初始化
Sensor_Init(); // 传感器初始化
// 主循环
while (1) {
// 1. 前进游动
Fish_Swim(SWIM_FORWARD);
// 2. 每游动3次检测一次轨迹偏离
static uint8_t swim_count = 0;
swim_count++;
if (swim_count >= 3) {
float deviation = Get_Trajectory_Deviation();
// 如果偏离角度超过阈值(如15度),进行校正
if (deviation > 15.0f) {
Fish_Swim(CORRECT_RIGHT); // 向右偏离则向左校正
} else if (deviation < -15.0f) {
Fish_Swim(CORRECT_LEFT); // 向左偏离则向右校正
}
swim_count = 0;
}
}
}
```
### 硬件连接
- SG90舵机:
- 信号线:PA0 (TIM2_CH1)
- 电源:5V(需外部电源供电)
- 地线:GND
- ST-LINK:连接SWD接口(SWCLK, SWDIO, GND, 3.3V)
### 调试步骤
1. 编译项目(F7),确保0错误0警告
2. 连接ST-LINK,点击Load按钮下载程序
3. 观察舵机摆动情况:
- 上电后舵机应回到90°中立位置
- 每600ms完成一次摆动周期(前进动作)
4. 轨迹校正测试:可暂时在`Get_Trajectory_Deviation`中返回模拟偏离值(如20.0f)测试校正动作
### 关键算法说明
轨迹校正采用阈值触发机制:
$$ \text{校正条件} =
\begin{cases}
\text{左校正} & \text{if } \theta < -15^\circ \\
\text{右校正} & \text{if } \theta > 15^\circ
\end{cases} $$
其中$\theta$为偏离角度(从传感器获取)。
### 进阶优化
1. **PID控制**:引入PID控制器实现平滑校正
```c
// PID校正函数(示例)
void PID_Correction(float deviation) {
static float integral = 0, prev_deviation = 0;
float Kp = 0.5, Ki = 0.01, Kd = 0.05;
float correction = Kp * deviation + Ki * integral + Kd * (deviation - prev_deviation);
// 应用校正(限制在±30°内)
correction = (correction > 30) ? 30 : (correction < -30) ? -30 : correction;
Set_Servo_Angle(TIM2, FISH_TAIL_SERVO, 90 + correction);
// 更新状态
integral += deviation;
prev_deviation = deviation;
}
```
2. **传感器融合**:结合加速度计和陀螺仪数据提高轨迹检测精度
### 常见问题解决
- **舵机不转动**:
1. 检查电源是否独立供电(STM32开发板USB供电可能不足)
2. 检查信号线连接(PA0)
3. 用示波器检测PA0是否有PWM波形
- **Keil编译错误**:
1. 确保所有头文件路径正确
2. 检查`USE_STDPERIPH_DRIVER`定义
§§相关问题§§
1. 如何选择适合仿生鱼项目的STM32型号?
2. SG90舵机的扭矩和转速参数如何影响仿生鱼设计?
3. 在Keil5中如何调试PWM信号输出?
4. 如何通过MPU6050实现仿生鱼的姿态检测?
5. 仿生鱼项目中如何实现水下通信功能?
6. 舵机控制中如何避免机械共振问题?</think>### STM32仿生鱼项目实现指南:舵机自主驱动与轨迹调整
#### 一、项目结构设计(Keil5工程)
```
Bionic_Fish_Project/
├── Core/ # 核心文件
│ ├── Inc/ # 头文件目录
│ │ ├── pwm.h
│ │ ├── servo.h
│ │ ├── sensor.h
│ │ └── sys.h
│ └── Src/ # 源文件目录
│ ├── main.c
│ ├── pwm.c
│ ├── servo.c
│ ├── sensor.c
│ └── sys.c
├── Drivers/
│ ├── CMSIS/ # ARM内核支持
│ └── STM32F1xx_HAL_Driver/ # HAL库
├── STM32F103C8Tx_FLASH.ld # 链接脚本
└── MDK-ARM/ # Keil工程文件
```
#### 二、关键文件实现(从零开始)
##### 1. PWM控制模块 (pwm.h)
```c
#ifndef __PWM_H
#define __PWM_H
#include "stm32f1xx_hal.h"
// 舵机通道定义
#define FISH_TAIL_SERVO TIM_CHANNEL_1
// PWM初始化函数
void PWM_Init(TIM_HandleTypeDef* htim);
// 设置舵机角度
void Set_Servo_Angle(TIM_HandleTypeDef* htim, uint32_t channel, float angle);
#endif
```
##### 2. PWM实现 (pwm.c)
```c
#include "pwm.h"
void PWM_Init(TIM_HandleTypeDef* htim) {
// 定时器基础配置(在main.c中完成)
}
void Set_Servo_Angle(TIM_HandleTypeDef* htim, uint32_t channel, float angle) {
// SG90舵机:0.5ms(0°) ~ 2.5ms(180°)
// 周期20ms(50Hz),计数器时钟72MHz
uint32_t pulse = (uint32_t)(500 + (2000 * angle) / 180.0); // 500-2500μs
// 设置PWM脉宽
TIM_OC_InitTypeDef sConfigOC = {0};
sConfigOC.OCMode = TIM_OCMODE_PWM1;
sConfigOC.Pulse = pulse; // 关键参数
sConfigOC.OCPolarity = TIM_OCPOLARITY_HIGH;
sConfigOC.OCFastMode = TIM_OCFAST_DISABLE;
HAL_TIM_PWM_ConfigChannel(htim, &sConfigOC, channel);
HAL_TIM_PWM_Start(htim, channel);
}
```
##### 3. 舵机控制模块 (servo.h)
```c
#ifndef __SERVO_H
#define __SERVO_H
typedef enum {
SWIM_FORWARD,
CORRECT_LEFT,
CORRECT_RIGHT
} SwimMode;
void Servo_Init(TIM_HandleTypeDef* htim);
void Fish_Swim(SwimMode mode);
#endif
```
##### 4. 舵机控制实现 (servo.c)
```c
#include "servo.h"
#include "pwm.h"
#include "main.h"
static TIM_HandleTypeDef* htim_servo;
void Servo_Init(TIM_HandleTypeDef* htim) {
htim_servo = htim;
// 初始位置90°
Set_Servo_Angle(htim_servo, FISH_TAIL_SERVO, 90);
}
void Fish_Swim(SwimMode mode) {
switch(mode) {
case SWIM_FORWARD:
// 鱼尾摆动推进
Set_Servo_Angle(htim_servo, FISH_TAIL_SERVO, 60);
HAL_Delay(300);
Set_Servo_Angle(htim_servo, FISH_TAIL_SERVO, 120);
HAL_Delay(300);
break;
case CORRECT_LEFT:
// 向左校正
Set_Servo_Angle(htim_servo, FISH_TAIL_SERVO, 120);
HAL_Delay(500);
break;
case CORRECT_RIGHT:
// 向右校正
Set_Servo_Angle(htim_servo, FISH_TAIL_SERVO, 60);
HAL_Delay(500);
break;
}
// 恢复中立位置
Set_Servo_Angle(htim_servo, FISH_TAIL_SERVO, 90);
}
```
##### 5. 轨迹传感器模块 (sensor.h)
```c
#ifndef __SENSOR_H
#define __SENSOR_H
float Get_Trajectory_Deviation(void);
void Sensor_Init(void);
#endif
```
##### 6. 轨迹检测实现 (sensor.c)
```c
#include "sensor.h"
// 简化实现(实际需连接陀螺仪)
float Get_Trajectory_Deviation(void) {
// 伪代码:读取陀螺仪Z轴角度
// float gyro_z = MPU6050_Read_GyroZ();
// return gyro_z * 0.1; // 转换为角度偏差
// 示例返回值(测试用)
static float deviation = 0;
deviation += 5.0f; // 模拟每次增加5°偏差
if(deviation > 30) deviation = -30;
return deviation;
}
void Sensor_Init(void) {
// 初始化IMU传感器(I2C通信)
}
```
##### 7. 主程序 (main.c)
```c
#include "main.h"
#include "pwm.h"
#include "servo.h"
#include "sensor.h"
TIM_HandleTypeDef htim2;
int main(void) {
// 1. HAL库初始化
HAL_Init();
SystemClock_Config();
// 2. 定时器PWM初始化
htim2.Instance = TIM2;
htim2.Init.Prescaler = 71; // 72MHz/(71+1)=1MHz
htim2.Init.CounterMode = TIM_COUNTERMODE_UP;
htim2.Init.Period = 19999; // 20ms周期(50Hz)
htim2.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
HAL_TIM_PWM_Init(&htim2);
// 3. 外设初始化
PWM_Init(&htim2);
Servo_Init(&htim2);
Sensor_Init();
// 4. 主循环
while (1) {
// 自主游动
Fish_Swim(SWIM_FORWARD);
HAL_Delay(200);
// 轨迹校正(每5次循环检测)
static uint8_t count = 0;
if(++count >= 5) {
float deviation = Get_Trajectory_Deviation();
if(deviation > 15.0f) {
Fish_Swim(CORRECT_RIGHT); // 向右偏则向左校正
}
else if(deviation < -15.0f) {
Fish_Swim(CORRECT_LEFT); // 向左偏则向右校正
}
count = 0;
}
}
}
// 定时器PWM通道配置
void HAL_TIM_PWM_MspInit(TIM_HandleTypeDef* htim_pwm) {
if(htim_pwm->Instance == TIM2) {
GPIO_InitTypeDef GPIO_InitStruct = {0};
__HAL_RCC_TIM2_CLK_ENABLE();
__HAL_RCC_GPIOA_CLK_ENABLE();
// PA0: TIM2_CH1
GPIO_InitStruct.Pin = GPIO_PIN_0;
GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
}
}
```
#### 三、Keil5操作步骤
1. **创建新工程**:
- 打开Keil5 → Project → New μVision Project
- 选择芯片型号:STM32F103C8
2. **添加文件结构**:
```mermaid
graph TD
A[Target 1] --> B[Source Group 1]
B --> C[main.c]
B --> D[pwm.c]
B --> E[servo.c]
B --> F[sensor.c]
A --> G[Device]
G --> H[Startup_stm32f103xb.s]
```
3. **工程配置**:
- 魔术棒按钮 → C/C++ → Define: `STM32F103xB, USE_HAL_DRIVER`
- Include Paths: 添加所有头文件目录
- Debug → 选择ST-Link Debugger
- Utilities → 勾选"Use Debug Driver"
4. **编译与下载**:
- 点击Build按钮(F7)编译
- 连接ST-Link → 点击Load按钮下载程序
#### 四、舵机控制原理
SG90舵机控制参数:
$$ \text{周期} = 20\text{ms} \quad (50\text{Hz}) $$
$$ \text{脉宽} =
\begin{cases}
0.5\text{ms} & (0^\circ) \\
1.5\text{ms} & (90^\circ) \\
2.5\text{ms} & (180^\circ)
\end{cases} $$
定时器参数计算(72MHz时钟):
$$ \text{预分频值} = \frac{72\text{MHz}}{1\text{MHz}} - 1 = 71 $$
$$ \text{自动重载值} = \frac{20\text{ms}}{1\mu\text{s}} - 1 = 19,999 $$
$$ \text{角度对应值} = 500 + \frac{\text{角度}}{180} \times 2000 $$
#### 五、轨迹校正算法
采用阈值触发校正:
$$ \text{校正动作} =
\begin{cases}
\text{左校正} & \text{if } \theta < -15^\circ \\
\text{右校正} & \text{if } \theta > 15^\circ \\
\text{无操作} & \text{otherwise}
\end{cases} $$
其中$\theta$为偏离角度
#### 六、硬件连接
| SG90舵机 | STM32引脚 |
|----------|-----------|
| 信号线(橙) | PA0 (TIM2_CH1) |
| VCC(红) | 5V外部电源 |
| GND(棕) | 共地 |
> **重要**:舵机必须使用独立电源供电,STM32开发板的3.3V输出无法驱动舵机
#### 七、进阶优化建议
1. **PID轨迹校正**:
```c
// 在servo.c中添加
void PID_Correction(float deviation) {
static float integral = 0, prev_error = 0;
const float Kp = 0.8, Ki = 0.01, Kd = 0.1;
float correction = Kp * deviation
+ Ki * integral
+ Kd * (deviation - prev_error);
// 应用校正(限制在±30°内)
correction = (correction > 30) ? 30 : (correction < -30) ? -30 : correction;
Set_Servo_Angle(htim_servo, FISH_TAIL_SERVO, 90 + correction);
// 更新状态
integral += deviation;
prev_error = deviation;
}
```
2. **运动模式优化**:
```c
// 更自然的鱼尾波动
void Advanced_Swim(void) {
for(int i=0; i<5; i++) {
float angle = 90 + 30 * sin(HAL_GetTick() * 0.01);
Set_Servo_Angle(htim_servo, FISH_TAIL_SERVO, angle);
HAL_Delay(50);
}
}
```
3. **低功耗设计**:
```c
// 在main.c的循环中添加
if(HAL_GetTick() - last_action > 5000) {
__WFI(); // 进入睡眠模式
}
```
#### 八、调试技巧
1. **逻辑分析仪使用**:
- 连接PA0引脚,验证PWM波形
- 检查脉宽范围:500-2500μs
2. **串口调试**:
```c
// 在main.c中添加
#include <stdio.h>
// 重定向printf
int __io_putchar(int ch) {
ITM_SendChar(ch);
return ch;
}
// 在主循环中打印
printf("Deviation: %.2f°\r\n", Get_Trajectory_Deviation());
```
3. **状态指示LED**:
```c
// 校正时点亮LED
if(deviation > 15.0f) {
HAL_GPIO_WritePin(GPIOC, GPIO_PIN_13, GPIO_PIN_RESET); // 点亮
Fish_Swim(CORRECT_RIGHT);
}
```
#### 九、常见问题解决
1. **舵机抖动不工作**:
- 检查电源:必须5V/2A独立供电
- 添加100μF电容到舵机电源引脚
- 确保PWM信号地线与MCU共地
2. **Keil编译错误**:
- 未定义`HAL_TIM_PWM_Init`:确认包含HAL库
- 链接错误:检查启动文件是否匹配芯片型号
3. **轨迹检测不准确**:
- 增加传感器滤波算法
- 使用卡尔曼滤波器融合多传感器数据
> **提示**:实际IMU集成请参考MPU6050数据手册[^1]
阅读全文
相关推荐




















