How to pilot more than four pwm pin - Arduino BLE 33 sense

Hello,

I'm brand new to arduino and c++ , and I'm sorry if I asks stupid question.

So Im trying to pilot rgb strips with my arduino 33 ble sense ( I removed the ble code and accelerometor code from my code to make the question more clear)

I used to set 1 pin for each color when initialising my RGBW class and just display it with an analog write. But when I added a new RGBW nothing works anymore.
Turns out MBedOS can only control up to 4 PWm(if I understood correctly its because there are only 4 PWM modules and you wouldnt be able to have different frequency on the same module ? a limitation that doesnt bother me on a led strip)

I had to change a bit of my architecture and my Output class doesnt really make sense anymore but I decided to keep it anyway for the logic already was implemented.
I am now trying to use nrfx_pwm library which is a lot harder than just analogWrite and I must admit I am a bit over my head and I hope you guys could help me. I might have done a pretty simple mistake that I dont see here. And as I have absolutely nothing to read the output signal ( didnt think that this project would need it) I cant really tell whats happening.

Thanks in advance

#include "Arduino.h"
#include <iostream>
#include "nrfx_pwm.h"



class Output{
  private:
    int pin;
    int old; 
  public:
    int value; 
    Output(int pin){
       this->pin = pin;
       //pinMode(pin, OUTPUT);
    }
/*
    void Display(){
        analogWrite(pin, value);
    }*/

    void Switch(){
        if(value != 0){
           Off();
        }else{
         On();
        }
    }

    void Off(){
      if(value != 0){
           old = value;
           value =0;   
          // Display();
        }
        
      }

    void On(){
      if(value == 0 && old != 0){
         value = old;
         old = 0;
       //  Display();
       }
 
      }

      void Debug(){     
         
           Serial.printf("[%i]-[%i]-[%i|",value,old,pin);
           Serial.print(digitalPinToPinName(pin));
           Serial.print("]\n");
      }
    
};

class Color{
  public:
  int R;
  int G;
  int B;
  int W;
  Color(int R, int G, int B,int W=0){
    this->R=R;
    this->G=G;
    this->B=B;
    this->W=W;
  }
};
Color Red(255,0,0);
Color Blue(0,0,255);
Color Green(0,255,0);
Color Orange(255,127,0);
Color White(0,0,0,255);



int pwmModuleCount = 0;
/* Allocate PWM instances. */
static nrfx_pwm_t nordic_nrf5_pwm_instance[] = {
#if NRFX_PWM0_ENABLED
    NRFX_PWM_INSTANCE(0),
#endif
#if NRFX_PWM1_ENABLED
    NRFX_PWM_INSTANCE(1),
#endif
#if NRFX_PWM2_ENABLED
    NRFX_PWM_INSTANCE(2),
#endif
#if NRFX_PWM3_ENABLED
    NRFX_PWM_INSTANCE(3),
#endif
};


class RGBW{
  private:
   Output R;
   Output G;
   Output B;
   Output W;
   int count;
   int brakeCount;

   
   nrfx_pwm_config_t config;
   int pwmUnit;
   nrf_pwm_sequence_t sequence;
   nrf_pwm_values_individual_t seq_values;
  public:
   bool Blink;
   RGBW(int RPin,int GPin, int BPin,int WPin): R(RPin), G(GPin),B(BPin),W(WPin){
     count =0; 

        config = NRFX_PWM_DEFAULT_CONFIG;
        config.output_pins[0]  = digitalPinToPinName(RPin);
        config.output_pins[1]  = digitalPinToPinName(GPin);
        config.output_pins[2]  = digitalPinToPinName(BPin);
        config.output_pins[3]  = digitalPinToPinName(WPin);
        config.top_value    = 256;
        config.load_mode    = NRF_PWM_LOAD_INDIVIDUAL;  
           
         pwmUnit = pwmModuleCount++;
         
        uint32_t err_code = nrfx_pwm_init(&nordic_nrf5_pwm_instance[pwmUnit],
                                      &config,
                                      NULL);
        APP_ERROR_CHECK(err_code);
   }
   void SetRGBW(int R,int G,int B, int W,int Intensity = 100){
      this->R.value = (R * Intensity)/100;
      this->G.value = (G * Intensity)/100;
      this->B.value = (B * Intensity)/100;
      this->W.value = (W * Intensity)/100;

      On();

   }

   void SetRGBW(Color color,int intensity = 100){
     SetRGBW(color.R,color.G,color.B,0,intensity);
   }

   void On(){
      R.On();
      G.On();
      B.On();
      W.On();
      Display();
   }

   void Off(){
      R.Off();
      G.Off();
      B.Off();
      W.Off();
      Display();
   }

   void Switch(){
      R.Switch();
      G.Switch();
      B.Switch();
      W.Switch();
      Display();
    }
   
   void Display(){
    seq_values.channel_0 = R.value;//(255 - R.value) / 4;
    seq_values.channel_1 = G.value;//(255 - R.value) / 4;
    seq_values.channel_2 = B.value;//(255 - R.value) / 4;
    seq_values.channel_3 = W.value;//(255 - R.value) / 4;

    sequence.values.p_individual = &seq_values;
    sequence.length = NRF_PWM_VALUES_LENGTH(seq_values);
    sequence.repeats = 1;
    sequence.end_delay = 0;

     nrfx_pwm_simple_playback(&nordic_nrf5_pwm_instance[pwmUnit], &sequence, 1, NRFX_PWM_FLAG_LOOP);
    // R.Display();
    // G.Display();
    // B.Display();
    // W.Display();
   }

    void StartBrake(Nano33BLEAccelerometerData accelerometerData,int delay = 10){
      if(!Blink){
           brakeCount = 1000/delay;
           int intensity = (accelerometerData.y * -50) + 50;
           if(intensity > 100) intensity = 100;
           if(intensity < 50) intensity = 50;
           SetRGBW(Red,intensity);
       }
    }

    void EndBrake(){
      SetRGBW(Red,50);
    }

  //Gets called  at each loop turn
   void Refresh(int delay = 10){
    count++;
    count = count % 1000000;
 
    if(brakeCount > 0){
      brakeCount--;
      if(brakeCount==0){
          EndBrake();
       }  
    }
        
     if(count % (500/delay) == 0 && Blink) {
        Switch();
      } 

      if(count % (5000/delay) == 0) {
        Debug();
      } 
   }

  void Debug(){
        
        Serial.printf("Count:[%i]  - Blink: [%s]\n",count,Blink ? "true" : "false");
        Serial.printf("R: ");
        R.Debug();
        Serial.printf("G: ");
        G.Debug();
        Serial.printf("B: ");
        B.Debug();
        Serial.printf("W: ");
        W.Debug();
        
        

    }
   
};

RGBW Left(3,4,5,2);
RGBW Right(7,8,9,6);

// the setup routine runs once when you press reset:
void setup() {
   Serial.begin(4800);

   Right.SetRGBW(Red,50);
   Right.Blink= true;
   

  Left.SetRGBW(Red,50);
  Left.Blink= true;


}

int loopCount =0;
 bool connected = false;
// the loop routine runs over and over again forever:
void loop() {

  loopCount = loopCount % (60 *100);
  loopCount++;

  
  Right.Refresh();
  Left.Refresh();
 
  delay(10);
}

I guess my question wasnt clear I am going to try to be more specific:
I instantialize my pwn as such

nrfx_pwm_config_t config;
config = NRFX_PWM_DEFAULT_CONFIG;

          config.output_pins[0]  = digitalPinToPinName(RPin);
          config.output_pins[1]  = digitalPinToPinName(GPin);
          config.output_pins[2]  = digitalPinToPinName(BPin);
          config.output_pins[3]  = digitalPinToPinName(WPin);
          config.top_value    = 256;
         // config.load_mode    = NRF_PWM_LOAD_INDIVIDUAL;  
          config.irq_priority = PWM_DEFAULT_CONFIG_IRQ_PRIORITY,
          config.base_clock   = NRF_PWM_CLK_1MHz,
         config.count_mode   = NRF_PWM_MODE_UP,
         
        config.load_mode    = NRF_PWM_LOAD_COMMON,
        config.step_mode    = NRF_PWM_STEP_AUTO,
             
           pwmUnit = pwmModuleCount++;
           
          uint32_t err_code = nrfx_pwm_init(&nordic_nrf5_pwm_instance[pwmUnit],
                                        &config,
                                        NULL);

And then try to lit it as such :

       nrf_pwm_sequence_t sequence;
       nrf_pwm_values_individual_t seq_values;
      seq_values.channel_0 = R.value;
      seq_values.channel_1 = G.value;
      seq_values.channel_2 = B.value;
      seq_values.channel_3 = W.value;
      sequence.values.p_individual = &seq_values;
      sequence.length = NRF_PWM_VALUES_LENGTH(seq_values);
      sequence.repeats = 1;
      sequence.end_delay = 0;
  
       nrfx_pwm_simple_playback(&nordic_nrf5_pwm_instance[pwmUnit], &sequence, 1, NRFX_PWM_FLAG_LOOP);

But unfortunately it doesnt work and I have no idea why, if someone has any Idea I will be very grateful :slight_smile:

After much digging I found that : nrf_pwm_sequence_t Struct Reference

nrf_pwm_values_t values Pointer to an array with duty cycle values. This array must be in Data RAM.

I believe this might be my issue. The way I wrote my code I think my values arent going to Data RAM ( the 8kb of SRAM on my uno ? )

Everything you declare is in ram. there is no 'const' or 'PROGMEM' so what is the issue?

blh64:
Everything you declare is in ram. there is no 'const' or 'PROGMEM' so what is the issue?

I don't know, I'm brand new to this and this was my only lead.
But I've found this bit of code : example from someone who seems to know a lot more about what he's doing than myself who states :

// This array cannot be allocated on stack (hence "static") and it must // be in RAM (hence no "const", though its content is not changed). static uint16_t /const/ seq_values[]

I'll do some test ASAP and will post here if I manage to find out whats my problem is

The Arduino Nano 33 BLE specifications say that all 14 digital I/O pins support PWM. Have you tried analogWrite()?

johnwasser:
The Arduino Nano 33 BLE specifications say that all 14 digital I/O pins support PWM. Have you tried analogWrite()?

It does support PWM on all pins but not at the same time, if you try to analogwrite on more than 4pins the nano crash (orange light) my initial design used it and worked prefectly untill I added a new RGBW class :frowning:
If I understood properly the microcontroller only has 4 "logical" ? units that can manage up to 4 channels. Thus I can use 4x4 pwm.

I have narrowed my Issue : The problem seems to be where I declare my class instance the following code work with
TestClass Test; Inside the setup, but if I declare it outside (I want to use it in setup and loop, it doesnt work anymore would anyone have an Idea about why ?

#include <stdio.h>
#include <string.h>
#include "nrfx_pwm.h"



static nrf_pwm_values_individual_t seq_values[4];

 static nrfx_pwm_t nordic_nrf5_pwm_instance[] = {
      NRFX_PWM_INSTANCE(0),
      NRFX_PWM_INSTANCE(1),
      NRFX_PWM_INSTANCE(2),
      NRFX_PWM_INSTANCE(3),
  };


class TestClass{
  public:
  nrf_pwm_sequence_t sequence;
  nrfx_pwm_config_t config;

  TestClass(){
          config = NRFX_PWM_DEFAULT_CONFIG;
          config.output_pins[0]  = digitalPinToPinName(3);
          config.output_pins[1]  = digitalPinToPinName(4);
          config.output_pins[2]  = digitalPinToPinName(5);
          config.output_pins[3]  = digitalPinToPinName(2);
          config.top_value    = 256;
        
         config.irq_priority = PWM_DEFAULT_CONFIG_IRQ_PRIORITY;
         config.base_clock   = NRF_PWM_CLK_1MHz;
         config.count_mode   = NRF_PWM_MODE_UP;
         
         config.load_mode    = NRF_PWM_LOAD_INDIVIDUAL;
         config.step_mode    = NRF_PWM_STEP_AUTO;

          APP_ERROR_CHECK(nrfx_pwm_init(&nordic_nrf5_pwm_instance[0], &config,NULL));
    }

    void Display(){
        seq_values[0].channel_0 = 0;
        seq_values[0].channel_1 = 255;
        seq_values[0].channel_2 = 255;
        seq_values[0].channel_3 = 255;
        sequence.values.p_individual = &seq_values[0];
        sequence.length = NRF_PWM_VALUES_LENGTH(seq_values[0]);
        sequence.repeats = 0;
        sequence.end_delay = 0;

        (void)nrfx_pwm_simple_playback(&nordic_nrf5_pwm_instance[0], &sequence, 1,
                                      NRFX_PWM_FLAG_LOOP);
      
      }
  
 };


void setup() {
  
  TestClass test;

  test.Display();
              
}

void loop() {
  // put your main code here, to run repeatedly:

}

Don't do hardware-related stuff inside of constructors. Create a separate begin method that you call from the setup and that actually calls the nrfx_pwm_init function.

Pieter

PieterP:
Don't do hardware-related stuff inside of constructors. Create a separate begin method that you call from the setup and that actually calls the nrfx_pwm_init function.

Pieter

THIS ! .. Thanks a lot :blush: , if I move the code in the constructor to a method in the init everything works fine... Coding for hardware is a strange thing, could you point me to a ressource I could read that would explain why I shouldnt have do that ?

It's simply that the Arduino runs code at startup to configure the hardware. Because of the way C++ works, constructors run before that, which means you're trying to do something too early.

You can probably find a tutorial that explains what a C++ program runs and in which order as it starts.

This topic was automatically closed 120 days after the last reply. New replies are no longer allowed.