Support for Nano BLE33 and Sense

Hello

I have been using the Nano BLE 33 for some time - good speed, built in IMU. I am wondering what it would take to support this platform?

One thing I found interesting is that when using the 4x4 PWM chip in it, I can feed it the entire SV PWM 3-Phase waveform and with no further actions it will DMA those at the specified interval and send it out continuously until I ask it not to. This leaves me with the question - how much of a requirement is a feedback mechanism? Is this feedback doing continual adjustments or just on ramp up an down periods? Load changes? Any experience would be appreciated.

Thanks

I see no one has answered so far. I believe the Nano 33 has not been tested, but not completely sure. The 33 BLE also iirc had some PWM issues w.r.t. simultaneous output on more than 3 pins, some mbed OS silent crash, and you cannot use it for 6PWM control. The other Nano, the IoT iirc, didn’t have this problem but also not tested with SimpleFOC.

There is only one way to tell, load up the libraries, build the open loop velocity example and test with an oscilloscope to at least see if you get any signals on the pins. We could help with the pin assignment if you need.

Cheers!

I have been using the nrfx_pwm calls to avoid the 3 limit issue and to do either the 3 pwms together or the entire wave of pwms together. I can get those when I’m at my computer.

I am not sure SimpleFOC currently supports that. I have a feeling you might be bumping up against an immovable object.

Hey,

The BLE and the Sense are nRF52840 based boards, and this MCU is currently not supported by SimpleFOC. Its on my list, but as I’ve heard some negative feedback (@Valentine) about the chip’s PWM capabilities I’ve not prioritised it for now.

If you like the Nano form factor, the Nano 33 IoT is supported.

In terms of DMA-based PWM, this is conceivable for open-loop, but for closed loop control the feedback loop via sensors is essential to the FOC algorithm working. It will continuously adjust the PWM (duty cycle) to respond to the motor’s current position - so will certainly be affected by load changes.

So - Trying to make sense of the Driver Source Code - If I understand from quickly looking it over - I should be using most of the library generically with the exception of current sensing (need the voltage and resolution for the type of board for the Nano BLE33) and the PWM Driving (custom hardware calls for configure 2pwm, 3pwm, 4pwm, 6pwm, and write 2pwm, 3pwm, 4pwm, 6pwm, and configure pairs for the Nano BLE33)

It seems there are custom timer calls for these (for interrupting at the appropriate time) and the actual write PWM calls.

I have been using another library for custom timer calls (NRF52_MBED_TimerInterrupt.h) - would it be possible to require this library and use this same calls in the custom hardware sections?

Anything Obvious that I am missing?

I do not have a scope to play with these.

Sorry for the late reply. Yes, you could include this library, and it might make implementing the hw-specific calls easier. Check out the RaspiPico implementation for a “library based” solution.

Did you make any progress? I strongly advise solving the PWM side first, so open-loop and sensor-based closed loop can work, and then attack current sensing once that is all working fine…

I have a full program developed that currently uses interrupts and PWM writes to send out my PWM pulses (I set the pulse width for the three channels and then set an interrupt to get the next set out in the future.). In looking at all the code examples it appears the loop call functions need to be called at a pretty good clip to do what I think is a similar function. Is there a simple way that I can use the library to do the PWM calculations and then incorporate these values into my existing program?

I am assuming the Pole-Pairs and AS5600 determine my position and then this determines the appropriate phase PWM values to be written (based on mode etc) and then these get written out. I also assume that your looping is just skipping over writing when the PWMs are in mid flight. Is this correct?

In putzing around I believe I have an architecture of ARDUINO_ARDUINO_NANO33BLE and I suppose I could try to add that to the acceptable platforms and also to dud Hardware calls to simply extract the values I require and then write them myself. Is there a section that will allow the Library on this platform with dud hardware calls that can be added and then expanded for any new architecture or is everyone blazing their own trails?

Thanks again

Hello - I had hoped the minimalistic code base could be used for development on platforms currently not supported.

Using the minimalistic code base I have managed to compile the parts I require on the BLE33 (BLDC_3PWM with MagSensorI2C). I found that I did need to remove the code dealing with the experimental portion of a locked I2C bus as this really upset the BLE33 compile process. Slowly getting there.

I have managed to get the minimalistic code running on the BLE33 and it does seem to function. It appears that the Generic Code in the hardware driver is configuring the PWM and writing the PWMs to drive the Motor. The initial code is calibrating etc and then spinning up the motor - all using the standard 3 PWM configuration and writing.

I have now been trying to now write the custom drivers for the BLE33 hardware - I am not so worried about the setup of the PWM on the BLE33 as I can do that in the setup section of the sketch but I am trying to get the _writeDutyCycle3PWM modified but it will not compile with any subroutines that are not standard Arduino commands.

To do this I have a ble33_mcu.cpp file in the hardware_specific directory with the architecture flags of
#if ( ARDUINO_ARCH_NRF52840 && TARGET_NAME == ARDUINO_NANO33BLE )
in it. (this was also added to the generic_mcu.ccp file with a #elif command)

This is what I am trying to add to the routine

(seq1_values).channel_0 = (255 - constrain(int(255.0dc_a),0,255));
(seq1_values).channel_1 = (255 - constrain(int(255.0dc_b),0,255));
(seq1_values).channel_2 = (255 - constrain(int(255.0dc_c),0,255));
(void)nrfx_pwm_simple_playback(&pwm1, &seq1, 1, NRFX_PWM_FLAG_LOOP);

The seq1 values are Static variables that are set and then used in the nrfx_pwm_simple_playback calling routine. Is there something I need to predefine in order to allow the routine to modify the seq1 values and call the nrfx routine?

The errors I get are

sketch/src/drivers/hardware_specific/ble33_mcu.cpp: In function ‘void _writeDutyCycle3PWM(float, float, float, int, int, int)’:
sketch/src/drivers/hardware_specific/ble33_mcu.cpp:44:5: error: ‘seq1_values’ was not declared in this scope
(seq1_values).channel_0 = (255 - constrain(int(255.0dc_a),0,255));
^~~~~~~~~~~
sketch/src/drivers/hardware_specific/ble33_mcu.cpp:47:35: error: ‘pwm1’ was not declared in this scope
(void)nrfx_pwm_simple_playback(&pwm1, &seq1, 1, NRFX_PWM_FLAG_LOOP);
^~~~

My goal is to integrate this into my architecture where I write the PWM and then start an interrupt timer that will tell me when the PWM wave has completed. At that point in time I would like to run the FOC commands again (loopFOC & move) and load the next PWM set of 3 values.

I have no experience with the low level hardware drivers etc. and any help would be appreciated.

Regards

Hi @mbgrubman !

Sounds like you’re making some great progress. It would be super to support all the Arduino boards, including the Sense and BLE… I have the boards at home, so maybe I can help in testing.

The compilation problems you are describing are due to missing definitions. If you define some static variables in a cpp file, and want to access them from another cpp file, you have to declare references to them somewhere, typically in a header file included from the cpp file.

Do you have a fork of the simplefoc GitHub? If so I could check out all your code and take a look…

This I don’t really understand… are you trying to replace the loopFOC() and move() calls with an interrupt-driven system?
If so, I would strongly encourage you to try the “normal” simplefoc way first - where loopFOC() and move() are called continuously in the main loop() function.
Once your hardware driver code is working in this way, you can replace the loop() function with an interrupt based solution.

Note however I am not convinced this will work well - loopFOC() and move() do quite a lot, and are probably too “slow” to be called from within an interrupt on these MCUs.

Typically on this kind of speed MCU you would want to call them in a tight loop as often as you can, to obtain the most FOC-iterations per second that you can. It will have direct impact on the current consumption, smoothness of motion and maximum speed you can attain.

Thank you for the quick response.

I have only used GitHub to download items - I have never uploaded or created forks etc. I will try to find some examples of defining static variables and functions. These variables are created in the first section of my sketch and the nrfx_ routine is from another library loaded in the sketch. I believe this nrfx_ library is the only way to get into the bowels of PWM functions on the Nordic platform (a 4x4 chip with DMA etc - see https://content.arduino.cc/assets/Nano_BLE_MCU-nRF52840_PS_v1.1.pdf for more details on it and all the other built in functions such as Encoders) - with this I can change the PWM frequency, the create a count_Up_Down PWM trigger which essentially would create centre aligned PWMs.

This is in the Pre-amble

// PWM Library
#include "nrfx_pwm.h"

static nrfx_pwm_t pwm1 = NRFX_PWM_INSTANCE(0);

static nrf_pwm_values_individual_t seq1_values[] = {0, 0, 0, 0};

static nrf_pwm_sequence_t seq1 = {
    .values = {
        .p_individual = seq1_values
    },
    .length          = NRF_PWM_VALUES_LENGTH(seq1_values),
    .repeats         = 1,
    .end_delay       = 0
};

the Setup section has the following

    nrfx_pwm_config_t config1 = {
        .output_pins  = {
          0x80 + 32 + 2, // Arduino pin 10
          0X80 + 32 + 1, // Arduinopin 11
          0X80 + 32 + 8, // Arduino pin 12
            NRFX_PWM_PIN_NOT_USED,
        },
        .irq_priority = 7,
        .base_clock   = NRF_PWM_CLK_2MHz,
        .count_mode   = NRF_PWM_MODE_UP_AND_DOWN,
        .top_value    = 255,
        .load_mode    = NRF_PWM_LOAD_INDIVIDUAL,
        .step_mode    = NRF_PWM_STEP_TRIGGERED,
    };

    nrfx_pwm_init(&pwm1, &config1, NULL);

And from the loop section I can change the 3 variables that I tried to add to the subroutine earlier and the call the playback routine as described earlier.

(*seq1_values).channel_0 = (255 - constrain(int(255.0*dc_a),0,255));
(*seq1_values).channel_1 = (255 - constrain(int(255.0*dc_b),0,255));
(*seq1_values).channel_2 = (255 - constrain(int(255.0*dc_c),0,255));
(void)nrfx_pwm_simple_playback(&pwm1, &seq1, 1, NRFX_PWM_FLAG_LOOP);

In the interrupt that is called based on when the PWM waveform has completed I call a

nrfx_pwm_stop(&pwm1,false);

If this code was not used then if someone could simple remove the code from the compiler that references the experimental code regarding a locked I2C bus (maybe put within a

#if !( ARDUINO_ARCH_NRF52840 && TARGET_NAME == ARDUINO_NANO33BLE )

section) and then the rest would compile and load on the BLE33 (and I assume the Sense) and run using the standard Generic Hardware calls. I modified /Sensors/MagneticSensorsI2C.cpp & h, and /base_classes/sensor.h.

I do not have a scope so I don’t know if those calls would be center aligned PWMs. I think I saw in the Arduino docs that the PWM frequency by default on the BLE33 is 50Khz.

In regards to my interrupt structure - I do not want to replace the two main loop calls - simply call them on a regular interval determined on the interrupt. I have many other tasks that need to be done other than just running the motor in my project (flight controls, SBUS Receiving & Transmitting, GPS decoding, etc). Unless I am assuming wrong - the only way to perform the other tasks and keep the fast running motor code running is via interrupts. If this controller becomes overloaded I may need to look at a smart FOC board with its own processor that can be used simply to manage the motor position and speed control.

Thanks again and Regards

Hey, it definitely sounds like you’re on the right track.

You can certainly just clone the GitHub repo, and work inside your cloned copy. But if you’re interested in sharing the code, then a workflow like this is really helpful: you need a GitHub account, then you can “fork” the simplefoc repository on GitHub. The “fork” is a complete copy of simplefoc, but living in your GitHub account. So now you can clone your fork of simplefoc to your local PC, and when you make changes to the code you can push them back to your fork on GitHub, and others can see them. Once everything is working as you expect, you could even send a “Pull Request” on GitHub, to merge your changes back to the main library.

That is generally the way people go. Unless you have a very powerful MCU like a ESP32 or STM32F7, running simplefoc plus a little communications is about all the MCU can manage.

If you do try it all on the BLE33, then I would assume it is probably better to run the FOC in a tight loop, and try to do the other stuff more sporadically (e.g. interrupt based). Not sure, it isn’t 100% clear. One thing to definitely avoid is synchronous calls to sensors like on I2C. You want to make very sure your IO doesn’t block the loopFOC() calls.
You could measure your loopFOC() iteration speed to get an idea of how much headroom you have. You should be aiming for >1kHz (e.g. iterations per second) on the loopFOC() function, and for snappy performance >10kHz is probably a better goal.

1 Like

Hello everybody,
I created a pull request for NRF52 series so that simplefoc works with this family.

That works very well on Micro:bit V2 (NRF52833) I often use. I also tried with the NRF52832.

This PWM library should also work with Nano BLE33, I suppose.

I don’t use the NRF library (nrfx-pwm.h).

Hey, thanks so much @Marc_O !

@mbgrubman perhaps you could take a look at @Marc_O’s code and see if it works for you on the BLE33?

When I get back from vacation in one week I will begin to test and merge it, if someone else doesn’t do so first.

Here is my try with Micro:bit V2 with a maxon motor ECi40 7pole pairs.

1 Like

Thanks - I have not had a chance to look at Marc’s code yet.

Being not too familiar with the default setup of the BLE33 - My worry is that by default the analogWrite commands are defaulted to Start Synced and are not synced at all when called individually. To ensure Center Aligned pulses and that they are grouped together I am using the nrf library calls - again the PWM is done by a 4x4 setup meaning it can drive 4 independent groups of up to 4 signals - these signals will have the same PWM pulse width but each can have separate duty cycles. The PWM cycle is calculated by a 16Mhz clock rate with a divider to allow 8Mhz,4Mhz,2Mhz,1Mhz,500khz, 250khz,125Khz clock rates and a TOP_Value for the group to which the PWM counts up to and down to zero (ie a 1Mhz clock rate and a TOP_value of 100 would count up to 100 and down to zero for a total pulse width of 200ms). Each duty cycle value is converted to the range to 0 of the TOP_value and the output is turned on when the counter rises above the duty cycle value on the way up to the TOP_value and Off again when it passes below the duty cycle value on the way counting down to zero.

To do this - I created a reference to an extern Call in the hardware_api.h file to a subroutine in my main sketch. I then commented out the analogWrite commands in the ble33_mcu.cpp file and added the call to my subroutine.

My code is running - I am using a PWM clock rate of 4Mhz with a Top_Value of 99 which equates to a PWM Pulse Length of 49.5 mSec (20202 Hz) and I am executing the FOC calls at this rate. I am seeing the following
loopFOC call requires about 735 uSecs on the BLE33
motorMove call requires about 670uSes on the BLE33

Currently I get really smooth and quiet operation down to about 1 Rad/s and then it will not operate below this - I will continue to adjust parameters and experiment. I am using an AS5600 on I2C.

My target requirement is a motor that can run in the range of 30RPM to 300RPM. I would also like to have silent operation so I am looking at having am PWM frequency in the 20K range and my goal would be to run the FOC routines at this frequency - which appears doable at this point.

A question and comments

  • I tried Open Loop and really overheated the board and motor. I tried to add the ohms value of the motor to the motor call (with the Pole Pairs parameter) but it appears this is not supported in this branch - Can I set this as parameter value to that open loop can limit the drive voltage?
  • As a side note I tried reading the currents but found the calls are blocking and this really eats processing time. Again I turned to Nordic calls to perform this in the background using the chips, their DMA, and interrupts but I have not implement the tie in to the current read functions yet. I will do this over the next few days.

Thanks again and Regards

You can use the driver.voltage_limit to limit the voltage in open-loop mode. Open loop mode is by its nature inefficient, so it is expected that more current is used and things run hotter than if you get closed loop working…

This would be awesome. The generic current sense is not really intended for real world use. A MCU-specific current sense for the nordic chips would be great!

if you look at Marc_O’s code in nrf52_mcu.cpp (now on the dev branch of SimpleFOC) you will see this is exactly what he is doing. All the generic PWM calls have been replaced with NRF52-specific calls.
I would encourage you to try his code, since it seems he has already solved the problems you’re investigating.

How fast can it go? Normally we aim for quite a bit faster than this, with a top value of >1000. This is because when you limit the driver voltage in software the effective range is reduced, and having a full scale range of only 99 would be very low for this.

8Mhz at 199 would be a similar Frequency, 16Mhz at 399 should also be similar

Just - had a quick look. I don’t understand all the gyrations he is going through in the driver but it seems he is using the nrf calls and has come to the same conclusion running 32K at a top value of 256 or 20k at a top value of 400.

I am using a SimpleFOC V2 board for my project and at some point I thought I had exceeded the rate acceptable to that chip - but I have not experienced that lately (I think I was loading a larger array into memory and letting the PWM chip with DMA cycle through the SpaceVevtor array in open loop - this DMA method can run a pattern automatically like the pulsing LED when in Upload mode).

I have also played with changing the top_value on the fly but found when in the DMA mode I needed to stop playback and re-init and start playback. I had used my own interrupts to stop the playback at the end of a wave and restart it. I had fumbled with the interrupts available via the PWM chip (pwm complete or wave complete) but could not seem to get them to work - no one seemed interested on those forums. I think this would be easier when just sending one-shot PWMs like this.

For reference- I used this code for Non-blocking Analog reads that KlausK had on the arduino forums as an example AnalogRead in ISR crashes system - #11 by sbailey64 - Nano 33 BLE - Arduino Forum. It solved a problem of getting 20K analog reads a second. I modified the basic functions slightly for different pins etc (and not storing all the values) but basically the same. It does seem to use Interrupt 4 which still leaves Interrupt 3 available for other purposes.

Regards

1 Like