This discussion has been locked.
You can no longer post new replies to this discussion. If you have a question you can start a new discussion

Servo PPM Routines for Nano 33 BLE (Examples & Questions)

Good evening, (I have a few questions which are at the bottom, so I hope you don't mind me having posted the full length of these two programs first)

I've managed to produce some great results in developing PPM (pulse position modulation; related to PWM pulse width modulation) for a head tracker project I'm working on. I've included two fundamentally different programs as described below, which I'm testing using my Nano 33 BLE via the Arduino development environment...

Method 1: Servo PPM using 2 timers (4 channels)

This method produces the best results (although method 2 does indeed work quite well). The servos move virtually perfectly smooth almost 100% of the time. Timer 3 CC[0] sets the initial high value of the waveform, followed by CC[1] to [4] for the 4 channels. CC[5] is used for the PPM frame length which is set to 22500 microseconds. Each timer 3 compare activates timer 4 which in turn controls the pulse length.

The GPIOTE tasks are controlled by timer events via PPI channels. One complete frame length is processed by timer 3 independent of timer 3 IRQ handler. After the frame is complete, timer 3 stops and clears, then the new servo values are transferred, and the timer is started again. The delay until the next frame starts is dependent on timer 3 prescaler which is set to 1MHz, which could be increased, and the PPM channel values scaled accordingly.

Note: I've omitted all Timer "_Pos" instructions except for TIMER_INTENSET (for GPIOTE I decided to just leave them all in place). _Pos is required for INTENSET even though it has only one field in its register (note "Write 1 to enable..." in the description. Without _Pos it doesn't work). Although SHORTS has 2 fields in its register, it doesn't need _Pos because I'm using its 1st field.  

Fullscreen
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#include <nrf.h>
#define PPI_CHANNEL_0 (0)
#define PPI_CHANNEL_1 (1)
#define PPI_CHANNEL_2 (2)
#define PPI_CHANNEL_3 (3)
#define PPI_CHANNEL_4 (4)
#define PPI_CHANNEL_5 (5)
// #define PIN_GPIO (16) // Green for Nano 33 BLE
#define PIN_GPIO (2)
#define PORT (1)
/* Using timer 1 via MBED doesn't work, so perhaps it's being used */
void setup()
{
// Configure PIN_GPIO as output
// ----------------------------
NRF_GPIO->DIRSET = (1UL << PIN_GPIO);
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

Method 2: Servo PPM using 1 timer (8 channels)

Unlike method 1, the entire PPM waveform is constructed inside the timer handler and therefore is dependent on its timing accuracy/consistency. You can have as many channels as PPM supports, even though only one timer is being used. I've added a demo option which requires setting the "demo_multiplier" value and "sigPin" to an LED.

Synchronisation seems like a key point. For example, by considering method 1, it's clear that all 4 channel values are transferred whilst the timer is stopped. However for this single timer method, the channel values are transferred without stopping the timer. **Refer to the 5th post for clarification about this** 

Fullscreen
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// The following program is adapted from here: https://forum.arduino.cc/t/mbed-os-isr-linkage-on-arduino-33-ble/898811 and here: https://quadmeup.com/generate-ppm-signal-with-arduino
// -------------------------------------------
#include <nrf_timer.h>
NRF_TIMER_Type* timer = NRF_TIMER4;
IRQn_Type timer_irq = TIMER4_IRQn;
#define NUMBER_OF_CHANNELS 8 // Number of channels
#define CHANNEL_DEFAULT_VALUE 950 // Default servo value
#define FRAME_LENGTH 22500 // PPM frame length in microseconds (1ms = 1000µs)
#define PULSE_LENGTH 500 // Pulse length
#define sigPin 10 // 23 is the Nano 33 BLE Green LED. 10 is ~D10... See here for pin definitions: https://github.com/arduino/ArduinoCore-nRF528x-mbedos/blob/master/variants/ARDUINO_NANO33BLE/pins_arduino.h#L54
volatile int demo_multiplier = 1; // 200 is good for testing via the onboard LEDs (Don't forget to change "sigPin" above)
volatile int ppm [NUMBER_OF_CHANNELS];
extern "C" void TIMER4_IRQHandler_v()
{
static int cur_chan_numb;
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

Here are my questions

  1. When are timer CC values updated if the transfer is initiated whilst the timer is running? 
  2. Commented in timer 3 handler for method 1 is "dummy" from here Nordic Timer Example. I thought that "NRF_TIMER3->EVENTS_COMPARE[n] = 0" took care of that, so please kindly explain to me what the two dummy lines do?
  3. How important is __WFE() and what does it do? (I have it commented, and everything seems fine)
  4. With respect to the findings as detailed in my "Note:" near the beginning of this post in relation to INTENSET for Timer, please explain why _Pos is required even though INTENSET's register only has one field?
  5. Please advise of any improvements for either method? Although both work well, method 1 as I already said is very smooth but limited to 4 channels via two timers, unless for example reusing the CC values by using a timer handler via EVENTS_COMPARE[4] to stop it, transfer the next set of PPM values, and restart again. Like it's already doing but two stages instead of one, but that would of course introduce a timer handler interruption part way in constructing the waveform.