Christmas Lights Special - Microcontroller Basics (PIC10F200)

Published

Ho! Ho! Ho! Christmas is coming soon, so it’s time for some magic. And I’ll show you some real magic today. As we all want to feel relaxed before the Christmas holidays, I won’t give you any complex algorithms, I won’t even teach you any new instructions. We already know everything we need to make today’s device.

Today we will make a racing light (where the lights turn on one after another, seeming to “race” through the different LEDs) which you can install in your room or put on a Christmas tree or wherever you want.

Racing light (where the lights turn on one after another, seeming to “race” through the different LEDs)

What do you think - how many independent LEDs can we control with the PIC10F200? The most obvious answer is 3 because, as we remember, it has only 3 GPIOs - GP0, GP1, and GP2. But what if I say that the correct answer is 6? Without any shift registers or other chips, just the PIC10F200 and six LEDs. Sounds like magic, and the name of this magic is charlieplexing.

If you are very curious you can read this Wiki article about the trick, otherwise just keep reading this tutorial and I’ll explain the main idea. Let’s consider the schematic diagram of the racing light (figure 1).

Schematics diagram of running light
Figure 1 - Schematics diagram of running light

As you can see in figure 1, we connected six LEDs in an unusual way - we collected the LEDs into opposite pairs and connected these pairs between the three GPIOs.

The charlieplexing method is based on the fact that GPIOs are tri-state which means that they can be in three different states - output high, output low, input high-Z. The last state means that the pin acts as the input and its resistance (or ‘impedance’ in some sources) is very high. In this case, we can consider that this pin is not connected at all and doesn’t affect the circuit.

Let’s consider what happens in the schematic (figure 1) when GPIOs are in different states. Let’s imagine that GP0 is output high, GP1 is output low, and GP2 is high-Z. In this case, the bottom pins of LED3, LED4, LED5, and LED6 are kind of disconnected because of the high impedance of GP2, thus none of these LEDs can be lit up. We have LED1 and LED2 left. As GP0 is output high, then the LED whose anode is connected to this pin will be lit up, and this is LED1. So in this combination of GPIOs states only LED1 will turn on and all other LEDs will be turned off.

With the same reasoning we can make a list of the states which lead to turning on all other LEDs (see table below).

Table showing a list of the states which lead to turning on all other LEDs 

All other combinations should be avoided to prevent undefined behaviour. One important conclusion of this table is that only one LED can be lit up at once. If you want several LEDs to be lit simultaneously, you should use dynamic indication. It means that you need to switch the pin states at a high frequency so that the eye will not recognize the switching and it will seem like LEDs are lit at once. We will not do this in the current program, I just want you to know that such application is possible.

OK, let’s now consider the code that will implement our racing lights.

#include "p10f200.inc"

__CONFIG _WDT_OFF & _CP_OFF & _MCLRE_OFF

    ORG 0x0000

i   EQU   10   ;define 0x10 register as the delay variable

j   EQU   11   ;define 0x11 register as the delay variable

k   EQU   12   ;define 0x12 register as the delay variable

led   EQU   13   ;define 0x13 register as the LED number

INIT

    MOVLW  ~(1<<T0CS) ;Enable GPIO2

    OPTION    

    MOVLW ((1 << GP0)|(1 << GP1)|(1 << GP2));set GP0, GP1, GP2 as inputs

    TRIS GPIO

LOOP

    MOVLW 1   ;Light up LED1

    CALL LIGHT_LED

    MOVLW 2   ;Light up LED2

    CALL LIGHT_LED

    MOVLW 3   ;Light up LED3

    CALL LIGHT_LED

    MOVLW 4   ;Light up LED4

    CALL LIGHT_LED

    MOVLW 5   ;Light up LED5

    CALL LIGHT_LED

    MOVLW 6   ;Light up LED6

    CALL LIGHT_LED

    MOVLW 5   ;Light up LED5

    CALL LIGHT_LED

    MOVLW 4   ;Light up LED4

    CALL LIGHT_LED

    MOVLW 3   ;Light up LED3

    CALL LIGHT_LED

    MOVLW 2   ;Light up LED2

    CALL LIGHT_LED

    GOTO LOOP     ;loop forever

DELAY   ;Start DELAY subroutine here

    MOVLW 2   ;Load initial value for the delay    

    MOVWF i   ;Copy the value to the register 0x10

    MOVWF j   ;Copy the value to the register 0x11

    MOVWF k   ;Copy the value to the register 0x12

DELAY_LOOP   ;Start delay loop

    DECFSZ i, F   ;Decrement the register i and check if not zero

    GOTO DELAY_LOOP   ;If not then go to the DELAY_LOOP label

    DECFSZ j, F   ;Else decrement the register j, check if it is not 0

    GOTO DELAY_LOOP   ;If not then go to the DELAY_LOOP label

    DECFSZ k, F   ;Else decrement the register k, check if it is not 0

    GOTO DELAY_LOOP   ;If not then go to the DELAY_LOOP label

    RETLW 0   ;Else return from the subroutine

SELECT_LED   ;Turn on LED1

    DECFSZ led, F               ;Decrement the register 'led' and check if not zero

    GOTO LED2   ;If not then go to the LED2 label    

    MOVLW ~((1 << GP0)|(1 << GP1))

    TRIS GPIO   ;Otherwise set GP0 and GP1 as outputs

    MOVLW 1 << GP0   ;Set GP0 pin as output high

    MOVWF GPIO

    RETLW 0   ;and return from the subroutine

LED2

    DECFSZ led, F               ;Decrement the register 'led' and check if not zero

    GOTO LED3   ;If not then go to the LED3 label    

    MOVLW ~((1 << GP0)|(1 << GP1))

    TRIS GPIO   ;Otherwise set GP0 and GP1 as outputs

    MOVLW 1 << GP1   ;Set GP1 pin as output high    

    MOVWF GPIO

    RETLW 0   ;and return from the subroutine

LED3

    DECFSZ led, F               ;Decrement the register 'led' and check if not zero

    GOTO LED4   ;If not then go to the LED4 label    

    MOVLW ~((1 << GP1)|(1 << GP2))

    TRIS GPIO   ;Otherwise set GP1 and GP2 as outputs

    MOVLW 1 << GP1   ;Set GP1 pin as output high

    MOVWF GPIO

    RETLW 0   ;and return from the subroutine

LED4

    DECFSZ led, F               ;Decrement the register 'led' and check if not zero

    GOTO LED5   ;If not then go to the LED5 label    

    MOVLW ~((1 << GP1)|(1 << GP2))

    TRIS GPIO   ;Otherwise set GP1 and GP2 as outputs

    MOVLW 1 << GP2   ;Set GP2 pin as output high

    MOVWF GPIO

    RETLW 0   ;and return from the subroutine

LED5

    DECFSZ led, F               ;Decrement the register 'led' and check if not zero

    GOTO LED6   ;If not then go to the LED6 label    

    MOVLW ~((1 << GP0)|(1 << GP2))

    TRIS GPIO   ;Otherwise set GP0 and GP2 as outputs

    MOVLW 1 << GP0   ;Set GP0 pin as output high

    MOVWF GPIO

    RETLW 0   ;and return from the subroutine

LED6

    DECFSZ led, F               ;Decrement the register 'led' and check if not zero

    RETLW 0   ;If not return from the subroutine    

    MOVLW ~((1 << GP0)|(1 << GP2))

    TRIS GPIO   ;Otherwise set GP0 and GP2 as outputs

    MOVLW 1 << GP2   ;Set GP2 pin as output high

    MOVWF GPIO

    RETLW 0   ;and return from the subroutine

LIGHT_LED   ;Light one LED and perform delay

    MOVWF led   ;Copy the content of the W into 'led' register

    CALL SELECT_LED   ;Call SELECT_LED subroutine

    CALL DELAY   ;Call DELAY subroutine

    RETLW 0   ;Return from the subroutine

    END

The program seems quite long but in fact it consists of a series of very similar blocks so it’s not difficult to understand.

The program seems quite long but in fact it consists of a series of very similar blocks so it’s not difficult to understand.

Lines 1 to 13 don’t bring anything new so I’ll just stop on some of them.

In the lines 4-6 we define ‘i’, ‘j’ and ‘k’ registers to use them in the DELAY subroutine. In line 7 we define the ‘led’ register which will contain the LED number that we want to light up.

In lines 12-13, we configure GP0, GP1, and GP2 as high-Z inputs. Technically, we can skip this initialization as, after a microcontroller reset, all GPIOs are supposedly configured as high-Z inputs but it’s a good habit to initialize registers and variables at the beginning of the program to avoid undefined behavior.

Code in lines 14 to 35 from the written program code above.

The main loop starts at line 14 and consists of a series of almost identical pairs of lines. We will consider just one such pair.

In line 15 we load the value ‘1’ into the W register. In this case ‘1’ represents the LED number that we want to light up.

In line 16 we call the LIGHT_LED subroutine which actual lights up the selected LED and then makes the specified delay. This delay defines the speed of the running light.

Lines 17 to 34 are the same as lines 15 and 16 repeated over and over. The only difference is that we load different values into the W register to light up different LEDs. As you may notice, the sequence is 1, 2, 3, 4, 5, 6, 5, 4, 3, 2, .... That means that the light will run back and forth. You may change the sequence on your own or expand the sequence with new lines, there is still plenty of free memory in the microcontroller. We’ve only spent 82 words of our available 256.

Code in lines 37 to 49 from the written program code above. This is where the DELAY subroutine is located.

The DELAY subroutine is located at lines 37-49 and should be quite familiar by this point, so there is nothing to add.

Code in lines 51 to 58 from the written program code above.

In lines 51-98 there is the SELECT_LED subroutine. It also consists of several repeating blocks, so we will just consider one of them in detail, and all others briefly.

In line 52, we decrement the ‘led’ register and check to see if the result is 0. As I mentioned before, the ‘led’ register contains the number of LED we want to light up. So if initially ‘led’ was 1, then after line 52, its content will become ‘0’, line 53 will be skipped, and we will move to line 54. If ‘led’ was not 1 then line 53 is not skipped and we move to the label ‘LED2’ where we decrement the ‘led’ again (line 60). If it was 2 then after two decrements it finally becomes 0, and lines 62-66 are implemented, otherwise we keep decrementing it (lines 68,76,84,92) and checking if it has become 0. Thus, if the ‘led’ value initially was 1 to 6 the corresponding LED will turn on. If ‘led’ has any other value then there won’t be any effect.

But let’s return to line 52. So let’s consider that ‘led’ was 1 and we got to line 54.

In line 54, we set bits GP0 and GP1 as 0 leaving all other bits as 1, and then load this value into the TRISGPIO register (line 55). By means of these lines, pins GP0 and GP1 will be configured as outputs, and pin GP2 will be configured as high-Z input.

The only thing left is to set GP0 as output high, and we do this in lines 56, 57. After that, LED1 will be lit up.

Code in lines 59 to 98 from the written program code above.

Similar things happen in lines 59-66, 67-74, 75-82, 83-90, and 91-98. The only difference is the pins’ configurations according to the table above, so that they light up their corresponding LEDs.

Code in lines 100 to 106 from the written program code above.

The last thing to consider is the ‘LIGHT_LED’ subroutine which we called from the main loop of the program. This subroutine starts at line 100. Even though it is at the very end of the program, it is actually the first level subroutine from which we call the subroutines that actually turn on the LEDs we want and create the delays.

In line 101, we copy the content of the W register into the ‘led’ register. As you remember we loaded the LED number into the W register in lines 15, 17, 19, and so on. Since we can’t apply the DECFSZ instruction to the W register we have to copy its value into some file register, in our case ‘led’, which can then be decremented during the SELECT_LED subroutine. Then, in line 102, we call the subroutine ‘SELECT_LED’ that we went over previously. After that we call the ‘DELAY’ subroutine (line 103) to let the selected LED stay turned on for the specified time.


And that’s all. I promised not to overload you with the hard explanations this time. In conclusion, I want to give some advice to those who will repeat this device.

If for some reason you get the undefined behavior of the LEDs (like several LEDs are on at once or order is incorrect) but you are sure that you connected everything right, then disconnect the wires GP0 and GP1 from the debugger as it may affect these pins in high-Z mode and cause some artifacts.

You can increase the number of LEDs by connecting several LEDs in series, e.i. you can connect up to three LEDs instead of just one LED. The voltage drop on the LED is about 1.5-2.5V depending on its color and type, thus if the power supply voltage is 5V you can connect 2-3 LEDs in series. In this case you need to decrease the value of the resistors R1, R2, and R3 to achieve the good brightness.

I recommend you use the LEDs of a single color to get the same brightness for all the LEDs. The voltage drop can differ, and that will make the brightness differ as well. You can also make your own pattern of LEDs blinking by changing the main loop. And, as mentioned earlier, you can make several LEDs to be turned on at once by means of the dynamic indication, though that will be a little more complicated..

And now, for real this time, that’s it! As homework, I’d suggest you to have fun during the Christmas holidays and relax from work. Next year we’ll keep discovering fun applications of the PIC10F200 microcontroller and make several cool things with it. Stay with us!

Merry Christmas and Happy New Year to you, my dear readers!

Make Bread with our CircuitBread Toaster!

Get the latest tools and tutorials, fresh from the toaster.

What are you looking for?