Saturday, January 23, 2021

Binary Code Modulation (BCM) aka Bit Angle Modulation (BAM) library for controlling led brightness

Binary Code Modulation (BCM) it's an amazing method of controlling led brightness and was invented by Artistic Licence. It's like PWM but not really. The main advantage over PWM is the low CPU usage regardless of how many leds it controls.

This library provides a fast implementation of Binary Code Modulation useful for controlling RGB leds and dimming multiple leds for creating animations like led cubes. A complete cycle takes only 8 timer interrupts and each interrupt takes only 4us on a 8MHz CPU. The leds can be on different ports.

Bit Code Modulation (BCM) aka Bit Angle Modulation (BAM) library for RGB led dimming - 8-bit

How Binary Code Modulation (BCM) works and how it differs from PWM

To dim a led with PMW is simple. If you want the led to be half as bright you turn the led on for 50% of the cycle and 50% for the other half. Or 20% on and 80% off for an even dimmer led.

 

PWM example

Bit Angle Modulation uses the weight of each bit in a binary number. For example in one byte there are 8 bits with numbers from 0 to 7. Bit 0 is called the Least Significant Bit (LSB) and it's weight is 1. Next bit 1 has a weight of 2, bit 2 has a weight of 4, then 8, 16, 32, 64 and 128. Bit 7 is called the Most Significant Bit (MSB) because it has the highest weight - 128.

8-bit binary weight

With 8-bit BCM resolution there are 256 levels of brightness because 2^8 = 256 from 0 to 255. Number "0" means 0% duty cycle, 255 represents 100% duty cycle and 128 is 50% duty cycle (256 * 0.5).

To convert from a duty cycle percentage to a BCM number multiply 256 with the percentage. E.g:

30% duty cycle: 256 * 0.3 = 77

To convert from a BCM number to a percentage divide the number by 256. E.g:

77 / 256 = 0.3 (30%) duty cycle

Example of dimming a led with the Binary Code Modulation method with 30% duty cycle

77 in binary is 0b01001101 and represents 30% duty cycle with the BCM method. The microcontroller is clocked at 8MHz and a timer is set in CTC mode with 256 prescaler. The led refresh rate will be 122Hz and the timer interrupt will trigger 8 times in one cycle, 1 time for each binary bit. One cycle is 1 / 122Hz = 8.2ms. The interrupt takes only 4us to run the led dimming code regardless of how many leds there are. Hope you can already see the benefits of BCM and how less CPU time it takes to control 8, 16 or even 32 leds. I will be talking about how to calculate all this in the how to use the library section.

Example of 77 in binary

One cycle is made of 256 ticks. 

Bit 0 has a 1 and the weight is 1 so the led will be on for 1 tick

Bit 1 has a 0 and the weight is 2 - the led will be off for 2 ticks

Bit 2 has a 1 and the weight is 4 - the led will be on for 4 ticks

Bit 3 has a 1 and the weight is 8 - the led will be on for 8 ticks

Bit 4 has a 0 and the weight is 16 - the led will be off for 16 ticks

Bit 5 has a 0 and the weight is 32 - the led will be off for 32 ticks

Bit 6 has a 1 and the weight is 64 - the led will be on for 64 ticks

Bit 7 has a 0 and the weight is 128 - the led will be off for 128 ticks

Adding ticks: 1 + 4 + 8 + 64 = 77. Here is a snapshot taken from PulseView with how the BCM pulses look like for number 77 or 30% duty cycle.

Binary Code Modulation pulses in PulseView
On channel 1 is the dimmed led and on channel 2 is the start and end of the timer interrupt for each of the 8 bits. Zooming in the interrupt was measured and it takes around 4us. Notice how the on/of time increases with each bit.

Binary Code Modulation (BCM) aka Bit Angle Modulation (BAM) library for AVR microcontrollers

Setting up the library

Selecting which timer to use: Timer 0, 1 or 2

#define MICROS_TIMER 			MICROS_TIMER0

Selecting the prescaler for the timer

#define PRESCALER 			256

The lower the prescaler the faster the timer interrupt and thus the higher refresh rate for the leds. A higher refresh rate prevents led flickering. The CPU frequency must be high enough especially if you have other interrupts. Check the following spreadsheet and select the prescaler depending on your CPU frequency. The ISR BIT 0 TRIGGER TIME is the lowest time the interrupt triggers for bit 0. So the ISR must finish the code faster than this time. You can download the spreadsheet down below and try other prescalers.

Prescaler vs CPU frequency for Binary Code Modulation (BCM)

The formula for calculating the leds refresh rate for Binary Code Modulation is 

F_CPU / prescaler / 256

Setting the ports and pins for the leds

#define LEDS_ON_PORT_B			TRUE
#define LEDS_ON_PORT_C			FALSE
#define LEDS_ON_PORT_D			FALSE
#define LEDS_ON_PORT_E			FALSE

#define LEDS_ON_PINS_PORTB		(_BV(3) | _BV(4))
#define LEDS_ON_PINS_PORTC		0
#define LEDS_ON_PINS_PORTD		0
#define LEDS_ON_PINS_PORTE		0

LEDS_ON_PORT_x - on what ports the leds are located. Place FALSE if you don't want that port used by this library or TRUE if the leds are on that port

LEDS_ON_PINS_PORTx - to what pins are the leds connected for this particular port. This is a bitmask used by the library to mask out the pins that are not used by the leds. For example if there are two leds on port B on pins 3 and 4 write (_BV(3) | _BV(4)). The order doesn't matter. Put 0 on ports that are not used.

For efficiency it is preferred for the leds to be on the same port.

Initializing the library

BCM_init()

Sets up the selected timer in CTC mode and enables global interrupts.

Encoding the Binary Code Modulation number

BCM_encode(dutyCycle[], portLetter, LEDPins[], nrOfLEDs)
Uses a global array with 8 elements - 1 for each bit - and each bit in the byte represents the state for each led. The ISR uses this encoding to control the leds.

dutyCycle[] - the duty cycle for each led from 0 to 255

portLetter - on what port are the leds located. Must be a char like so: 'B', 'C', 'D'.

LEDPins[] - the pin numbers for each led. Must be in the same order as the dutyCycle[]

nrOfLEDs - how many leds or the size for the two arrays

Binary Code Modulation timer interrupt

ISR(ISR_VECT){
	
	bitpos++;
	bitpos &= 7; // reset to 0 if > 7
	
	// Re-trigger after 1, 2, 4, 8, 16... cycles
	REG_OCR <<= 1;
	
	if(bitpos == 0){
		REG_OCR = 1;
		BCM_CYCLE_END = 1; // flag for main loop
	}
	
// Turn off then on only the led pins
#if LEDS_ON_PORT_B == TRUE
	PORTB = (PORTB & (~(LEDS_ON_PINS_PORTB))) | timesliceB[bitpos];
#endif

#if LEDS_ON_PORT_C == TRUE
	PORTC = (PORTC & (~(LEDS_ON_PINS_PORTC))) | timesliceC[bitpos];
#endif

#if LEDS_ON_PORT_D == TRUE
	PORTD = (PORTD & (~(LEDS_ON_PINS_PORTD))) | timesliceD[bitpos];
#endif

#if LEDS_ON_PORT_E == TRUE
	PORTE = (PORTE & (~(LEDS_ON_PINS_PORTE))) | timesliceE[bitpos];
#endif
}

This is the timer interrupt inside the library. Only the ports that you have selected will be included with the code. The BCM_CYCLE_END is a flag that indicates to the main loop that a new encoding can be made with a new value. Because the main loop is faster than the ISR we don't want the encode function to run pointless multiple times without the BCM completing at least 1 cycle. The main loop must clear this flag.

Example 1 - controlling the brightness of 3 leds on different ports (could be an RGB led):

#include <util/delay.h>
#include "bcm-8bit.h"

int main(void){
	
#define NR_OF_LEDS_PORTB			1
#define NR_OF_LEDS_PORTE			2

	uint8_t dutyCyclePORTB[NR_OF_LEDS_PORTB] = {77};       
	uint8_t dutyCyclePORTE[NR_OF_LEDS_PORTE] = {128, 0};  

	uint8_t const LEDPinsPORTB[NR_OF_LEDS_PORTB] = {3}; // pin PB3
	uint8_t const LEDPinsPORTE[NR_OF_LEDS_PORTE] = {4, 5}; // pins PE4, PE5
	
	// System initialization
	BCM_init();   
	
	BCM_encode(dutyCyclePORTB, 'B', LEDPinsPORTB, NR_OF_LEDS_PORTB);
	BCM_encode(dutyCyclePORTE, 'E', LEDPinsPORTE, NR_OF_LEDS_PORTE);

	while(1){
		if(BCM_CYCLE_END){
			BCM_CYCLE_END = 0;
			
			// Duty cycles for leds on port B
			dutyCyclePORTB[0]++;
			
			// Duty cycles for leds on port E
			dutyCyclePORTE[0]++; // led on pin PE4
			dutyCyclePORTE[1]++; // led on pin PE5
			
			// Encode the new duty cycles duty cycles
			BCM_encode(dutyCyclePORTB, 'B', LEDPinsPORTB, NR_OF_LEDS_PORTB);
			BCM_encode(dutyCyclePORTE, 'E', LEDPinsPORTE, NR_OF_LEDS_PORTE);
			
			// Delay so the changes will be visible to the eye
			_delay_ms(20);
		}
	}
	
	return 0;
}


Download

Version 1.0

bcm-8bit.h

Spreadsheet for prescaler selection based on CPU frequency


Monday, January 18, 2021

7 segment display library for AVR microcontrollers

There are many ways to control a seven segment display - using a dedicated IC or shift registers which are preferred because they don't require many pins. However this library is made for when you have the segments driven directly from microcontroller pins and each digit is controlled using a transistor.

You have the option of padding the numbers with zeros and displaying them at a certain position, useful for making digital clocks.

Seven segment display library for AVR microcontrollers

What is a 7-segment display

As the name suggests it is a display that is made up of 7 segments. Each segment is simply an LED. Including the dot there are actually 8 LEDs and this fits perfectly on an 8-bit microcontroller's port. This display is mainly made for numerical values but some alphabetical characters can be displayed as well.

Types of 7 segment displays

There are two types of seven segment displays - common cathode and common anode. Common cathode displays have all the ground sides (cathodes) of the LEDs connected together while common anode displays have all the positive sides (anodes) of LEDs tied together.

The 7 segment display can have from 1 up to 6 or 8 digits. One digit can display numbers from 0 to 9 and a dot for numbers with decimals. On 4 digit seven segment display the maximum number that can be displayed is 9999.

7 segment display internal equivalent circuit OPD-Q5621LE-BW
Figure 1: Internal equivalent circuit of a 7 segment display from OPD-Q5621LE-BW datasheet

 

Saturday, January 16, 2021

Button debouncing library for AVR microcontrollers

This library code provides an easy way for reading and de-bouncing one or multiple buttons connected to a microcontroller. A setup function takes care of setting the pins to input high using the internal pull-up resistors. A flag will be set when one or multiple buttons are pressed then the user can read which button or a combination of buttons are pressed. There is also an option to detect a long button press.

Button debouncing library for AVR microcontrollers

What is button debouncing anyway?

Saturday, January 9, 2021

millis and micros library for AVR microcontrollers - milliseconds and microseconds time tracking

Having precise timing in microcontrollers is important in many projects. For this you can use the two libraries presented in this article - millis & micros.

millis library triggers a timer interrupt every 1 millisecond and increments the milliseconds variable. The user can select the size of the milliseconds variable ranging from char (8 bits) to long long (64 bits) with and overflow from 255 milliseconds to 584.9 million years.

micros library is almost the same as for millis except that the interrupt triggers every 100 microseconds and the variable microseconds is incremented with 100 microseconds. The overflow is between 255 microseconds and 584942 years. Triggering the overflow faster than 1 / 100uS will make the CPU slow especially on slower CPUs.

For both libraries the user can select which timer to use: Timer0, Timer1 or Timer2.

In the case of millis library it is recommended that all the active ISR must take less than 1 millisecond to complete otherwise the millis ISR would be delayed. For the micros the ISRs must finish in less than 100 microseconds. The faster the CPU clock the better.

For the milliseconds and microseconds variable decide if you really need a 32 or 64 variable (long and long long) because the bigger the variable the longer it takes to increment it. For example on a 1MHz CPU it takes about 77 microseconds to increment a long long variable. A clock can be made even with a char because it doesn't matter if it overflows since the time will be kept in minutes and hours vars. 

millis & micros library for AVR microcontrollers

 

Thursday, January 7, 2021

ISP programming rig for microcontrollers

I had to program many types of microcontrollers over the years and so I was thinking why not build a simple programming rig to make things easier. This rig is for the In-system programming (ISP) method and not for the UART method.

How to easily upload the code to any microcontrollers using ISP rig

The board in the above image is not the programming rig but a digital clock shown as an example. Notice the 6 pin header near the cap. The pins are not soldered through hole but on SMD pads. This way it can be easily de-soldered after finishing the project and on some space constrained projects this is a must.

Monday, November 9, 2020

Constant current LED driver using ATtiny13 - Headlamp flashlight

Here is a simple constant current source for driving 7 white 5mm LEDs and also 2 red LEDs. I have made the circuit for a headlamp flashlight that had a soft button which required a microcontroller. To save the battery life the microcontroller is powering down when the lights are off with a current draw of only 0.3uA.

Constant current LED driver using ATtiny13 - Headlamp flashlight

Thursday, October 15, 2020

Capacitive transformerless power supply schematic for PIR motion sensor NV-1111.35

In the previous article I have explained how a PIR motion sensor works. There you can also find the schematic for the NV-1111.35 PIR sensor which is based on a popular three stage op-amp topology. Understanding this can help you understand other pyroelectric sensors as well.

This sensor is powered by a capacitive transformerless power supply with an output of 8V and 7mA of current.

Capacitive power supply for pyroelectric (PIR) sensors