Bit Angle Modulation

Dec 29, 2019

To control the brightness of an LED with a microcontroller, PWM is probably the most obvious solution.

Most microcontrollers already have dedicated hardware to generate the PWM-Signal without CPU interaction. But what if you like to control more LEDs than your microcontroller has PWM channels? Of cores, you can implement software-based PWM. However, this can be a processing-intensive task.

Maybe there is another way...

Bit Angle Modulation

Pulse Width Modulation (PWM) and Bit Angle Modulation (BAM) use the same principle of varying the turn-on/turn-off time of the LED and thereby create the illusion of a change in brightness.

Let's say we want do dim an LED with an 8-bit resolution. First, we need to assign a "weight" to each of the 8-bit.

So we assign "1" to bit number 0 and double the value for each of the following bits.

For better visualization, I have put  the values of each bit in an accurate scale diagram:

You may think now: "This is the binary system. What has this to do with PWM or BAM?"

Binary and digital outputs both can have one of two states at a time. 1 or 0 High or Low. The position and thereby the "weight" of a bit can be directly translated to an on-time/off-time relationship.

To make thins a bit clearer, let's take the graph from above and rearrange it a little bit:

Now we have all the bits, represented as the weighted value, plotted after each other.

Practical Example:

Let's say we ha a LED that we want to dim one led with an 8-bit resolution and  3.922kHz (255us period) frequency.

The current dim value is 168. The on/off time is 168us on and 87us off. This will result in a duty cycle of approximately 66%.

Using Pulse Width Modulation, this would mean turning the LED on for 168us and then turning it off for 87us.

Using Bit Angle Modulation now, the LED would be turned off for the first 7us (1us + 2us +4 us), then turned on for 8us, turned off again for 16us, tuned on for 32us, off for 64is and finally on for 128us. The accumulated on-time per period is still 168us, and the off-time is 87us.

 

 

Both PWM and BAM have the same on/off time. However, the distribution of the on/off time in the period is different.

Interactive Demo

Use the slider below to change the value.

Bit Angle Modulation:

Your browser does not support the canvas element.

Pulse Width Modulation:

Your browser does not support the canvas element.

Value:

Ok, so ...?

You may think now: "Ok. That's nice and all, but why exactly would I want to use this instead of PWM?"

Advantages:

BAM only requires one update cycle per bit of resolution. For an 8-Bit range, only eight updates per period are necessary, while PWM would require 256. So your code for updating the output only needs to run eight times instead of 256 times.

This also implies that you only have to write to your output register a maximum of 8 times instead of a maximum of 256 times.

If your LEDs are not directly connected to the microcontroller but driven over a shift-register or port-expander, this is especially beneficial.

Disadvantages:

The full range of a dimmer channel must be a power of two. For example, 0-127 or 0-255 is possible, but 0-100 is not.

Properly triggering on a BAM signal with an oscilloscope is impossible because there is no stable edge in the signal. This makes debugging difficult.

Limitations:

The shorts on time is still the same as with PWM. Therefore, the same limitations in terms of frequency and resolution as for PWM apply.

If the dim value crosses from the lower half to the upper half of its range, it will result in LED flickering. However, Mirror Image Bit Angle Modulation (MIBAM) solves this problem.

Implementation Example

This demo controls the brightness of eight LEDs on PORT-A of an Atmel ATmega2560.

BAM-Demo.c

/***********************************************************************
 *
 *  DATE        : 24.01.2016
 *  Author:		: Christian Marty
 *
 ***********************************************************************/
#include <avr/io.h>
#include <avr/interrupt.h>

void initTimer2(void);
void updateBam(void);

uint8_t LED_dimmwerte[8] = {1,2,4,8,16,32,64,128}; // Set dimming values for LEDs here
	
volatile uint8_t bamBitTime = 0;	
volatile uint8_t bamPortOutput;
volatile uint8_t bamPosition = 0;

int main(void)
{
	DDRA = 0xFF;	// LED-Port as output
	sei();			// Enable Interrupts
	
	initTimer2();	// Initialize BAM timer

	while(1)
    {
		// Run you app here
    }
}

//--------------------------------------------------------------------------------------------
// Initialize Timer 2 for BAM
//--------------------------------------------------------------------------------------------
void initTimer2(void)
{
	TCCR2A = 0b00000000; // Timer Mode Setting -> Normal Mode
	TCCR2B = 0b00000110; // Prescaler 256 -> 16Mhz / 256 = 62.5kHz 
	TIMSK2 = 0b00000001; // Enable Timer Overflow Interrupt
}

//--------------------------------------------------------------------------------------------
// Timer Interrupt Handler
//--------------------------------------------------------------------------------------------
ISR(TIMER2_OVF_vect)
{
	TCNT2 = bamBitTime;		// Set time until next update
	
	PORTA = bamPortOutput;	// Set Output
	
	updateBam();			// Calculate values for next update
}

//--------------------------------------------------------------------------------------------
// Calculate the data for the next BAM Cycle
//--------------------------------------------------------------------------------------------
void updateBam(void)
{
	// Calculate the Output for LED-PORT
	bamPortOutput = 0;
	for(uint8_t i= 0; i<8; i++)
	{
		bamPortOutput = (bamPortOutput <<1);
		bamPortOutput |= ((LED_dimmwerte[i] >> bamPosition) & 0x01);
	}
	
	// Calculate length of the next BAM Cycle
	bamBitTime = (0xFF << bamPosition+1); 
	
	// Calculate the position of the next BAM Cycle
	bamPosition ++;
	if(bamPosition >= 8) bamPosition = 0;
}