编程技术分享

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

0%

简介:** bbLaser 是一款基于 ESP32 主控的矢量激光投影仪,能够将图像、动画和文字以激光束的形式投影到超远距离。它支持播放 SD 卡中的内容,也支持实时数据流传输。

关注微信公众号,提前获取相关推文

核心需求:

  1. 矢量图形投影: 核心功能是精确控制激光束,按照矢量图形数据进行扫描,从而投影出图像、动画和文字。
  2. 内容来源多样性:
    • SD 卡播放: 能够读取和播放存储在 SD 卡中的矢量图形文件。
    • 实时数据流: 能够接收来自外部设备(如电脑、手机)的实时矢量数据流并进行投影。
  3. 高性能与高精度: 需要保证投影的图像质量,包括清晰度、色彩(如果支持)、稳定性和流畅度。
  4. 可靠性与稳定性: 系统需要长时间稳定运行,避免因软件错误导致投影异常或设备故障。
  5. 可扩展性: 软件架构应具备良好的可扩展性,方便未来添加新功能,例如更复杂的动画效果、更丰富的输入接口、更高级的控制算法等。
  6. 用户友好性 (软件层面): 虽然没有物理 UI,但在软件设计上要考虑易用性,例如清晰的配置接口、易于理解的日志信息、方便的调试手段等。
  7. 资源效率: ESP32 的资源有限,需要优化代码,高效利用内存和 CPU 资源。

技术选型与方法

基于以上需求,我们选择以下技术和方法:

  1. 主控芯片:ESP32

    • 优势: 低成本、高性能、集成 Wi-Fi 和 Bluetooth、丰富的 GPIO 资源、成熟的开发生态(ESP-IDF)。
    • 适用性: 完全满足 bbLaser 的控制和通信需求。
  2. 开发框架:ESP-IDF (Espressif IoT Development Framework)

    • 优势: 官方提供的开发框架,功能强大、文档完善、社区支持良好、包含 FreeRTOS 实时操作系统。
    • 适用性: 提供构建复杂嵌入式系统所需的基础设施,包括任务调度、内存管理、设备驱动等。
  3. 编程语言:C

    • 优势: 高效、灵活、接近硬件、广泛应用于嵌入式系统开发。
    • 适用性: ESP-IDF 主要使用 C 语言开发,C 语言也最适合对硬件进行底层控制和性能优化。
  4. 实时操作系统 (RTOS):FreeRTOS (集成在 ESP-IDF 中)

    • 优势: 轻量级、实时性好、支持多任务处理、成熟稳定。
    • 适用性: bbLaser 需要同时处理多个任务,例如 SD 卡读取、数据流接收、激光控制等,RTOS 可以有效地管理这些并发任务,提高系统响应速度和效率。
  5. 矢量图形数据格式:自定义二进制格式 (或 SVG 简化版)

    • 考虑: 需要选择一种高效且易于解析的矢量图形数据格式。
    • 选择理由: 为了性能和效率,可以设计一种自定义的二进制格式,专门针对激光投影仪的需求进行优化。也可以考虑使用 SVG 简化版,但需要考虑解析 SVG 的开销。
  6. 激光控制技术:高速 PWM 和精确的电机控制

    • 原理: 通过高速 PWM 控制激光器的开关和强度,通过精确控制 X、Y 轴电机(通常是步进电机或伺服电机)来控制激光束的扫描方向。
    • 实现: ESP32 的 PWM 模块和 GPIO 可以实现精确的激光控制和电机驱动。
  7. 数据流传输协议:WebSocket (或自定义 UDP 协议)

    • 考虑: 实时数据流传输需要选择合适的网络协议。
    • 选择理由:
      • WebSocket: 双向通信、易于实现客户端(Web 浏览器、App)、基于 TCP,可靠性高。适用于需要双向交互和可靠数据传输的场景。
      • 自定义 UDP 协议: 更轻量级、延迟更低,但需要自行处理数据可靠性和顺序性。适用于对延迟敏感但可以容忍少量数据丢失的场景。 对于实时激光投影,延迟可能更关键,可以考虑 UDP 协议。 但为了简化开发,初期可以使用 WebSocket。
  8. SD 卡文件系统:FAT32 (或 LittleFS)

    • 考虑: 需要选择 ESP32 支持的文件系统来访问 SD 卡。
    • 选择理由:
      • FAT32: 通用性好,与 PC 兼容性强,方便文件传输,但效率相对较低。
      • LittleFS: 闪存友好型文件系统,效率高,寿命长,但与 PC 兼容性较差。 对于嵌入式系统,LittleFS 通常更优,但考虑到用户可能需要频繁更换 SD 卡内容,FAT32 可能更方便。 可以根据实际使用场景选择。

软件架构设计

我们采用分层架构来设计 bbLaser 的嵌入式软件系统,以提高模块化、可维护性和可扩展性。 架构主要分为以下几个层次:

1. 硬件抽象层 (HAL - Hardware Abstraction Layer)

  • 功能: 直接与 ESP32 硬件交互,提供统一的硬件访问接口,隐藏底层硬件差异。
  • 模块:
    • hal_gpio.c/h: GPIO 控制 (激光器开关、电机使能、限位开关等)。
    • hal_pwm.c/h: PWM 控制 (激光器强度调制)。
    • hal_spi.c/h: SPI 接口 (如果需要外接 SPI 设备,例如 SPI Flash)。
    • hal_i2c.c/h: I2C 接口 (如果需要外接 I2C 设备,例如传感器)。
    • hal_uart.c/h: UART 接口 (调试串口)。
    • hal_sdcard.c/h: SD 卡接口 (底层 SDIO 或 SPI 驱动)。
    • hal_wifi.c/h: Wi-Fi 接口 (ESP-IDF Wi-Fi API 的封装)。
    • hal_timer.c/h: 定时器接口 (用于精确 timing 和任务调度)。
    • hal_motor.c/h: 电机驱动接口 (步进电机或伺服电机控制)。

2. 设备驱动层 (Device Driver Layer)

  • 功能: 基于 HAL 层,提供更高层次、更易用的设备驱动接口,封装硬件操作细节。
  • 模块:
    • driver_laser.c/h: 激光器驱动 (初始化、开关控制、强度控制)。
    • driver_motor.c/h: 电机驱动 (初始化、步进控制、位置控制、速度控制)。
    • driver_sdcard.c/h: SD 卡驱动 (文件系统挂载、文件读写操作,基于 ESP-IDF VFS 和 SDMMC/SPI 驱动)。
    • driver_wifi.c/h: Wi-Fi 驱动 (网络连接管理、数据收发,基于 ESP-IDF Wi-Fi 和 TCP/IP 协议栈)。

3. 核心服务层 (Core Service Layer)

  • 功能: 提供系统核心服务,例如任务管理、事件管理、内存管理、配置管理、日志管理等。
  • 模块:
    • service_task.c/h: 任务管理 (基于 FreeRTOS 任务管理 API)。
    • service_event.c/h: 事件管理 (基于 FreeRTOS 事件组或队列)。
    • service_memory.c/h: 内存管理 (动态内存分配、内存池管理)。
    • service_config.c/h: 配置管理 (读取和存储系统配置参数,可以从 Flash 或 SD 卡加载)。
    • service_log.c/h: 日志管理 (记录系统运行日志,方便调试和故障排查)。
    • service_vector_graphics.c/h: 矢量图形处理服务 (解析矢量数据、路径规划、坐标转换)。
    • service_animation.c/h: 动画管理服务 (帧序列管理、动画播放控制)。

4. 应用逻辑层 (Application Logic Layer)

  • 功能: 实现 bbLaser 的核心应用逻辑,包括数据流处理、SD 卡内容播放、激光投影控制、用户命令处理等。
  • 模块:
    • app_stream_handler.c/h: 实时数据流处理 (接收和解析数据流,例如 WebSocket 或 UDP)。
    • app_sdcard_player.c/h: SD 卡播放器 (读取 SD 卡文件,解析矢量数据,控制动画播放)。
    • app_laser_projector.c/h: 激光投影控制器 (接收矢量数据,生成电机控制指令和激光 PWM 信号,控制激光投影过程)。
    • app_command_handler.c/h: 命令处理器 (处理来自串口、Wi-Fi 或其他接口的控制命令)。
    • app_main.c: 主应用程序入口,初始化系统,创建任务,启动各个模块。

5. 数据层 (Data Layer - 隐含)

  • 功能: 存储和管理应用程序的数据,例如矢量图形数据、配置文件、动画帧数据等。
  • 形式: 数据可以存储在 SD 卡文件、Flash 存储器、内存中。 数据层并非一个独立的模块,而是贯穿于各个层次,数据在不同层次之间传递和处理。

代码实现 (C 语言)

为了演示软件架构和核心功能,以下提供一些关键模块的 C 代码示例。 由于代码量限制,这里只给出部分核心代码片段,并非完整可编译的代码,但足以说明设计思路和实现方法。 完整的代码实现将超过 3000 行,这里仅展示关键部分,后续会逐步扩展和完善代码示例。

(1) HAL 层 - hal_gpio.hhal_gpio.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
// hal_gpio.h
#ifndef HAL_GPIO_H
#define HAL_GPIO_H

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

typedef enum {
GPIO_MODE_INPUT,
GPIO_MODE_OUTPUT
} gpio_mode_t;

typedef enum {
GPIO_LEVEL_LOW,
GPIO_LEVEL_HIGH
} gpio_level_t;

typedef enum {
GPIO_PULLUP,
GPIO_PULLDOWN,
GPIO_PULLNONE
} gpio_pull_mode_t;

esp_err_t hal_gpio_init(gpio_num_t gpio_num, gpio_mode_t mode);
esp_err_t hal_gpio_set_mode(gpio_num_t gpio_num, gpio_mode_t mode);
esp_err_t hal_gpio_set_level(gpio_num_t gpio_num, gpio_level_t level);
gpio_level_t hal_gpio_get_level(gpio_num_t gpio_num);
esp_err_t hal_gpio_config_pull_mode(gpio_num_t gpio_num, gpio_pull_mode_t pull_mode);
esp_err_t hal_gpio_set_direction(gpio_num_t gpio_num, gpio_mode_t direction);

#endif // HAL_GPIO_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
// hal_gpio.c
#include "hal_gpio.h"
#include "driver/gpio.h"
#include "esp_err.h"

esp_err_t hal_gpio_init(gpio_num_t gpio_num, gpio_mode_t mode) {
gpio_config_t io_conf;
io_conf.intr_type = GPIO_INTR_DISABLE; // Disable interrupt
io_conf.mode = (mode == GPIO_MODE_OUTPUT) ? GPIO_MODE_OUTPUT : GPIO_MODE_INPUT;
io_conf.pin_bit_mask = (1ULL << gpio_num);
io_conf.pull_down_en = GPIO_PULLDOWN_DISABLE;
io_conf.pull_up_en = GPIO_PULLUP_DISABLE;
return gpio_config(&io_conf);
}

esp_err_t hal_gpio_set_mode(gpio_num_t gpio_num, gpio_mode_t mode) {
return hal_gpio_init(gpio_num, mode); // Re-init with new mode
}

esp_err_t hal_gpio_set_level(gpio_num_t gpio_num, gpio_level_t level) {
return gpio_set_level(gpio_num, (level == GPIO_LEVEL_HIGH) ? 1 : 0);
}

gpio_level_t hal_gpio_get_level(gpio_num_t gpio_num) {
return (gpio_get_level(gpio_num) == 1) ? GPIO_LEVEL_HIGH : GPIO_LEVEL_LOW;
}

esp_err_t hal_gpio_config_pull_mode(gpio_num_t gpio_num, gpio_pull_mode_t pull_mode) {
gpio_pull_mode_t esp_pull_mode;
switch (pull_mode) {
case GPIO_PULLUP: esp_pull_mode = GPIO_PULLUP_ONLY; break;
case GPIO_PULLDOWN: esp_pull_mode = GPIO_PULLDOWN_ONLY; break;
case GPIO_PULLNONE: esp_pull_mode = GPIO_PULLUP_DOWN_DISABLE; break;
default: return ESP_ERR_INVALID_ARG;
}
return gpio_pullup_dis(gpio_num); // Ensure pull-up/down is initially disabled, then set as needed.
if (pull_mode != GPIO_PULLNONE) {
return gpio_pulldown_dis(gpio_num); // Ensure pull-up/down is initially disabled, then set as needed.
}
return ESP_OK; // Should not reach here if invalid arg is handled.
}


esp_err_t hal_gpio_set_direction(gpio_num_t gpio_num, gpio_mode_t direction) {
return hal_gpio_set_mode(gpio_num, direction); // Direction is part of mode in ESP-IDF
}

(2) HAL 层 - hal_pwm.hhal_pwm.c (简化示例)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// hal_pwm.h
#ifndef HAL_PWM_H
#define HAL_PWM_H

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

typedef enum {
PWM_CHANNEL_0,
PWM_CHANNEL_1,
// ... more channels
PWM_CHANNEL_MAX
} pwm_channel_t;

esp_err_t hal_pwm_init(pwm_channel_t channel, gpio_num_t gpio_num, uint32_t frequency_hz, uint32_t duty_cycle_percent);
esp_err_t hal_pwm_set_duty_cycle(pwm_channel_t channel, uint32_t duty_cycle_percent);
esp_err_t hal_pwm_set_frequency(pwm_channel_t channel, uint32_t frequency_hz);
esp_err_t hal_pwm_start(pwm_channel_t channel);
esp_err_t hal_pwm_stop(pwm_channel_t channel);

#endif // HAL_PWM_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
60
61
62
63
// hal_pwm.c
#include "hal_pwm.h"
#include "driver/ledc.h"
#include "esp_err.h"

// Configure LEDC peripheral (example configuration - needs to be adjusted for bbLaser)
#define LEDC_TIMER LEDC_TIMER_0
#define LEDC_MODE LEDC_LOW_SPEED_MODE
#define LEDC_OUTPUT_IO (5) // Example GPIO for PWM output - MUST BE CONFIGURED FOR LASER CONTROL
#define LEDC_CHANNEL LEDC_CHANNEL_0
#define LEDC_DUTY_RES LEDC_TIMER_10_BIT // Resolution of duty cycle: 10 bits (0-1023)
#define LEDC_FREQUENCY (5000) // PWM frequency in Hz - ADJUST FOR LASER REQUIREMENTS

esp_err_t hal_pwm_init(pwm_channel_t channel, gpio_num_t gpio_num, uint32_t frequency_hz, uint32_t duty_cycle_percent) {
ledc_timer_config_t ledc_timer = {
.duty_resolution = LEDC_DUTY_RES, // resolution of PWM duty
.freq_hz = frequency_hz, // frequency of PWM signal
.speed_mode = LEDC_MODE, // timer mode
.timer_num = LEDC_TIMER, // timer index
.clk_cfg = LEDC_AUTO_CLK, // Auto select the source clock
};
ESP_ERROR_CHECK(ledc_timer_config(&ledc_timer));

ledc_channel_config_t ledc_channel_cfg = {
.channel = LEDC_CHANNEL,
.duty = (uint32_t)((duty_cycle_percent * ((1 << LEDC_DUTY_RES) - 1)) / 100), // Initial duty cycle
.gpio_num = gpio_num,
.speed_mode = LEDC_MODE,
.hpoint = 0,
.timer_sel = LEDC_TIMER
};
ESP_ERROR_CHECK(ledc_channel_config(&ledc_channel_cfg));
return ESP_OK;
}

esp_err_t hal_pwm_set_duty_cycle(pwm_channel_t channel, uint32_t duty_cycle_percent) {
uint32_t duty = (uint32_t)((duty_cycle_percent * ((1 << LEDC_DUTY_RES) - 1)) / 100);
ESP_ERROR_CHECK(ledc_set_duty(LEDC_MODE, LEDC_CHANNEL, duty));
ESP_ERROR_CHECK(ledc_update_duty(LEDC_MODE, LEDC_CHANNEL));
return ESP_OK;
}

esp_err_t hal_pwm_set_frequency(pwm_channel_t channel, uint32_t frequency_hz) {
ledc_timer_config_t ledc_timer = { // Re-configure timer with new frequency
.duty_resolution = LEDC_DUTY_RES,
.freq_hz = frequency_hz,
.speed_mode = LEDC_MODE,
.timer_num = LEDC_TIMER,
.clk_cfg = LEDC_AUTO_CLK,
};
ESP_ERROR_CHECK(ledc_timer_config(&ledc_timer));
return ESP_OK;
}

esp_err_t hal_pwm_start(pwm_channel_t channel) {
// PWM starts automatically after configuration, no explicit start needed in ESP-IDF LEDC driver.
return ESP_OK;
}

esp_err_t hal_pwm_stop(pwm_channel_t channel) {
ESP_ERROR_CHECK(ledc_stop(LEDC_MODE, LEDC_CHANNEL, 0)); // 0 for immediate stop
return ESP_OK;
}

(3) 设备驱动层 - driver_laser.hdriver_laser.c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// driver_laser.h
#ifndef DRIVER_LASER_H
#define DRIVER_LASER_H

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

typedef enum {
LASER_STATE_OFF,
LASER_STATE_ON
} laser_state_t;

esp_err_t driver_laser_init(gpio_num_t enable_gpio, pwm_channel_t pwm_channel, gpio_num_t pwm_gpio);
esp_err_t driver_laser_set_state(laser_state_t state);
esp_err_t driver_laser_set_intensity(uint32_t intensity_percent); // 0-100%
laser_state_t driver_laser_get_state(void);

#endif // DRIVER_LASER_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
// driver_laser.c
#include "driver_laser.h"
#include "hal_gpio.h"
#include "hal_pwm.h"
#include "esp_err.h"

static gpio_num_t laser_enable_gpio;
static pwm_channel_t laser_pwm_channel;
static gpio_num_t laser_pwm_gpio;
static laser_state_t current_laser_state = LASER_STATE_OFF;
static uint32_t current_intensity = 0;

esp_err_t driver_laser_init(gpio_num_t enable_gpio, pwm_channel_t pwm_channel, gpio_num_t pwm_gpio) {
laser_enable_gpio = enable_gpio;
laser_pwm_channel = pwm_channel;
laser_pwm_gpio = pwm_gpio;

ESP_ERROR_CHECK(hal_gpio_init(laser_enable_gpio, GPIO_MODE_OUTPUT));
ESP_ERROR_CHECK(hal_gpio_set_level(laser_enable_gpio, GPIO_LEVEL_LOW)); // Initially off

ESP_ERROR_CHECK(hal_pwm_init(laser_pwm_channel, laser_pwm_gpio, 5000, 0)); // Initialize PWM, 0% duty cycle

current_laser_state = LASER_STATE_OFF;
current_intensity = 0;
return ESP_OK;
}

esp_err_t driver_laser_set_state(laser_state_t state) {
if (state == LASER_STATE_ON) {
ESP_ERROR_CHECK(hal_gpio_set_level(laser_enable_gpio, GPIO_LEVEL_HIGH));
ESP_ERROR_CHECK(hal_pwm_start(laser_pwm_channel));
} else { // LASER_STATE_OFF
ESP_ERROR_CHECK(hal_gpio_set_level(laser_enable_gpio, GPIO_LEVEL_LOW));
ESP_ERROR_CHECK(hal_pwm_stop(laser_pwm_channel));
}
current_laser_state = state;
return ESP_OK;
}

esp_err_t driver_laser_set_intensity(uint32_t intensity_percent) {
if (intensity_percent > 100) intensity_percent = 100;
current_intensity = intensity_percent;
if (current_laser_state == LASER_STATE_ON) { // Only set PWM if laser is ON
ESP_ERROR_CHECK(hal_pwm_set_duty_cycle(laser_pwm_channel, intensity_percent));
}
return ESP_OK;
}

laser_state_t driver_laser_get_state(void) {
return current_laser_state;
}

(4) 设备驱动层 - driver_motor.hdriver_motor.c (步进电机简化示例)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// driver_motor.h
#ifndef DRIVER_MOTOR_H
#define DRIVER_MOTOR_H

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

typedef enum {
MOTOR_X,
MOTOR_Y
} motor_axis_t;

typedef enum {
MOTOR_DIRECTION_CW, // Clockwise
MOTOR_DIRECTION_CCW // Counter-Clockwise
} motor_direction_t;

esp_err_t driver_motor_init(motor_axis_t axis, gpio_num_t step_gpio, gpio_num_t dir_gpio, gpio_num_t enable_gpio);
esp_err_t driver_motor_step(motor_axis_t axis, int32_t steps, motor_direction_t direction, uint32_t speed_hz); // Relative steps
esp_err_t driver_motor_set_position(motor_axis_t axis, int32_t position); // Absolute position (if encoder feedback is used)
int32_t driver_motor_get_position(motor_axis_t axis);

#endif // DRIVER_MOTOR_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
60
61
62
63
64
65
// driver_motor.c
#include "driver_motor.h"
#include "hal_gpio.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_err.h"

#define MOTOR_STEP_PULSE_DELAY_US 5 // Minimum delay for step pulse, adjust as needed

typedef struct {
gpio_num_t step_gpio;
gpio_num_t dir_gpio;
gpio_num_t enable_gpio;
int32_t current_position; // Track motor position (optional, if no encoder)
} motor_config_t;

static motor_config_t motor_configs[2]; // MOTOR_X and MOTOR_Y

esp_err_t driver_motor_init(motor_axis_t axis, gpio_num_t step_gpio, gpio_num_t dir_gpio, gpio_num_t enable_gpio) {
motor_configs[axis].step_gpio = step_gpio;
motor_configs[axis].dir_gpio = dir_gpio;
motor_configs[axis].enable_gpio = enable_gpio;
motor_configs[axis].current_position = 0; // Initialize position

ESP_ERROR_CHECK(hal_gpio_init(step_gpio, GPIO_MODE_OUTPUT));
ESP_ERROR_CHECK(hal_gpio_init(dir_gpio, GPIO_MODE_OUTPUT));
ESP_ERROR_CHECK(hal_gpio_init(enable_gpio, GPIO_MODE_OUTPUT));

ESP_ERROR_CHECK(hal_gpio_set_level(enable_gpio, GPIO_LEVEL_HIGH)); // Enable motor by default (adjust if needed)
return ESP_OK;
}

esp_err_t driver_motor_step(motor_axis_t axis, int32_t steps, motor_direction_t direction, uint32_t speed_hz) {
if (steps == 0) return ESP_OK;

motor_config_t *config = &motor_configs[axis];
uint32_t step_delay_us = (speed_hz > 0) ? (1000000 / speed_hz) : 1000; // Default delay if speed is 0

ESP_ERROR_CHECK(hal_gpio_set_level(config->dir_gpio, (direction == MOTOR_DIRECTION_CW) ? GPIO_LEVEL_HIGH : GPIO_LEVEL_LOW));

for (int i = 0; i < abs(steps); i++) {
ESP_ERROR_CHECK(hal_gpio_set_level(config->step_gpio, GPIO_LEVEL_HIGH));
ets_delay_us(MOTOR_STEP_PULSE_DELAY_US); // Short pulse
ESP_ERROR_CHECK(hal_gpio_set_level(config->step_gpio, GPIO_LEVEL_LOW));
ets_delay_us(step_delay_us - MOTOR_STEP_PULSE_DELAY_US); // Delay between steps

if (direction == MOTOR_DIRECTION_CW) {
config->current_position++;
} else {
config->current_position--;
}
}
return ESP_OK;
}

esp_err_t driver_motor_set_position(motor_axis_t axis, int32_t position) {
// For simple stepper without encoder, this is just setting the tracked position, not actual motor position control.
// For closed-loop control (with encoder), more complex logic is needed to move to target position.
motor_configs[axis].current_position = position;
return ESP_OK;
}

int32_t driver_motor_get_position(motor_axis_t axis) {
return motor_configs[axis].current_position;
}

(5) 核心服务层 - service_vector_graphics.hservice_vector_graphics.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
// service_vector_graphics.h
#ifndef SERVICE_VECTOR_GRAPHICS_H
#define SERVICE_VECTOR_GRAPHICS_H

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

// Example Vector Data Structure (Simplified)
typedef struct {
uint16_t x;
uint16_t y;
uint8_t intensity; // 0-255
uint8_t command; // e.g., 0=MOVE_TO, 1=LINE_TO, 2=LASER_ON, 3=LASER_OFF
} vector_point_t;

typedef struct {
uint32_t point_count;
vector_point_t *points;
} vector_data_t;

esp_err_t service_vector_graphics_parse_data(const uint8_t *data_buffer, uint32_t data_len, vector_data_t *vector_data);
esp_err_t service_vector_graphics_generate_motor_path(const vector_data_t *vector_data); // Example - further processing

#endif // SERVICE_VECTOR_GRAPHICS_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
// service_vector_graphics.c
#include "service_vector_graphics.h"
#include "esp_err.h"
#include "esp_log.h"
#include <stdlib.h> // malloc, free

static const char *TAG = "VG_SERVICE";

esp_err_t service_vector_graphics_parse_data(const uint8_t *data_buffer, uint32_t data_len, vector_data_t *vector_data) {
// Example: Simple binary format parsing (adjust to your actual format)
if (data_len % sizeof(vector_point_t) != 0) {
ESP_LOGE(TAG, "Invalid vector data length");
return ESP_ERR_INVALID_ARG;
}

uint32_t point_count = data_len / sizeof(vector_point_t);
vector_data->point_count = point_count;
vector_data->points = (vector_point_t *)malloc(sizeof(vector_point_t) * point_count);
if (vector_data->points == NULL) {
ESP_LOGE(TAG, "Memory allocation failed for vector points");
return ESP_ERR_NO_MEM;
}

memcpy(vector_data->points, data_buffer, data_len); // Simple copy - in real case, byte-order handling and validation might be needed

ESP_LOGI(TAG, "Parsed %u vector points", point_count);
return ESP_OK;
}

esp_err_t service_vector_graphics_generate_motor_path(const vector_data_t *vector_data) {
// Example: Placeholder - In real implementation, this function would:
// 1. Process vector points (commands like MOVE_TO, LINE_TO, etc.)
// 2. Generate a sequence of motor steps (X, Y movements) and laser intensity changes
// 3. Optimize path for speed and smoothness (e.g., acceleration/deceleration)
ESP_LOGI(TAG, "Generating motor path (placeholder)");
if (vector_data == NULL || vector_data->points == NULL) {
ESP_LOGW(TAG, "No vector data to process");
return ESP_OK; // Or return error if vector data is required
}

for (uint32_t i = 0; i < vector_data->point_count; i++) {
vector_point_t *point = &vector_data->points[i];
ESP_LOGD(TAG, "Point %u: X=%u, Y=%u, Intensity=%u, Command=%u", i, point->x, point->y, point->intensity, point->command);
// ... (Motor control and laser intensity setting logic would go here)
}

return ESP_OK;
}

(6) 应用逻辑层 - app_laser_projector.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
// app_laser_projector.c
#include "app_laser_projector.h"
#include "driver_laser.h"
#include "driver_motor.h"
#include "service_vector_graphics.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_log.h"

static const char *TAG = "LASER_PROJECTOR";

void app_laser_projector_task(void *pvParameters) {
ESP_LOGI(TAG, "Laser Projector Task started");

// Example: Assume we have vector data in 'my_vector_data' (obtained from SD card or stream)
extern vector_data_t my_vector_data; // Assume this is populated elsewhere

while (1) {
if (my_vector_data.points != NULL) {
ESP_LOGI(TAG, "Processing vector data...");
service_vector_graphics_generate_motor_path(&my_vector_data); // Generate motor path from vector data
// ... (Further processing to execute motor movements and laser control based on generated path)

// Example: Simple loop to move to each point (very basic and inefficient, for illustration only)
for (uint32_t i = 0; i < my_vector_data.point_count; i++) {
vector_point_t *point = &my_vector_data.points[i];
// Example motor control (replace with proper path planning and motor control logic)
driver_motor_step(MOTOR_X, point->x, MOTOR_DIRECTION_CW, 1000); // Example speed 1000 Hz
driver_motor_step(MOTOR_Y, point->y, MOTOR_DIRECTION_CW, 1000);
driver_laser_set_intensity(point->intensity * 100 / 255); // Scale intensity to 0-100%
if (point->command == 2) { // LASER_ON command
driver_laser_set_state(LASER_STATE_ON);
} else if (point->command == 3) { // LASER_OFF command
driver_laser_set_state(LASER_STATE_OFF);
}
vTaskDelay(pdMS_TO_TICKS(10)); // Small delay between points (adjust as needed)
}
ESP_LOGI(TAG, "Vector data processing complete.");
// ... (Free vector data memory, etc.)
my_vector_data.points = NULL; // Reset for next data
} else {
// No vector data to process, wait for data or do other background tasks
vTaskDelay(pdMS_TO_TICKS(100));
}
}
}

esp_err_t app_laser_projector_init(void) {
ESP_LOGI(TAG, "Initializing Laser Projector Application...");

// Example GPIO and PWM channel assignments - ADJUST TO YOUR HARDWARE!
gpio_num_t laser_enable_pin = GPIO_NUM_2;
pwm_channel_t laser_pwm_channel_id = PWM_CHANNEL_0;
gpio_num_t laser_pwm_pin = GPIO_NUM_4;
gpio_num_t motor_x_step_pin = GPIO_NUM_16;
gpio_num_t motor_x_dir_pin = GPIO_NUM_17;
gpio_num_t motor_x_enable_pin = GPIO_NUM_18;
gpio_num_t motor_y_step_pin = GPIO_NUM_19;
gpio_num_t motor_y_dir_pin = GPIO_NUM_21;
gpio_num_t motor_y_enable_pin = GPIO_NUM_22;


ESP_ERROR_CHECK(driver_laser_init(laser_enable_pin, laser_pwm_channel_id, laser_pwm_pin));
ESP_ERROR_CHECK(driver_motor_init(MOTOR_X, motor_x_step_pin, motor_x_dir_pin, motor_x_enable_pin));
ESP_ERROR_CHECK(driver_motor_init(MOTOR_Y, motor_y_step_pin, motor_y_dir_pin, motor_y_enable_pin));

// Create Laser Projector Task
BaseType_t task_created = xTaskCreatePinnedToCore(
app_laser_projector_task, // Task function
"LaserProjectorTask", // Task name
4096, // Stack size (adjust as needed)
NULL, // Task parameters
10, // Task priority (adjust as needed)
NULL, // Task handle
APP_CPU_NUM // Core ID (APP_CPU_NUM or PRO_CPU_NUM)
);
if (task_created != pdTRUE) {
ESP_LOGE(TAG, "Failed to create Laser Projector Task");
return ESP_FAIL;
}

ESP_LOGI(TAG, "Laser Projector Application Initialized.");
return ESP_OK;
}

(7) 主应用程序入口 - app_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
// app_main.c
#include <stdio.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_log.h"
#include "app_laser_projector.h"

static const char *TAG_MAIN = "APP_MAIN";

void app_main(void) {
ESP_LOGI(TAG_MAIN, "bbLaser Projector Firmware starting...");

// Initialize Laser Projector Application
if (app_laser_projector_init() != ESP_OK) {
ESP_LOGE(TAG_MAIN, "Laser Projector Initialization failed!");
return;
}

ESP_LOGI(TAG_MAIN, "bbLaser Projector Firmware initialized and running.");

// Main application loop (can be minimal in this example, projector task handles most work)
while (1) {
vTaskDelay(pdMS_TO_TICKS(1000)); // Example: Sleep for 1 second
// ... (Optional: Add other background tasks or monitoring here)
}
}

项目开发流程

  1. 需求细化与文档编写: 详细定义 bbLaser 的各项功能和性能指标,编写需求规格文档。
  2. 硬件设计与验证: 完成硬件原理图设计、PCB 设计、元器件选型和采购,制作硬件原型并进行硬件调试和验证。
  3. 软件架构设计与模块划分: 根据需求和硬件设计,进行软件架构设计,划分模块,定义模块接口。
  4. HAL 层和驱动层开发: 编写 HAL 层代码,实现对 ESP32 硬件的底层访问。开发设备驱动层,封装硬件操作,提供高层接口。
  5. 核心服务层开发: 实现核心服务模块,例如任务管理、事件管理、内存管理、配置管理、日志管理、矢量图形处理、动画管理等。
  6. 应用逻辑层开发: 编写应用逻辑代码,实现 SD 卡播放、实时数据流处理、激光投影控制、用户命令处理等核心功能。
  7. 单元测试: 对每个模块进行单元测试,确保模块功能的正确性。
  8. 集成测试: 将各个模块集成起来进行测试,验证模块之间的协同工作是否正常。
  9. 系统测试: 进行全面的系统测试,包括功能测试、性能测试、稳定性测试、可靠性测试等。
  10. 软件优化与性能调优: 根据测试结果,对软件进行优化,提高性能,降低资源消耗。
  11. 代码审查与代码规范: 进行代码审查,确保代码质量,遵循代码规范。
  12. 版本控制与代码管理: 使用 Git 等版本控制工具进行代码管理,方便代码维护和协作开发。
  13. 文档编写: 编写软件设计文档、API 文档、用户手册等,方便后续维护和使用。
  14. 维护与升级: 发布软件版本,进行 bug 修复和功能升级,提供固件更新机制 (例如 OTA 更新)。

测试验证与维护升级

  • 测试验证:

    • 单元测试: 使用 CUnit, Unity 等单元测试框架,对 HAL 层、驱动层、核心服务层等模块进行单元测试,确保每个模块的功能正确性。
    • 集成测试: 测试模块之间的接口和数据交互,例如测试数据流从网络接收到激光投影的整个流程。
    • 系统测试:
      • 功能测试: 验证所有功能是否符合需求规格,例如 SD 卡播放、实时流投影、各种矢量图形和动画效果。
      • 性能测试: 测试投影速度、响应延迟、资源占用率 (CPU, 内存)。
      • 稳定性测试 (压力测试): 长时间运行测试,模拟各种异常情况,验证系统稳定性。
      • 可靠性测试: 测试错误处理机制,例如 SD 卡错误、网络连接中断等情况下的系统表现。
      • 用户体验测试: 评估投影效果、操作流畅性、易用性。
    • 自动化测试: 尽可能使用自动化测试工具和脚本,提高测试效率和覆盖率。
  • 维护升级:

    • Bug 修复: 快速响应用户反馈的 bug,进行修复并发布更新版本。
    • 功能升级: 根据用户需求和产品规划,添加新功能,例如更复杂的动画效果、新的数据流协议、更高级的控制算法。
    • 性能优化: 持续优化软件性能,提高投影质量和效率。
    • 固件更新机制: 实现安全的固件更新机制,例如 OTA (Over-The-Air) 无线更新,方便用户升级设备。
    • 版本控制与发布: 使用版本控制系统管理代码,规范化发布流程,记录版本更新日志。
    • 用户反馈渠道: 建立用户反馈渠道,收集用户意见和建议,持续改进产品。

总结

以上代码和架构设计提供了一个构建 bbLaser 矢量激光投影仪嵌入式软件系统的框架。 实际项目中,需要根据具体硬件和需求进行详细设计和实现。 关键在于分层架构的设计、模块化编程、良好的代码规范、完善的测试验证流程,以及持续的维护和升级。 通过这些方法,可以构建一个可靠、高效、可扩展的嵌入式系统平台,满足 bbLaser 产品的需求,并为未来的功能扩展和产品升级打下坚实的基础。

为了满足 3000 行代码的要求,以上代码示例还需要进一步扩展和完善,包括:

  • 更完整的 HAL 层和驱动层实现: 例如 SD 卡驱动、Wi-Fi 驱动、更详细的 PWM 和 GPIO 控制函数。
  • 更完善的核心服务层: 例如配置管理、日志管理、更复杂的矢量图形处理和动画管理算法。
  • 更丰富的应用逻辑层功能: 例如 SD 卡文件系统操作、WebSocket 或 UDP 数据流处理、更精细的电机控制和激光扫描算法、用户命令解析和处理。
  • 错误处理和异常处理机制: 在各个模块中添加完善的错误检查和处理代码,提高系统鲁棒性。
  • 详细的代码注释和文档: 为每个函数、数据结构、模块添加详细的注释,方便代码理解和维护。
  • 更多的示例代码和测试代码: 提供更多的代码示例,演示各个模块的使用方法,并编写单元测试代码。

后续可以继续扩展代码示例,例如添加 SD 卡播放器、WebSocket 数据流处理、更复杂的矢量图形渲染和动画效果的实现代码,以逐步达到 3000 行以上的代码量。 同时,也会更深入地讲解各个模块的设计细节和实现技巧。

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