编程技术分享

分享编程知识,探讨技术创新

0%

简介:非常简单易学的小四轴,只用到了两个芯片和700行代码。

好的,作为一名高级嵌入式软件开发工程师,我将根据你提供的“非常简单易学的小四轴,只用到了两个芯片和700行代码”的项目背景,详细阐述最适合这种简单四轴的代码设计架构,并提供具体的C代码示例,力求全面、深入且实践性强,最终帮助你理解并构建一个可靠、高效、可扩展的嵌入式系统平台。
关注微信公众号,提前获取相关推文

项目背景理解与需求分析

首先,我们需要深入理解“非常简单易学的小四轴,只用到了两个芯片和700行代码”这句话背后的含义。

  • 简单易学: 这意味着系统的复杂性相对较低,设计目标是清晰、直观,方便学习和理解。代码结构应该简洁明了,避免过度设计和复杂的抽象。
  • 两个芯片: 极少的芯片数量暗示了高度集成的解决方案。很可能其中一个芯片是主控微控制器(MCU),负责核心的计算和控制任务;另一个芯片可能是传感器芯片,例如集成IMU(惯性测量单元,包含加速度计和陀螺仪),用于姿态感知。也可能两个芯片都是MCU,一个负责主控,另一个负责传感器数据采集或者电机控制。
  • 700行代码: 非常精简的代码量,这进一步印证了系统的简单性。我们需要在有限的代码行数内实现核心功能,这意味着代码必须高效、紧凑,避免冗余和不必要的复杂性。

基于以上理解,我们可以初步分析出项目的核心需求:

  1. 姿态感知: 通过IMU传感器获取四轴飞行器的姿态信息(角度、角速度),这是实现稳定飞行的基础。
  2. 电机控制: 控制四个电机的转速,实现四轴的起飞、悬停、转向、升降等飞行动作。
  3. 遥控接收 (假设): 虽然描述中没有明确提及,但四轴飞行器通常需要遥控控制。因此,我们需要考虑遥控信号的接收和解析。
  4. 稳定飞行控制: 设计控制算法,根据姿态信息和遥控指令,调整电机转速,实现四轴的稳定飞行。
  5. 电源管理 (硬件层面,软件需配合): 虽然软件不直接管理电源硬件,但需要考虑功耗优化,以延长电池续航时间。
  6. 错误处理与安全机制 (基础): 在简单系统中,基本的错误处理和安全机制是必要的,例如电机异常检测、低电压保护等。

代码设计架构:分层与模块化

为了构建一个可靠、高效、可扩展的系统平台,并考虑到代码量限制和系统简单性,最适合的代码设计架构是分层架构模块化设计相结合。

1. 分层架构 (Layered Architecture)

分层架构将系统划分为不同的层次,每一层负责特定的功能,并向上层提供服务。这种架构的优点是:

  • 解耦性: 各层之间相互独立,修改某一层的代码不会影响其他层,降低了维护和升级的难度。
  • 可重用性: 底层模块可以被多个上层模块复用,提高了代码的重用率。
  • 易于理解和维护: 层次结构清晰,易于理解系统的整体架构和各部分的功能。

对于简单的四轴项目,我们可以考虑以下分层:

  • 硬件抽象层 (HAL - Hardware Abstraction Layer): 最底层,直接与硬件交互。提供统一的接口,供上层访问硬件资源,屏蔽硬件差异。例如,GPIO控制、SPI/I2C通信、PWM输出、ADC读取等。
  • 驱动层 (Driver Layer): 基于HAL层,为特定的硬件设备提供驱动程序。例如,IMU传感器驱动、电机驱动、遥控接收器驱动等。
  • 控制层 (Control Layer): 实现四轴的飞行控制算法,例如姿态解算、PID控制、电机混控等。
  • 应用层 (Application Layer): 最高层,负责系统的整体逻辑和任务调度。例如,主循环、遥控指令解析、飞行模式管理等。

2. 模块化设计 (Modular Design)

模块化设计将系统分解为独立的模块,每个模块负责特定的功能。模块之间通过清晰的接口进行通信。模块化设计的优点是:

  • 提高代码可读性和可维护性: 模块化使代码结构更清晰,易于理解和维护。
  • 提高代码可重用性: 模块可以被独立开发、测试和重用。
  • 便于团队协作: 模块化设计允许团队成员并行开发不同的模块。

在四轴项目中,我们可以将系统划分为以下模块:

  • 传感器模块 (Sensor Module): 负责IMU传感器的数据采集和预处理。
  • 姿态解算模块 (Attitude Estimation Module): 根据传感器数据计算四轴的姿态信息(例如,使用互补滤波或卡尔曼滤波)。
  • 遥控接收模块 (RC Receiver Module): 负责接收和解析遥控信号。
  • 电机控制模块 (Motor Control Module): 负责控制四个电机的转速。
  • PID控制模块 (PID Control Module): 实现PID控制算法,用于稳定姿态和高度。
  • 主循环模块 (Main Loop Module): 系统的核心,负责任务调度和整体逻辑控制。

代码实现 (C语言)

下面我将提供详细的C代码实现,覆盖上述架构和模块,并力求代码简洁、易懂、注释详尽。由于篇幅限制,我将重点展示核心模块的代码,并提供必要的框架代码和注释,帮助你构建完整的项目。

项目文件结构 (建议)

为了更好地组织代码,建议采用以下文件结构:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
quadcopter_project/
├── inc/ # 头文件目录
│ ├── hal.h # 硬件抽象层头文件
│ ├── drivers.h # 驱动层头文件
│ ├── control.h # 控制层头文件
│ ├── app.h # 应用层头文件
│ ├── config.h # 配置头文件 (例如,引脚定义、参数配置等)
├── src/ # 源文件目录
│ ├── hal.c # 硬件抽象层源文件
│ ├── drivers.c # 驱动层源文件
│ ├── control.c # 控制层源文件
│ ├── app.c # 应用层源文件
│ ├── main.c # 主函数源文件
├── lib/ # 库文件目录 (如果有外部库)
├── doc/ # 文档目录 (例如,设计文档、用户手册等)
├── build/ # 编译输出目录
├── Makefile # 构建脚本
├── README.md # 项目说明文件

1. 配置文件 (inc/config.h)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
#ifndef CONFIG_H
#define CONFIG_H

// --- 硬件引脚定义 ---
#define LED_PIN 13 // 板载LED引脚 (假设)

// IMU传感器引脚 (假设使用SPI接口)
#define IMU_SPI_CS_PIN 10
#define IMU_SPI_CLK_PIN 11
#define IMU_SPI_MISO_PIN 12
#define IMU_SPI_MOSI_PIN 9

// 电机控制引脚 (假设使用PWM)
#define MOTOR1_PWM_PIN 2
#define MOTOR2_PWM_PIN 3
#define MOTOR3_PWM_PIN 4
#define MOTOR4_PWM_PIN 5

// 遥控接收器引脚 (假设使用PWM信号)
#define RC_ROLL_PIN 6
#define RC_PITCH_PIN 7
#define RC_YAW_PIN 8
#define RC_THROTTLE_PIN 9
#define RC_MODE_PIN 10


// --- 系统参数配置 ---
#define PWM_FREQUENCY 500 // PWM频率 (Hz)
#define PID_LOOP_TIME 10 // PID控制循环时间 (ms)

// PID 参数 (需要根据实际情况调整)
#define PID_KP_ROLL 1.0f
#define PID_KI_ROLL 0.1f
#define PID_KD_ROLL 0.01f

#define PID_KP_PITCH 1.0f
#define PID_KI_PITCH 0.1f
#define PID_KD_PITCH 0.01f

#define PID_KP_YAW 1.0f
#define PID_KI_YAW 0.1f
#define PID_KD_YAW 0.01f

#define PID_KP_ALTITUDE 1.0f // 假设有高度传感器或气压计
#define PID_KI_ALTITUDE 0.1f
#define PID_KD_ALTITUDE 0.01f


// --- 其他配置 ---
#define DEBUG_ENABLED 1 // 调试使能

#if DEBUG_ENABLED
#define DEBUG_PRINT(...) printf(__VA_ARGS__) // 调试打印宏 (需要实现 printf 函数)
#else
#define DEBUG_PRINT(...) // 空宏,禁用调试打印
#endif


#endif // CONFIG_H

2. 硬件抽象层 (HAL - inc/hal.h, src/hal.c)

inc/hal.h:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
#ifndef HAL_H
#define HAL_H

#include <stdint.h>
#include <stdbool.h>

// --- GPIO 控制 ---
void HAL_GPIO_Init(uint32_t pin, uint32_t mode);
void HAL_GPIO_WritePin(uint32_t pin, bool value);
bool HAL_GPIO_ReadPin(uint32_t pin);

// --- SPI 通信 ---
void HAL_SPI_Init(uint32_t spi_instance, uint32_t clock_speed);
uint8_t HAL_SPI_Transfer(uint32_t spi_instance, uint8_t tx_data);
void HAL_SPI_SelectChip(uint32_t spi_instance);
void HAL_SPI_DeselectChip(uint32_t spi_instance);

// --- PWM 输出 ---
void HAL_PWM_Init(uint32_t pwm_channel, uint32_t frequency);
void HAL_PWM_SetDutyCycle(uint32_t pwm_channel, float duty_cycle); // 占空比 0.0 - 1.0

// --- 定时器 (用于延时和定时任务) ---
void HAL_Timer_Init(uint32_t timer_instance, uint32_t period_ms);
void HAL_Delay_ms(uint32_t ms);
uint32_t HAL_GetTick_ms(void); // 获取系统运行时间 (毫秒)


// --- ADC 读取 (如果需要) ---
uint16_t HAL_ADC_Read(uint32_t adc_channel);


// --- 初始化 HAL ---
void HAL_Init(void);


#endif // HAL_H

src/hal.c (示例 - 针对特定MCU平台,需要根据实际MCU进行适配)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
#include "hal.h"
#include "config.h"

#include <stdio.h> // For printf (调试用)

// 假设使用 STM32 或者 ESP32 等 MCU,这里只是伪代码示例,需要根据实际MCU的HAL库进行适配

// --- GPIO 控制 ---
void HAL_GPIO_Init(uint32_t pin, uint32_t mode) {
// 初始化 GPIO 引脚为输入/输出模式,根据 mode 设置上拉/下拉等
DEBUG_PRINT("GPIO Pin %lu Init, Mode: %lu\r\n", pin, mode);
// ... (实际 MCU GPIO 初始化代码) ...
}

void HAL_GPIO_WritePin(uint32_t pin, bool value) {
// 设置 GPIO 引脚输出高/低电平
DEBUG_PRINT("GPIO Pin %lu Write: %s\r\n", pin, value ? "HIGH" : "LOW");
// ... (实际 MCU GPIO 写操作代码) ...
}

bool HAL_GPIO_ReadPin(uint32_t pin) {
// 读取 GPIO 引脚电平
// ... (实际 MCU GPIO 读操作代码) ...
return false; // 示例,实际需要返回读取的值
}


// --- SPI 通信 ---
void HAL_SPI_Init(uint32_t spi_instance, uint32_t clock_speed) {
// 初始化 SPI 外设,设置时钟频率、模式等
DEBUG_PRINT("SPI %lu Init, Clock Speed: %lu\r\n", spi_instance, clock_speed);
// ... (实际 MCU SPI 初始化代码) ...
}

uint8_t HAL_SPI_Transfer(uint32_t spi_instance, uint8_t tx_data) {
// SPI 数据传输 (发送并接收一个字节)
DEBUG_PRINT("SPI %lu Transfer: 0x%02X\r\n", spi_instance, tx_data);
// ... (实际 MCU SPI 数据传输代码) ...
return 0x00; // 示例,实际需要返回接收到的数据
}

void HAL_SPI_SelectChip(uint32_t spi_instance) {
// 片选信号拉低,选择 SPI 从设备
DEBUG_PRINT("SPI %lu Chip Select\r\n", spi_instance);
// ... (实际 MCU SPI 片选控制代码,例如 GPIO 控制 CS 引脚) ...
}

void HAL_SPI_DeselectChip(uint32_t spi_instance) {
// 片选信号拉高,取消选择 SPI 从设备
DEBUG_PRINT("SPI %lu Chip Deselect\r\n", spi_instance);
// ... (实际 MCU SPI 片选控制代码) ...
}


// --- PWM 输出 ---
void HAL_PWM_Init(uint32_t pwm_channel, uint32_t frequency) {
// 初始化 PWM 通道,设置频率
DEBUG_PRINT("PWM Channel %lu Init, Frequency: %lu\r\n", pwm_channel, frequency);
// ... (实际 MCU PWM 初始化代码) ...
}

void HAL_PWM_SetDutyCycle(uint32_t pwm_channel, float duty_cycle) {
// 设置 PWM 占空比
DEBUG_PRINT("PWM Channel %lu Set Duty Cycle: %f\r\n", pwm_channel, duty_cycle);
// ... (实际 MCU PWM 设置占空比代码) ...
}


// --- 定时器 ---
void HAL_Timer_Init(uint32_t timer_instance, uint32_t period_ms) {
// 初始化定时器,设置定时周期
DEBUG_PRINT("Timer %lu Init, Period: %lu ms\r\n", timer_instance, period_ms);
// ... (实际 MCU 定时器初始化代码) ...
}

void HAL_Delay_ms(uint32_t ms) {
// 延时函数 (阻塞式延时)
DEBUG_PRINT("Delay %lu ms\r\n", ms);
// ... (实际 MCU 延时函数,例如使用 HAL_Delay() 或自旋等待) ...
// 简单的自旋等待示例 (不精确,仅供演示)
uint32_t start_time = HAL_GetTick_ms();
while (HAL_GetTick_ms() - start_time < ms);
}

uint32_t HAL_GetTick_ms(void) {
// 获取系统运行时间 (毫秒)
// ... (实际 MCU 获取系统时间的代码,例如使用 SysTick 或其他定时器) ...
return 0; // 示例,实际需要返回系统时间
}


// --- ADC 读取 ---
uint16_t HAL_ADC_Read(uint32_t adc_channel) {
// 读取 ADC 通道的值
DEBUG_PRINT("ADC Channel %lu Read\r\n", adc_channel);
// ... (实际 MCU ADC 读取代码) ...
return 0; // 示例,实际需要返回 ADC 读取的值
}


// --- 初始化 HAL ---
void HAL_Init(void) {
DEBUG_PRINT("HAL Init\r\n");
// 初始化 HAL 模块,例如初始化时钟、外设等
// ... (实际 MCU HAL 初始化代码,例如 SystemClock_Config() 等) ...

// 初始化 GPIO (示例 - 初始化 LED 引脚为输出)
HAL_GPIO_Init(LED_PIN, GPIO_MODE_OUTPUT);
HAL_GPIO_WritePin(LED_PIN, false); // 初始状态 LED 灭
}

3. 驱动层 (Drivers - inc/drivers.h, src/drivers.c)

inc/drivers.h:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
#ifndef DRIVERS_H
#define DRIVERS_H

#include <stdint.h>
#include <stdbool.h>
#include "hal.h"

// --- IMU 传感器驱动 ---
typedef struct {
float accel_x; // 加速度 x (g)
float accel_y; // 加速度 y (g)
float accel_z; // 加速度 z (g)
float gyro_x; // 角速度 x (rad/s)
float gyro_y; // 角速度 y (rad/s)
float gyro_z; // 角速度 z (rad/s)
} IMU_Data_t;

bool IMU_Init(void);
bool IMU_ReadData(IMU_Data_t *data);


// --- 电机驱动 ---
bool Motor_Init(uint32_t motor_channel);
bool Motor_SetSpeed(uint32_t motor_channel, float speed_percentage); // 速度百分比 0.0 - 1.0


// --- 遥控接收器驱动 ---
typedef struct {
float roll; // 横滚 [-1.0, 1.0]
float pitch; // 俯仰 [-1.0, 1.0]
float yaw; // 偏航 [-1.0, 1.0]
float throttle; // 油门 [0.0, 1.0]
uint8_t mode; // 飞行模式 (例如,0: 稳定模式, 1: 特技模式)
} RC_Data_t;

bool RC_Receiver_Init(void);
bool RC_Receiver_ReadData(RC_Data_t *data);


#endif // DRIVERS_H

src/drivers.c (示例 - 针对假设的 IMU 和电机驱动,需要根据实际硬件进行适配)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
#include "drivers.h"
#include "config.h"

#include <math.h> // For M_PI

// --- IMU 传感器驱动 ---
#define IMU_SPI_INSTANCE 0 // 假设使用 SPI0
#define IMU_SPI_CLOCK_SPEED 1000000 // 1MHz SPI 时钟
#define IMU_ADDR_WHO_AM_I 0x75 // 示例寄存器地址
#define IMU_ADDR_ACCEL_X_LSB 0x28 // 示例加速度数据寄存器地址
#define IMU_ADDR_GYRO_X_LSB 0x22 // 示例陀螺仪数据寄存器地址
#define IMU_WHO_AM_I_VALUE 0x68 // 示例 IMU ID 值

#define IMU_ACCEL_SCALE (9.80665f / 16384.0f) // 示例加速度计量程和灵敏度 (需要查阅 IMU 数据手册)
#define IMU_GYRO_SCALE (M_PI / 180.0f / 131.0f) // 示例陀螺仪量程和灵敏度 (需要查阅 IMU 数据手册)


bool IMU_Init(void) {
DEBUG_PRINT("IMU Init\r\n");
HAL_SPI_Init(IMU_SPI_INSTANCE, IMU_SPI_CLOCK_SPEED);

// 检查 IMU ID
HAL_SPI_SelectChip(IMU_SPI_INSTANCE);
HAL_SPI_Transfer(IMU_SPI_INSTANCE, IMU_ADDR_WHO_AM_I | 0x80); // 读寄存器命令 (假设最高位为读标志)
uint8_t who_am_i = HAL_SPI_Transfer(IMU_SPI_INSTANCE, 0x00); // 接收数据
HAL_SPI_DeselectChip(IMU_SPI_INSTANCE);

if (who_am_i != IMU_WHO_AM_I_VALUE) {
DEBUG_PRINT("IMU ID Error: 0x%02X (Expected 0x%02X)\r\n", who_am_i, IMU_WHO_AM_I_VALUE);
return false;
}
DEBUG_PRINT("IMU ID: 0x%02X\r\n", who_am_i);

// 初始化 IMU 其他寄存器 (例如,配置加速度计和陀螺仪量程、采样率等)
// ... (根据 IMU 数据手册配置寄存器) ...

return true;
}

bool IMU_ReadData(IMU_Data_t *data) {
if (data == NULL) return false;

HAL_SPI_SelectChip(IMU_SPI_INSTANCE);

// 读取加速度计数据 (假设 X, Y, Z 轴数据寄存器地址连续)
HAL_SPI_Transfer(IMU_SPI_INSTANCE, IMU_ADDR_ACCEL_X_LSB | 0x80 | 0x40); // 读多个寄存器命令 (假设最高位为读标志,次高位为自动地址增加标志)
uint8_t accel_x_lsb = HAL_SPI_Transfer(IMU_SPI_INSTANCE, 0x00);
uint8_t accel_x_msb = HAL_SPI_Transfer(IMU_SPI_INSTANCE, 0x00);
uint8_t accel_y_lsb = HAL_SPI_Transfer(IMU_SPI_INSTANCE, 0x00);
uint8_t accel_y_msb = HAL_SPI_Transfer(IMU_SPI_INSTANCE, 0x00);
uint8_t accel_z_lsb = HAL_SPI_Transfer(IMU_SPI_INSTANCE, 0x00);
uint8_t accel_z_msb = HAL_SPI_Transfer(IMU_SPI_INSTANCE, 0x00);

// 读取陀螺仪数据 (假设 X, Y, Z 轴数据寄存器地址连续)
HAL_SPI_Transfer(IMU_SPI_INSTANCE, IMU_ADDR_GYRO_X_LSB | 0x80 | 0x40); // 读多个寄存器命令
uint8_t gyro_x_lsb = HAL_SPI_Transfer(IMU_SPI_INSTANCE, 0x00);
uint8_t gyro_x_msb = HAL_SPI_Transfer(IMU_SPI_INSTANCE, 0x00);
uint8_t gyro_y_lsb = HAL_SPI_Transfer(IMU_SPI_INSTANCE, 0x00);
uint8_t gyro_y_msb = HAL_SPI_Transfer(IMU_SPI_INSTANCE, 0x00);
uint8_t gyro_z_lsb = HAL_SPI_Transfer(IMU_SPI_INSTANCE, 0x00);
uint8_t gyro_z_msb = HAL_SPI_Transfer(IMU_SPI_INSTANCE, 0x00);

HAL_SPI_DeselectChip(IMU_SPI_INSTANCE);

// 组合数据,并转换为物理单位
data->accel_x = (float)(((int16_t)((accel_x_msb << 8) | accel_x_lsb)) * IMU_ACCEL_SCALE);
data->accel_y = (float)(((int16_t)((accel_y_msb << 8) | accel_y_lsb)) * IMU_ACCEL_SCALE);
data->accel_z = (float)(((int16_t)((accel_z_msb << 8) | accel_z_lsb)) * IMU_ACCEL_SCALE);

data->gyro_x = (float)(((int16_t)((gyro_x_msb << 8) | gyro_x_lsb)) * IMU_GYRO_SCALE);
data->gyro_y = (float)(((int16_t)((gyro_y_msb << 8) | gyro_y_lsb)) * IMU_GYRO_SCALE);
data->gyro_z = (float)(((int16_t)((gyro_z_msb << 8) | gyro_z_lsb)) * IMU_GYRO_SCALE);

DEBUG_PRINT("IMU Data: Accel(%.2f, %.2f, %.2f)g, Gyro(%.2f, %.2f, %.2f)rad/s\r\n",
data->accel_x, data->accel_y, data->accel_z,
data->gyro_x, data->gyro_y, data->gyro_z);

return true;
}


// --- 电机驱动 ---
#define MOTOR1_PWM_CHANNEL 1
#define MOTOR2_PWM_CHANNEL 2
#define MOTOR3_PWM_CHANNEL 3
#define MOTOR4_PWM_CHANNEL 4

bool Motor_Init(uint32_t motor_channel) {
DEBUG_PRINT("Motor %lu Init\r\n", motor_channel);
uint32_t pwm_pin;
uint32_t pwm_channel_id;

switch (motor_channel) {
case 1: pwm_pin = MOTOR1_PWM_PIN; pwm_channel_id = MOTOR1_PWM_CHANNEL; break;
case 2: pwm_pin = MOTOR2_PWM_PIN; pwm_channel_id = MOTOR2_PWM_CHANNEL; break;
case 3: pwm_pin = MOTOR3_PWM_PIN; pwm_channel_id = MOTOR3_PWM_CHANNEL; break;
case 4: pwm_pin = MOTOR4_PWM_PIN; pwm_channel_id = MOTOR4_PWM_CHANNEL; break;
default: DEBUG_PRINT("Invalid motor channel: %lu\r\n", motor_channel); return false;
}

HAL_PWM_Init(pwm_channel_id, PWM_FREQUENCY);
return true;
}

bool Motor_SetSpeed(uint32_t motor_channel, float speed_percentage) {
if (speed_percentage < 0.0f || speed_percentage > 1.0f) {
DEBUG_PRINT("Invalid speed percentage: %f\r\n", speed_percentage);
return false;
}

uint32_t pwm_channel_id;
switch (motor_channel) {
case 1: pwm_channel_id = MOTOR1_PWM_CHANNEL; break;
case 2: pwm_channel_id = MOTOR2_PWM_CHANNEL; break;
case 3: pwm_channel_id = MOTOR3_PWM_CHANNEL; break;
case 4: pwm_channel_id = MOTOR4_PWM_CHANNEL; break;
default: DEBUG_PRINT("Invalid motor channel: %lu\r\n", motor_channel); return false;
}

HAL_PWM_SetDutyCycle(pwm_channel_id, speed_percentage);
DEBUG_PRINT("Motor %lu Speed: %f%%\r\n", motor_channel, speed_percentage * 100.0f);
return true;
}


// --- 遥控接收器驱动 (示例 - 假设使用 PWM 信号,需要根据实际接收器类型进行适配) ---
#define RC_ROLL_CHANNEL 1
#define RC_PITCH_CHANNEL 2
#define RC_YAW_CHANNEL 3
#define RC_THROTTLE_CHANNEL 4
#define RC_MODE_CHANNEL 5

#define RC_PWM_MIN_US 1000 // 假设 PWM 最小值 (微秒)
#define RC_PWM_MAX_US 2000 // 假设 PWM 最大值 (微秒)
#define RC_PWM_MID_US 1500 // 假设 PWM 中间值 (微秒)


bool RC_Receiver_Init(void) {
DEBUG_PRINT("RC Receiver Init\r\n");
// 初始化遥控接收器相关引脚为输入模式,并配置中断或轮询方式读取 PWM 信号
// ... (根据实际遥控接收器和 MCU 的输入捕获或外部中断功能进行配置) ...
return true;
}

bool RC_Receiver_ReadData(RC_Data_t *data) {
if (data == NULL) return false;

// 读取遥控器 PWM 信号脉宽 (假设已经有函数可以读取 PWM 脉宽,例如 `HAL_IC_GetPulseWidth_us(pin)`)
uint32_t roll_pwm_us = RC_PWM_MID_US; // 假设读取 PWM 脉宽,这里用中间值代替
uint32_t pitch_pwm_us = RC_PWM_MID_US;
uint32_t yaw_pwm_us = RC_PWM_MID_US;
uint32_t throttle_pwm_us = RC_PWM_MIN_US; // 油门初始值通常为最低
uint32_t mode_pwm_us = RC_PWM_MIN_US; // 模式通道


// 映射 PWM 脉宽到控制量 [-1.0, 1.0] 或 [0.0, 1.0]
data->roll = (float)(roll_pwm_us - RC_PWM_MID_US) / (RC_PWM_MAX_US - RC_PWM_MID_US);
data->pitch = (float)(pitch_pwm_us - RC_PWM_MID_US) / (RC_PWM_MAX_US - RC_PWM_MID_US);
data->yaw = (float)(yaw_pwm_us - RC_PWM_MID_US) / (RC_PWM_MAX_US - RC_PWM_MID_US);
data->throttle = (float)(throttle_pwm_us - RC_PWM_MIN_US) / (RC_PWM_MAX_US - RC_PWM_MIN_US);
data->mode = (mode_pwm_us > RC_PWM_MID_US) ? 1 : 0; // 简单模式切换示例

// 限制控制量范围
if (data->roll > 1.0f) data->roll = 1.0f;
if (data->roll < -1.0f) data->roll = -1.0f;
if (data->pitch > 1.0f) data->pitch = 1.0f;
if (data->pitch < -1.0f) data->pitch = -1.0f;
if (data->yaw > 1.0f) data->yaw = 1.0f;
if (data->yaw < -1.0f) data->yaw = -1.0f;
if (data->throttle > 1.0f) data->throttle = 1.0f;
if (data->throttle < 0.0f) data->throttle = 0.0f;


DEBUG_PRINT("RC Data: Roll=%.2f, Pitch=%.2f, Yaw=%.2f, Throttle=%.2f, Mode=%d\r\n",
data->roll, data->pitch, data->yaw, data->throttle, data->mode);

return true;
}

4. 控制层 (Control - inc/control.h, src/control.c)

inc/control.h:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
#ifndef CONTROL_H
#define CONTROL_H

#include <stdint.h>
#include <stdbool.h>
#include "drivers.h"

// --- 姿态解算 ---
typedef struct {
float roll; // 横滚角 (弧度)
float pitch; // 俯仰角 (弧度)
float yaw; // 偏航角 (弧度)
} Attitude_t;

bool Attitude_Estimate(const IMU_Data_t *imu_data, float dt_sec, Attitude_t *attitude);


// --- PID 控制器 ---
typedef struct {
float kp;
float ki;
float kd;
float integral;
float prev_error;
} PID_Controller_t;

void PID_Init(PID_Controller_t *pid, float kp, float ki, float kd);
float PID_Compute(PID_Controller_t *pid, float setpoint, float current_value, float dt_sec);


// --- 电机混控 ---
typedef struct {
float motor1; // 电机 1 速度百分比 [0.0, 1.0]
float motor2; // 电机 2 速度百分比 [0.0, 1.0]
float motor3; // 电机 3 速度百分比 [0.0, 1.0]
float motor4; // 电机 4 速度百分比 [0.0, 1.0]
} Motor_Output_t;

void Motor_MixControl(const RC_Data_t *rc_data, const Attitude_t *attitude, Motor_Output_t *motor_output);

#endif // CONTROL_H

src/control.c (示例 - 简化的姿态解算、PID 控制和电机混控)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
#include "control.h"
#include "config.h"
#include <math.h>

// --- 姿态解算 (简化的互补滤波) ---
static Attitude_t estimated_attitude = {0.0f, 0.0f, 0.0f}; // 静态变量保存姿态

bool Attitude_Estimate(const IMU_Data_t *imu_data, float dt_sec, Attitude_t *attitude) {
if (imu_data == NULL || attitude == NULL) return false;

// 互补滤波参数 (需要根据实际情况调整)
float alpha = 0.98f; // 加速度计权重
float beta = 1.0f - alpha; // 陀螺仪权重

// 从加速度计计算 roll 和 pitch 角 (假设四轴水平静止时,加速度计只感受到重力)
float accel_roll = atan2f(imu_data->accel_y, imu_data->accel_z);
float accel_pitch = atan2f(-imu_data->accel_x, sqrtf(imu_data->accel_y * imu_data->accel_y + imu_data->accel_z * imu_data->accel_z));

// 互补滤波融合加速度计和陀螺仪数据
estimated_attitude.roll = alpha * (estimated_attitude.roll + imu_data->gyro_x * dt_sec) + beta * accel_roll;
estimated_attitude.pitch = alpha * (estimated_attitude.pitch + imu_data->gyro_y * dt_sec) + beta * accel_pitch;
estimated_attitude.yaw += imu_data->gyro_z * dt_sec; // 陀螺仪积分 yaw 角 (可能需要磁力计校正 yaw)

// 将结果赋值给输出
attitude->roll = estimated_attitude.roll;
attitude->pitch = estimated_attitude.pitch;
attitude->yaw = estimated_attitude.yaw;

DEBUG_PRINT("Attitude: Roll=%.2f, Pitch=%.2f, Yaw=%.2f (deg)\r\n",
attitude->roll * 180.0f / M_PI, attitude->pitch * 180.0f / M_PI, attitude->yaw * 180.0f / M_PI);

return true;
}


// --- PID 控制器 ---
void PID_Init(PID_Controller_t *pid, float kp, float ki, float kd) {
pid->kp = kp;
pid->ki = ki;
pid->kd = kd;
pid->integral = 0.0f;
pid->prev_error = 0.0f;
}

float PID_Compute(PID_Controller_t *pid, float setpoint, float current_value, float dt_sec) {
float error = setpoint - current_value;

pid->integral += error * dt_sec;

// 积分限幅 (防止积分饱和)
float integral_limit = 10.0f; // 需要根据实际情况调整
if (pid->integral > integral_limit) pid->integral = integral_limit;
if (pid->integral < -integral_limit) pid->integral = -integral_limit;

float derivative = (error - pid->prev_error) / dt_sec;

float output = pid->kp * error + pid->ki * pid->integral + pid->kd * derivative;

pid->prev_error = error;
return output;
}


// --- 电机混控 (X 型四轴) ---
void Motor_MixControl(const RC_Data_t *rc_data, const Attitude_t *attitude, Motor_Output_t *motor_output) {
if (rc_data == NULL || attitude == NULL || motor_output == NULL) return;

// 目标姿态角 (遥控指令映射到目标角度)
float target_roll = rc_data->roll * 30.0f * M_PI / 180.0f; // 例如,遥控杆最大角度对应 +/- 30 度
float target_pitch = rc_data->pitch * 30.0f * M_PI / 180.0f;
float target_yaw_rate = rc_data->yaw * 1.0f; // 例如,遥控杆最大偏航对应 +/- 1 rad/s 角速度

// PID 控制器 (Roll, Pitch, Yaw 角度环)
static PID_Controller_t pid_roll, pid_pitch, pid_yaw;
static bool pid_initialized = false;
if (!pid_initialized) {
PID_Init(&pid_roll, PID_KP_ROLL, PID_KI_ROLL, PID_KD_ROLL);
PID_Init(&pid_pitch, PID_KP_PITCH, PID_KI_PITCH, PID_KD_PITCH);
PID_Init(&pid_yaw, PID_KP_YAW, PID_KI_YAW, PID_KD_YAW);
pid_initialized = true;
}

float roll_control_output = PID_Compute(&pid_roll, target_roll, attitude->roll, (float)PID_LOOP_TIME / 1000.0f);
float pitch_control_output = PID_Compute(&pid_pitch, target_pitch, attitude->pitch, (float)PID_LOOP_TIME / 1000.0f);
float yaw_rate_control_output = PID_Compute(&pid_yaw, target_yaw_rate, imu_data->gyro_z, (float)PID_LOOP_TIME / 1000.0f); // 使用陀螺仪 Z 轴角速度


// 基础油门 (遥控油门映射到基础电机速度)
float base_throttle = rc_data->throttle;

// 电机混控计算 (X 型四轴电机顺序: 前右1, 后左2, 前左3, 后右4)
motor_output->motor1 = base_throttle - roll_control_output - pitch_control_output - yaw_rate_control_output; // 前右
motor_output->motor2 = base_throttle - roll_control_output + pitch_control_output + yaw_rate_control_output; // 后左
motor_output->motor3 = base_throttle + roll_control_output - pitch_control_output + yaw_rate_control_output; // 前左
motor_output->motor4 = base_throttle + roll_control_output + pitch_control_output - yaw_rate_control_output; // 后右


// 电机速度限幅 [0.0, 1.0]
if (motor_output->motor1 > 1.0f) motor_output->motor1 = 1.0f;
if (motor_output->motor1 < 0.0f) motor_output->motor1 = 0.0f;
if (motor_output->motor2 > 1.0f) motor_output->motor2 = 1.0f;
if (motor_output->motor2 < 0.0f) motor_output->motor2 = 0.0f;
if (motor_output->motor3 > 1.0f) motor_output->motor3 = 1.0f;
if (motor_output->motor3 < 0.0f) motor_output->motor3 = 0.0f;
if (motor_output->motor4 > 1.0f) motor_output->motor4 = 1.0f;
if (motor_output->motor4 < 0.0f) motor_output->motor4 = 0.0f;


DEBUG_PRINT("Motor Output: M1=%.2f%%, M2=%.2f%%, M3=%.2f%%, M4=%.2f%%\r\n",
motor_output->motor1 * 100.0f, motor_output->motor2 * 100.0f,
motor_output->motor3 * 100.0f, motor_output->motor4 * 100.0f);
}

5. 应用层 (App - inc/app.h, src/app.c)

inc/app.h:

1
2
3
4
5
6
7
8
9
10
#ifndef APP_H
#define APP_H

#include <stdint.h>
#include <stdbool.h>

void App_Init(void);
void App_Run(void);

#endif // APP_H

src/app.c:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
#include "app.h"
#include "hal.h"
#include "drivers.h"
#include "control.h"
#include "config.h"

#include <stdio.h> // For printf (调试用)

void App_Init(void) {
DEBUG_PRINT("App Init\r\n");
HAL_Init();
IMU_Init();
Motor_Init(1);
Motor_Init(2);
Motor_Init(3);
Motor_Init(4);
RC_Receiver_Init();

DEBUG_PRINT("App Init Done\r\n");
}

void App_Run(void) {
DEBUG_PRINT("App Run\r\n");

IMU_Data_t imu_data;
RC_Data_t rc_data;
Attitude_t attitude;
Motor_Output_t motor_output;

uint32_t last_loop_time = HAL_GetTick_ms();

while (1) {
uint32_t current_time = HAL_GetTick_ms();
uint32_t dt_ms = current_time - last_loop_time;
float dt_sec = (float)dt_ms / 1000.0f;

if (dt_ms >= PID_LOOP_TIME) { // 控制循环频率
last_loop_time = current_time;

// 读取传感器数据
if (IMU_ReadData(&imu_data)) {
// 姿态解算
Attitude_Estimate(&imu_data, dt_sec, &attitude);
}

// 读取遥控数据
if (RC_Receiver_ReadData(&rc_data)) {
// 电机混控
Motor_MixControl(&rc_data, &attitude, &motor_output);

// 设置电机速度
Motor_SetSpeed(1, motor_output.motor1);
Motor_SetSpeed(2, motor_output.motor2);
Motor_SetSpeed(3, motor_output.motor3);
Motor_SetSpeed(4, motor_output.motor4);
}

HAL_GPIO_WritePin(LED_PIN, !HAL_GPIO_ReadPin(LED_PIN)); // LED 闪烁,指示程序运行
}

HAL_Delay_ms(1); // 适当延时,降低 CPU 占用率
}
}

6. 主函数 (main.c)

1
2
3
4
5
6
7
8
#include "app.h"

int main(void) {
App_Init();
App_Run();

return 0;
}

编译与构建 (Makefile 示例)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
# Makefile for Quadcopter Project

PROJECT_NAME = quadcopter

# --- 编译器和工具链设置 ---
CC = arm-none-eabi-gcc # 替换为你的 ARM 编译器
CFLAGS = -g -O2 -Wall -mthumb -mcpu=cortex-m4 # 根据你的 MCU 架构调整
LDFLAGS = -mthumb -mcpu=cortex-m4 -specs=nosys.specs -lnosys

# --- 源文件和目标文件目录 ---
SRC_DIR = src
INC_DIR = inc
BUILD_DIR = build

SRC_FILES = $(wildcard $(SRC_DIR)/*.c)
OBJ_FILES = $(patsubst $(SRC_DIR)/%.c, $(BUILD_DIR)/%.o, $(SRC_FILES))
TARGET_ELF = $(BUILD_DIR)/$(PROJECT_NAME).elf
TARGET_BIN = $(BUILD_DIR)/$(PROJECT_NAME).bin

# --- 包含头文件路径 ---
INC_PATHS = -I$(INC_DIR)

# --- 构建规则 ---

all: $(TARGET_BIN)

$(TARGET_ELF): $(OBJ_FILES)
$(CC) $(LDFLAGS) -o $@ $^

$(TARGET_BIN): $(TARGET_ELF)
arm-none-eabi-objcopy -O binary $< $@

$(BUILD_DIR)/%.o: $(SRC_DIR)/%.c
@mkdir -p $(BUILD_DIR)
$(CC) $(CFLAGS) $(INC_PATHS) -c $< -o $@

clean:
rm -rf $(BUILD_DIR)

flash: $(TARGET_BIN)
# 添加烧录命令,例如使用 OpenOCD 或 ST-Link Utility 等
# 示例 (需要根据你的烧录工具和 MCU 型号调整)
openocd -f interface/stlink-v2.cfg -f target/stm32f4x.cfg -c "program $(TARGET_BIN) 0x08000000 reset exit"

.PHONY: all clean flash

项目实践验证与维护升级

  1. 实践验证:

    • 硬件调试: 使用示波器、逻辑分析仪等工具,验证硬件电路的正确性,例如电源、时钟、SPI/I2C 通信、PWM 输出等。
    • 单元测试: 针对每个模块进行单元测试,例如 IMU 驱动、PID 控制器等,确保模块功能的正确性。
    • 集成测试: 将各个模块集成起来进行测试,例如传感器数据读取、姿态解算、电机控制等,验证模块之间的协同工作。
    • 飞行测试: 在安全的环境下进行飞行测试,逐步调整 PID 参数,优化飞行性能。
  2. 维护升级:

    • Bug 修复: 及时修复测试和使用过程中发现的 Bug。
    • 功能扩展: 根据需求扩展系统功能,例如增加高度控制、定点悬停、自动返航等功能。
    • 性能优化: 优化代码,提高系统性能,例如降低功耗、提高控制精度、缩短响应时间等。
    • 代码重构: 随着项目的发展,适时进行代码重构,提高代码可读性和可维护性。
    • 文档更新: 及时更新设计文档、用户手册等文档,保持文档与代码的一致性。

总结

以上代码架构和代码示例提供了一个构建简单四轴飞行器的基础框架。你需要根据你实际使用的硬件平台(MCU、IMU、电机、遥控接收器等)进行相应的适配和调整。 700 行代码的限制意味着你需要精简代码,专注于核心功能,并牺牲一些高级特性和复杂性。

为了达到 3000 行代码的目标,可以进一步扩展以下方面:

  • 更详细的 HAL 实现: 针对具体的 MCU 平台,提供更完善的 HAL 代码,包括各种外设的初始化、配置和操作函数,并增加错误处理和状态检查。
  • 更完善的驱动层: 实现更健壮的传感器驱动,例如 IMU 校准、数据滤波、温度补偿等。实现更精确的电机驱动,例如 ESC 通信协议解析、反向 EMF 反馈控制等。
  • 更复杂的控制算法: 实现更高级的姿态解算算法(例如,扩展卡尔曼滤波)、更精细的 PID 控制参数整定方法、更复杂的飞行模式管理(例如,姿态模式、定高模式、定点模式等)。
  • 安全机制增强: 增加更完善的安全机制,例如电机过流保护、电池低电压保护、失控保护等。
  • 调试和日志功能增强: 实现更完善的调试和日志输出功能,方便问题定位和分析。
  • 代码注释和文档完善: 为所有代码添加详细的注释,并编写完善的设计文档和用户手册。
  • 多种编译配置和平台支持: 通过 Makefile 或其他构建系统,支持多种编译配置(例如,Debug/Release 模式)和不同的硬件平台。

通过以上扩展,你可以将代码量扩展到 3000 行以上,并构建一个更完善、更强大的嵌入式四轴飞行器系统平台。记住,实践是检验真理的唯一标准,不断地进行测试、验证和迭代,才能最终打造出可靠、高效、可扩展的嵌入式产品。

欢迎关注我的其它发布渠道