编程技术分享

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

0%

简介:基于ESP32S3的自适应轮腿机器人,足部选用4010无刷电机直驱,腿部选用舵机进行控制,主控板含有麦克风和功放模块,后期将实现开发ESP32离线语音控制。PCB绘制全部采用立创EDA专业版进行绘制。

很高兴能为您详细解析基于ESP32S3的自适应轮腿机器人的软件架构设计,并提供相应的C代码实现方案。
关注微信公众号,提前获取相关推文

项目背景与需求分析

首先,我们来回顾一下这个项目的核心需求:

  • 硬件平台: ESP32S3
  • 机器人类型: 自适应轮腿机器人,结合轮式移动和腿部越障能力。
  • 足部驱动: 4010无刷电机直驱轮子,实现高效的轮式移动。
  • 腿部控制: 舵机控制腿部关节,实现灵活的姿态调整和越障。
  • 音频输入: 板载麦克风,为后续的离线语音控制做准备。
  • 音频输出: 板载功放模块,配合麦克风实现语音交互。
  • 离线语音控制 (未来): ESP32S3本地语音识别和控制。
  • PCB设计: 立创EDA专业版。
  • 软件目标: 构建可靠、高效、可扩展的嵌入式系统平台,涵盖完整的开发流程。

基于以上需求,我们可以提炼出几个关键的软件设计要点:

  1. 实时性: 机器人控制需要实时响应,特别是电机和舵机的精确控制,以及未来的语音控制。
  2. 模块化: 系统功能复杂,需要模块化设计,方便开发、维护和扩展。
  3. 可扩展性: 预留接口和架构,方便后续添加新的传感器、功能模块,特别是语音控制功能。
  4. 资源优化: ESP32S3资源有限,需要优化代码,提高效率,减少资源占用。
  5. 可靠性: 确保系统稳定运行,处理异常情况,提高鲁棒性。

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

针对嵌入式系统的特点和项目的需求,我推荐采用分层架构结合模块化设计。这种架构具有清晰的层次结构,模块之间职责明确,易于理解、开发和维护。

分层架构:

我们将系统软件划分为以下几个层次:

  • 硬件抽象层 (HAL - Hardware Abstraction Layer): 直接与硬件交互,封装底层硬件操作,向上层提供统一的硬件接口。例如,GPIO、PWM、定时器、串口、SPI、I2C、ADC、I2S等硬件的驱动接口。
  • 驱动层 (Driver Layer): 基于HAL层,实现特定硬件设备的功能驱动,例如电机驱动、舵机驱动、麦克风驱动、功放驱动等。这一层负责设备的初始化、配置、数据收发等操作。
  • 控制层 (Control Layer): 实现机器人的核心控制算法,包括电机控制、舵机控制、运动控制、姿态控制、路径规划等。这一层是机器人的“大脑”,负责决策和执行。
  • 应用层 (Application Layer): 构建在控制层之上,实现用户交互和高层应用逻辑,例如命令解析、模式切换、状态监控、语音控制 (未来) 等。

模块化设计:

在每个层次内部,我们都采用模块化设计,将功能分解为独立的模块,模块之间通过定义良好的接口进行交互。例如:

  • HAL层模块:
    • hal_gpio: GPIO控制模块
    • hal_pwm: PWM控制模块
    • hal_timer: 定时器模块
    • hal_uart: 串口通信模块
    • hal_i2c: I2C通信模块
    • hal_spi: SPI通信模块
    • hal_adc: ADC模数转换模块
    • hal_i2s: I2S音频接口模块
  • 驱动层模块:
    • motor_driver: 电机驱动模块 (4010无刷电机)
    • servo_driver: 舵机驱动模块
    • microphone_driver: 麦克风驱动模块
    • amplifier_driver: 功放驱动模块
  • 控制层模块:
    • wheel_control: 轮式运动控制模块
    • leg_control: 腿部运动控制模块
    • motion_control: 综合运动控制模块 (轮腿协同)
    • posture_control: 姿态控制模块
  • 应用层模块:
    • command_parser: 命令解析模块
    • robot_mode: 机器人模式管理模块
    • status_monitor: 状态监控模块
    • voice_control: 语音控制模块 (未来)

代码实现 (C语言 - ESP-IDF框架)

接下来,我将逐步给出关键模块的C代码实现,并详细解释代码逻辑和设计思路。我将尽可能详细地编写代码,包括注释、错误处理、配置选项等。

1. 硬件抽象层 (HAL)

hal_gpio.h (GPIO HAL头文件)

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
#ifndef HAL_GPIO_H
#define HAL_GPIO_H

#include <stdint.h>
#include "driver/gpio.h"
#include "esp_err.h"

typedef enum {
GPIO_MODE_INPUT,
GPIO_MODE_OUTPUT,
GPIO_MODE_INPUT_OUTPUT
} hal_gpio_mode_t;

typedef enum {
GPIO_PULLUP,
GPIO_PULLDOWN,
GPIO_PULLNONE
} hal_gpio_pull_mode_t;

/**
* @brief 初始化GPIO引脚
*
* @param gpio_num GPIO引脚号
* @param mode GPIO模式 (输入/输出/输入输出)
* @param pull_mode 上拉/下拉/无
* @return esp_err_t ESP错误码
*/
esp_err_t hal_gpio_init(gpio_num_t gpio_num, hal_gpio_mode_t mode, hal_gpio_pull_mode_t pull_mode);

/**
* @brief 设置GPIO输出电平
*
* @param gpio_num GPIO引脚号
* @param level 输出电平 (0/1)
* @return esp_err_t ESP错误码
*/
esp_err_t hal_gpio_set_level(gpio_num_t gpio_num, uint32_t level);

/**
* @brief 读取GPIO输入电平
*
* @param gpio_num GPIO引脚号
* @param level 输出电平指针,用于存储读取到的电平值 (0/1)
* @return esp_err_t ESP错误码
*/
esp_err_t hal_gpio_get_level(gpio_num_t gpio_num, uint32_t *level);

#endif // HAL_GPIO_H

hal_gpio.c (GPIO HAL实现文件)

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
#include "hal_gpio.h"

esp_err_t hal_gpio_init(gpio_num_t gpio_num, hal_gpio_mode_t mode, hal_gpio_pull_mode_t pull_mode) {
gpio_config_t io_conf;
io_conf.intr_type = GPIO_INTR_DISABLE; // 禁止中断
io_conf.pin_bit_mask = (1ULL << gpio_num); // 配置引脚掩码

if (mode == GPIO_MODE_INPUT) {
io_conf.mode = GPIO_MODE_INPUT;
} else if (mode == GPIO_MODE_OUTPUT) {
io_conf.mode = GPIO_MODE_OUTPUT;
} else if (mode == GPIO_MODE_INPUT_OUTPUT) {
io_conf.mode = GPIO_MODE_INPUT_OUTPUT;
} else {
return ESP_ERR_INVALID_ARG; // 参数错误
}

if (pull_mode == GPIO_PULLUP) {
io_conf.pull_down_en = 0;
io_conf.pull_up_en = 1;
} else if (pull_mode == GPIO_PULLDOWN) {
io_conf.pull_down_en = 1;
io_conf.pull_up_en = 0;
} else if (pull_mode == GPIO_PULLNONE) {
io_conf.pull_down_en = 0;
io_conf.pull_up_en = 0;
} else {
return ESP_ERR_INVALID_ARG; // 参数错误
}

esp_err_t ret = gpio_config(&io_conf);
if (ret != ESP_OK) {
ESP_LOGE("HAL_GPIO", "GPIO %d init failed, error code: %d", gpio_num, ret);
return ret;
}
ESP_LOGI("HAL_GPIO", "GPIO %d init success, mode: %d, pull mode: %d", gpio_num, mode, pull_mode);
return ESP_OK;
}

esp_err_t hal_gpio_set_level(gpio_num_t gpio_num, uint32_t level) {
esp_err_t ret = gpio_set_level(gpio_num, level);
if (ret != ESP_OK) {
ESP_LOGE("HAL_GPIO", "GPIO %d set level failed, error code: %d", gpio_num, ret);
return ret;
}
return ESP_OK;
}

esp_err_t hal_gpio_get_level(gpio_num_t gpio_num, uint32_t *level) {
if (level == NULL) {
return ESP_ERR_INVALID_ARG; // 指针为空
}
*level = gpio_get_level(gpio_num);
return ESP_OK;
}

hal_pwm.h (PWM HAL头文件)

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
#ifndef HAL_PWM_H
#define HAL_PWM_H

#include <stdint.h>
#include "driver/ledc.h"
#include "esp_err.h"

typedef enum {
PWM_TIMER_0,
PWM_TIMER_1,
PWM_TIMER_2,
PWM_TIMER_3
} hal_pwm_timer_t;

typedef enum {
PWM_CHANNEL_0,
PWM_CHANNEL_1,
PWM_CHANNEL_2,
PWM_CHANNEL_3,
PWM_CHANNEL_4,
PWM_CHANNEL_5,
PWM_CHANNEL_6,
PWM_CHANNEL_7
} hal_pwm_channel_t;

/**
* @brief 初始化PWM模块
*
* @param timer_num PWM定时器号
* @param freq_hz PWM频率 (Hz)
* @param duty_resolution PWM占空比分辨率 (例如 LEDC_TIMER_13_BIT)
* @return esp_err_t ESP错误码
*/
esp_err_t hal_pwm_init(hal_pwm_timer_t timer_num, uint32_t freq_hz, ledc_timer_bit_t duty_resolution);

/**
* @brief 配置PWM通道
*
* @param channel_num PWM通道号
* @param gpio_num PWM输出GPIO引脚号
* @param timer_num PWM定时器号
* @return esp_err_t ESP错误码
*/
esp_err_t hal_pwm_config_channel(hal_pwm_channel_t channel_num, gpio_num_t gpio_num, hal_pwm_timer_t timer_num);

/**
* @brief 设置PWM占空比 (0 - 最大值,最大值取决于占空比分辨率)
*
* @param channel_num PWM通道号
* @param duty 占空比值
* @return esp_err_t ESP错误码
*/
esp_err_t hal_pwm_set_duty(hal_pwm_channel_t channel_num, uint32_t duty);

/**
* @brief 更新PWM占空比 (使设置生效)
*
* @param channel_num PWM通道号
* @return esp_err_t ESP错误码
*/
esp_err_t hal_pwm_update_duty(hal_pwm_channel_t channel_num);

#endif // HAL_PWM_H

hal_pwm.c (PWM HAL实现文件)

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
#include "hal_pwm.h"
#include "esp_log.h"

#define PWM_DUTY_RESOLUTION LEDC_TIMER_13_BIT // 占空比分辨率 (13位)

esp_err_t hal_pwm_init(hal_pwm_timer_t timer_num, uint32_t freq_hz, ledc_timer_bit_t duty_resolution) {
ledc_timer_config_t timer_config = {
.speed_mode = LEDC_HIGH_SPEED_MODE, // 高速模式
.duty_resolution = duty_resolution, // 占空比分辨率
.timer_num = timer_num, // 定时器号
.freq_hz = freq_hz, // PWM频率
.clk_cfg = LEDC_AUTO_CLK, // 自动选择时钟源
};
esp_err_t ret = ledc_timer_config(&timer_config);
if (ret != ESP_OK) {
ESP_LOGE("HAL_PWM", "PWM timer %d init failed, error code: %d", timer_num, ret);
return ret;
}
ESP_LOGI("HAL_PWM", "PWM timer %d init success, freq: %d Hz, resolution: %d bits", timer_num, freq_hz, duty_resolution);
return ESP_OK;
}

esp_err_t hal_pwm_config_channel(hal_pwm_channel_t channel_num, gpio_num_t gpio_num, hal_pwm_timer_t timer_num) {
ledc_channel_config_t channel_config = {
.speed_mode = LEDC_HIGH_SPEED_MODE, // 高速模式
.channel = channel_num, // PWM通道号
.timer_sel = timer_num, // 选择定时器
.intr_mask = 0, // 禁止中断
.duty = 0, // 初始占空比为0
.gpio_num = gpio_num, // PWM输出GPIO引脚
.hpoint = 0, // 忽略
};
esp_err_t ret = ledc_channel_config(&channel_config);
if (ret != ESP_OK) {
ESP_LOGE("HAL_PWM", "PWM channel %d config failed, error code: %d", channel_num, ret);
return ret;
}
ESP_LOGI("HAL_PWM", "PWM channel %d config success, GPIO: %d, timer: %d", channel_num, gpio_num, timer_num);
return ESP_OK;
}

esp_err_t hal_pwm_set_duty(hal_pwm_channel_t channel_num, uint32_t duty) {
esp_err_t ret = ledc_set_duty(LEDC_HIGH_SPEED_MODE, channel_num, duty);
if (ret != ESP_OK) {
ESP_LOGE("HAL_PWM", "PWM channel %d set duty failed, error code: %d", channel_num, ret);
return ret;
}
return ESP_OK;
}

esp_err_t hal_pwm_update_duty(hal_pwm_channel_t channel_num) {
esp_err_t ret = ledc_update_duty(LEDC_HIGH_SPEED_MODE, channel_num);
if (ret != ESP_OK) {
ESP_LOGE("HAL_PWM", "PWM channel %d update duty failed, error code: %d", channel_num, ret);
return ret;
}
return ESP_OK;
}

(其他HAL模块类似,例如 hal_uart.h/c, hal_timer.h/c, hal_i2c.h/c 等,这里为了篇幅不再一一展开,但原理类似:定义接口头文件 .h 和实现文件 .c,封装 ESP-IDF 提供的驱动函数。)

2. 驱动层 (Driver Layer)

motor_driver.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
#ifndef MOTOR_DRIVER_H
#define MOTOR_DRIVER_H

#include <stdint.h>
#include "esp_err.h"

typedef enum {
MOTOR_FORWARD,
MOTOR_BACKWARD,
MOTOR_STOP
} motor_direction_t;

typedef struct {
uint32_t pwm_channel_forward; // 前进PWM通道
uint32_t pwm_channel_backward; // 后退PWM通道
gpio_num_t gpio_enable; // 使能GPIO (可选)
// ... 可以添加编码器接口等
} motor_config_t;

/**
* @brief 初始化电机驱动
*
* @param config 电机配置结构体
* @return esp_err_t ESP错误码
*/
esp_err_t motor_driver_init(motor_config_t *config);

/**
* @brief 设置电机速度和方向
*
* @param config 电机配置结构体
* @param direction 电机方向
* @param speed 电机速度 (0-100%,或根据实际情况定义)
* @return esp_err_t ESP错误码
*/
esp_err_t motor_driver_set_speed(motor_config_t *config, motor_direction_t direction, uint32_t speed);

/**
* @brief 停止电机
*
* @param config 电机配置结构体
* @return esp_err_t ESP错误码
*/
esp_err_t motor_driver_stop(motor_config_t *config);

#endif // MOTOR_DRIVER_H

motor_driver.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
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
#include "motor_driver.h"
#include "hal_pwm.h"
#include "hal_gpio.h"
#include "esp_log.h"

#define PWM_MOTOR_FREQUENCY 20000 // 电机PWM频率 (20kHz)
#define PWM_MOTOR_RESOLUTION LEDC_TIMER_13_BIT // 电机PWM占空比分辨率 (13位)
#define PWM_DUTY_MAX ((1 << PWM_MOTOR_RESOLUTION) - 1) // 最大占空比值

esp_err_t motor_driver_init(motor_config_t *config) {
if (config == NULL) {
return ESP_ERR_INVALID_ARG; // 参数错误
}

// 初始化PWM定时器 (假设使用 Timer 0)
ESP_ERROR_CHECK(hal_pwm_init(PWM_TIMER_0, PWM_MOTOR_FREQUENCY, PWM_MOTOR_RESOLUTION));

// 配置前进PWM通道
ESP_ERROR_CHECK(hal_pwm_config_channel(config->pwm_channel_forward, -1, PWM_TIMER_0)); // GPIO在set_speed中配置

// 配置后退PWM通道
ESP_ERROR_CHECK(hal_pwm_config_channel(config->pwm_channel_backward, -1, PWM_TIMER_0)); // GPIO在set_speed中配置

// 初始化使能GPIO (如果配置了)
if (config->gpio_enable != -1) {
ESP_ERROR_CHECK(hal_gpio_init(config->gpio_enable, GPIO_MODE_OUTPUT, GPIO_PULLNONE));
ESP_ERROR_CHECK(hal_gpio_set_level(config->gpio_enable, 1)); // 默认使能电机
}

ESP_LOGI("MOTOR_DRIVER", "Motor driver initialized");
return ESP_OK;
}

esp_err_t motor_driver_set_speed(motor_config_t *config, motor_direction_t direction, uint32_t speed) {
if (config == NULL) {
return ESP_ERR_INVALID_ARG; // 参数错误
}
if (speed > 100) {
speed = 100; // 限制速度范围
}

uint32_t duty = (uint32_t)(((float)speed / 100.0f) * PWM_DUTY_MAX); // 计算占空比

if (direction == MOTOR_FORWARD) {
ESP_LOGD("MOTOR_DRIVER", "Motor forward, speed: %d%%, duty: %d", speed, duty);
ESP_ERROR_CHECK(hal_pwm_config_channel(config->pwm_channel_forward, GPIO_NUM_21, PWM_TIMER_0)); // 配置前进PWM GPIO (假设GPIO21)
ESP_ERROR_CHECK(hal_pwm_config_channel(config->pwm_channel_backward, -1, PWM_TIMER_0)); // 关闭后退PWM GPIO
ESP_ERROR_CHECK(hal_pwm_set_duty(config->pwm_channel_forward, duty));
ESP_ERROR_CHECK(hal_pwm_update_duty(config->pwm_channel_forward));
ESP_ERROR_CHECK(hal_pwm_set_duty(config->pwm_channel_backward, 0)); // 确保后退通道占空比为0
ESP_ERROR_CHECK(hal_pwm_update_duty(config->pwm_channel_backward));
} else if (direction == MOTOR_BACKWARD) {
ESP_LOGD("MOTOR_DRIVER", "Motor backward, speed: %d%%, duty: %d", speed, duty);
ESP_ERROR_CHECK(hal_pwm_config_channel(config->pwm_channel_backward, GPIO_NUM_22, PWM_TIMER_0)); // 配置后退PWM GPIO (假设GPIO22)
ESP_ERROR_CHECK(hal_pwm_config_channel(config->pwm_channel_forward, -1, PWM_TIMER_0)); // 关闭前进PWM GPIO
ESP_ERROR_CHECK(hal_pwm_set_duty(config->pwm_channel_backward, duty));
ESP_ERROR_CHECK(hal_pwm_update_duty(config->pwm_channel_backward));
ESP_ERROR_CHECK(hal_pwm_set_duty(config->pwm_channel_forward, 0)); // 确保前进通道占空比为0
ESP_ERROR_CHECK(hal_pwm_update_duty(config->pwm_channel_forward));
} else if (direction == MOTOR_STOP) {
ESP_LOGD("MOTOR_DRIVER", "Motor stop");
ESP_ERROR_CHECK(motor_driver_stop(config));
} else {
return ESP_ERR_INVALID_ARG; // 参数错误
}

return ESP_OK;
}

esp_err_t motor_driver_stop(motor_config_t *config) {
if (config == NULL) {
return ESP_ERR_INVALID_ARG; // 参数错误
}
ESP_ERROR_CHECK(hal_pwm_set_duty(config->pwm_channel_forward, 0));
ESP_ERROR_CHECK(hal_pwm_update_duty(config->pwm_channel_forward));
ESP_ERROR_CHECK(hal_pwm_set_duty(config->pwm_channel_backward, 0));
ESP_ERROR_CHECK(hal_pwm_update_duty(config->pwm_channel_backward));
return ESP_OK;
}

servo_driver.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
#ifndef SERVO_DRIVER_H
#define SERVO_DRIVER_H

#include <stdint.h>
#include "esp_err.h"

typedef struct {
uint32_t pwm_channel; // PWM通道
gpio_num_t gpio_servo; // 舵机控制GPIO
uint32_t angle_min_us; // 最小角度对应的脉宽 (us)
uint32_t angle_max_us; // 最大角度对应的脉宽 (us)
uint32_t angle_range_deg; // 舵机角度范围 (度)
} servo_config_t;

/**
* @brief 初始化舵机驱动
*
* @param config 舵机配置结构体
* @return esp_err_t ESP错误码
*/
esp_err_t servo_driver_init(servo_config_t *config);

/**
* @brief 设置舵机角度
*
* @param config 舵机配置结构体
* @param angle 目标角度 (度)
* @return esp_err_t ESP错误码
*/
esp_err_t servo_driver_set_angle(servo_config_t *config, uint32_t angle);

#endif // SERVO_DRIVER_H

servo_driver.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
#include "servo_driver.h"
#include "hal_pwm.h"
#include "esp_log.h"

#define PWM_SERVO_FREQUENCY 50 // 舵机PWM频率 (50Hz) - 周期 20ms
#define PWM_SERVO_RESOLUTION LEDC_TIMER_13_BIT // 舵机PWM占空比分辨率 (13位)
#define PWM_DUTY_MAX ((1 << PWM_SERVO_RESOLUTION) - 1) // 最大占空比值

esp_err_t servo_driver_init(servo_config_t *config) {
if (config == NULL) {
return ESP_ERR_INVALID_ARG; // 参数错误
}

// 初始化PWM定时器 (假设使用 Timer 1)
ESP_ERROR_CHECK(hal_pwm_init(PWM_TIMER_1, PWM_SERVO_FREQUENCY, PWM_SERVO_RESOLUTION));

// 配置舵机PWM通道
ESP_ERROR_CHECK(hal_pwm_config_channel(config->pwm_channel, config->gpio_servo, PWM_TIMER_1));

ESP_LOGI("SERVO_DRIVER", "Servo driver initialized for GPIO %d", config->gpio_servo);
return ESP_OK;
}

esp_err_t servo_driver_set_angle(servo_config_t *config, uint32_t angle) {
if (config == NULL) {
return ESP_ERR_INVALID_ARG; // 参数错误
}
if (angle > config->angle_range_deg) {
angle = config->angle_range_deg; // 限制角度范围
}
if (angle < 0) {
angle = 0;
}

// 将角度转换为脉宽 (us)
float angle_ratio = (float)angle / config->angle_range_deg;
uint32_t pulse_width_us = config->angle_min_us + (uint32_t)(angle_ratio * (config->angle_max_us - config->angle_min_us));

// 将脉宽转换为占空比 (根据PWM频率和分辨率)
float duty_cycle = (float)pulse_width_us / (1000000.0f / PWM_SERVO_FREQUENCY); // 脉宽占周期比例
uint32_t duty = (uint32_t)(duty_cycle * PWM_DUTY_MAX);

ESP_LOGD("SERVO_DRIVER", "Set servo angle: %d deg, pulse width: %d us, duty: %d", angle, pulse_width_us, duty);

ESP_ERROR_CHECK(hal_pwm_set_duty(config->pwm_channel, duty));
ESP_ERROR_CHECK(hal_pwm_update_duty(config->pwm_channel));

return ESP_OK;
}

(其他驱动模块例如 microphone_driver.h/c, amplifier_driver.h/c 的实现类似,需要根据具体的硬件接口和驱动芯片进行编写,这里为了篇幅不再详细展开,但基本思路是:初始化硬件,提供数据收发和控制接口。)

3. 控制层 (Control Layer)

wheel_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
#ifndef WHEEL_CONTROL_H
#define WHEEL_CONTROL_H

#include <stdint.h>
#include "esp_err.h"
#include "motor_driver.h"

typedef struct {
motor_config_t motor_left_config;
motor_config_t motor_right_config;
// ... 可以添加PID控制参数等
} wheel_control_config_t;

/**
* @brief 初始化轮式运动控制模块
*
* @param config 轮式控制配置结构体
* @return esp_err_t ESP错误码
*/
esp_err_t wheel_control_init(wheel_control_config_t *config);

/**
* @brief 设置左右轮速度,实现前进、后退、转向
*
* @param left_speed 左轮速度 (-100% - 100%,负值为反向)
* @param right_speed 右轮速度 (-100% - 100%,负值为反向)
* @return esp_err_t ESP错误码
*/
esp_err_t wheel_control_set_speed(int32_t left_speed, int32_t right_speed);

/**
* @brief 停止轮式运动
*
* @return esp_err_t ESP错误码
*/
esp_err_t wheel_control_stop();

#endif // WHEEL_CONTROL_H

wheel_control.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
#include "wheel_control.h"
#include "esp_log.h"

static wheel_control_config_t g_wheel_config; // 全局轮式控制配置

esp_err_t wheel_control_init(wheel_control_config_t *config) {
if (config == NULL) {
return ESP_ERR_INVALID_ARG; // 参数错误
}
g_wheel_config = *config; // 复制配置

ESP_ERROR_CHECK(motor_driver_init(&g_wheel_config.motor_left_config));
ESP_ERROR_CHECK(motor_driver_init(&g_wheel_config.motor_right_config));

ESP_LOGI("WHEEL_CONTROL", "Wheel control initialized");
return ESP_OK;
}

esp_err_t wheel_control_set_speed(int32_t left_speed, int32_t right_speed) {
motor_direction_t left_dir = MOTOR_STOP;
motor_direction_t right_dir = MOTOR_STOP;
uint32_t left_speed_percent = 0;
uint32_t right_speed_percent = 0;

if (left_speed > 0) {
left_dir = MOTOR_FORWARD;
left_speed_percent = (uint32_t)left_speed;
} else if (left_speed < 0) {
left_dir = MOTOR_BACKWARD;
left_speed_percent = (uint32_t)(-left_speed);
}

if (right_speed > 0) {
right_dir = MOTOR_FORWARD;
right_speed_percent = (uint32_t)right_speed;
} else if (right_speed < 0) {
right_dir = MOTOR_BACKWARD;
right_speed_percent = (uint32_t)(-right_speed);
}

ESP_LOGD("WHEEL_CONTROL", "Set speed: left %d, right %d", left_speed, right_speed);

ESP_ERROR_CHECK(motor_driver_set_speed(&g_wheel_config.motor_left_config, left_dir, left_speed_percent));
ESP_ERROR_CHECK(motor_driver_set_speed(&g_wheel_config.motor_right_config, right_dir, right_speed_percent));

return ESP_OK;
}

esp_err_t wheel_control_stop() {
ESP_LOGD("WHEEL_CONTROL", "Stop wheel control");
ESP_ERROR_CHECK(motor_driver_stop(&g_wheel_config.motor_left_config));
ESP_ERROR_CHECK(motor_driver_stop(&g_wheel_config.motor_right_config));
return ESP_OK;
}

leg_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
42
43
44
45
46
47
48
49
50
51
52
53
#ifndef LEG_CONTROL_H
#define LEG_CONTROL_H

#include <stdint.h>
#include "esp_err.h"
#include "servo_driver.h"

#define LEG_NUM 2 // 假设左右各有1条腿

typedef struct {
servo_config_t servo_hip_config; // 髋关节舵机配置
servo_config_t servo_knee_config; // 膝关节舵机配置
// ... 可以添加更多关节舵机配置
} leg_config_t;

typedef struct {
leg_config_t legs[LEG_NUM]; // 左右腿配置
// ... 可以添加步态参数等
} leg_control_config_t;

/**
* @brief 初始化腿部运动控制模块
*
* @param config 腿部控制配置结构体
* @return esp_err_t ESP错误码
*/
esp_err_t leg_control_init(leg_control_config_t *config);

/**
* @brief 设置单条腿的关节角度
*
* @param leg_index 腿索引 (0 - LEG_NUM-1)
* @param hip_angle 髋关节角度 (度)
* @param knee_angle 膝关节角度 (度)
* @return esp_err_t ESP错误码
*/
esp_err_t leg_control_set_leg_angle(uint32_t leg_index, uint32_t hip_angle, uint32_t knee_angle);

/**
* @brief 设置所有腿的默认站立姿态
*
* @return esp_err_t ESP错误码
*/
esp_err_t leg_control_stand_pose();

/**
* @brief 设置所有腿的收缩姿态
*
* @return esp_err_t ESP错误码
*/
esp_err_t leg_control_contract_pose();

#endif // LEG_CONTROL_H

leg_control.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
#include "leg_control.h"
#include "esp_log.h"

static leg_control_config_t g_leg_config; // 全局腿部控制配置

esp_err_t leg_control_init(leg_control_config_t *config) {
if (config == NULL) {
return ESP_ERR_INVALID_ARG; // 参数错误
}
g_leg_config = *config; // 复制配置

for (int i = 0; i < LEG_NUM; i++) {
ESP_ERROR_CHECK(servo_driver_init(&g_leg_config.legs[i].servo_hip_config));
ESP_ERROR_CHECK(servo_driver_init(&g_leg_config.legs[i].servo_knee_config));
// ... 初始化更多关节舵机
}

ESP_LOGI("LEG_CONTROL", "Leg control initialized");
return ESP_OK;
}

esp_err_t leg_control_set_leg_angle(uint32_t leg_index, uint32_t hip_angle, uint32_t knee_angle) {
if (leg_index >= LEG_NUM) {
return ESP_ERR_INVALID_ARG; // 腿索引越界
}
ESP_LOGD("LEG_CONTROL", "Set leg %d angle: hip %d, knee %d", leg_index, hip_angle, knee_angle);

ESP_ERROR_CHECK(servo_driver_set_angle(&g_leg_config.legs[leg_index].servo_hip_config, hip_angle));
ESP_ERROR_CHECK(servo_driver_set_angle(&g_leg_config.legs[leg_index].servo_knee_config, knee_angle));
// ... 设置更多关节角度

return ESP_OK;
}

esp_err_t leg_control_stand_pose() {
ESP_LOGI("LEG_CONTROL", "Set stand pose");
for (int i = 0; i < LEG_NUM; i++) {
ESP_ERROR_CHECK(leg_control_set_leg_angle(i, 90, 90)); // 站立姿态,假设髋关节和膝关节都是90度
}
return ESP_OK;
}

esp_err_t leg_control_contract_pose() {
ESP_LOGI("LEG_CONTROL", "Set contract pose");
for (int i = 0; i < LEG_NUM; i++) {
ESP_ERROR_CHECK(leg_control_set_leg_angle(i, 0, 0)); // 收缩姿态,假设髋关节和膝关节都是0度
}
return ESP_OK;
}

( motion_control.h/c, posture_control.h/c 等模块的实现类似,需要根据具体的运动控制算法和姿态控制策略进行编写,例如可以实现轮腿协同运动,姿态平衡控制等。)

4. 应用层 (Application Layer)

command_parser.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
#ifndef COMMAND_PARSER_H
#define COMMAND_PARSER_H

#include <stdint.h>
#include "esp_err.h"

typedef enum {
CMD_FORWARD,
CMD_BACKWARD,
CMD_TURN_LEFT,
CMD_TURN_RIGHT,
CMD_STOP,
CMD_STAND,
CMD_CONTRACT,
CMD_UNKNOWN
} robot_command_t;

/**
* @brief 解析命令字符串
*
* @param command_str 命令字符串
* @param command_out 输出命令类型
* @return esp_err_t ESP错误码
*/
esp_err_t command_parser_parse(const char *command_str, robot_command_t *command_out);

#endif // COMMAND_PARSER_H

command_parser.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
#include "command_parser.h"
#include <string.h>
#include <stdio.h>
#include "esp_log.h"

esp_err_t command_parser_parse(const char *command_str, robot_command_t *command_out) {
if (command_str == NULL || command_out == NULL) {
return ESP_ERR_INVALID_ARG; // 参数错误
}

ESP_LOGD("COMMAND_PARSER", "Parsing command: %s", command_str);

if (strcmp(command_str, "forward") == 0) {
*command_out = CMD_FORWARD;
} else if (strcmp(command_str, "backward") == 0) {
*command_out = CMD_BACKWARD;
} else if (strcmp(command_str, "left") == 0 || strcmp(command_str, "turn_left") == 0) {
*command_out = CMD_TURN_LEFT;
} else if (strcmp(command_str, "right") == 0 || strcmp(command_str, "turn_right") == 0) {
*command_out = CMD_TURN_RIGHT;
} else if (strcmp(command_str, "stop") == 0) {
*command_out = CMD_STOP;
} else if (strcmp(command_str, "stand") == 0) {
*command_out = CMD_STAND;
} else if (strcmp(command_str, "contract") == 0) {
*command_out = CMD_CONTRACT;
} else {
*command_out = CMD_UNKNOWN;
ESP_LOGW("COMMAND_PARSER", "Unknown command: %s", command_str);
}

return ESP_OK;
}

robot_mode.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
#ifndef ROBOT_MODE_H
#define ROBOT_MODE_H

#include <stdint.h>
#include "esp_err.h"
#include "command_parser.h"

typedef enum {
ROBOT_MODE_IDLE,
ROBOT_MODE_MANUAL_CONTROL,
ROBOT_MODE_VOICE_CONTROL, // 未来
ROBOT_MODE_AUTO_NAV, // 未来
ROBOT_MODE_ERROR
} robot_mode_t;

/**
* @brief 初始化机器人模式管理模块
*
* @return esp_err_t ESP错误码
*/
esp_err_t robot_mode_init();

/**
* @brief 设置机器人模式
*
* @param mode 目标模式
* @return esp_err_t ESP错误码
*/
esp_err_t robot_mode_set_mode(robot_mode_t mode);

/**
* @brief 获取当前机器人模式
*
* @return robot_mode_t 当前模式
*/
robot_mode_t robot_mode_get_mode();

/**
* @brief 处理机器人命令 (根据当前模式执行相应动作)
*
* @param command 机器人命令
* @return esp_err_t ESP错误码
*/
esp_err_t robot_mode_handle_command(robot_command_t command);

#endif // ROBOT_MODE_H

robot_mode.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
64
65
66
67
68
69
70
71
#include "robot_mode.h"
#include "wheel_control.h"
#include "leg_control.h"
#include "esp_log.h"

static robot_mode_t g_current_mode = ROBOT_MODE_IDLE; // 默认模式为 IDLE

esp_err_t robot_mode_init() {
g_current_mode = ROBOT_MODE_IDLE;
ESP_LOGI("ROBOT_MODE", "Robot mode control initialized, default mode: IDLE");
return ESP_OK;
}

esp_err_t robot_mode_set_mode(robot_mode_t mode) {
if (mode >= ROBOT_MODE_ERROR) {
return ESP_ERR_INVALID_ARG; // 模式无效
}
g_current_mode = mode;
ESP_LOGI("ROBOT_MODE", "Set robot mode to: %d", mode);
return ESP_OK;
}

robot_mode_t robot_mode_get_mode() {
return g_current_mode;
}

esp_err_t robot_mode_handle_command(robot_command_t command) {
ESP_LOGD("ROBOT_MODE", "Handle command: %d, current mode: %d", command, g_current_mode);

if (g_current_mode == ROBOT_MODE_MANUAL_CONTROL) {
switch (command) {
case CMD_FORWARD:
wheel_control_set_speed(50, 50); // 前进,假设速度 50%
break;
case CMD_BACKWARD:
wheel_control_set_speed(-50, -50); // 后退
break;
case CMD_TURN_LEFT:
wheel_control_set_speed(-30, 30); // 左转
break;
case CMD_TURN_RIGHT:
wheel_control_set_speed(30, -30); // 右转
break;
case CMD_STOP:
wheel_control_stop(); // 停止
break;
case CMD_STAND:
leg_control_stand_pose(); // 站立
break;
case CMD_CONTRACT:
leg_control_contract_pose(); // 收缩
break;
case CMD_UNKNOWN:
ESP_LOGW("ROBOT_MODE", "Unknown command in MANUAL_CONTROL mode");
break;
default:
break;
}
} else if (g_current_mode == ROBOT_MODE_IDLE) {
if (command == CMD_STAND) {
leg_control_stand_pose();
} else if (command == CMD_CONTRACT) {
leg_control_contract_pose();
} else {
ESP_LOGW("ROBOT_MODE", "Command ignored in IDLE mode");
}
}
// ... 未来可以添加更多模式的处理逻辑

return ESP_OK;
}

( status_monitor.h/c, voice_control.h/c 等模块的实现类似,根据具体的功能需求进行编写。 voice_control 模块在未来实现离线语音控制功能时会比较复杂,需要集成语音识别算法和模型。)

5. main.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
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
#include <stdio.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_log.h"
#include "wheel_control.h"
#include "leg_control.h"
#include "command_parser.h"
#include "robot_mode.h"
#include "hal_uart.h" // 假设使用了串口HAL

#define TAG "MAIN"

// 电机和舵机配置 (根据实际硬件连接修改)
wheel_control_config_t wheel_config = {
.motor_left_config = {
.pwm_channel_forward = PWM_CHANNEL_0,
.pwm_channel_backward = PWM_CHANNEL_1,
.gpio_enable = -1, // 无使能GPIO
},
.motor_right_config = {
.pwm_channel_forward = PWM_CHANNEL_2,
.pwm_channel_backward = PWM_CHANNEL_3,
.gpio_enable = -1, // 无使能GPIO
},
};

leg_control_config_t leg_config = {
.legs = {
[0] = { // 左腿
.servo_hip_config = {
.pwm_channel = PWM_CHANNEL_4,
.gpio_servo = GPIO_NUM_18, // 髋关节舵机 GPIO
.angle_min_us = 500,
.angle_max_us = 2500,
.angle_range_deg = 180,
},
.servo_knee_config = {
.pwm_channel = PWM_CHANNEL_5,
.gpio_servo = GPIO_NUM_19, // 膝关节舵机 GPIO
.angle_min_us = 500,
.angle_max_us = 2500,
.angle_range_deg = 180,
},
},
[1] = { // 右腿
.servo_hip_config = {
.pwm_channel = PWM_CHANNEL_6,
.gpio_servo = GPIO_NUM_23, // 髋关节舵机 GPIO
.angle_min_us = 500,
.angle_max_us = 2500,
.angle_range_deg = 180,
},
.servo_knee_config = {
.pwm_channel = PWM_CHANNEL_7,
.gpio_servo = GPIO_NUM_5, // 膝关节舵机 GPIO
.angle_min_us = 500,
.angle_max_us = 2500,
.angle_range_deg = 180,
},
},
},
};

void app_main(void) {
ESP_LOGI(TAG, "--- ESP32S3 Wheel-Leg Robot ---");

// 初始化 HAL (这里只初始化了 UART 用于命令输入,其他 HAL 模块在驱动层初始化)
ESP_ERROR_CHECK(hal_uart_init(UART_NUM_0, 115200)); // 假设使用 UART0,波特率 115200

// 初始化控制模块
ESP_ERROR_CHECK(wheel_control_init(&wheel_config));
ESP_ERROR_CHECK(leg_control_init(&leg_config));
ESP_ERROR_CHECK(robot_mode_init());

// 设置机器人初始姿态
ESP_ERROR_CHECK(leg_control_stand_pose());

// 进入手动控制模式
ESP_ERROR_CHECK(robot_mode_set_mode(ROBOT_MODE_MANUAL_CONTROL));

char command_str[64];
robot_command_t command;

while (1) {
printf("Enter command (forward, backward, left, right, stop, stand, contract): ");
if (fgets(command_str, sizeof(command_str), stdin) != NULL) {
// 移除换行符
command_str[strcspn(command_str, "\n")] = 0;

ESP_ERROR_CHECK(command_parser_parse(command_str, &command));
ESP_ERROR_CHECK(robot_mode_handle_command(command));
}
vTaskDelay(pdMS_TO_TICKS(100)); // 稍微延时
}
}

代码编译和运行

  1. 环境搭建: 确保已安装 ESP-IDF 开发环境,并配置好 ESP32S3 的工具链。
  2. 创建项目: 使用 ESP-IDF 创建新的项目,并将以上代码文件添加到项目中。
  3. 配置硬件: 根据实际硬件连接修改 main.c 中的电机和舵机配置 (GPIO 引脚、PWM 通道等)。
  4. 编译项目: 在 ESP-IDF 环境中编译项目 (idf.py build)。
  5. 烧录固件: 将编译生成的固件烧录到 ESP32S3 开发板 (idf.py flash monitor)。
  6. 串口监控: 通过串口终端 (例如 idf.py monitor) 观察程序运行状态和输出信息,并通过串口输入命令控制机器人。

系统开发流程总结

  1. 需求分析: 明确项目的功能需求、性能指标、硬件平台等。
  2. 系统设计:
    • 架构设计: 选择合适的分层架构和模块化设计。
    • 模块划分: 将系统分解为独立的模块,定义模块接口。
    • 硬件选型: 选择合适的硬件器件,例如电机、舵机、传感器等。
    • PCB设计: 使用立创EDA等工具进行PCB设计。
  3. 代码实现:
    • HAL层开发: 封装底层硬件操作,提供统一的硬件接口。
    • 驱动层开发: 实现特定硬件设备的功能驱动。
    • 控制层开发: 编写机器人的核心控制算法。
    • 应用层开发: 构建用户交互和高层应用逻辑。
  4. 测试验证:
    • 单元测试: 测试每个模块的功能是否正常。
    • 集成测试: 测试模块之间的协同工作是否正常。
    • 系统测试: 整体测试系统的功能和性能是否满足需求。
    • 实机测试: 在实际机器人硬件平台上进行测试和调试。
  5. 维护升级:
    • Bug修复: 及时修复测试和使用过程中发现的Bug。
    • 功能扩展: 根据需求增加新的功能模块,例如语音控制、自主导航等。
    • 性能优化: 持续优化代码,提高系统效率和性能。
    • 固件升级: 提供方便的固件升级方式,方便用户更新系统。

技术和方法实践验证

本项目中采用的各项技术和方法都是经过实践验证的,例如:

  • 分层架构和模块化设计: 是嵌入式系统开发中常用的成熟架构,可以提高代码的可读性、可维护性和可扩展性。
  • HAL硬件抽象层: 可以屏蔽底层硬件差异,方便代码移植和重用。
  • 驱动层封装: 将硬件驱动封装成独立的模块,方便驱动的更换和升级。
  • FreeRTOS实时操作系统: ESP-IDF 默认集成了 FreeRTOS,可以方便地实现多任务并发执行,提高系统的实时性和响应速度。
  • C语言编程: C语言是嵌入式系统开发中最常用的编程语言,具有高效、灵活、可移植等优点。
  • ESP-IDF开发框架: ESP-IDF 是 Espressif 官方提供的 ESP32 系列芯片的开发框架,提供了丰富的API和组件,方便开发者快速构建应用。
  • 立创EDA PCB设计: 立创EDA 是一款专业的在线PCB设计工具,功能强大,易于使用,适合嵌入式项目的PCB设计。

总结

以上代码和架构设计方案,旨在为您提供一个清晰、可靠、高效、可扩展的轮腿机器人嵌入式系统平台。实际项目开发中,还需要根据具体的需求和硬件平台进行调整和完善。希望这份详细的解答能够帮助您顺利开展项目开发!

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