Open RoboDurden opened 1 year ago
Okay i added a nice IrqMotor class extending BLDCMotor:
https://github.com/RoboDurden/Arduino-FOC/blob/master/src/IrqMotor.h
class IrqMotor: public BLDCMotor
{
public:
IrqMotor(int pp, float R = NOT_SET, float KV = NOT_SET, float L = NOT_SET);
// overriding
int initFOC() override;
void loopFOC() {};
void move(float target = NOT_SET) override;
void IrqHandler();
unsigned long iAdcMicros = 0;
unsigned long iAdcMicrosLoop = 0;
int iUsLoopFOC = 300;
protected:
unsigned long iAdcLast = 0;
unsigned long iAdcMicrosLoopLast = 0;
};
https://github.com/RoboDurden/Arduino-FOC/blob/master/src/IrqMotor.cpp
IrqMotor* pIrqMotor = NULL;
boolean bIrqMotorThreadNotRunning = true; // thread safety
IrqMotor::IrqMotor(int pp, float _R, float _KV, float _inductance)
: BLDCMotor(pp, _R, _KV, _inductance)
{
pIrqMotor = this;
}
int IrqMotor::initFOC()
{
int iRet = BLDCMotor::initFOC();
adc_interrupt_flag_clear(ADC_INT_EOIC);
adc_interrupt_enable(ADC_INT_EOIC);
nvic_irq_enable(ADC_CMP_IRQn, 3, 3);
return iRet;
}
void IrqMotor::move(float new_target)
{
if(_isset(new_target)) target = new_target;
}
void IrqMotor::IrqHandler()
{
unsigned long iNow = _micros();
iAdcMicros = iNow - iAdcLast;
iAdcLast = iNow;
if (enabled)
{
if (bIrqMotorThreadNotRunning && (iNow - iAdcMicrosLoopLast > iUsLoopFOC) ) // 250 us = 4 kHz
{
bIrqMotorThreadNotRunning = false;
BLDCMotor::loopFOC();
BLDCMotor::move(target);
iAdcMicrosLoop = iNow - iAdcMicrosLoopLast;
iAdcMicrosLoopLast = iNow;
bIrqMotorThreadNotRunning = true;
}
}
}
extern "C"
{
void ADC_CMP_IRQHandler(void)
{
if ( adc_interrupt_flag_get(ADC_INT_EOIC) != RESET)
{
adc_interrupt_flag_clear(ADC_INT_EOIC);
if (pIrqMotor) pIrqMotor->IrqHandler();
}
}
}
And i only have about 1% less performance with no more time restrictions in the loop():
22 Volt
T5 T10 T20 T50
KV [rpm/V] current [A]
IrqMotor
4.49 0.15
9.17 0.27
9.64 0.28
12.6 1.0
BLDCMotor
4.48 0.15
9,23 0.27
9,75 0.28
13,03 1.2
BLDCMotor + loop()::delayMicroseconds(500);
4.49 0.15
8.89 0.3 :-(
9.28 0.3 :-(
11.33 0.6
It only needs
#define IRQMOTOR
#ifdef IRQMOTOR
IrqMotor motor = IrqMotor(BLDC_POLE_PAIRS, NOT_SET, NOT_SET, 0.00036858);
#else
BLDCMotor motor = BLDCMotor(BLDC_POLE_PAIRS, NOT_SET, NOT_SET, 0.00036858);
#endif
All the other init and loop code can stay in place: https://github.com/RoboDurden/Split_Hoverboard_SimpleFOC/blob/main/src/main.cpp
@Candas1 where is your _powtwo
defined ? I needed to add #define _powtwo(n)(1 << n)
to SimpleFOCDrivers\src\voltageGenericVoltageSense.cpp
:-/
Ideas welcome !
Maybe @robcazzaro will like to check my
ADC_CMP_IRQHandler
for thread safty. Is 3,3 the lowest possible interrupt priority ?
From a very quick look at the docs, a priority of 0, 0 should work and be the highest priority for the processor. But I haven't checked if the Arduino library uses priority 0 (should not)
the Hall interrupts run at 3,0 priority!
If i let the ADC_INT_EOIC
= EndOfInsertedConversion run at 0,0 , the hall interrupts seem to get postponed to when the 300 us loopFOC+move have finished :-(
I only get the near loop()::loopFOC()
performence when i let it run at lower priority than 3,0. I think that is 3,3.
I guess that there are only two bits for
void nvic_irq_enable(uint8_t nvic_irq, uint8_t nvic_irq_pre_priority, uint8_t nvic_irq_sub_priority)
Then 3,3 would be the highest number possible and therefore the lowest priority that allows the hall interrupts (and whatever code users will add) to be nested = interrupt the ADC_CMP_IRQHandler(void)
-> IrqMotor::IrqHandler()
and be executed immediately :-)
I first tried the wrapping approach like SmoothedSensor
.
But the loop()
code reads element variables like motor.enabled
which i can not update when some FocMotor function changes the _wrapped.enabled
:-/
I do not think that the simpleFOC community will accept a pull request to add my IrqMotor the the Arduino-FOC.
Should i try for a SimpleFOCDrivers/motors/IrqMotor.cpp
?
I guess i better move my class to the Split_Hoverboard_simpleFOC
repo.
Hopefully Simple FOC\src\current_sense\hardware_specific\gd32\gd32f130\gd32f130_mcu.cpp
will make it into the Arduino-FOC
main branch :-)
Then 3,3 would be the highest number possible and therefore the lowest priority that allows the hall interrupts (and whatever code users will add) to be nested = interrupt the
ADC_CMP_IRQHandler(void)
->IrqMotor::IrqHandler()
and be executed immediately :-)
Well, you could change the Hall interrupt to be 0,0, and the ADC to be 0,1 or even 1,0. The system uses negative priority numbers for the non-maskable hardware interrupts, and you have full control over the others. But if there are no other interrupts, 3.0 and 3,3 are de facto equivalent to 0,0 and 0,3...
Hi,
I made the interrupt a parameter to keep it in case it's useful. I synched arduino-foc-drivers thinking _powtwo will be released also in the main library, but it seems it's pending. If this PR is accepted, loopFOC should be a bit faster.
They will not accept IRQMotor, it's very GD32 specific, and I am not even sure if they will accept my drivers. There are still many changes pending, arduino-gd32 project is not really active.
I thought about making the gd32 drivers a submodule so it's separated from the library but it seems this can generate other problems.
@robcazzaro
you could change the Hall interrupt to be 0,0,
No, the Hall interrupts use the Arduino-Core attachInterrupt(digitalPinToInterrupt(pinA), doA, CHANGE);
which why the HallSensor instantly worked on the GD32. But the nvic priorities are hard coded into the Arduino-Core :-(
and you have full control over the others.
No, i have no control what other interrupt code/libraries end users might want to add. Therefore i need the lowest possible priority.
@Candas1 yes i also think they would never accept it. But they themselves are aware that running loopFOC in the main loop is not ideal. I added an optional TIMER2 interrupt to my IrqMotor which would make it usuful for any platform:
int IrqMotor::initFOC(unsigned int iHz)
{
int iRet = BLDCMotor::initFOC();
if (iHz)
{
oTimer.setPeriodTime(iHz, FORMAT_HZ);
oTimer.attachInterrupt(&timer_cb);
nvic_irq_enable(TIMER2_IRQn, 3, 3);
oTimer.start();
}
else
{
adc_interrupt_flag_clear(ADC_INT_EOIC);
adc_interrupt_enable(ADC_INT_EOIC);
nvic_irq_enable(ADC_CMP_IRQn, 3, 3);
}
return iRet;
}
If there would be a SetPriority function in the Arduino-Core, this TimerX interrupt might run on any Arduino platform.
Perfomance with motor.initFOC(3000);
is not bad at all:
22 Volt
T5 T10 T20 T50
IrqMotor:Timer2 with loop()::delayMicroseconds(500);
4.45 0.15
9.05 0.3
9.52 0.28
12.33 1.8
IrqMotor:EOIC with loop()::delayMicroseconds(500);
4.45 0.15
9.08 0.26
9.52 0.28
12.33 1.0
BLDCMotor without loop()::delayMicroseconds(500);
4.46 0.15
9,01 0.27
9,43 0.27
11.70 1.3
That's a library so I think they let you run it from wherever you want.
Okay @Candas1 i have moved my class to #include "../include/IrqMotor.h" and now i am back to
lib_deps =
koendv/RTT Stream@^1.3.0
https://github.com/Candas1/Arduino-FOC.git#dev-gd32
https://github.com/Candas1/Arduino-FOC-drivers.git#dev
Would be nice if you temporarily add this to GenericVoltageSense.cpp
#ifndef _powtwo
#define _powtwo(n)(1 << n)
#endif
Would also be nice if you push your dev code to the main branches. Because i am quite happy with your work already and now would continue with the I2C-Hoverboard code. You then can happily continue with your dev branches and push to the main branch when the performance gets better :-)
I won't be able to work on it next few days. You just have to add it to your local copy
Yes take your time :-) It is just that when my repo is ready for a youtube tutorial, users should download lib_deps from your main branch so you can continue to experiment with your dev branches !
I now already successfully moved the entire simpleFOC code as well as all io code to a new class Hoverboard
and the main.cpp
is nicley empty and ready for users to add their code:
https://github.com/RoboDurden/Split_Hoverboard_SimpleFOC/blob/main/include/Hoverboard.h https://github.com/RoboDurden/Split_Hoverboard_SimpleFOC/blob/main/src/Hoverboard.cpp
#include "Hoverboard.h"
#ifdef DEBUG_STLINK
#include <RTTStream.h>
RTTStream rtt;
#endif
Hoverboard oHoverboard;
float target = 0;
LowPassFilter LPF_target(0.5); // the higher the longer new values need to take effect
Commander command = Commander(SERIALDEBUG);
void doTarget(char* cmd) { command.scalar(&target, cmd); }
#ifdef IRQMOTOR
void doUsLoopFoc(char* cmd) { float f; command.scalar(&f, cmd); oHoverboard.motor.iUsLoopFOC = f;}
#endif
unsigned long iTimeSetupFinished = 0;
void setup()
{
#ifdef DEBUG_UART
DEBUG_UART.begin(DEBUG_UART_BAUD);
SimpleFOCDebug::enable(&DEBUG_UART);
motor.useMonitoring(DEBUG_UART);
#endif
//Serial2.begin(DEBUG_UART_BAUD); // when using Serial1 as DEBUG_UART
#ifdef DEBUG_STLINK
SimpleFOCDebug::enable(&rtt);
oHoverboard.motor.useMonitoring(rtt);
#endif
OUTN("Split Hoverboards with C++ SimpleFOC :-)")
if (!oHoverboard.Init())
{
OUTN("oHoverboard.Init() failed :-(")
return;
}
// add target command T
command.add('t', doTarget, "target voltage");
#ifdef IRQMOTOR
command.add('f', doUsLoopFoc, "set us of loopFoc");
#endif
oHoverboard.Blink(3,oHoverboard.oLedGreen);
iTimeSetupFinished = millis();
}
unsigned long iTimeLast = 0;
unsigned long iTimeSend = 0;
void loop()
{
unsigned long iNowMs = _micros();
unsigned long iLoopMS = iNowMs - iTimeLast;
iTimeLast = iNowMs;
unsigned long iNow = millis();
if ( oHoverboard.Move(LPF_target(target)) ) // return only once TRUE when current calibrating is done
{
target = 10;
}
command.run(); // user communication
#ifdef IRQMOTOR
delayMicroseconds(500);
#endif
if (iTimeSend > iNow) return;
iTimeSend = iNow + 100; // TIME_SEND
OUT2T("A",oHoverboard.GetDcCurrent())
OUT2T("hall",oHoverboard.sensor.pulse_diff);
#ifdef IRQMOTOR
OUT2T("adc us",oHoverboard.motor.iAdcMicros)
OUT2T("adc Hz",1000000 / oHoverboard.motor.iAdcMicros)
OUT2T("iAdcMicrosLoop",oHoverboard.motor.iAdcMicrosLoop)
#endif
OUT2T(target,oHoverboard.motor.target)
OUT2T("v",oHoverboard.GetRpmPerVolt() )
OUT2N("loop us",iLoopMS)
}
Next step could already be to add my https://github.com/RoboDurden/GD32_I2C_Slave :-)
I will let you know when the main is updated. Feel free to share this is you think it's good enough, but you will have to support those users, please don't send them to me. I want to focus on Simplefoc improvements.
I am happy to help my users: https://github.com/RoboDurden/Hoverboard-Firmware-Hack-Gen2.x/issues/16 well, happy to try to help..
That's a good user I mean, the other users 😂
Thanks @Candas1 for adding that
ADC_USE_INTERRUPT
code toSimple FOC\src\current_sense\hardware_specific\gd32\gd32f130/gd32f130_mcu.cpp
.//#define ADC_USE_INTERRUPT // If defined, will use the End of conversion interrupt of inserted ADC
I think this was only for testing and you are going to remove it ?But i think i can use that code to call
motor.loopFOC()
( andmotor.move()
) right when the two low-side mofet currents have been sampled (which need to be in sync with the bldc pwm.) I had to set a priority lower then the HallSensor interrupts which useThen i think, the hall interrupts can interrupt the 300 microseconds long
loopFOC()
(called nested interrupts = nvic = Nested vector interrupt control)At the moment i simply add many extern to the
gd32f130_mcu.cpp
to callmotor.XY
and allowOUT2T
log output:Then this works nearly as nicely as having
motor.loopFOC
andmotor.move
in the mainloop()
:The sound of the motor has an additional frequency which i think links to
iUsLoopFOC
.And now i can add
delayMicroseconds(500);
to the main loop() with no effect on motor performance :-)Current consumption (for 22V and T10) is flucktuating from 0.27 A to 0.3 A for both methods. But with my interrupt driven method , the 0.3 A flickers on my lcd power supply a bit more often than the loop::motor.loopFOC() method does. So the old method is slightly more efficient. Do not know why !
The motor.loopFOC + motor.move takes about 300 micro seconds :-( So a frequency of more then 3 kHz is not possible :-/ That is a bit frustrating considering that the very simple Gen2 block commutation runs at 16 kHz ?
It is okay Candas if you do not help here. I think your focus is on using SimpleFOC with a Gen2 board for a very specific robot task, whereas i want a replacement of the Gen2.x firmware where users can add whatever they want to the main loop().
Maybe @robcazzaro will like to check my
ADC_CMP_IRQHandler
for thread safty. Is 3,3 the lowest possible interrupt priority ?