Raspberry Pi Pico W Board Compatability

Hi there! I have been attempting to learn all about SimpleFOC for a few month now, but this will be my first post here.

I am attempting to make the steer by wire project but utilizing 2 Raspberry Pi Pico W boards. I have gotten some simple closed loop velocity tests working on just a plain Pico but when I attempt to compile the example code for a Pico w it fails. Has anyone attempted to use this version of the Pico with simpleFOC? hopefully I was able to get my question across well enough :slight_smile:

Hey, and welcome to SimpleFOC, @cheppy44 !

Regarding the Pico W, it’s on my ToDo list, I have a few of them here to test with.

I wrote the RP2040 support, and based on what I’ve read so far it should be quite possible to use SimpleFOC with the Pi Pico W as well.

My understanding is that they (RPi) aren’t so good with their Arduino support, or Arduino isn’t so interested in supporting them or something… in any case there are problems with the official Arduino core for RP2040, and its stuck on an old framework version.

So AFAICT people are recommending to use an alternative Arduino core for the Pico W, instructions for example here: Pico W with the Arduino IDE | DroneBot Workshop

But I think this alternative core is based on a newer version of the RP2040 SDK, so there may be adjustments you have to make in our code to make it compatible, since we use the SDK calls directly to initialise the PWM and current sensing :-/

It’s all a bit of a mess, and I’m sorry about that. In the end, I am not sure if it will solve more problems or cause more problems if we switch away from the official core for our RP2040 support… I was kind of hoping Arduino/Raspberry would sort it out by now.

Thanks @runger for getting back to me so quickly!

It is a bit of a bummer to hear that the Pico W does not quite work yet. Originally I was using the alternative Arduino core for the Pico and it was running into the issue with the PWM. Do you think there is any hope for the Pico W, or should I look into using a different controller?

This community seems very awesome and I can not wait to learn more about it.

Hey @cheppy44,

I will get to the Pico W, but given the amount of stuff on my desk, I expect it won’t be until next year…

It would also be a great benefit if the official Arduino framework supported it, then there would not be compatibility issues with framework versions…

So for the short term, I’d encourage you to get a different MCU, like the ESP32 if you like WiFi, or the regular Pico if you can do without…

In the medium term, e.g. sometime next year, I hope we can sort it out and support the Pico W “out of the box”…

Hey @cheppy44 ,

An update on this: the dev branch of simplefoc should now support the using the Pico W with the alternate “earlehillpower” Arduino core for pico. I’m still testing, but it compiles for me for the Pico W now.

So if you’d like to give it a try, feedback on how well it works would be very welcomed!

2 Likes

Hello,
We are using the dev branch for the Pico RP2040 in a custom driver PCB and it works great. We are using Pico’s 2nd core for the FOC loop and Pico’s 1st core for everything else (UI etc). We tested (among others) using 24V and 48V motors (ACT 42BLF03, ACT 86BLF04) and recently found out that the 2nd Pico core crashes at higher motor currents (only @48V/7A). We discovered that our current measurement center line (zero Amps) is at 3.0V and so for higher currents the ADC gets over-saturated and maybe that crashes the 2nd core…
Regards,
Alexander

Are you suggesting it’s a hardware issue?

Or more likely a software problem?
I’ll take another look at the RP2040 ADC code…

And thank you very much for taking the time to report on it! We very much appreciate hearing about both your successes and any problems! :slight_smile:

1 Like

I’m not sure how I can isolate the issue. Hardware engineer says we have an OPA in between operating at 3.3V and so it cannot output something more than that to the ADC. The RP2040 2nd core hang (where the FOC loop runs) happens only at higher currents (in all motion control modes we tested, so velocity and torque voltage). So the ADC measurements are actually not used, only in my own software parts where I compute the maximum of all MOSFETS inline current like this (also in the 2nd core):

        phaseCurrent = current_sense->getPhaseCurrents();
        phaseCurrentAmps = max( max(abs(phaseCurrent.a), abs(phaseCurrent.b)), abs(phaseCurrent.c) );

The idea is to use this as an MOSFET overcurrent-protection. Also, today I have added a watchdog (5ms) to the 2nd core (FOC loop) so that the board will reset if it hangs again (and the start code will activate the lowside MOSFETs in our 6 PWM configuration, so the MOSFETs are not floating at crash time)… Also, I thought maybe it’s related to Hall-interrupts and tried to reproduce the hang with low currents and same motor RPM but it does not happen then. It is dependent on current…

Are you supplies isolated?
It sounds like maybe with those high drive currents you could develop some ripple due to high load or maybe BEMF from motor onto the motor supply. Might be worth looking at with a scope ? Although the reason for it only to be on core2 is a bit of a mystery…

1 Like

You are probably right, it might be an (increasing) ripple voltage on the 3.3v line that crashes the RP2040. It is already 800mV with low current motor tests (hardware engineer will improve this).
Also, I’m trying to add more robustness to the motor controller ‘firmware’, especially for these two cases (which can happen at high motor speed) that we currently implemented like this:

  1. The RP2040 hangs for some reason, the watchdog (5ms) is triggered and the RP2040 will reset - in startup code, the first thing that happens is switching on all the low-side MOSFETs and switching off high-side MOSFETs.
  2. We detect over-current via the inline shunts and disable the motor controller (switching off all MOSFETs, high-impedance)

I don’t like both switching scenarios much - now my idea is to not stress the components (MOSFETs+TVS diodes) on the PCB and use a PWM signal with duty cycle e.g. 10% to switch the low-side MOSFETs with it. Would that be a good approach for those two cases (in a general 6 PWM configuration)? What is the less stressfull way for the PCB components to switch off motor controller (at high speed) ? :slight_smile:

This would do it!

PWM is cycling between full off and full on, I don’t think this is any less “stressing” than just turning the low mosfets on. But I am not sure why you even need to do that - the currents in the motor will dissipate themselves, if you are not moving anything. Do you mean to try and implement a braking system?
What MOSFET are you using? that might also help someone in making some advice.

We use NCEP023N10LL (100V/330A) and SMBJ85CA TVS (~90V). Actually, I’m not trying to implement a braking system, just finding a safe way to ‘shutdown’ the system when the motor controller crashes :slight_smile: Yes, probably simply free-wheeling is a safe way to deal with the back EMF of our 48V motor…

We finally found a solution to get rid of the Pico core1 crashes. In the FOC library heart, we have added a current limiter. As soon as the current increases higher than a set limit (the power supply can deliver) it will limit the Uq voltages via a PID controller (for all motion control methods). Our power supplies will also limit the current, however this can drop the voltage in a short time frame and maybe that sometimes crashes the Pico.

...
// -----PCB current-limiter ---------------
  if (current_sense){
    if (phaseCurrentLimiter > 0.01){
      float ampslimit = phaseCurrentLimiter * 0.9; 
      if (phaseCurrentAmpsLP > ampslimit){
        float voltlimit = PID_current_phases(ampslimit - phaseCurrentAmpsLP);
        if (voltage.q >= 0) voltage.q = max(0, voltlimit);
          else voltage.q = min(0, -voltlimit);                   
      }
    }    
  }
...

Here you can see how the PCB power supply voltage oscillates when our power supply already limits the current and when not using our current limiter. When using the current limiter, this oscillation (and the Pico crashes) are gone. NOTE: the plot only shows a 2A supply and a low current (<10A) motor test. We only experienced the crashes with 10A supply, higher motor currents and without current limiter.

While testing our PCB with higher currents (20A) the 10A fuse broke and that produced a short-circuit on the PCB (damaged a MOSFET). Ideally, this should not happen and the gate driver should switch off high and low side MOSFETs, however that is a very short time frame (around 9-10V) and if that is not working properly, a short-circuit will happen. Also, the MOSFETs may not fully switch already at that voltage…

That was the reason why we now have added a under- and over-voltage protection (via RP2040 ADC3). As soon as the voltage drops below a trigger point (let’s say VSupply * 0.7), the motor driver will be disabled. It works beautifully.

...
// undervoltage protection
      if ( cfg.pcb.pinSupply != 0 ){
        if (cfg.pcb.underVoltage){
          if (detectedSupplyVoltage < (cfg.motor.u * 0.7)){
            motor->disable();
            doPauseFocLoop = true;                  
            underVoltageTriggered = true;
          }
        }
...

Now we can switch-off the power supply during high motor loads and the Pico will switch-off the motors driver.

Maybe this is something we should bring to the FOC library? Ideally, this is something one should implement in hardware, we did it in software and can adjust everything (current limit, trigger voltages etc.) in the settings.

1 Like

Could you try to run this loop on the RP2040, I’m curious to see how it performs without FPU?

long start_ticks = 0;
long stop_ticks = 0;
float elapsed_ticks = 0.0f;
float angle = 0.0f;
int count = 0;

start_ticks = micros();
for (angle = -pi; angle <= pi; angle += 0.001, count++) {

float angle2 = angle;
angle2 = _normalizeAngle(angle2);
float _ca = _cos(angle2);
float _sa = _sin(angle2);

}

stop_ticks = micros();

elapsed_ticks = stop_ticks-start_ticks;

Serial.print("Iterations: ");
Serial.println(count);
Serial.print("elapsed_ticks: ");
Serial.println(elapsed_ticks, 4);
Serial.print("time per iterasion: ");
Serial.println(elapsed_ticks / count, 4);

delay(5000);

Sure, here’s the result:
Iterations: 6284
elapsed_ticks: 17907.0000
time per iterasion: 2.8496

Here’s the full working code.


#include <Arduino.h>

#ifndef _round
#define _round(x) ((x)>=0?(long)((x)+0.5f):(long)((x)-0.5f))
#endif

#define _PI 3.14159265359f
#define _PI_2 1.57079632679f
#define _PI_3 1.0471975512f
#define _2PI 6.28318530718f
#define _3PI_2 4.71238898038f

const int sine_array[200] = {0,79,158,237,316,395,473,552,631,710,789,867,946,1024,1103,1181,1260,1338,1416,1494,1572,1650,1728,1806,1883,1961,2038,2115,2192,2269,2346,2423,2499,2575,2652,2728,2804,2879,2955,3030,3105,3180,3255,3329,3404,3478,3552,3625,3699,3772,3845,3918,3990,4063,4135,4206,4278,4349,4420,4491,4561,4631,4701,4770,4840,4909,4977,5046,5113,5181,5249,5316,5382,5449,5515,5580,5646,5711,5775,5839,5903,5967,6030,6093,6155,6217,6279,6340,6401,6461,6521,6581,6640,6699,6758,6815,6873,6930,6987,7043,7099,7154,7209,7264,7318,7371,7424,7477,7529,7581,7632,7683,7733,7783,7832,7881,7930,7977,8025,8072,8118,8164,8209,8254,8298,8342,8385,8428,8470,8512,8553,8594,8634,8673,8712,8751,8789,8826,8863,8899,8935,8970,9005,9039,9072,9105,9138,9169,9201,9231,9261,9291,9320,9348,9376,9403,9429,9455,9481,9506,9530,9554,9577,9599,9621,9642,9663,9683,9702,9721,9739,9757,9774,9790,9806,9821,9836,9850,9863,9876,9888,9899,9910,9920,9930,9939,9947,9955,9962,9969,9975,9980,9985,9989,9992,9995,9997,9999,10000,10000};

// normalizing radian angle to [0,2PI]
float _normalizeAngle(float angle){
  float a = fmod(angle, _2PI);
  return a >= 0 ? a : (a + _2PI);
}

float _sin(float a){
  if(a < _PI_2){
    //return sine_array[(int)(199.0f*( a / (_PI/2.0)))];
    //return sine_array[(int)(126.6873f* a)];           // float array optimized
    return 0.0001f*sine_array[_round(126.6873f* a)];      // int array optimized
  }else if(a < _PI){
    // return sine_array[(int)(199.0f*(1.0f - (a-_PI/2.0) / (_PI/2.0)))];
    //return sine_array[398 - (int)(126.6873f*a)];          // float array optimized
    return 0.0001f*sine_array[398 - _round(126.6873f*a)];     // int array optimized
  }else if(a < _3PI_2){
    // return -sine_array[(int)(199.0f*((a - _PI) / (_PI/2.0)))];
    //return -sine_array[-398 + (int)(126.6873f*a)];           // float array optimized
    return -0.0001f*sine_array[-398 + _round(126.6873f*a)];      // int array optimized
  } else {
    // return -sine_array[(int)(199.0f*(1.0f - (a - 3*_PI/2) / (_PI/2.0)))];
    //return -sine_array[796 - (int)(126.6873f*a)];           // float array optimized
    return -0.0001f*sine_array[796 - _round(126.6873f*a)];      // int array optimized
  }
}

float _cos(float a){
  float a_sin = a + _PI_2;
  a_sin = a_sin > _2PI ? a_sin - _2PI : a_sin;
  return _sin(a_sin);
}

void setup(){
  delay(2000);
  Serial.begin(115200);
}

void loop(){
  long start_ticks = 0;
  long stop_ticks = 0;
  float elapsed_ticks = 0.0f;
  float angle = 0.0f;
  int count = 0;

  start_ticks = micros();
  for (angle = -_PI; angle <= _PI; angle += 0.001, count++) {

  float angle2 = angle;
  angle2 = _normalizeAngle(angle2);
  float _ca = _cos(angle2);
  float _sa = _sin(angle2);

  }

  stop_ticks = micros();

  elapsed_ticks = stop_ticks-start_ticks;

  Serial.print("Iterations: ");
  Serial.println(count);
  Serial.print("elapsed_ticks: ");
  Serial.println(elapsed_ticks, 4);
  Serial.print("time per iterasion: ");
  Serial.println(elapsed_ticks / count, 4);

  delay(5000);
}

Btw, speed is not everything - RP2040 has some cool features that makes a good basis for the FOC library: dual cores (the FOC loop runs smoothly in its own core without any interruptions) and 4 programmable I/O state machines for GPIO read/write code (e.g. allows you to monitor GPIO pins without any interruptions - we use it as logic analyser for the FOC library running in the same code)…

1 Like

Those are some sweet hacks, decent times.

thx for running the test.

@greymfm sure time is relative. My goal is to run a stepper w. 50 pp, so responsiveness does matter if I want to achieve high rev´s. Naturally the responsiveness of the sensor and the coms between sensor and MCU plays a huge part in this.

I’m glad to see some more people using rp2040. It is so easy to work with, even if it doesn’t have as great support as STM32. The PIO is really cool too. I need to figure out how to work with the multiple cores.

Hey @greymfm that is really cool.

You mean the RMS bus current in this case, right? Is this done with single-shunt low or high side sensing, or where do you get the current from?

I think it is a useful feature, and it is interesting to hear that it is working well for you despite being implemented in software only.

Also this could be very useful to some people!

I think so! If possible, I would like to find a way to include it that has minimum impact on the existing code. For example, I think the voltage monitoring could be implemented without having to change the motor class, while the current limiting is a little more difficult to integrate.

We have a VoltageSense class (not yet working) in our drivers repository.

One of the problems is the way the ADCs can be used on all the different MCUs (please keep in mind we support 14 MCU types, not just Pico). I implemented the Pico ADC code as a “ADCEngine”, which the current sensing code then uses. This also opens the door to using the ADCEngine for other uses.
But unfortunately, not all our current sensing code follows this paradigm, and as an additional complication, for the MCUs that have low-side sensing support the hardware-specific code gets very complicated.
So for the moment it would be tricky to support this on STM32 or ESP32…

if you’re ok with it, I would like to work slowly on integrating this functionality, and beginning with the DC bus voltage sense (under/overvoltage protection) in the drivers library. But its going to take some time before all the different MCU types can be supported…

1 Like

The current limit set could be RMS, right, so something like 0.7 *A

We use inline current measurement for all 3 phases and just take the maximum absolute amplitude of all phases all times (see code in my first post in this thread further above). Our calculation and limiting voltage.q happens in BLDCMotor::loopFOC.

And yes, we use the RP2040 ADCEngine code with one added ADC channel for the voltage supply measurements and we use our InlineCurrentSense class to ask for the measured supply voltage (that’s kind of a ‘hack’ if you will). Also, as you suggested we have everything optional (everything can be turned off in the BLDCMotor::loopFOC).

So, yes, the most difficult part is to make the ADC modular (wrapper), or to just add the voltage sensing to the CurrentSense class (as we did) because it already knows best how to use the ADC for each MCU type…