Outsource currentsensor, motorctl, motordrivers to common/

since board_single uses mostly the same code as board_control and
board_motorctl several files are outsorced to common folder to prevent
dupliate code and different versions
This commit is contained in:
jonny_jr9
2023-09-07 12:30:22 +02:00
parent ee5bad53ee
commit 13b896accb
20 changed files with 5 additions and 1170 deletions

View File

@@ -4,6 +4,9 @@ idf_component_register(
"buzzer.cpp"
"uart_common.cpp"
"types.cpp"
"motordrivers.cpp"
"motorctl.cpp"
"currentsensor.cpp"
INCLUDE_DIRS
"."
PRIV_REQUIRES nvs_flash

75
common/currentsensor.cpp Normal file
View File

@@ -0,0 +1,75 @@
extern "C" {
#include "hal/timer_types.h"
#include "esp_log.h"
}
#include "currentsensor.hpp"
//tag for logging
static const char * TAG = "current-sensors";
//--------------------------
//------- getVoltage -------
//--------------------------
//local function to get average voltage from adc
float getVoltage(adc1_channel_t adc, uint32_t samples){
//measure voltage
int measure = 0;
for (int j=0; j<samples; j++){
measure += adc1_get_raw(adc);
ets_delay_us(50);
}
return (float)measure / samples / 4096 * 3.3;
}
//=============================
//======== constructor ========
//=============================
currentSensor::currentSensor (adc1_channel_t adcChannel_f, float ratedCurrent_f){
//copy config
adcChannel = adcChannel_f;
ratedCurrent = ratedCurrent_f;
//init adc
adc1_config_width(ADC_WIDTH_BIT_12); //max resolution 4096
adc1_config_channel_atten(adcChannel, ADC_ATTEN_DB_11); //max voltage
//calibrate
calibrateZeroAmpere();
}
//============================
//=========== read ===========
//============================
float currentSensor::read(void){
//measure voltage
voltage = getVoltage(adcChannel, 30);
//scale voltage to current
if (voltage < centerVoltage){
current = (1 - voltage / centerVoltage) * -ratedCurrent;
} else if (voltage > centerVoltage){
current = (voltage - centerVoltage) / (3.3 - centerVoltage) * ratedCurrent;
}else {
current = 0;
}
ESP_LOGI(TAG, "read sensor adc=%d: voltage=%.3fV, centerVoltage=%.3fV => current=%.3fA", (int)adcChannel, voltage, centerVoltage, current);
return current;
}
//===============================
//===== calibrateZeroAmpere =====
//===============================
void currentSensor::calibrateZeroAmpere(void){
//measure voltage
float prev = centerVoltage;
centerVoltage = getVoltage(adcChannel, 100);
ESP_LOGW(TAG, "defined centerVoltage (0A) to %.3f (previous %.3f)", centerVoltage, prev);
}

20
common/currentsensor.hpp Normal file
View File

@@ -0,0 +1,20 @@
#include <driver/adc.h>
//supported current sensor working method:
//0V = -ratedCurrent
//centerVoltage = 0A
//3.3V = ratedCurrent
class currentSensor{
public:
currentSensor (adc1_channel_t adcChannel_f, float ratedCurrent);
void calibrateZeroAmpere(void); //set current voltage to voltage representing 0A
float read(void); //get current ampere
private:
adc1_channel_t adcChannel;
float ratedCurrent;
uint32_t measure;
float voltage;
float current;
float centerVoltage = 3.3/2;
};

346
common/motorctl.cpp Normal file
View File

@@ -0,0 +1,346 @@
#include "motorctl.hpp"
#include "esp_log.h"
#include "types.hpp"
//tag for logging
static const char * TAG = "motor-control";
#define TIMEOUT_IDLE_WHEN_NO_COMMAND 8000
//=============================
//======== constructor ========
//=============================
//constructor, simultaniously initialize instance of motor driver 'motor' and current sensor 'cSensor' with provided config (see below lines after ':')
controlledMotor::controlledMotor(single100a_config_t config_driver, motorctl_config_t config_control):
motor(config_driver),
cSensor(config_control.currentSensor_adc, config_control.currentSensor_ratedCurrent) {
//copy parameters for controlling the motor
config = config_control;
//copy configured default fading durations to actually used variables
msFadeAccel = config.msFadeAccel;
msFadeDecel = config.msFadeDecel;
init();
//TODO: add currentsensor object here
//currentSensor cSensor(config.currentSensor_adc, config.currentSensor_ratedCurrent);
}
//============================
//========== init ============
//============================
void controlledMotor::init(){
commandQueue = xQueueCreate( 1, sizeof( struct motorCommand_t ) );
//cSensor.calibrateZeroAmpere(); //currently done in currentsensor constructor TODO do this regularly e.g. in idle?
}
//----------------
//----- fade -----
//----------------
//local function that fades a variable
//- increments a variable (pointer) by given value
//- sets to target if already closer than increment
//TODO this needs testing
void fade(float * dutyNow, float dutyTarget, float dutyIncrement){
float dutyDelta = dutyTarget - *dutyNow;
if ( fabs(dutyDelta) > fabs(dutyIncrement) ) { //check if already close to target
*dutyNow = *dutyNow + dutyIncrement;
}
//already closer to target than increment
else {
*dutyNow = dutyTarget;
}
}
//----------------------------
//----- getStateFromDuty -----
//----------------------------
//local function that determines motor the direction from duty range -100 to 100
motorstate_t getStateFromDuty(float duty){
if(duty > 0) return motorstate_t::FWD;
if (duty < 0) return motorstate_t::REV;
return motorstate_t::IDLE;
}
//==============================
//=========== handle ===========
//==============================
//function that controls the motor driver and handles fading/ramp, current limit and deadtime
void controlledMotor::handle(){
//TODO: History: skip fading when motor was running fast recently / alternatively add rot-speed sensor
//--- receive commands from queue ---
if( xQueueReceive( commandQueue, &commandReceive, ( TickType_t ) 0 ) )
{
ESP_LOGI(TAG, "Read command from queue: state=%s, duty=%.2f", motorstateStr[(int)commandReceive.state], commandReceive.duty);
state = commandReceive.state;
dutyTarget = commandReceive.duty;
receiveTimeout = false;
timestamp_commandReceived = esp_log_timestamp();
//--- convert duty ---
//define target duty (-100 to 100) from provided duty and motorstate
//this value is more suitable for the fading algorithm
switch(commandReceive.state){
case motorstate_t::BRAKE:
//update state
state = motorstate_t::BRAKE;
//dutyTarget = 0;
dutyTarget = fabs(commandReceive.duty);
break;
case motorstate_t::IDLE:
dutyTarget = 0;
break;
case motorstate_t::FWD:
dutyTarget = fabs(commandReceive.duty);
break;
case motorstate_t::REV:
dutyTarget = - fabs(commandReceive.duty);
break;
}
}
//--- timeout, no data ---
//turn motors off if no data received for long time (e.g. no uart data / control offline)
//TODO no timeout when braking?
if ((esp_log_timestamp() - timestamp_commandReceived) > TIMEOUT_IDLE_WHEN_NO_COMMAND && !receiveTimeout){
receiveTimeout = true;
state = motorstate_t::IDLE;
dutyTarget = 0;
ESP_LOGE(TAG, "TIMEOUT, no target data received for more than %ds -> switch to IDLE", TIMEOUT_IDLE_WHEN_NO_COMMAND/1000);
}
//--- calculate increment ---
//calculate increment for fading UP with passed time since last run and configured fade time
int64_t usPassed = esp_timer_get_time() - timestampLastRunUs;
if (msFadeAccel > 0){
dutyIncrementAccel = ( usPassed / ((float)msFadeAccel * 1000) ) * 100; //TODO define maximum increment - first run after startup (or long) pause can cause a very large increment
} else {
dutyIncrementAccel = 100;
}
//calculate increment for fading DOWN with passed time since last run and configured fade time
if (msFadeDecel > 0){
dutyIncrementDecel = ( usPassed / ((float)msFadeDecel * 1000) ) * 100;
} else {
dutyIncrementDecel = 100;
}
//--- BRAKE ---
//brake immediately, update state, duty and exit this cycle of handle function
if (state == motorstate_t::BRAKE){
ESP_LOGD(TAG, "braking - skip fading");
motor.set(motorstate_t::BRAKE, dutyTarget);
//dutyNow = 0;
return; //no need to run the fade algorithm
}
//--- calculate difference ---
dutyDelta = dutyTarget - dutyNow;
//positive: need to increase by that value
//negative: need to decrease
//----- FADING -----
//fade duty to target (up and down)
//TODO: this needs optimization (can be more clear and/or simpler)
if (dutyDelta > 0) { //difference positive -> increasing duty (-100 -> 100)
if (dutyNow < 0) { //reverse, decelerating
fade(&dutyNow, dutyTarget, dutyIncrementDecel);
}
else if (dutyNow >= 0) { //forward, accelerating
fade(&dutyNow, dutyTarget, dutyIncrementAccel);
}
}
else if (dutyDelta < 0) { //difference negative -> decreasing duty (100 -> -100)
if (dutyNow <= 0) { //reverse, accelerating
fade(&dutyNow, dutyTarget, - dutyIncrementAccel);
}
else if (dutyNow > 0) { //forward, decelerating
fade(&dutyNow, dutyTarget, - dutyIncrementDecel);
}
}
//----- CURRENT LIMIT -----
if ((config.currentLimitEnabled) && (dutyDelta != 0)){
currentNow = cSensor.read();
if (fabs(currentNow) > config.currentMax){
float dutyOld = dutyNow;
//adaptive decrement:
//Note current exceeded twice -> twice as much decrement: TODO: decrement calc needs finetuning, currently random values
dutyIncrementDecel = (currentNow/config.currentMax) * ( usPassed / ((float)msFadeDecel * 1500) ) * 100;
float currentLimitDecrement = ( (float)usPassed / ((float)1000 * 1000) ) * 100; //1000ms from 100 to 0
if (dutyNow < -currentLimitDecrement) {
dutyNow += currentLimitDecrement;
} else if (dutyNow > currentLimitDecrement) {
dutyNow -= currentLimitDecrement;
}
ESP_LOGW(TAG, "current limit exceeded! now=%.3fA max=%.1fA => decreased duty from %.3f to %.3f", currentNow, config.currentMax, dutyOld, dutyNow);
}
}
//--- define new motorstate --- (-100 to 100 => direction)
state=getStateFromDuty(dutyNow);
//--- DEAD TIME ----
//ensure minimum idle time between direction change to prevent driver overload
//FWD -> IDLE -> FWD continue without waiting
//FWD -> IDLE -> REV wait for dead-time in IDLE
//TODO check when changed only?
if ( //not enough time between last direction state
( state == motorstate_t::FWD && (esp_log_timestamp() - timestampsModeLastActive[(int)motorstate_t::REV] < config.deadTimeMs))
|| (state == motorstate_t::REV && (esp_log_timestamp() - timestampsModeLastActive[(int)motorstate_t::FWD] < config.deadTimeMs))
){
ESP_LOGD(TAG, "waiting dead-time... dir change %s -> %s", motorstateStr[(int)statePrev], motorstateStr[(int)state]);
if (!deadTimeWaiting){ //log start
deadTimeWaiting = true;
ESP_LOGW(TAG, "starting dead-time... %s -> %s", motorstateStr[(int)statePrev], motorstateStr[(int)state]);
}
//force IDLE state during wait
state = motorstate_t::IDLE;
dutyNow = 0;
} else {
if (deadTimeWaiting){ //log end
deadTimeWaiting = false;
ESP_LOGW(TAG, "dead-time ended - continue with %s", motorstateStr[(int)state]);
}
ESP_LOGV(TAG, "deadtime: no change below deadtime detected... dir=%s, duty=%.1f", motorstateStr[(int)state], dutyNow);
}
//--- save current actual motorstate and timestamp ---
//needed for deadtime
timestampsModeLastActive[(int)getStateFromDuty(dutyNow)] = esp_log_timestamp();
//(-100 to 100 => direction)
statePrev = getStateFromDuty(dutyNow);
//--- apply new target to motor ---
motor.set(state, fabs(dutyNow));
//note: BRAKE state is handled earlier
//--- update timestamp ---
timestampLastRunUs = esp_timer_get_time(); //update timestamp last run with current timestamp in microseconds
}
//===============================
//========== setTarget ==========
//===============================
//function to set the target mode and duty of a motor
//puts the provided command in a queue for the handle function running in another task
void controlledMotor::setTarget(motorstate_t state_f, float duty_f){
commandSend = {
.state = state_f,
.duty = duty_f
};
ESP_LOGD(TAG, "Inserted command to queue: state=%s, duty=%.2f", motorstateStr[(int)commandSend.state], commandSend.duty);
//send command to queue (overwrite if an old command is still in the queue and not processed)
xQueueOverwrite( commandQueue, ( void * )&commandSend);
//xQueueSend( commandQueue, ( void * )&commandSend, ( TickType_t ) 0 );
}
//===============================
//========== getStatus ==========
//===============================
//function which returns the current status of the motor in a motorCommand_t struct
motorCommand_t controlledMotor::getStatus(){
motorCommand_t status = {
.state = state,
.duty = dutyNow
};
//TODO: mutex
return status;
};
//===============================
//=========== setFade ===========
//===============================
//function for editing or enabling the fading/ramp of the motor control
//set/update fading duration/amount
void controlledMotor::setFade(fadeType_t fadeType, uint32_t msFadeNew){
//TODO: mutex for msFade variable also used in handle function
switch(fadeType){
case fadeType_t::ACCEL:
msFadeAccel = msFadeNew;
break;
case fadeType_t::DECEL:
msFadeDecel = msFadeNew;
break;
}
}
//enable (set to default value) or disable fading
void controlledMotor::setFade(fadeType_t fadeType, bool enabled){
uint32_t msFadeNew = 0; //define new fade time as default disabled
if(enabled){ //enable
//set to default/configured value
switch(fadeType){
case fadeType_t::ACCEL:
msFadeNew = config.msFadeAccel;
break;
case fadeType_t::DECEL:
msFadeNew = config.msFadeDecel;
break;
}
}
//apply new Fade value
setFade(fadeType, msFadeNew);
}
//==================================
//=========== toggleFade ===========
//==================================
//toggle fading between OFF and default value
bool controlledMotor::toggleFade(fadeType_t fadeType){
uint32_t msFadeNew = 0;
bool enabled = false;
switch(fadeType){
case fadeType_t::ACCEL:
if (msFadeAccel == 0){
msFadeNew = config.msFadeAccel;
enabled = true;
} else {
msFadeNew = 0;
}
break;
case fadeType_t::DECEL:
if (msFadeDecel == 0){
msFadeNew = config.msFadeAccel;
enabled = true;
} else {
msFadeNew = 0;
}
break;
}
//apply new Fade value
setFade(fadeType, msFadeNew);
//return new state (fading enabled/disabled)
return enabled;
}

83
common/motorctl.hpp Normal file
View File

@@ -0,0 +1,83 @@
#pragma once
extern "C"
{
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/queue.h"
#include "esp_log.h"
#include "esp_timer.h"
}
#include "motordrivers.hpp"
#include "currentsensor.hpp"
//=======================================
//====== struct/type declarations ======
//=======================================
//outsourced to common/types.hpp
#include "types.hpp"
//===================================
//====== controlledMotor class ======
//===================================
class controlledMotor {
public:
//--- functions ---
controlledMotor(single100a_config_t config_driver, motorctl_config_t config_control); //constructor with structs for configuring motordriver and parameters for control TODO: add configuration for currentsensor
void handle(); //controls motor duty with fade and current limiting feature (has to be run frequently by another task)
void setTarget(motorstate_t state_f, float duty_f = 0); //adds target command to queue for handle function
motorCommand_t getStatus(); //get current status of the motor (returns struct with state and duty)
void setFade(fadeType_t fadeType, bool enabled); //enable/disable acceleration or deceleration fading
void setFade(fadeType_t fadeType, uint32_t msFadeNew); //set acceleration or deceleration fade time
bool toggleFade(fadeType_t fadeType); //toggle acceleration or deceleration on/off
//TODO set current limit method
private:
//--- functions ---
void init(); //creates currentsensor objects, motordriver objects and queue
//--- objects ---
//motor driver
single100a motor;
//queue for sending commands to the separate task running the handle() function very fast
QueueHandle_t commandQueue = NULL;
//current sensor
currentSensor cSensor;
//--- variables ---
//struct for storing control specific parameters
motorctl_config_t config;
motorstate_t state = motorstate_t::IDLE;
float currentMax;
float currentNow;
float dutyTarget;
float dutyNow;
float dutyIncrementAccel;
float dutyIncrementDecel;
float dutyDelta;
uint32_t msFadeAccel;
uint32_t msFadeDecel;
uint32_t ramp;
int64_t timestampLastRunUs;
bool deadTimeWaiting = false;
uint32_t timestampsModeLastActive[4] = {};
motorstate_t statePrev = motorstate_t::FWD;
struct motorCommand_t commandReceive = {};
struct motorCommand_t commandSend = {};
uint32_t timestamp_commandReceived = 0;
bool receiveTimeout = false;
};

167
common/motordrivers.cpp Normal file
View File

@@ -0,0 +1,167 @@
#include "motordrivers.hpp"
#include "esp_log.h"
#include "types.hpp"
//TODO: move from ledc to mcpwm?
//https://docs.espressif.com/projects/esp-idf/en/v4.3/esp32/api-reference/peripherals/mcpwm.html#
//https://github.com/espressif/esp-idf/tree/v4.3/examples/peripherals/mcpwm/mcpwm_basic_config
//Note fade functionality provided by LEDC would be very useful but unfortunately is not usable here because:
//"Due to hardware limitations, there is no way to stop a fade before it reaches its target duty."
//tag for logging
static const char * TAG = "motordriver";
//ms to wait in IDLE before BRAKE until relay actually switched
#define BRAKE_RELAY_DELAY_MS 300
//====================================
//===== single100a motor driver ======
//====================================
//-----------------------------
//-------- constructor --------
//-----------------------------
//copy provided struct with all configuration and run init function
single100a::single100a(single100a_config_t config_f){
config = config_f;
init();
}
//----------------------------
//---------- init ------------
//----------------------------
//function to initialize pwm output, gpio pins and calculate maxDuty
void single100a::init(){
//--- configure ledc timer ---
ledc_timer_config_t ledc_timer;
ledc_timer.speed_mode = LEDC_HIGH_SPEED_MODE;
ledc_timer.timer_num = config.ledc_timer;
ledc_timer.duty_resolution = config.resolution; //13bit gives max 5khz freq
ledc_timer.freq_hz = config.pwmFreq;
ledc_timer.clk_cfg = LEDC_AUTO_CLK;
//apply configuration
ledc_timer_config(&ledc_timer);
//--- configure ledc channel ---
ledc_channel_config_t ledc_channel;
ledc_channel.channel = config.ledc_channel;
ledc_channel.duty = 0;
ledc_channel.gpio_num = config.gpio_pwm;
ledc_channel.speed_mode = LEDC_HIGH_SPEED_MODE;
ledc_channel.hpoint = 0;
ledc_channel.timer_sel = config.ledc_timer;
ledc_channel.intr_type = LEDC_INTR_DISABLE;
ledc_channel.flags.output_invert = 0; //TODO: add config option to invert the pwm output?
//apply configuration
ledc_channel_config(&ledc_channel);
//--- define gpio pins as outputs ---
gpio_pad_select_gpio(config.gpio_a);
gpio_set_direction(config.gpio_a, GPIO_MODE_OUTPUT);
gpio_pad_select_gpio(config.gpio_b);
gpio_set_direction(config.gpio_b, GPIO_MODE_OUTPUT);
gpio_pad_select_gpio(config.gpio_brakeRelay);
gpio_set_direction(config.gpio_brakeRelay, GPIO_MODE_OUTPUT);
//--- calculate max duty according to selected resolution ---
dutyMax = pow(2, ledc_timer.duty_resolution) -1;
ESP_LOGW(TAG, "initialized single100a driver");
ESP_LOGW(TAG, "resolution=%dbit, dutyMax value=%d, resolution=%.4f %%", ledc_timer.duty_resolution, dutyMax, 100/(float)dutyMax);
}
//---------------------------
//----------- set -----------
//---------------------------
//function to put the h-bridge module in the desired state and duty cycle
void single100a::set(motorstate_t state_f, float duty_f){
//scale provided target duty in percent to available resolution for ledc
uint32_t dutyScaled;
if (duty_f > 100) { //target duty above 100%
dutyScaled = dutyMax;
} else if (duty_f < 0) { //target at or below 0%
state_f = motorstate_t::IDLE;
dutyScaled = 0;
} else { //target duty 0-100%
//scale duty to available resolution
dutyScaled = duty_f / 100 * dutyMax;
}
ESP_LOGV(TAG, "target-state=%s, duty=%d/%d, duty_input=%.3f%%", motorstateStr[(int)state_f], dutyScaled, dutyMax, duty_f);
//TODO: only when previous mode was BRAKE?
if (state_f != motorstate_t::BRAKE){
//reset brake wait state
brakeWaitingForRelay = false;
//turn of brake relay
gpio_set_level(config.gpio_brakeRelay, 0);
}
//put the single100a h-bridge module in the desired state update duty-cycle
//TODO maybe outsource mode code from once switch case? e.g. idle() brake()...
switch (state_f){
case motorstate_t::IDLE:
ledc_set_duty(LEDC_HIGH_SPEED_MODE, config.ledc_channel, dutyScaled);
ledc_update_duty(LEDC_HIGH_SPEED_MODE, config.ledc_channel);
//TODO: to fix bugged state of h-bridge module when idle and start again, maybe try to leave pwm signal on for some time before updating a/b pins?
//no brake: (freewheel)
//gpio_set_level(config.gpio_a, config.aEnabledPinState);
//gpio_set_level(config.gpio_b, !config.bEnabledPinState);
gpio_set_level(config.gpio_a, config.aEnabledPinState);
gpio_set_level(config.gpio_b, config.bEnabledPinState);
break;
case motorstate_t::BRAKE:
//prevent full short (no brake resistors) due to slow relay, also reduces switching load
if (!brakeWaitingForRelay){
ESP_LOGW(TAG, "BRAKE: turned on relay, waiting in IDLE for %d ms, then apply brake", BRAKE_RELAY_DELAY_MS);
//switch driver to IDLE for now
gpio_set_level(config.gpio_a, config.aEnabledPinState);
gpio_set_level(config.gpio_b, config.bEnabledPinState);
//start brake relay
gpio_set_level(config.gpio_brakeRelay, 1);
timestamp_brakeRelayPowered = esp_log_timestamp();
brakeWaitingForRelay = true;
}
//check if delay for relay to switch has passed
else if ((esp_log_timestamp() - timestamp_brakeRelayPowered) > BRAKE_RELAY_DELAY_MS) {
ESP_LOGD(TAG, "applying brake with brake-resistors at duty=%.2f%%", duty_f);
//switch driver to BRAKE
gpio_set_level(config.gpio_a, !config.aEnabledPinState);
gpio_set_level(config.gpio_b, !config.bEnabledPinState);
//apply duty
//FIXME switch between BREAK and IDLE with PWM and ignore pwm-pin? (needs test)
ledc_set_duty(LEDC_HIGH_SPEED_MODE, config.ledc_channel, dutyScaled);
ledc_update_duty(LEDC_HIGH_SPEED_MODE, config.ledc_channel);
} else {
//waiting... stay in IDLE
ESP_LOGD(TAG, "waiting for brake relay to close (IDLE)...");
gpio_set_level(config.gpio_a, config.aEnabledPinState);
gpio_set_level(config.gpio_b, config.bEnabledPinState);
}
break;
case motorstate_t::FWD:
ledc_set_duty(LEDC_HIGH_SPEED_MODE, config.ledc_channel, dutyScaled);
ledc_update_duty(LEDC_HIGH_SPEED_MODE, config.ledc_channel);
//forward:
gpio_set_level(config.gpio_a, config.aEnabledPinState);
gpio_set_level(config.gpio_b, !config.bEnabledPinState);
break;
case motorstate_t::REV:
ledc_set_duty(LEDC_HIGH_SPEED_MODE, config.ledc_channel, dutyScaled);
ledc_update_duty(LEDC_HIGH_SPEED_MODE, config.ledc_channel);
//reverse:
gpio_set_level(config.gpio_a, !config.aEnabledPinState);
gpio_set_level(config.gpio_b, config.bEnabledPinState);
break;
}
}

65
common/motordrivers.hpp Normal file
View File

@@ -0,0 +1,65 @@
#pragma once
extern "C"
{
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "driver/gpio.h"
#include "esp_log.h"
#include "driver/ledc.h"
#include "esp_err.h"
}
#include <cmath>
//====================================
//===== single100a motor driver ======
//====================================
//--------------------------------------------
//---- struct, enum, variable declarations ---
//--------------------------------------------
//motorstate_t, motorstateStr outsourced to common/types.hpp
#include "types.hpp"
//struct with all config parameters for single100a motor driver
typedef struct single100a_config_t {
gpio_num_t gpio_pwm;
gpio_num_t gpio_a;
gpio_num_t gpio_b;
gpio_num_t gpio_brakeRelay;
ledc_timer_t ledc_timer;
ledc_channel_t ledc_channel;
bool aEnabledPinState;
bool bEnabledPinState;
ledc_timer_bit_t resolution;
int pwmFreq;
} single100a_config_t;
//--------------------------------
//------- single100a class -------
//--------------------------------
class single100a {
public:
//--- constructor ---
single100a(single100a_config_t config_f); //provide config struct (see above)
//--- functions ---
void set(motorstate_t state, float duty_f = 0); //set mode and duty of the motor (see motorstate_t above)
//TODO: add functions to get the current state and duty
private:
//--- functions ---
void init(); //initialize pwm and gpio outputs, calculate maxDuty
//--- variables ---
single100a_config_t config;
uint32_t dutyMax;
motorstate_t state = motorstate_t::IDLE;
bool brakeWaitingForRelay = false;
uint32_t timestamp_brakeRelayPowered;
};