6
\$\begingroup\$

I have been recently getting into programming and micro controllers. I found myself that i don't really know how to do multiplexing, animation, easily displaying pictures on LED display. I have been looking for some code examples, but most of the articles I found, where about the hardware and the software was not even mentioned, the code examples are usually poorly commented and on a platform i don't know (I'm using TI MSP430 MCUs) and the operating theory of the software is never explained.

I'm assuming this is because a lot of MCU beginner come from a software background, and they need more explanation on the hardware.

My question is: How to implement Multiplexing, animation,bitmaps nicely. How should it be done? What is the theory of the software for these applications?

Any comments, link to articles, book, etc... are very much appreciated.

Also I have to stress that I have done it, and it's working, but i think my codes for it looks horrible, it would be impossible to manage in a larger program and there should be a better way to do it. But anyway here are two videos of multiplexing projects: http://www.youtube.com/user/undeadtreat/videos

\$\endgroup\$
  • \$\begingroup\$ I am not sure what you are asking. Multiplexing can apply to electronics (digital or analog), to data communication, and probably to a lot of other subjects. What do you man by this term? Animations and bitmaps are techniques used in combination with specific graphics hardware, so you have to study that hardware. Your question seems to be an almost (or mostly) software question, which is probably off-topic for SE. \$\endgroup\$ – Wouter van Ooijen Jul 18 '12 at 8:27
  • \$\begingroup\$ I have written that on LED displays. And as the leds are on the pins of the MCU (or a 595 shift register) there is no graphic hardware, i want to implement it in software, on the "bare metal" of an MCU. \$\endgroup\$ – Máté Magyar Jul 18 '12 at 8:35
  • 1
    \$\begingroup\$ Hey don't close this yet! I'm writing a pretty long answer. \$\endgroup\$ – AndrejaKo Jul 18 '12 at 8:38
  • \$\begingroup\$ Not planning on closing it, waiting for your answer :) \$\endgroup\$ – Máté Magyar Jul 18 '12 at 8:57
  • 1
    \$\begingroup\$ @Matt - AndrejaKo wasn't afraid you would close it, but other users, for possibly being off-topic. \$\endgroup\$ – stevenvh Jul 18 '12 at 10:11
7
\$\begingroup\$

This is a bit difficult to explain, since the way you'll do multiplexing depends on the type of the display and the type of the display depends on the type of the multiplexing you want and it gets complicated quickly.

I'll explain how I did multiplexing for a project where I used 5x7 LED displays.It will be similar for other displays which are 8x8 or smaller and bigger displays can be made by combining 8x8 segments or using several multiplexers, but I'll explain a bit more later.

So first let's start with the display itself and the LED.

5x7 common row anode display

This is a common row anode display. The LED comes from light emitting diode, and as any diode, it has two terminals: The anode and the cathode and using the conventional current direction, current can only go through the diode in the direction of the symbol's arrow.

As you can see, this screen is basically a matrix and you'll need to control both rows and columns. With this display the current needs to be sourced from the rows and sinked from the columns. With some microcontrollers you may be able to connect all rows and columns to its pins using resistors on rows or on columns and directly set some of them to high and others to low and sink and source current that way. The MSP430 series usually can't do that because they usually can provide very little current and work at low voltage. Also you'd need to dedicate whole microcontroller to a single display, so it gets expensive quickly.

Here we come to the multiplexing part: Human eyes can't quickly detect LEDs turning on and off, so we can turn some of them off and have others be on and if we do it quickly enough, it won't be noticeable. There are circuits such as multiplexers, demultiplexers, shift register, counters, current sources and sinks and others which can be useful here.

The basic idea is to control as much LEDs with as little microcontroller pins as possible and to have a separate source of current from the LEDs which won't rely on the logic circuits. This is important because the logic ICs can usually provide very little current and may be able to turn on few LEDs, while the screen has many LEDs.

So basically we need to have current source (here for rows) and current sink (here for columns) which will be controlled using the logic ICs which are in turn controlled by the microcontroller. IF the current sources don't limit the current, we'll also need resistors for that.

Now onto practical implementation:

From the point of view of a single diode, we want to have this. Top switch controls the rows and the bottom switch controls the columns and the switches will be implemented by logic ICs. One thing that should be noted is that it's best to avoid driving a large (or any) number of LEDs directly from logic ICs. They can't source much current and LEDs usually require large amounts of it so that they are as bright as possible.

I myself here used a combination of PNP transistors driven by 74HC138 "3 to 8 line decoder".

74HC138

The device is basically same as demultiplexer, but there is no data input, just the address input, and the output is inverted. I connected the outputs to PNP transistors, as shown in the simulation. When the output is inactive, it gives high voltage, which prevents the transistor from conducting and turns the row off and then the output is in active state, the it gives low voltage which makes the transistor conduct. You select the output using the 3 address pins to give the binary number you want. There are even a current source ICs that will replace the individual transistors and that may have non-inverted input, but I can't recommend any, since I didn't get to work with them much.

This number can be decreased to 2 pins if you use a 595 series shift register (but I found that option to be less flexible).

74HC595

The register has basically 3 inputs: SER, RCLK and SRCLK, but if you short together RCLK and SRCLK, the SRCLK will be one pulse ahead of RCLK. SRCLK is used to store the data into the register and RCLK is used to shift data. So you feed the data using SER and then send a pulse out to SRCLK and RCLK and they will store the data and shift all bits by one place. Note that you'll need one extra shift at the end so that all bits are in their place. You can use this to output a series on 1s and then send a 0 which will in turn trigger each row of the screen. Why I found this less flexible, I'll explain later.

Now for the columns: As I wrote, in this type of screen, the current needs to exit at the columns, so we need a current sink to do the job. A sink can be made using a number of NPN transistors, as shown in the simulation. Each will allow the current to flow through the LED and the LED will shine. For this task, you can also use current sink ICs such as ULN2803A. STmicro makes them in DIP package, if you want to experiment on a breadboard. ULN2803A

The ULN2803A already has biasing resistors for the transistors matched for 5 V operation. This is higher than what you'll have with MSP430, bit I'll explain a bit more later on.

Now why I found that using 595s as row selectors using 2 pins isn't as nice: If you want to drive multiple screens, you could do that by having a separate column control for each screen and a single row control for all screen (or share row control among several screens). When you're loading the 595s with data using two control lines, it will output wrong data until you're finished with data transfer. So in my setup, I used a 74HC138 and the fact that I have a 7 row screen. The 138 will set the screen to 8th non-existing row (and by doing that, disable it) while the 74HC595s are loading their data. After the data is transferred, the 138 will select the correct row and display the image.

Also as a side-note: If you want to run several screens and use 595s, you can use single clock line for all of them and this way save MCU pins.

Next, for the actual data storage: You'll need to store the image inside the microcontroller in a way that will waste the least amount of memory, since microcontrollers in general have little memory. The way I used (and I assume that there are better ways than this), is to make a matrix whose dimensions are the same as screen dimensions. So in my case, that would be 7x5 matrix. The type of data should be one of the integer types and will depend on the number of screens. With chars, up to 8 screens can be stored in a single matrix for example.

The idea is that rows of the matrix represent rows of the screen and columns of the matrix represent columns of the screen. So you simply set the fields you want to be on to one. Make a program that will read column data for each row of the matrix, send data to 595s and then turn that row on on the screen. This may look a bit wasteful since we're using an entire matrix for single bits and that's where the common scenario of controlling multiple screens comes up. Since the matrix will at least be a matrix of chars, which are 8 bits long usually, and we set bits to 1 for the LEDs that should be on, we can store one screen and then using bitwise operations, shift entire matrix to the left one place and then store another screen of data. The procedure can be repeated a number of times until we have data for 8 screens. Since MSP430 is 16 bit, it should work fast with 16bit integers too, so we could easily control up to 16 screens this way. For larger data types, the operations are going to be a bit slower and sometimes that can be too slow.

Next few words about refresh rate: You can display the animations on screen by having one "process" display the data (say a timer based interrupt) and another to change the data (say in the main while loop). The timer based interrupt should make sure that each row is turned on many times every second so that the image appears smooth. The number often found on the Internet is at least 60 times a second, but numbers greater than this make the screen easier to read.

Finally, a bit about logic level conversion: MSP430 should run at up to 3.7 V and 4.2 V is the maximum voltage it can be exposed to. This leaves us a small problem: The usual nominal voltage for logic ICs is 5 V and the MSP430 can't run at that voltage. The good news here is that 595s can run as low as 2 V, so you can simply set the voltage to 3.7 V and run everything from it. Sometimes, this can be too low, so usually it's a good idea to get a logic level converter. There are complete logic level converters such as this one from Sparkfun. The schematic isn't too complicated either and can be made at home too.

And now for a little bit extra information if the screen seems too dark: LEDs and LED screens usually in addition to maximum average current have maximum pulse current rating too. It can be used to make the screen appear brighter than it is. Usually after each pulse, a rest period is required and that will be indicated in the datasheet. For example for the screen I used we have this:

peak current

So if we pick time period of such length that the average current is below 25 mA and average power dissipated at the screen is below 60 mW, we can have instantaneous current of up to 80 mA.

Example of code I used for row number 1:

 if (1 == interrupt_counter)//The interrupt counter is incremented using 
            {//interrupt counter++;
             //interrupt counter = interrupt counter % 7;
               A0=1;//multiplexer address bits
               A1=1;
               A2=1;

        empty_pulse();//sends out a pulse with no data
        empty_pulse();//since the register is 8 bit
        empty_pulse();//and I'm only using 5 bits
                      //so this macro writes a zero to the register
                      //and sends a clock pulse        

for (p1=0;p1<5;p1++)
          {                  

            data5=(screen_output[1][p1]>>1)%2;//data for screen number 5
            data4=(screen_output[1][p1]>>2)%2;//data for screen number 4
            data3=(screen_output[1][p1]>>3)%2;//the hard coded 1
            data2=(screen_output[1][p1]>>4)%2;//is row number and 
            data1=(screen_output[1][p1]>>5)%2;//p1 is column number    

            pulse();//only a clock pulse is sent
          }
          pulse();//one more pulse is needed, to make sure that the late clock
                  //is at correct place
          A0=1;//multiplexer is now set to output 1
          A1=0;
          A2=0;
}
\$\endgroup\$
  • 1
    \$\begingroup\$ That's indeed a long answer. Challenging Russell, are you? :-). Two notes. 1) there exists an HC238 version of the HC138 with the outputs inverted, so 1 out of 8 high. May be useful in some configurations. 2) often overlooked, the HC595 can only source or sink a total of 70 mA, Absolute Maximum Ratings. So 8 LEDs at 10 mA is already Bad, 8 x 20 mA is Evil. May be worth noting. \$\endgroup\$ – stevenvh Jul 18 '12 at 10:08
  • \$\begingroup\$ @stevenvh I need to work on providing short answers. Long ones are so easy to write. Also, I agree with the 595 point. \$\endgroup\$ – AndrejaKo Jul 18 '12 at 10:26
  • \$\begingroup\$ Great answer! although a little too hardware related , but it is very good, thank you! I thought of making an array to represent pixels, but I stuck at point where I supposed to put that array on the output. can you give an example code on how to push the array to the output? or give me some directions where to start? the power output of ICs is never a problem, a few transistors always can help :-) \$\endgroup\$ – Máté Magyar Jul 18 '12 at 10:40
  • \$\begingroup\$ @Matt Magyar I'll dig up what I used for a similar project. \$\endgroup\$ – AndrejaKo Jul 18 '12 at 10:41
  • \$\begingroup\$ @Matt Magyar I posted the code. If there's something you don't understand, tell me. \$\endgroup\$ – AndrejaKo Jul 18 '12 at 10:56
6
\$\begingroup\$

Timing: ideally you want a perfectly timed multiplexing, and then we think of timer interrupts, where you handle the display driver in the ISR. But there's the Law: get out of ISRs as quickly as possible. That means set a flag, for the main loop, do a couple of basic things, and out.

The actual handler is called in the main loop. This is basically a lists of checks which tasks must be done:

if 32ms timer expired then do keypad scan  
if display flag set then do display routine  
etc.  

Depending on your application this can be a long list, and worst case all flags are set at the same time, and then all the routines must be executed in the same main loop. That will cause a main loop to occasionally take a long time, and at other moments very short. This is not always a problem, but for our display it could mean flickering. A solution: make sure not all routines have to be executed at the same time, by interleaving them. The first time you only check the flag of long routine A, the next time long routine B. Short routines and routines with priority are always checked.

That way you often can keep the main loop as short as a few ms, with little variation. You can also sync your main loop to a timer, so that it always takes the same time. This will cause some dead time, so you'll have to see if you can afford that. If you can, use it to put the controller asleep, then you can decrease power a bit. Do the display routine first, so that it remains in sync.

If you do multiplexing go for as few columns as possible, this will increase the multiplexing frequency. If your main loop takes 3 ms and you have 8 columns to scan, then a complete scan will take 24 ms, that's 41 Hz, which is only just OK. If you can rearrange the matrix and decrease the number of columns to 6 the frequency will increase to 56 Hz. This 4-column setup will refresh at 83 Hz.

enter image description here

So what do you do? At the time cue turn off all LEDs, by making the row port all high or low, depending on your configuration. Rotate the column bit to the next column and set that on the output port. Then look up the row pattern for that column in the display table, and set that on the row port. And you're done. If that's all couldn't that have been done in the ISR? Possibly, but setting row or column output (or both) can also mean shift the data out to a couple of 74HC595 shift registers, and then it takes a lot longer.

edit re your comment
I'll use the four 7-segment display as an example. First thing you need is a 7-segment lookup table:

enter image description here

'                 abcdefg.         
sevenseg  db    0b11111100 ;0
          db    0b01100000 ;1
          db    0b11011010 ;2
          db    0b11110000 ;3
          db    0b01100110 ;4
          db    0b10110110 ;5
          db    0b10111110 ;6
          db    0b11100000 ;7
          db    0b11111110 ;8
          db    0b11110110 ;9

Then the digit values. If you generate these yourself it's easiest if you use a byte (counter) for each digit. If it's a counter you'll have to deal with carries from one digit to the next, but that's still easier than converting a 16-bit binary to BCD. For a RTC you'll need separate counters for hours and minutes anyway. If your data is binary, for instance coming from a digital temperature sensor, then you need a binary-to BCD conversion routine. I'll assume you have a four byte array with the digits for each display.

digits    ds    4; 4 digits, each 0..9
column    ds    1; shifts a "1" through bits 0..3
colcount  ds    1; column index, 0..3

I'll write the rest in a kind of pseudo-code, so you can easily convert it to either C or assembly.

; display routine
set port2 to 0b00000000 ; clear display
increment colcount
if colcount > 3 then
  colcount = :0
column := 1 shift left colcount bits
set port1 to column ; digit is selected
a := digits[colcount] ; digit value
a := sevenseg[a] ; digit value encoded into 7-segment pattern
set port2 to a ; output 7-segment pattern
return ; all done, go to have a beer

Further reading:
BCD arithmetics: Atmel application note describing conversion routines binary to BCD and vice versa. Controller-independent, so not just AVR.

\$\endgroup\$
  • \$\begingroup\$ Thank you for the answer! It is probably useful for a lot of people but my question is that how do you implement this exactly in software, like, do you use an if structure or switched case. Because i figured this out , and this is how i implemented (only except it that i done it completely in ISR and the MCKL and the CPU is mostly off) My question is more aimed to: how do you store the data, the numbers on the multiplexed display to simply display an integer or how do you store the pixels of an 8x8 array \$\endgroup\$ – Máté Magyar Jul 18 '12 at 8:52
  • \$\begingroup\$ @Matt - updated the answer with software routine \$\endgroup\$ – stevenvh Jul 18 '12 at 11:04
  • 1
    \$\begingroup\$ If all your timer ISR does is set a flag, then there's no point in having it. The MCU should already have a timer expired flag bit somewhere which you can check. \$\endgroup\$ – Rocketmagnet Jul 18 '12 at 17:02
  • \$\begingroup\$ @Rocketmagnet - You're right, maybe I exaggerated a bit. \$\endgroup\$ – stevenvh Jul 18 '12 at 17:04
  • 1
    \$\begingroup\$ @stevenvh - easily done. I did exactly this for years until I realised it was redundant. \$\endgroup\$ – Rocketmagnet Jul 18 '12 at 20:44

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.