Skip to content

Commit 041c85a

Browse files
authored
feat(motorgo-mini): Update to be singleton. Moved impl to cpp (#280)
* feat(motorgo-mini): Update to be singleton. Moved impl to cpp * update to set log level
1 parent e1b241f commit 041c85a

File tree

4 files changed

+238
-199
lines changed

4 files changed

+238
-199
lines changed
+1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
idf_component_register(
22
INCLUDE_DIRS "include"
3+
SRC_DIRS "src"
34
REQUIRES base_component button filters led math mt6701 pid task bldc_driver bldc_motor i2c adc
45
)

components/motorgo-mini/example/main/motorgo_mini_example.cpp

+4-1
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,10 @@ extern "C" void app_main(void) {
1313
espp::Logger logger({.tag = "MotorGo Mini Example", .level = espp::Logger::Verbosity::INFO});
1414
logger.info("Starting");
1515
//! [motorgo-mini example]
16-
espp::MotorGoMini motorgo_mini(espp::Logger::Verbosity::INFO);
16+
auto &motorgo_mini = espp::MotorGoMini::get();
17+
motorgo_mini.set_log_level(espp::Logger::Verbosity::INFO);
18+
motorgo_mini.init_motor_channel_1();
19+
motorgo_mini.init_motor_channel_2();
1720
auto &motor1 = motorgo_mini.motor1();
1821
auto &motor2 = motorgo_mini.motor2();
1922
auto &button = motorgo_mini.button();

components/motorgo-mini/include/motorgo-mini.hpp

+47-198
Original file line numberDiff line numberDiff line change
@@ -34,191 +34,146 @@ namespace espp {
3434
/// NOTE: on the MotorGo-Mini v1.3, the M2 motor sense is swapped in the
3535
/// schematic, with U and W swapped
3636
///
37+
/// The class is a singleton and can be accessed using the get() method.
38+
///
3739
/// \section motorgo_ex1 MotorGo-Mini Example
3840
/// \snippet motorgo_mini_example.cpp motorgo-mini example
3941
class MotorGoMini : public BaseComponent {
4042
public:
4143
using Encoder = espp::Mt6701<espp::Mt6701Interface::SSI>;
4244
using BldcMotor = espp::BldcMotor<espp::BldcDriver, Encoder>;
4345

44-
/// Configuration for the MotorGo-Mini
45-
struct Config {
46-
bool auto_init = true; ///< If true, the MotorGo-Mini will initialize itself in the constructor
47-
espp::Logger::Verbosity verbosity =
48-
espp::Logger::Verbosity::WARN; ///< The verbosity level for
49-
///< the logger of the MotorGo-Mini
50-
///< and its components
51-
};
52-
53-
/// Constructor
54-
/// \param verbosity The verbosity level for the logger of the MotorGo-Mini
55-
/// and its components
56-
explicit MotorGoMini(espp::Logger::Verbosity verbosity = espp::Logger::Verbosity::WARN)
57-
: BaseComponent("MotorGo-Mini", verbosity) {
58-
always_init();
59-
init();
46+
/// @brief Access the singleton instance of the MotorGoMini class
47+
/// @return Reference to the singleton instance of the MotorGoMini class
48+
static MotorGoMini &get() {
49+
static MotorGoMini instance;
50+
return instance;
6051
}
6152

62-
/// Constructor
63-
/// \param config The configuration for the MotorGo-Mini
64-
explicit MotorGoMini(const Config &config)
65-
: BaseComponent("MotorGo-Mini", config.verbosity) {
66-
always_init();
67-
if (config.auto_init) {
68-
init();
69-
}
70-
}
53+
MotorGoMini(const MotorGoMini &) = delete;
54+
MotorGoMini &operator=(const MotorGoMini &) = delete;
55+
MotorGoMini(MotorGoMini &&) = delete;
56+
MotorGoMini &operator=(MotorGoMini &&) = delete;
7157

7258
/// Get a reference to the external I2C bus
7359
/// \return A reference to the external I2C bus
74-
I2c &get_external_i2c() { return external_i2c_; }
60+
I2c &get_external_i2c();
7561

7662
/// Get a reference to the boot button
77-
espp::Button &button() { return button_; }
63+
espp::Button &button();
7864

7965
/// Get a reference to the yellow LED channel (channel 0)
8066
/// \return A reference to the yellow LED channel (channel 0)
81-
espp::Led::ChannelConfig &yellow_led() { return led_channels_[0]; }
67+
espp::Led::ChannelConfig &yellow_led();
68+
8269
/// Get a reference to the yellow LED channel (channel 0)
8370
/// \return A reference to the yellow LED channel (channel 0)
84-
espp::Led::ChannelConfig &led_channel0() { return led_channels_[0]; }
71+
espp::Led::ChannelConfig &led_channel0();
72+
8573
/// Set the duty cycle of the yellow LED
8674
/// \param duty The duty cycle of the yellow LED (0.0 - 100.0)
8775
/// \note The duty cycle is a percentage of the maximum duty cycle
8876
/// (which is 100.0)
8977
/// 0.0 is off, 100.0 is fully on
9078
/// \note For this function to have an effect, the LED timer must NOT be running
9179
/// (i.e. stop_breathing() must be called)
92-
void set_yellow_led_duty(float duty) { led_.set_duty(led_channels_[0].channel, duty); }
80+
void set_yellow_led_duty(float duty);
9381

9482
/// Get a reference to the red LED channel (channel 1)
9583
/// \return A reference to the red LED channel (channel 1)
96-
espp::Led::ChannelConfig &red_led() { return led_channels_[1]; }
84+
espp::Led::ChannelConfig &red_led();
85+
9786
/// Get a reference to the red LED channel (channel 1)
9887
/// \return A reference to the red LED channel (channel 1)
99-
espp::Led::ChannelConfig &led_channel1() { return led_channels_[1]; }
88+
espp::Led::ChannelConfig &led_channel1();
89+
10090
/// Set the duty cycle of the red LED
10191
/// \param duty The duty cycle of the red LED (0.0 - 100.0)
10292
/// \note The duty cycle is a percentage of the maximum duty cycle
10393
/// (which is 100.0)
10494
/// 0.0 is off, 100.0 is fully on
10595
/// \note For this function to have an effect, the LED timer must NOT be running
10696
/// (i.e. stop_breathing() must be called)
107-
void set_red_led_duty(float duty) { led_.set_duty(led_channels_[1].channel, duty); }
97+
void set_red_led_duty(float duty);
10898

10999
/// Get a reference to the LED object which controls the LEDs
110100
/// \return A reference to the Led object
111-
espp::Led &led() { return led_; }
101+
espp::Led &led();
112102

113103
/// \brief Get a reference to the Gaussian object which is used for breathing
114104
/// the LEDs.
115105
/// \return A reference to the Gaussian object
116-
espp::Gaussian &gaussian() { return gaussian_; }
106+
espp::Gaussian &gaussian();
117107

118108
/// Start breathing the LEDs
119109
/// \details This function starts the LED timer which will periodically update
120110
/// the LED duty cycle using the gaussian to create a breathing
121111
/// efect.
122-
void start_breathing() {
123-
io38_breathe_start_us = esp_timer_get_time();
124-
io8_breathe_start_us = esp_timer_get_time();
125-
static constexpr uint64_t led_timer_period_us = 30 * 1000; // 30 ms
126-
led_timer_.periodic(led_timer_period_us);
127-
}
112+
void start_breathing();
128113

129114
/// Stop breathing the LEDs
130115
/// \details This function stops the LED timer which will stop updating the
131116
/// LED duty cycle. It will also set the LED duty cycle to 0,
132117
/// effectively turning off the LEDs.
133-
void stop_breathing() {
134-
led_timer_.stop();
135-
led_.set_duty(led_channels_[0].channel, 0.0f);
136-
led_.set_duty(led_channels_[1].channel, 0.0f);
137-
}
118+
void stop_breathing();
138119

139120
/// Initialize the MotorGo-Mini's components for motor channel 1
140121
/// \details This function initializes the encoder and motor for motor channel
141122
/// 1. This consists of initializing encoder1 and motor1.
142-
void init_motor_channel_1() {
143-
bool run_task = true;
144-
std::error_code ec;
145-
encoder1_.initialize(run_task, ec);
146-
if (ec) {
147-
logger_.error("Could not initialize encoder1: {}", ec.message());
148-
return;
149-
}
150-
motor1_.initialize();
151-
}
123+
void init_motor_channel_1();
152124

153125
/// Initialize the MotorGo-Mini's components for motor channel 2
154126
/// \details This function initializes the encoder and motor for motor channel
155127
/// 2. This consists of initializing encoder2 and motor2.
156-
void init_motor_channel_2() {
157-
bool run_task = true;
158-
std::error_code ec;
159-
encoder2_.initialize(run_task, ec);
160-
if (ec) {
161-
logger_.error("Could not initialize encoder2: {}", ec.message());
162-
return;
163-
}
164-
motor2_.initialize();
165-
}
128+
void init_motor_channel_2();
166129

167130
/// Get a reference to the encoder 1
168131
/// \return A reference to the encoder 1
169-
Encoder &encoder1() { return encoder1_; }
132+
Encoder &encoder1();
170133

171134
/// Get a reference to the encoder 2
172135
/// \return A reference to the encoder 2
173-
Encoder &encoder2() { return encoder2_; }
136+
Encoder &encoder2();
174137

175138
/// Get a reference to the motor 1 driver
176139
/// \return A reference to the motor 1 driver
177-
espp::BldcDriver &motor1_driver() { return motor1_driver_; }
140+
espp::BldcDriver &motor1_driver();
178141

179142
/// Get a reference to the motor 2 driver
180143
/// \return A reference to the motor 2 driver
181-
espp::BldcDriver &motor2_driver() { return motor2_driver_; }
144+
espp::BldcDriver &motor2_driver();
182145

183146
/// Get a reference to the motor 1
184147
/// \return A reference to the motor 1
185-
BldcMotor &motor1() { return motor1_; }
148+
BldcMotor &motor1();
186149

187150
/// Get a reference to the motor 2
188151
/// \return A reference to the motor 2
189-
BldcMotor &motor2() { return motor2_; }
152+
BldcMotor &motor2();
190153

191154
/// Get a reference to the ADC_UNIT_1 OneshotAdc object
192155
/// \return A reference to the ADC_UNIT_1 OneshotAdc object
193-
espp::OneshotAdc &adc1() { return adc_1; }
156+
espp::OneshotAdc &adc1();
194157

195158
/// Get a reference to the ADC_UNIT_2 OneshotAdc object
196159
/// \return A reference to the ADC_UNIT_2 OneshotAdc object
197-
espp::OneshotAdc &adc2() { return adc_2; }
160+
espp::OneshotAdc &adc2();
198161

199162
/// Get the current sense value for motor 1 phase U
200163
/// \return The current sense value for motor 1 phase U in amps
201-
float motor1_current_u_amps() {
202-
return adc_1.read_mv(current_sense_m1_u_).value() * CURRENT_SENSE_MV_TO_A;
203-
}
164+
float motor1_current_u_amps();
204165

205166
/// Get the current sense value for motor 1 phase W
206167
/// \return The current sense value for motor 1 phase W in amps
207-
float motor1_current_w_amps() {
208-
return adc_1.read_mv(current_sense_m1_w_).value() * CURRENT_SENSE_MV_TO_A;
209-
}
168+
float motor1_current_w_amps();
210169

211170
/// Get the current sense value for motor 2 phase U
212171
/// \return The current sense value for motor 2 phase U in amps
213-
float motor2_current_u_amps() {
214-
return adc_2.read_mv(current_sense_m2_u_).value() * CURRENT_SENSE_MV_TO_A;
215-
}
172+
float motor2_current_u_amps();
216173

217174
/// Get the current sense value for motor 2 phase W
218175
/// \return The current sense value for motor 2 phase W in amps
219-
float motor2_current_w_amps() {
220-
return adc_1.read_mv(current_sense_m2_w_).value() * CURRENT_SENSE_MV_TO_A;
221-
}
176+
float motor2_current_w_amps();
222177

223178
protected:
224179
static constexpr auto I2C_PORT = I2C_NUM_0;
@@ -254,121 +209,15 @@ class MotorGoMini : public BaseComponent {
254209
// TODO: figure this out and update it :)
255210
static constexpr float CURRENT_SENSE_MV_TO_A = 1.0f;
256211

257-
void always_init() {
258-
start_breathing();
259-
init_spi();
260-
}
261-
262-
void init() {
263-
init_encoders();
264-
init_motors();
265-
}
266-
267-
void init_spi() {
268-
// Initialize the SPI bus for the encoders
269-
memset(&encoder_spi_bus_config_, 0, sizeof(encoder_spi_bus_config_));
270-
encoder_spi_bus_config_.mosi_io_num = -1;
271-
encoder_spi_bus_config_.miso_io_num = ENCODER_SPI_MISO_PIN;
272-
encoder_spi_bus_config_.sclk_io_num = ENCODER_SPI_SCLK_PIN;
273-
encoder_spi_bus_config_.quadwp_io_num = -1;
274-
encoder_spi_bus_config_.quadhd_io_num = -1;
275-
encoder_spi_bus_config_.max_transfer_sz = 100;
276-
// encoder_spi_bus_config_.isr_cpu_id = 0; // set to the same core as the esp-timer task (which
277-
// runs the encoders)
278-
auto err = spi_bus_initialize(ENCODER_SPI_HOST, &encoder_spi_bus_config_, SPI_DMA_CH_AUTO);
279-
if (err != ESP_OK) {
280-
logger_.error("Failed to initialize SPI bus for encoders: {}", esp_err_to_name(err));
281-
return;
282-
}
283-
284-
// Initialize the encoder 1
285-
memset(&encoder1_config, 0, sizeof(encoder1_config));
286-
encoder1_config.mode = 0;
287-
encoder1_config.clock_speed_hz = ENCODER_SPI_CLK_SPEED;
288-
encoder1_config.queue_size = 1;
289-
encoder1_config.spics_io_num = ENCODER_1_CS_PIN;
290-
// encoder1_config.cs_ena_pretrans = 2;
291-
// encoder1_config.input_delay_ns = 30;
292-
err = spi_bus_add_device(ENCODER_SPI_HOST, &encoder1_config, &encoder1_handle_);
293-
if (err != ESP_OK) {
294-
logger_.error("Failed to initialize Encoder 1: {}", esp_err_to_name(err));
295-
return;
296-
}
297-
298-
// Initialize the encoder 2
299-
memset(&encoder2_config, 0, sizeof(encoder2_config));
300-
encoder2_config.mode = 0;
301-
encoder2_config.clock_speed_hz = ENCODER_SPI_CLK_SPEED;
302-
encoder2_config.queue_size = 1;
303-
encoder2_config.spics_io_num = ENCODER_2_CS_PIN;
304-
// encoder2_config.cs_ena_pretrans = 2;
305-
// encoder2_config.input_delay_ns = 30;
306-
err = spi_bus_add_device(ENCODER_SPI_HOST, &encoder2_config, &encoder2_handle_);
307-
if (err != ESP_OK) {
308-
logger_.error("Failed to initialize Encoder 2: {}", esp_err_to_name(err));
309-
return;
310-
}
311-
}
212+
/// Constructor
213+
MotorGoMini();
312214

313-
void init_encoders() {
314-
bool run_task = true;
315-
std::error_code ec;
316-
encoder1_.initialize(run_task, ec);
317-
if (ec) {
318-
logger_.error("Could not initialize encoder1: {}", ec.message());
319-
}
320-
ec.clear();
321-
encoder2_.initialize(run_task, ec);
322-
if (ec) {
323-
logger_.error("Could not initialize encoder2: {}", ec.message());
324-
}
325-
ec.clear();
326-
}
215+
void always_init();
216+
void init_spi();
327217

328-
void init_motors() {
329-
motor1_.initialize();
330-
motor2_.initialize();
331-
}
218+
float breathe(float breathing_period, uint64_t start_us, bool restart = false);
332219

333-
float breathe(float breathing_period, uint64_t start_us, bool restart = false) {
334-
auto now_us = esp_timer_get_time();
335-
if (restart) {
336-
start_us = now_us;
337-
}
338-
auto elapsed_us = now_us - start_us;
339-
float elapsed = elapsed_us / 1e6f;
340-
float t = std::fmod(elapsed, breathing_period) / breathing_period;
341-
return gaussian_(t);
342-
}
343-
344-
bool IRAM_ATTR read_encoder(const auto &encoder_handle, uint8_t *data, size_t size) {
345-
static constexpr uint8_t SPIBUS_READ = 0x80;
346-
spi_transaction_t t = {
347-
.flags = 0,
348-
.cmd = 0,
349-
.addr = SPIBUS_READ,
350-
.length = size * 8,
351-
.rxlength = size * 8,
352-
.user = nullptr,
353-
.tx_buffer = nullptr,
354-
.rx_buffer = data,
355-
};
356-
if (size <= 4) {
357-
t.flags = SPI_TRANS_USE_RXDATA;
358-
t.rx_buffer = nullptr;
359-
}
360-
esp_err_t err = spi_device_polling_transmit(encoder_handle, &t);
361-
if (err != ESP_OK) {
362-
return false;
363-
}
364-
if (size <= 4) {
365-
// copy the data from the rx_data field
366-
for (size_t i = 0; i < size; i++) {
367-
data[i] = t.rx_data[i];
368-
}
369-
}
370-
return true;
371-
}
220+
bool read_encoder(const auto &encoder_handle, uint8_t *data, size_t size);
372221

373222
/// I2C bus for external communication
374223
I2c external_i2c_{{.port = I2C_PORT,

0 commit comments

Comments
 (0)