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:
@@ -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
75
common/currentsensor.cpp
Normal 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
20
common/currentsensor.hpp
Normal 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
346
common/motorctl.cpp
Normal 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
83
common/motorctl.hpp
Normal 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
167
common/motordrivers.cpp
Normal 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
65
common/motordrivers.hpp
Normal 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;
|
||||
};
|
||||
Reference in New Issue
Block a user