Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / IoT / Arduino

Improved Binary Counter for Arduino

4.62/5 (4 votes)
25 Jun 2018CPOL10 min read 14.8K  
Notes on connecting LEDs to Arduino and on microcontroller programming in general presented on improved binary counter.

Introduction

This article was inspired by the Arduino Challenge here on Codeproject. I read the Ryan's Arduino starter article on binary counter and thought that there were some things which could be improved. I commented on these and Ryan suggested that I could turn this into an article. 

So this article presents a binary counter which displays the count using 8 LEDs just as Ryan's counter does but I hope to show some practices which are more suitable for programming microcontroller (MCU) devices such as Arduino. I want to touch on these issues:

  • properly connecting LEDs to a microcontroller (MCU) such as Arduino
  • using the proper value of the resistor for LED
  • using dynamic memory in MCU programs
  • using C++ in MCU programs
  • considering the code efficiency in MCU programs

Background on LEDs and MCUs

What we need is 8 LEDs connected to the Arduino pins. In Ryan’s article, the LED anodes (positive ends) are connected to Arduino pins and cathodes (negative ends) are connected through a single resistor to GND.

This is not the right way to connect multiple LEDs. There should be a separate resistor for each LED. The problem of using single resistor is that the current flowing through this circuit is always the same, limited by the resistor and this current is divided between all the LEDs which are on at given time. For example, if the resistor is 1000 ohm (1 k), the current will be about 3 mA. If one LED is lit, 3 mA flows through it. If 2 LEDs are lit, 1.5 mA flows through each of them… If all 8 LEDs are lit, about 0.4 mA flows through each of them – which is too little for the LED to be bright.

Why should each LED have a separate resistor?

The current flowing through a LED depends on the voltage. If the voltage is below a certain threshold, the current is almost zero and the LED is off. As the voltage exceeds the threshold, the current quickly rises and we need to limit it using a resistor connected in series with the LED. If there is no resistor, the current will rise too high; the LED will overheat and fail.

The voltage threshold depends on the type of LED, but in general, it is around 2 V. You can (and should) find the real value in the documentation for the LED. You can usually see this right in the description of the LED in the e-shop when you buy it – it is called LED forward voltage. If not, there is usually a link to datasheet in the description. If not, just take your guess based on the color of the LED.

The current which should flow through the LED can also be found in the documentation. It is 20 mA for a “standard” LED and 2 mA for so called low-current or low-power LEDs. The low-power LEDs cost about the same as the standard LEDs and they use 10-times less power, so use them whenever you can.

How to calculate the value of the resistor?

So now we have the two basic values for using a LED – the forward voltage (Vf) and required current (If). We can calculate the value of the resistor (R) to limit the current (If) flowing through the LED:

R = (Vdd – Vf) / If

The Vdd is the voltage on the output pin of the MCU which we use. For Arduino Uno, it is 5 V.

Here is an example. We want to connect a red LED, the Vf is 1.9 V and desired current If is 2 mA (but use Amps in the equation), so If = 0.002 A.

R = (5 – 1.9) / 0.002 = 3.1 / 0.002 = 1550 ohm.

So ideally, we should use a 1550 ohm resistor. But the resistors are manufactured only in certain values, so we will use one which is close and preferably a higher value – higher because then the current will be lower. We could use, for example, 1800 ohm (1k8) or 2200 ohm (2k2). The closest commonly manufactured value of 1500 ohm (1k5) would also be fine.

Now you may sometimes see people connecting LEDs directly to Arduino pins without any resistors - and get away with this. Nothing burns. Well, you should not get used to the idea that if something works, it is right. If you are serious about developing embedded systems which really work, try to understand how every component in your system works and be positively sure that you are using it right. A LED connected to Arduino without the current-limiting resistor does not fail because the MCU is not able to provide enough current to overheat the LED. The pin of the MCU can provide a maximum of about 40 mA in case of Arduino Uno, so the LED will not be too overloaded. But the MCU is not happy to be overloaded like this. You should not rely on the MCU manufacturer protecting the output pins from overload. You can easily burn your Arduino.

Important note about the current from an MCU pin

As usual, you can find the exact numbers in the datasheet but the MCU used in Arduino uno can provide a maximum of 40 mA per pin but not from all the pins at the same time! The total current should not exceed 200 mA per the whole MCU package.

Background on the MCU Software

Writing programs for MCU is still quite a bit different from writing programs for the large and powerful computers. In desktops and laptops, we got used to gigabytes of RAM and gigahertzes of processor (CPU) speed and programming languages like Java or Python which make it easy to write complex programs, but which also use lot of memory and CPU time. This is fine for big computers - the powerful hardware will compensate for inefficient software. In MCUs, things are different. A typical MCU only has megahertz of CPU speed and kilobytes of memory. The efficiency of the program still matters in MCU.

If you ask why, it is because MCUs are used in so many devices which should be cheap, like flashlights, toys, etc. Every cent in price per piece counts if you produce millions of pieces. So the hardware is as cheap as possible and it cannot compensate for inefficient software.

Even if one day the hardware became so cheap that there would be no speed and memory limits, there is still the energy, the power. It's easy to forget it when you write your programs but the program is running on electricity. And the more operations you do in your program, the more energy you use. This is especially important in battery powered devices - which are everywhere around us, take just the remote control for your TV.

So with efficiency in mind, there are two things I didn't like in the Ryan's binary counter - using dynamic memory (new and delete) and converting number to bits by dividing it by two.

Using new and delete

In MCU programming, dynamic memory allocation is not used except for special cases. With so little available RAM you should be aware of how much you really use and for what. There is no virtual and virtually unlimited memory as in big computers. The new operator will not help you get more memory, it will just add overhead for getting and freeing memory which you could allocate statically. Usually, you know how much memory you need. If you need to get some data from the outside world and don't know how big it can be, you still have to design the program so that it works up to some limit and fails gracefully if the data is too big.

Working with bits

In C/C++, it is easy to manipulate and test individual bits. In our binary counter, we have an 8-bit variable which stores the current count and we need to convert this to individual bits to turn on/off the LEDs which show this value. There is no need to use expensive division; the variable is stored in binary in the memory. We just need to see what the value of each bit is. In the code below, I use a loop to convert the number in the counter mCount to individual bits to be set on output. It is not the most efficient way but hopefully it is readable.

C++
uint8_t mask = 1;  // binary 0000 0001
uint8_t bitVal;   
for(uint8_t i = 0; i < mDigitCount; i++) {
    bitVal = (mCount & mask) ? 1 : 0;
    digitalWrite(mPins[i], bitVal );
    mask = mask << 1;   // shift left to test next bit
}

Using the Code

Finally, we get to the code. I present three versions of the binary counter. First version is written in good old C language. The other two are in C++.

Why C Language?

Today, C or C++ language is used to program MCUs. There are advantages to using C++, which is object oriented, but there are also disadvantages - the program will probably be a bit larger and less efficient and not all compilers (toolchains) support C++. Even if your compiler supports C++, you cannot expect the same C++ on the big computers. You can expect what someone called “a strange subset of C++”.

So if you ask me, I'd say use C++ only if you really need it – if your program will benefit from this. Here are three reasons for using C++ in MCU programs (but sure, opinions may differ):

  • creating a large program which you feel it will be easier to manage in C++
  • creating library (nicely wrapped in a C++ class)
  • if you need multiple instances of some functionality - as shown in the binary counter in C++ below.

Hardware of the Counter

I used the LEDs connected according to Ryan's article - even though this is not the right way. You can see the right way with separate resistors in this article by Leonid Fofanov.

First version - C language

This is the version I would use for a simple binary counter project without any plans for making this a library or using more instances of the counter.

C++
// C - language version of binary counter.
// Uses 1006 B of flash, 20 B or RAM

// prototype
void CounterIncrement();

void setup()
{  
}

void loop()
{
  CounterIncrement();
  delay(500);
}

void CounterIncrement()
{   
   static const uint8_t digitCount = 8;
   static const uint8_t pins[digitCount] = {12, 11, 10, 9, 8, 7, 6, 5};
   static uint8_t count = 0;   
   static bool firstRun = true;

   if ( firstRun ) {
    firstRun = false;
    for(uint8_t i = 0; i < digitCount; i++) {
      pinMode(pins[i], OUTPUT);  
    }  
   }
   
    // Control the LEDs
    uint8_t mask = 0x80;  // binary 1000 0000
    uint8_t bitVal;   
    for(uint8_t i = 0; i < digitCount; i++) {
        bitVal = (count & mask) ? 1 : 0;
        digitalWrite(pins[i], bitVal );
        mask = mask >> 1;   // move to next bit
    }

    if(count == 255) {
        count = 0;
    } else {
        count++;  
    }
}

Version 2 - C++

Here is the binary counter written in C++. Besides getting rid of the dynamic memory and slow conversion to binary, I added the option to set the pins which will be used in constructor instead of hardcoding them into the class. So it is easier to use this class with different hardware setup. Also, I added begin function which is kind of Arduino’s standard function for initialization (I wish they called it init). It is better to set the pins to output mode in such a function then in the constructor because if we create an object of the class as a global variable, the constructor will be executed sometime early during the program startup and I am not sure if the Arduino pinMode functions which we use are already initialized. So I play it safe and use initialization function which will be called in setup.

The constructor takes an array of pin numbers as an input parameter together with number of bits used. So it is also possible to have counter with less than 8 bits.

Tip: Try two 4-bit counters instead of one 8-bit counter - see the commented out code for counter2.

C++
// Alternative version of Binary counter
// Uses 1094 B of flash and 27 B of RAM with 1 instance of the counter.
// 2 instances: 1182 B flash and 37 B RAM
class BinaryCounter {
 
  private:    
    uint8_t mDigitCount;
    uint8_t mPins[8];  
    uint8_t mCount = 0;    

  public:
    BinaryCounter(uint8_t pins[], uint8_t count);
    void begin();
    void increment();      
};

// The pins should be in order from least significant to most significant,
// so pins[0] is bit 0, pins[1] is bit 1, etc.
BinaryCounter::BinaryCounter(uint8_t pins[], uint8_t count)
{
  //  we support max. 8 digits
  mDigitCount = ( count <= 8 ) ? count : 8;
  for(uint8_t i = 0; i < mDigitCount; i++) {
    mPins[i] = pins[i];
  }  
}

void BinaryCounter::begin() {
  //set LED all pins to output mode
  for(uint8_t i = 0; i < mDigitCount; i++) {
    pinMode(mPins[i], OUTPUT);  
  }
}

void BinaryCounter::increment() {

    // Maximum value of the counter depends on number of bits
    // For 8-bit counter the shift code can't be used
    // it would overflow 8-bit variable.  
    const uint8_t maxVal = (mDigitCount == 8) ? 255 : (1 << mDigitCount) - 1;
    
    // Control the LEDs
    uint8_t mask = 1;  // binary 0000 0001
    uint8_t bitVal;   
    for(uint8_t i = 0; i < mDigitCount; i++) {
      bitVal = (mCount & mask) ? 1 : 0;
      digitalWrite(mPins[i], bitVal );
      mask = mask << 1;   // shift left to test next bit
    }
 
    // Increment the counter
    if(mCount >= maxVal ) {
        mCount = 0;
    } else {
        mCount++;  
    }
}

// Create object of binary counter
BinaryCounter counter1( (uint8_t[]){5, 6, 7, 8, 9, 10, 11, 12}, 8 );
// Two counters at the same time
//BinaryCounter counter1( (uint8_t[]){5, 6, 7, 8}, 4 );
//BinaryCounter counter2( (uint8_t[]){9, 10, 11, 12}, 4 );

void setup()
{ 
  counter1.begin();
  //counter2.begin();
}

void loop()
{  
  counter1.increment();  
  //counter2.increment();  
  delay(500);
}

Version 3 - C++

One thing I didn't like about the binary counter version 2 above was using the "normal" variable to store the pin numbers. I wanted to use const variable - but still make it possible to set the pins in constructor. To make this possible, I had to use feature added to C++ only in the C++11 standard to initialize the const array in constructor. C++11 is supported in Arduino IDE, so no problem here, but I admit it is quite a tough requirement for an MCU program.

So this version uses const variable for storing the pin numbers which saves 8 bytes of RAM and can lead to a faster, more efficient code because the compiler knows that the pins cannot change during runtime. I also used default parameter values in constructor to make it easier to use the counter with less than 8 pins.

C++
// Alternative version of Binary counter
// version 3 with const pins.
// Requires C++11 support in compiler.
// Uses 1076 B of flash and 19 B of RAM with 1 instance of the counter.
class BinaryCounter {
 
  private:    
    uint8_t mDigitCount;
    const uint8_t mPins[8];  
    uint8_t mCount = 0;    

  public:
    //BinaryCounter(uint8_t pins[], uint8_t count);
    BinaryCounter::BinaryCounter(uint8_t count, uint8_t p0, uint8_t p1=0, uint8_t p2=0, 
                                  uint8_t p3=0, uint8_t p4=0, uint8_t p5=0, uint8_t p6=0, 
                                  uint8_t p7=0);
    void begin();
    void increment();      
};

BinaryCounter::BinaryCounter(uint8_t count, uint8_t p0, uint8_t p1, uint8_t p2, uint8_t p3,
                            uint8_t p4, uint8_t p5, uint8_t p6, uint8_t p7)
 : mPins{p0, p1, p2, p3, p4, p5, p6, p7}               
{
  //  we support max. 8 digits
  mDigitCount = ( count <= 8 ) ? count : 8;  
}

void BinaryCounter::begin() {
  //set LED all pins to output mode
  for(uint8_t i = 0; i < mDigitCount; i++) {
    pinMode(mPins[i], OUTPUT);  
  }
}

void BinaryCounter::increment() {

    // Maximum value of the counter depends on number of bits
    // For 8-bit counter the shift code can't be used
    // it would overflow 8-bit variable.  
    const uint8_t maxVal = (mDigitCount == 8) ? 255 : (1 << mDigitCount) - 1;
    
    // Control the LEDs
    uint8_t mask = 1;  // binary 0000 0001
    uint8_t bitVal;   
    for(uint8_t i = 0; i < mDigitCount; i++) {
      bitVal = (mCount & mask) ? 1 : 0;
      digitalWrite(mPins[i], bitVal );
      mask = mask << 1;   // shift left to test next bit
    }
 
    // Increment the counter
    if(mCount >= maxVal ) {
        mCount = 0;
    } else {
        mCount++;  
    }
}

// Create object of binary counter
BinaryCounter counter1( 8, 5, 6, 7, 8, 9, 10, 11, 12  );
// Two counters at the same time
//BinaryCounter counter1(4, 5, 6, 7, 8 );
//BinaryCounter counter2(4, 9, 10, 11, 12 );

void setup()
{  
  counter1.begin();
  //counter2.begin();
}

void loop()
{  
  counter1.increment();  
  //counter2.increment();  
  delay(500);
}

History

  • 2018-06-25: First version

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)