Go Down

Topic: Simple bit angle modulation for 6 leds (Read 476 times) previous topic - next topic

dparson

Hi all, I've been having a blast learning how to do bit angle modulation and wrote some code which works great adjusting the brightness of 1 led but it is not working so well when I extended it to support more.

My first attempt controlling the pin 13 led is below and it works great, adjusting the brightness up and down with 4bit precision (0 - 15 brightness levels where 0 is completely off).  It took me awhile to write and understand so it has a lot of comments describing exactly what is happening.

Code: [Select]

#define PIN_LED 13

byte _bitMask;
byte _ledBrightness;
byte _increaseBrightness = true;
unsigned long _currentMillis, _lastUpdateMillis;

void setup() {
  pinMode(PIN_LED, OUTPUT);
}

void loop() {
 
  _currentMillis = millis();
 
  // Once every 250 milliseconds, change the led brightness.
  // It starts at 0 and goes up to 15, then back down to 0, and repeats.
  if (_currentMillis - _lastUpdateMillis > 249) {
    _lastUpdateMillis = _currentMillis;
       
    if      (_ledBrightness == 0)  _increaseBrightness = true;
    else if (_ledBrightness == 15) _increaseBrightness = false;
   
    if (_increaseBrightness)
      _ledBrightness++;
    else
      _ledBrightness--;
  }
 
  bitAngleModulationHandler();
}

void bitAngleModulationHandler() {
  _bitMask++;
  if (_bitMask > B1111) _bitMask = B0001; // Ensure mask goes from 1 to 15
 
                                                // if led brightness = 10 (binary B1010)
  if      (_bitMask == B0001) { updateLeds(); } // cycle  1 led off
  else if (_bitMask == B0010) { updateLeds(); } // cycle  2 led on
                    // B0011                       cycle  3 led unchanged, still on
  else if (_bitMask == B0100) { updateLeds(); } // cycle  4 led off
                    // B0101                       cycle  5 led unchanged, still off
                    // B0110                       cycle  6 led unchanged, still off
                    // B0111                       cycle  7 led unchanged, still off
  else if (_bitMask == B1000) { updateLeds(); } // cycle  8 led on
                    // B1001                       cycle  9 led unchanged, still on
                    // B1010                       cycle 10 led unchanged, still on
                    // B1011                       cycle 11 led unchanged, still on
                    // B1100                       cycle 12 led unchanged, still on
                    // B1101                       cycle 13 led unchanged, still on
                    // B1110                       cycle 14 led unchanged, still on
                    // B1111                       cycle 15 led unchanged, still on
                   
                    // So out of the available 15 cycles, a brightness of 10 sets the
                    // led to be on for 10 of them.
}

void updateLeds() {
  // if _bitMask = 0001 and _ledBrightness = 1010 (the number 10)
  // bitwise and = (0001 & 1010) = (0000) > 0 = false             
  bool isEnabled = (_bitMask & _ledBrightness) > 0;
  digitalWrite(PIN_LED, isEnabled);
}


After finally getting this to work I thought I had it all figured out and extended it to support an array of led's, but the brightness levels no longer smoothly control the leds, instead their brightness hop around a bit as they go up and down the 0-15 brightness scale.  I thought maybe it was a refresh rate problem but it's refreshing over 2500 Hz (I serial print this), so it must be something else.  I've hit a wall and wondered if a second pair of eyes might see what I'm doing wrong.  The circuit to test this is simple, just hooking up leds to pins 5-10.  So if you're interested, any help would be appreciated.  If you hook it up you should see the brightness start at 0 on all 6 leds, but the brightness will go up a couple notches, then go down 1, then go up a few more, then down 1, and so on.  Same on the way back down, it just won't smoothly adjust the brightness up and down anymore.

Code: [Select]

// Each element in the array follows the format { PIN, BRIGHTNESS, BRIGHTNESS_DIRECTION }
// PIN is the Arduino digital pin number
// BRIGHTNESS is 0-15
// BRIGHTNESS_DIRECTION is 0 or 1, where 1 means we should increase the brightness
byte _ledArray[][3] = {
  { 5, 0, 1 },
  { 6, 0, 1 },
  { 7, 0, 1 },
  { 8, 0, 1 },
  { 9, 0, 1 },
  { 10, 0, 1 }
};

byte _ledCount;
byte _bitMask;
unsigned long _currentMillis, _lastUpdateMillis;
unsigned long _refreshRate;
unsigned long _lastRefreshRateMillis;

void setup() {   
  Serial.begin(115200);
 
  // Determine number of led's in the array.
  _ledCount = sizeof(_ledArray) / 3; // each array element is 3 bytes
 
  // Set all the led pins to be outputs.
  for (byte led = 0; led < _ledCount; led++) {
    pinMode(_ledArray[led][0], OUTPUT); // Array index 0 = the Arduino pin number.
  }
}

void loop() {
 
  _currentMillis = millis();
 
  // Once every 250 milliseconds.
  if (_currentMillis - _lastUpdateMillis > 249) {
    _lastUpdateMillis = _currentMillis;
   
    // Loop through the led's, either increasing or decreasing
    // their brightness setting.
    for (byte led = 0; led < _ledCount; led++) {
      byte ledBrightness = _ledArray[led][1];
      byte ledBrightnessDirection = _ledArray[led][2];

      if      (ledBrightness == 0)  ledBrightnessDirection = 1;
      else if (ledBrightness == 15) ledBrightnessDirection = 0;
     
      if (ledBrightnessDirection)
        ledBrightness++;
      else
        ledBrightness--;
     
      _ledArray[led][1] = ledBrightness;
      _ledArray[led][2] = ledBrightnessDirection;
    }
  }
 
  // Switch led's on/off based on their brightness.
  bitAngleModulationHandler();
 
  // Keep track of the refresh rate and show it once every second.
  // For an array of 6 led's, refresh rate is around 2560 Hz.
  _refreshRate++;
  if (_currentMillis - _lastRefreshRateMillis > 999) {
    _lastRefreshRateMillis = _currentMillis;
    Serial.println(_refreshRate / 16); // 16 cycles to perform a full refresh
    _refreshRate = 0;
  }
}

void bitAngleModulationHandler() {
  _bitMask++;
  if (_bitMask > B1111) _bitMask = B0001; // Ensure mask goes from 1 to 15
 
  if      (_bitMask == B0001) { updateLeds(); }
  else if (_bitMask == B0010) { updateLeds(); }
  else if (_bitMask == B0100) { updateLeds(); }
  else if (_bitMask == B1000) { updateLeds(); }
}

void updateLeds() {
  for (byte led = 0; led < _ledCount; led++) {   
    digitalWrite(_ledArray[led][0], (_bitMask & _ledArray[led][1]) > 0);
  }
}

LarryD

Always blame yourself.

dparson

Yes thanks, I'm just working to learn the bit angle modulation technique as well.

Grumpy_Mike

Quote
// Once every 250 milliseconds,
is not a refresh rate of "over 2500 Hz"
In fact it is a refresh rate of 4Hz.

What is bit angle modulation I have never come across that. ( well I bet I have but not under that name )

That code looks like an incomprehensible mess to me, can you explain what it is supposed to do.

You need to post the code that is wrong if you want any help with it. Say what it does and say what you want it to do.

PaulRB

#4
Jun 15, 2015, 12:17 pm Last Edit: Jun 15, 2015, 12:19 pm by PaulRB
is not a refresh rate of "over 2500 Hz"
In fact it is a refresh rate of 4Hz.
Mike, can you read the code again. I'm not sure that's it. The brightness levels are adjusted at 4Hz, but the code that modulates the leds is not inside that if() statement, so I think that bit of code is "free-running" (i.e. called once each time loop() is called, and contains no delay()s), hence the OP's assertion that it is 2500Hz.

What is bit angle modulation I have never come across that. ( well I bet I have but not under that name )
Yes, I'm sure you have. The idea is, to achieve 15 brightness levels, instead of dividing each modulation period into 15 equal short periods, it is divided into 4 unequal periods of lengths t, 2t, 4t & 8t. For example, to achieve brightness level 10, conventionally an led would be lit for 10 of the 15 short periods and off for the remaining 5. With BAM, the led is on during the 2t and 8t periods and off during the t and 4t periods.

That code looks like an incomprehensible mess to me, can you explain what it is supposed to do.
I think I understand how the OP intended it to work, its not efficient but it demonstrates the principle. The benefit of BAM should be to consume fewer processor cycles so that the mcu can perform other tasks, but the OP's implementation is no more efficient than conventional software pwm at the moment.

But clearly I don't fully understand the code, because at the moment I can't spot the problem (as described in the OP)!

Hackscribble

... so I think that bit of code is "free-running" (i.e. called once each time loop() is called, and contains no delay()s), hence the OP's assertion that it is 2500Hz.
Building on what Paul said, since it free runs, I'm not sure that each of the 15 periods will be the same duration.  There is more code to execute when it's the period to set the LEDs than in the periods when the count is just being incremented.

@dparson, maybe you could try adding another timer so that the modulation handler is called at a regular interval.

Regards

Ray

Hackscribble.  Writing about making things.
arduino@hackscribble.com | www.hackscribble.com

PaulRB

There is more code to execute when it's the period to set the LEDs than in the periods when the count is just being incremented.
Think you may have spotted it there, Ray!

Perhaps adding a delayMicroseconds(200) at the end of loop() would prove it one way of the other.

dparson

#7
Jun 16, 2015, 12:32 pm Last Edit: Jun 16, 2015, 12:54 pm by dparson Reason: Added link tags to the video
Thanks everyone for taking a look at this effort.  I haven't seen an implementation of BAM that does not involve timers or shift registers, hopefully this will be a worthwhile effort for Arduino if it can be made to work :)

Quote from: Grumpy_Mike
What is bit angle modulation I have never come across that. ( well I bet I have but not under that name )
It's a programming technique that can be used to adjust the brightness of led's on pins that do not have hardware PWM support, and is relatively efficient with the micro controller's resources.  I learned about it from this RGB led cube video https://www.youtube.com/watch?v=I0sgqgUwIAQ and the presenter does a great job of showing what it is, visually.  His example code uses timers and shift registers so I didn't quite grasp how to implement BAM myself, and is why I'm trying to do it here.

Quote from: Grumpy_Mike
That code looks like an incomprehensible mess to me, can you explain what it is supposed to do.
Sure I'll try.  Looking at the first code example, you can ignore the brightness level logic in the main loop and instead just notice that the main loop runs a function called bitAngleModulationHandler() over and over, as fast as it can.  This function increments a variable named _bitMask from 1 to 15, then repeats, so _bitMask is just a counter.  When the count is equal to 4 special cases (1, 2, 4, 8 aka 0001, 0010, 0100, 1000) a little extra work is performed:  the function updateLeds() is called.  When this function is called the counter, _bitMask, is bitwise 'anded' with a variable that stores the brightness level that you want the led to have.  If the bitwise 'and' results in a number greater than 0, then the led is turned on.  This brightness variable can be 0 to 15 and the result of all this is the higher the number, the more often the led will be turned on.  In the code I showed how if the brightness = 10, then the led will be turned on when _bitMask is equal to 2 and 3, and then 8 through 15.

Back to the brightness level logic in the main loop, if I were to change it from 0-15 during every loop iteration, then the rate of change would be too quick and I could not see the different brightness levels.  So instead I put in an if() statement to only change the brightness of the led once every 250 milliseconds.  In the end I have the led at let's say half brightness for 1/4 second, then the next 1/4 second it's a slightly higher brightness, and so on.  You must do this with PWM as well or you'll never be able to see the different brightness levels of the leds.  The calls to digitalWrite are not slowed down by this if() statement, only the changes to the variable that stores the brightness.  So that's why the led refresh rate is very fast, it's the rate the code is changing the brightness level variable that is slow, 4 Hz as you mentioned.

In the second example I just tried to extend things so that multiple led's could be supported.  I made an array to hold 6 Arduino pin numbers and then loop through them, adjusting their brightness.  Besides that the bitAngleModulationHandler() and updateLeds() functions are the same, but for some reason the brightness of the led's is not changing smoothly, as I turn up the brightness from 0 to 3 the apparent brightness of the led goes up, but then from 3 to 4 it goes down, and then from 4 to 7 it goes up again, then 8 it goes down.  It's broken in some way.  Interestingly enough it works if I only put 1 Arduino pin number in the array, but with 2 or more this bug becomes apparent.

Quote from: PaulRB
I think I understand how the OP intended it to work, its not efficient but it demonstrates the principle. The benefit of BAM should be to consume fewer processor cycles so that the mcu can perform other tasks, but the OP's implementation is no more efficient than conventional software pwm at the moment.
I was thinking it was a little more efficient since the code only updates the led's 4 times:  when _bitMask = 0001, 0010, 0100, and 1000.  With software PWM wouldn't you do the digitalWrite 15 times for this 4bit resolution?  But I definitely know what you mean, the counter still has to increment every time.  I didn't see a way around this in software though until you mentioned using delayMicroseconds() and that gives me an idea.

Quote from: Hackscribble
maybe you could try adding another timer so that the modulation handler is called at a regular interval.
I tried calling updateLeds() during each count (basically removed the 4 if statements) so the same amount of time would pass for each increment of the _bitMask, but the result was the same.  Did this end up doing what you suggested? 

I tried another rewrite, simplifying how the brightness of each pin is stored and removed the bitwise 'and' in favor of the BitRead helper function, but it acts exactly the same as before.  Maybe it's a little more clear though and easier to spot the mistake.

Code: [Select]

// Arduino digital pins.
byte _ledArray[] = { 5, 6, 7, 8, 9, 10 };

byte _ledCount;
byte _bamCount = 0;
byte _ledBrightness = 0;
unsigned long _currentMillis, _lastUpdateMillis;

void setup() {  
  // Determine number of led's in the array.
  _ledCount = sizeof(_ledArray);
  
  // Set all the led pins to be outputs.
  for (byte led = 0; led < _ledCount; led++) {
    pinMode(_ledArray[led], OUTPUT);
  }  
}

void loop() {
  
  _currentMillis = millis();
  
  // Once every 250 milliseconds, increment the led brightness.
  // It cannot go over 15 so when it gets that bright, change it back to 0.
  if (_currentMillis - _lastUpdateMillis > 249) {
    _lastUpdateMillis = _currentMillis;
    _ledBrightness++;
    if (_ledBrightness > 15) _ledBrightness = 0;
  }
  
  // Switch led's on/off based on their brightness.
  bitAngleModulationHandler();
}

void bitAngleModulationHandler() {
  _bamCount++;
  if (_bamCount > 15) _bamCount = 1; // Ensure counter goes from 1-15.
  
  if      (_bamCount == B0001) { updateLeds(0); }
  else if (_bamCount == B0010) { updateLeds(1); }
  else if (_bamCount == B0100) { updateLeds(2); }
  else if (_bamCount == B1000) { updateLeds(3); }
}

void updateLeds(byte bitPosition) {
  for (byte led = 0; led < _ledCount; led++) {  
    byte pin = _ledArray[led];
    bool isEnabled = bitRead(_ledBrightness, bitPosition);
    digitalWrite(pin, isEnabled);
  }
}


With everyone's helpful thoughts and questions I got the idea to use micros() to implement the 4 on and off times.  I'll be able to check the micros() time and delay 4 different intervals for the 4 different bits.  It seems like it will be less efficient than the counting approach but since I can't figure out the bug, will give this a shot and report back tomorrow.

Hackscribble

Could you try making these additions to your latest code?  Running on a Uno and just watching the onboard LED (but with 6 LED pins defined in the array), this seems to give a smooth transition.  But you may get different results.

Code: [Select]
#define PWM_DELAY 1000UL // microseconds
unsigned long _currentMicros, _lastUpdateMicros;

// change end of loop() to this ...
  _currentMicros = micros();
  if (_currentMicros - _lastUpdateMicros > PWM_DELAY)
  {
    _lastUpdateMicros = _currentMicros;
    bitAngleModulationHandler();
  }
Hackscribble.  Writing about making things.
arduino@hackscribble.com | www.hackscribble.com

dparson

#9
Jun 17, 2015, 05:53 am Last Edit: Jun 17, 2015, 06:12 am by dparson
Quote from: Hackscribble
Could you try making these additions to your latest code?  Running on a Uno and just watching the onboard LED (but with 6 LED pins defined in the array), this seems to give a smooth transition.  But you may get different results.
Thank you for finding the bug!  So letting the microcontroller modulate as fast as possible resulted in jitter, it would not refresh the led brightness (count from 1-15 and do the digital writes) consistently enough for a smooth brightness change for every available brightness value.   Maybe the timer 0 interrupt Arduino sets up is the cause if any of this is correct?  https://www.youtube.com/watch?v=U7I0GkwW1yE  Maybe by slowing the modulation down and ensuring it starts at a consistent interval makes any jitter much smaller.

To show off the BAM, this code creates a pulsing led effect on 12 leds at once...leds on pins 2-13.  It works perfectly and is an implementation of bam without the use of timers or shift registers.  So it's a pure software based approach.

Code: [Select]

// Each element in the array follows the format { PIN, BRIGHTNESS, BRIGHTNESS_DIRECTION }
// PIN is the Arduino digital pin number
// BRIGHTNESS is 0-15
// BRIGHTNESS_DIRECTION is 0 or 1, where 1 means we should increase the brightness
byte _ledArray[][3] = {
  { 2, 0, 1 },
  { 3, 0, 1 },
  { 4, 0, 1 },
  { 5, 0, 1 },
  { 6, 0, 1 },
  { 7, 0, 1 },
  { 8, 0, 1 },
  { 9, 0, 1 },
  { 10, 0, 1 },
  { 11, 0, 1 },
  { 12, 0, 1 },
  { 13, 0, 1 }
};

byte _ledCount;
byte _bamCounter;
unsigned long _currentMillis, _lastUpdateMillis;
unsigned long _currentMicros, _lastUpdateMicros;

void setup() {
  
  // Determine number of led's in the array.
  _ledCount = sizeof(_ledArray) / 3; // each array element is 3 bytes
  
  // Set all the led pins to be outputs.
  for (byte led = 0; led < _ledCount; led++) {
    pinMode(_ledArray[led][0], OUTPUT); // Array index 0 = the Arduino pin number.
    _ledArray[led][1] = random(16);     // Set a random brightness.
    _ledArray[led][2] = random(2);      // Set a random direction.
  }
}

void loop() {
  
  // Once every 30 milliseconds update the brightness of each led, creating a pulsing effect
  // by raising and lowering the brightness of each led.
  _currentMillis = millis();
  if (_currentMillis - _lastUpdateMillis > 29) {
    _lastUpdateMillis = _currentMillis;

    for (byte led = 0; led < _ledCount; led++) {
      byte ledBrightness = _ledArray[led][1];
      byte ledBrightnessDirection = _ledArray[led][2];

      if      (ledBrightness == 0)  ledBrightnessDirection = 1;
      else if (ledBrightness == 15) ledBrightnessDirection = 0;
      
      if (ledBrightnessDirection)
        ledBrightness++;
      else
        ledBrightness--;
      
      _ledArray[led][1] = ledBrightness;
      _ledArray[led][2] = ledBrightnessDirection;
    }
  }
  
  // Rather than using a timer to run the modulation at a regular interval, here's
  // a simple approach.  Many thanks to Hackscribble and PaulRB from the Arduino forums
  // for this.  You've got to ensure the modulation is run at a regular interval for
  // smooth brightness changes.  700 is the longest delay that still produced a
  // smoothly changing brightness to my eye.  The benefit of a longer delay is the
  // microcontroller has more time for other things, but too long of a delay causes flicker
  // since the refresh rate is too slow.
  _currentMicros = micros();
  if (_currentMicros - _lastUpdateMicros > 700) {
    _lastUpdateMicros = _currentMicros;
    bitAngleModulationHandler();
  }
}

void bitAngleModulationHandler() {
  _bamCounter++;
  if (_bamCounter > B1111) _bamCounter = B0001; // Ensure count goes from 1 to 15
  
  if      (_bamCounter == B0001) { updateLeds(0); }
  else if (_bamCounter == B0010) { updateLeds(1); }
  else if (_bamCounter == B0100) { updateLeds(2); }
  else if (_bamCounter == B1000) { updateLeds(3); }
}

void updateLeds(byte bitPosition) {
  for (byte led = 0; led < _ledCount; led++) {
    byte pin = _ledArray[led][0];
    byte brightness = _ledArray[led][1];
    byte isEnabled = bitRead(brightness, bitPosition);
    digitalWrite(pin, isEnabled);
  }
}

spandit

Thanks for posting this - I had been working on a project to flicker 12 x 12V LED strips (connected via Darlington transistors to a lead acid battery). Had been trying to use a soft PWM solution as didn't have any shift registers at the time (and still not sure how to code PWM for them) but on anything over 6 LEDs, I think it ran out of processing power and it stopped working.

Using your code allowed me to ditch all the external libraries/routines and it's easy for the Nano to handle. I now have 12 flickering candles that can also be programmed as chase lights or something else (they're in a circle 10m across, connected using CAT5 and a lot of hot glue :D :D)

Go Up
 


Please enter a valid email to subscribe

Confirm your email address

We need to confirm your email address.
To complete the subscription, please click the link in the email we just sent you.

Thank you for subscribing!

Arduino
via Egeo 16
Torino, 10131
Italy