Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@ jobs:
target: esp32s3
- path: 'components/aw9523/example'
target: esp32
- path: 'components/bdc_driver/example'
target: esp32s3
- path: 'components/binary-log/example'
target: esp32s3
- path: 'components/bldc_haptics/example'
Expand Down Expand Up @@ -141,6 +143,8 @@ jobs:
target: esp32
- path: 'components/motorgo-mini/example'
target: esp32s3
- path: 'components/motorgo-plink/example'
target: esp32s3
- path: 'components/mcp23x17/example'
target: esp32s3
- path: 'components/mt6701/example'
Expand Down
2 changes: 2 additions & 0 deletions .github/workflows/upload_components.yml
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ jobs:
components/aw9523
components/base_component
components/base_peripheral
components/bdc_driver
components/binary-log
components/bldc_driver
components/bldc_haptics
Expand Down Expand Up @@ -92,6 +93,7 @@ jobs:
components/mcp23x17
components/monitor
components/motorgo-mini
components/motorgo-plink
components/mt6701
components/ndef
components/neopixel
Expand Down
4 changes: 4 additions & 0 deletions components/bdc_driver/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
idf_component_register(
INCLUDE_DIRS "include"
SRC_DIRS "src"
REQUIRES base_component esp_driver_gpio esp_driver_mcpwm)
23 changes: 23 additions & 0 deletions components/bdc_driver/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# BDC (Brushed DC) Motor Driver Component

[![Badge](https://components.espressif.com/components/espp/bdc_driver/badge.svg)](https://components.espressif.com/components/espp/bdc_driver)

The `BdcDriver` component wraps the `ESP MCPWM Peripheral
<https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/peripherals/mcpwm.html>`_
to drive a brushed DC motor through a dual-PWM H-bridge style interface.

It provides:

- two MCPWM outputs per motor channel
- direct duty-cycle control for each output
- a signed speed helper where positive commands drive output A, negative
commands drive output B, and zero disables both outputs
- optional driver-enable GPIO handling
- readback of the last commanded normalized and raw duty values for logging and
debugging

## Example

The [example](./example) shows a simple sinusoidal speed sweep using two GPIOs.
Update the GPIO assignments in the example to match your hardware before
running it on a real motor driver.
21 changes: 21 additions & 0 deletions components/bdc_driver/example/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# The following lines of boilerplate have to be in your project's CMakeLists
# in this exact order for cmake to work correctly
cmake_minimum_required(VERSION 3.20)

set(ENV{IDF_COMPONENT_MANAGER} "0")
include($ENV{IDF_PATH}/tools/cmake/project.cmake)

set(EXTRA_COMPONENT_DIRS
"../../../components/"
)

set(
COMPONENTS
"main bdc_driver logger"
CACHE STRING
"List of components to include"
)

project(bdc_driver_example)

set(CMAKE_CXX_STANDARD 20)
28 changes: 28 additions & 0 deletions components/bdc_driver/example/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
# BDC Driver Example

This example shows how to use the `espp::BdcDriver` component to drive a brushed
DC motor through a dual-PWM motor driver.

## How to use example

### Hardware Required

This example requires a brushed DC motor driver or H-bridge that accepts two PWM
inputs for a single motor channel.

Update the `motor_gpio_a` and `motor_gpio_b` constants in
`example/main/bdc_driver_example.cpp` to match your hardware before flashing the
example.

### Build and Flash

Build the project and flash it to the board, then run monitor tool to view
serial output:

```bash
idf.py -p PORT flash monitor
```

(Replace PORT with the name of the serial port to use.)

(To exit the serial monitor, type `Ctrl-]`.)
2 changes: 2 additions & 0 deletions components/bdc_driver/example/main/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
idf_component_register(SRC_DIRS "."
INCLUDE_DIRS ".")
47 changes: 47 additions & 0 deletions components/bdc_driver/example/main/bdc_driver_example.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
#include <cmath>
#include <numbers>

#include <freertos/FreeRTOS.h>
#include <freertos/task.h>

#include "bdc_driver.hpp"
#include "logger.hpp"

namespace {
static auto logger = espp::Logger({
.tag = "BdcDriver Example",
.level = espp::Logger::Verbosity::INFO,
});
} // namespace

extern "C" void app_main() {
//! [bdc_driver example]
constexpr gpio_num_t motor_gpio_a = GPIO_NUM_16;
constexpr gpio_num_t motor_gpio_b = GPIO_NUM_17;

espp::BdcDriver driver({
.gpio_a = motor_gpio_a,
.gpio_b = motor_gpio_b,
.group_id = 0,
.log_level = espp::Logger::Verbosity::INFO,
});

if (!driver.initialized()) {
logger.error("Failed to initialize BDC driver");
return;
}

float phase = 0.0f;
while (true) {
float command = 0.8f * std::sin(phase);
driver.set_speed(command);
auto duty = driver.duty_cycle();
auto raw = driver.raw_duty();
logger.info("cmd={:.3f} | pwm_a duty={:.1f}% raw={}/{} | pwm_b duty={:.1f}% raw={}/{}", command,
duty[0] * 100.0f, raw[0], driver.max_raw_duty(), duty[1] * 100.0f, raw[1],
driver.max_raw_duty());
phase = std::fmod(phase + 0.15f, 2.0f * std::numbers::pi_v<float>);
vTaskDelay(pdMS_TO_TICKS(500));
}
//! [bdc_driver example]
}
1 change: 1 addition & 0 deletions components/bdc_driver/example/sdkconfig.defaults
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
CONFIG_IDF_TARGET="esp32s3"
21 changes: 21 additions & 0 deletions components/bdc_driver/idf_component.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
## IDF Component Manager Manifest File
license: "MIT"
description: "BDC (Brushed DC) motor driver component for ESP-IDF"
url: "https://github.com/esp-cpp/espp/tree/main/components/bdc_driver"
repository: "git://github.com/esp-cpp/espp.git"
maintainers:
- William Emfinger <waemfinger@gmail.com>
documentation: "https://esp-cpp.github.io/espp/bldc/bdc_driver.html"
examples:
- path: example
tags:
- cpp
- Component
- BDC
- Motor
- Driver
- MCPWM
dependencies:
idf:
version: '>=5.0'
espp/base_component: '>=1.0'
145 changes: 145 additions & 0 deletions components/bdc_driver/include/bdc_driver.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
#pragma once

#include <array>
#include <atomic>
#include <cstddef>
#include <cstdint>

#include <driver/gpio.h>
#include <driver/mcpwm_prelude.h>

#include "base_component.hpp"

namespace espp {
/// Driver for brushed DC motors controlled by two PWM outputs.
///
/// The component uses one MCPWM operator per motor channel and is intended for
/// H-bridge style drivers that accept two PWM inputs. Positive signed speed
/// commands drive output A, negative commands drive output B, and zero disables
/// both outputs.
///
/// \section bdc_driver_example Example
/// \snippet bdc_driver_example.cpp bdc_driver example
class BdcDriver : public BaseComponent {
public:
/// Default MCPWM timer resolution used by the driver.
static constexpr size_t DEFAULT_TIMER_RESOLUTION_HZ = 80 * 1000 * 1000;
/// Default PWM carrier frequency used by the driver.
static constexpr size_t DEFAULT_PWM_FREQUENCY_HZ = 20 * 1000;

/// Configuration for one brushed DC motor driver.
struct Config {
gpio_num_t gpio_a{GPIO_NUM_NC}; ///< PWM GPIO for forward/output A.
gpio_num_t gpio_b{GPIO_NUM_NC}; ///< PWM GPIO for reverse/output B.
gpio_num_t gpio_enable{GPIO_NUM_NC}; ///< Optional active-high enable pin.
int group_id{0}; ///< MCPWM group to allocate the timer and operator from.
size_t timer_resolution_hz{
DEFAULT_TIMER_RESOLUTION_HZ}; ///< MCPWM timer clock resolution in Hz.
size_t pwm_frequency_hz{DEFAULT_PWM_FREQUENCY_HZ}; ///< PWM carrier frequency in Hz.
bool invert_output_a{false}; ///< True to invert output A at the GPIO matrix.
bool invert_output_b{false}; ///< True to invert output B at the GPIO matrix.
Logger::Verbosity log_level{Logger::Verbosity::WARN}; ///< Component log verbosity.
};

/// Construct the brushed DC motor driver.
/// \param config Driver configuration.
explicit BdcDriver(const Config &config);

/// Destroy the driver and release all MCPWM resources.
~BdcDriver();

/// Check whether initialization completed successfully.
/// \return True if the MCPWM resources were created and the outputs were
/// initialized; false otherwise.
bool initialized() const;

/// Enable the motor driver output.
/// \return True if the timer started successfully and the optional enable pin
/// was asserted; false otherwise.
bool enable();

/// Disable the motor driver output.
/// \return True if the outputs were forced low and the timer was stopped;
/// false otherwise.
bool disable();

/// Check whether the driver is currently enabled.
/// \return True if the timer is running; false otherwise.
bool is_enabled() const;

/// Set the normalized duty cycles for the two driver outputs.
/// \param duty_a Duty cycle for output A in the range [0.0, 1.0].
/// \param duty_b Duty cycle for output B in the range [0.0, 1.0].
/// \return True if both outputs were updated successfully; false otherwise.
bool set_duty(float duty_a, float duty_b);

/// Set a signed normalized motor command.
/// \param speed Signed speed command in the range [-1.0, 1.0]. Positive
/// values drive output A, negative values drive output B, and
/// zero disables both outputs.
/// \return True if the outputs were updated successfully; false otherwise.
bool set_speed(float speed);

/// Stop the motor by disabling both PWM outputs.
/// \return True if both outputs were set low; false otherwise.
bool stop();

/// Get the last commanded normalized duty cycles.
/// \return Array containing output A then output B duty cycles in the range
/// [0.0, 1.0].
std::array<float, 2> duty_cycle() const;

/// Get the last commanded raw duty counts.
/// \return Array containing output A then output B raw compare values.
std::array<uint32_t, 2> raw_duty() const;

/// Get the maximum raw duty count for this driver's timer configuration.
/// \return Maximum raw duty count corresponding to 100% duty.
uint32_t max_raw_duty() const;

/// Get the configured PWM carrier frequency.
/// \return PWM carrier frequency in Hz.
size_t pwm_frequency_hz() const;

/// Get the configured timer resolution.
/// \return MCPWM timer resolution in Hz.
size_t timer_resolution_hz() const;

/// Get the configured MCPWM group.
/// \return MCPWM group index used by this driver.
int group_id() const;

/// Get output A's GPIO.
/// \return Output A GPIO.
gpio_num_t gpio_a() const;

/// Get output B's GPIO.
/// \return Output B GPIO.
gpio_num_t gpio_b() const;

protected:
bool init(const Config &config);
bool configure_enable_gpio();
bool configure_timer_and_operator();
bool configure_outputs(const Config &config);
bool configure_generator(mcpwm_gen_handle_t generator, mcpwm_cmpr_handle_t comparator);
bool apply_output(size_t index, float duty);
void cleanup();

gpio_num_t gpio_a_{GPIO_NUM_NC};
gpio_num_t gpio_b_{GPIO_NUM_NC};
gpio_num_t gpio_enable_{GPIO_NUM_NC};
int group_id_{0};
size_t timer_resolution_hz_{DEFAULT_TIMER_RESOLUTION_HZ};
size_t pwm_frequency_hz_{DEFAULT_PWM_FREQUENCY_HZ};
uint32_t period_ticks_{0};
bool initialized_{false};
std::atomic<bool> enabled_{false};
mcpwm_timer_handle_t timer_{nullptr};
mcpwm_oper_handle_t operator_{nullptr};
std::array<mcpwm_cmpr_handle_t, 2> comparators_{};
std::array<mcpwm_gen_handle_t, 2> generators_{};
std::array<float, 2> duty_cycle_{0.0f, 0.0f};
std::array<uint32_t, 2> raw_duty_{0, 0};
}; // class BdcDriver
} // namespace espp
Loading
Loading