LED bar-graph driver

westaust55

Moderator
I have be using a series of IF...THEN type statements to control a group of LED's as a bar graph but then seem to recall seeing a recent thread asking if there was a better way. (cannot find that thread now!)

Had a look this evening and in the interests of sharing code snippets here is my contribution that should work on all PICAXE's.

Changing the No of LED's is a relatively easy task in terms of the calcs. Just need to use a word variable for pattern and amend the output subroutine.

My LED group is driven via an i2c bus 8-bit IO expander but changing the pattn2led subroutine will easily accomodate those using 74HC595 shift registers.

Code:
; -----[ Program Code ]----------------------------------------------------
;
; [B][COLOR="Red"]DELETED HERE - SEE POST No 4 BELOW FOR BETTER CODE[/COLOR][/B]
;===========================================

and if you have an X1 or X2 type PICAXE it is even easier with:

Code:
; -----[ Program Code ]----------------------------------------------------
;
; [B][COLOR="Red"]DELETED HERE - SEE POST No 4 BELOW FOR BETTER CODE[/COLOR][/B]
;===========================================
 
Last edited:

BCJKiwi

Senior Member
Unless there are multiple i2c devices the hi2csetup line only needs to issued once in the program (before main)

If there are multiple i2c devices, the hi2csetup is required for each one once at the beginning and then the address can be issued in the hi2cout command when the device address changes else only the data needs to be sent as in;
hi2cout [expand_0] (pattern)
or
hi2cout (pattern)
 

westaust55

Moderator
Unless there are multiple i2c devices the hi2csetup line only needs to issued once in the program (before main)

If there are multiple i2c devices, the hi2csetup is required for each one once at the beginning and then the address can be issued in the hi2cout command when the device address changes else only the data needs to be sent as in;
hi2cout [expand_0] (pattern)
or
hi2cout (pattern)

Yes, you are correct BCJKiwi.

The llines were extracted from a 200 line template/program I am building up which has around 14 i2c devices setup in it albeit in testing currently only about half connected at any one time. Had to shuffle a few SYMBOL command lines down to "cut" a portion that was basically a complete snippet for others to look at.

I had the complete hi2csetup line in at the start of the subroutine so that for those with a 74HC595 shift register, they need only replace the subroutine rather than edit/remove other lines.
Could also remove the line stating SYMBOL expand_0 = %01000000 but if that is left in it does not get acted upon or take up program space in the PICAXE.

In terms of usefulness:
The results of the improved X1/X2 version reduced my code by 90 bytes compared to having a series of IF...THEN statements to set up the patterns for the LEDs.

Edit: realised that for non X1/X2 parts, people will need to change the i2c commands to match those available on the "smaller" PICAXE chips
 
Last edited:

westaust55

Moderator
To tidy up the code and use i2c commands available on non X1 parts I have quickly reworked the code snippets given previously.

As before, for those using 74HC595 shift register IC's for driving their LED's they will need to ammend the subroutine Pattn2led to suit and remove the i2c comms related commands in the Init portion at the start.

The ammended code for the non X1 PICAXE parts:
Code:
; -----[ Program Code ]----------------------------------------------------
;
Init: SYMBOL adc_ldr  = 6          ; light dependant resistor analogue input
      SYMBOL expand_0 = %01000000  ; %0100 = Chip ID, 000 = Addr - For the 8 Red LED's
      SYMBOL NoLEDs   = 8
      SYMBOL DIVISOR  = 255 ; divisor=(2^NoLEDs)–1
                            ; based on value in the range 0 to 255, 
                            ; if say 10 bits and NoLEDs = 10 then divisor = 1023
      SYMBOL litelvl  = b0
      SYMBOL delaytime = b3
      delaytime       = 1500
      I2CSLAVE expand_0, i2cslow, i2cbyte

Main: READADC adc_ldr, litelvl
      w2 = litelvl * NoLEDs / divisor 
      pattern = 1   
      FOR b2 = 1 TO  b4  ; use w2 or use b4 (b4 is the lower byte of w2)
         pattern = pattern << 1
      NEXT
      pattern = pattern -1
	
      GOSUB Pattn2led
      PAUSE delaytime
      GOTO Main
      END
;
; -----[ Subroutines ]-----------------------------------------------------
;
Pattn2led: WRITEI2C expand_0, (pattern)
           RETURN
;===========================================
and for the X1 parts even less maths and program space required with:
Code:
; -----[ Program Code ]----------------------------------------------------
;
Init: SYMBOL adc_ldr  = 6          ; light dependant resistor analogue input
      SYMBOL expand_0 = %01000000  ; %0100 = Chip ID, 000 = Addr - For the 8 Red LED's
      SYMBOL NoLEDs   = 8
      SYMBOL DIVISOR  = 255 ; divisor=(2^NoLEDs)–1
                            ; based on value in the range 0 to 255, 
                            ; if say 10 bits and NoLEDs = 10 then divisor = 1023
      SYMBOL litelvl  = b0
      SYMBOL delaytime = b3
      delaytime       = 1500
      i2cmaster, expand_0, i2cslow, i2cbyte

Main: readadc adc_ldr,litelvl
      w2 = litelvl * NoLEDs / divisor 
      Pattern =  DCD w2 - 1
      		
      GOSUB pattn2led
      pause delaytime
      goto main
      END
;
; -----[ Subroutines ]-----------------------------------------------------
;
pattn2led: hi2cout expand_0 (pattern)
           RETURN
;===========================================
 

BCJKiwi

Senior Member
Interesting approach.

The one I have taken is to put the patterns in to eeprom
symbol offset = 200
eeprom 200,(%00000000) '0
eeprom 201,(%10000000) '1
eeprom 202,(%11000000) '2
eeprom 203,(%11100000) '3
eeprom 204,(%11110000) '4
eeprom 205,(%11111000) '5
eeprom 206,(%11111100) '6
eeprom 207,(%11111110) '7
eeprom 208,(%11111111) '8

Then divide the readadc (w0) by a suitable factor to produce 0 thru 8
b4 = w0 /32 Min 0 Max 8 'get step number

then
b4=b4 + Offset 'set eeprom offset address to read from
read b4,b5
then
hi2cout [expand_0] (b5)

This is simplified and summarised as various parts are in different subroutines with added features in places.

Find this useful as a standardised approach to 7seg displays, bar graphs, I/O expanders etc.
for example;
Have a bargraph driven by an I/O expander which works in both directions - i.e. 0 is no LEDs on, increasing negative starts at the right and turns on more LEDs toward the left, increasing positive starts on the left and turns on more LEDs to the right.
Another is for a 16 led circle as a compass output.
 

westaust55

Moderator
using the PCF8574 8-bit IO Expander

I am using the PCF8574 8-bit IO Expander.

This chip is designed to have high power (25mA) LED drive current with its outputs low.
To keep with more conventional logic where High is ON, I have added a transistor at each of the outputs so the LED's are on when a bit/output is high. This is also in line with the logic when using the 74HC595 shift register for the same task (without extra transistors)

It does mean that I need to send the value $0 to the PCF8574 when a program starts to have the LED's off as on startup the PCF8574 IO are set as inputs and the outputs are by default high (ON) for my logic conventions.
 

Attachments

Last edited:

westaust55

Moderator
BCJKiwi said:
Have a bargraph driven by an I/O expander which works in both directions - i.e. 0 is no LEDs on, increasing negative starts at the right and turns on more LEDs toward the left, increasing positive starts on the left and turns on more LEDs to the right.
To do the same without lookup tables,
using the formula/method I gave in post 4, for all PICAXE models and change the bar-graph from starting at the right side to starting at the led side
just use the REV command, and add the line

Code:
[B]pattern = pattern REV 8[/B]
so if Pattern was %00000111" to illuminate the right three LED's
after the REV command
Pattern = %11100000" which illuminates the left three LED's
 
Last edited:

BCJKiwi

Senior Member
Hmm, don't really want to get into a long discussion on this.
Many of the patterns I have are not simple sequences (like a 7seg for example). The bar graph is actually 10 bars in a bar graph led block and uses 10 of the 16 I/O of an MCP23017 and is transmitted in two bytes.
Also did a lot of testing on code size and execution speed and found that some instructions are quite slow but don't remember if Rev was amongst them ot not.
So it's all a matter of what one prefers - so many options - so little time....
 

westaust55

Moderator
Do not disagree.

I primarily set out to demonstrate a compact approach (not necessarily fastest) that did not use a string of IF..THEN statement that I had used or tables to achieve a bar graph for 8 (or more) LEDs

The method I have would be just as compact to drive 10 or 16 LED's.
for 9 to 16 LED's, just keep calculated number in a word and then pass the two bytes of that word to the IO Expander.

as you say, there are many options to get the same end result.
 

BCJKiwi

Senior Member
Just dug up the old speed tests where various techniques were tested, shift, Rev, for-next, if-elseif-endif, lookup, Case, and the table in eeprom.

The tests were looking at program size and speed.
Most of the 'smart' commands are relatively slow and use quite a lot of program space.

The patterns in eeprom were the fastest and most flexible by a long way.
In practice, depending on the actual routine and if repetitive (as in repeated tests), have found it faster to transfer the stored eeprom values thru to the scratchpad once at the beginning of the program and then use @ptr, @ptrinc/dec instead of read - all depends on the requirements of the program.

All these eeprom and scratchpad patterns look bulky but don't take up program space as they have their own memory allocations in 28X1.
Also the pattern approach easy to debug as the patterns can more easily be visualised, but I guess this is all just the way my mind works.

Will look at your approach in more detail as it is, as indicated earlier, 'interesting' and I enjoy the challenge of finding a 'better' way.
 

westaust55

Moderator
LED driver for bar-graph and point/dot on bar effect

Agree with what BCJKiwi says and accept that other methods may be faster.

Some people DO find a visual approach with patterns laid out far easier to understand. I have found in my work arena where others have great difficulty visualizing/ appreciating a 3D model of a process plant in their mind from 2D drawings.

I too have an X1 part, where EEPROM held data does not impact on program memory space.

For those with &#8220;smaller&#8221; PICAXE chips such as the 08 and 08M EEPROM memory and program memory are one and the same and far more restricted. Then the use of EEPROM tables are more relevant.

As indicated in post No 3:
In terms of usefulness:
The results of the improved X1/X2 version reduced my code by 90 bytes compared to having a series of IF...THEN statements to set up the patterns for the LEDs.
The maths method can still be flexible. For example, I started this thread to present one (of many) possible methods of driving a number of LED&#8217;s to give a bar graph effect. If one desires a single point moving along a bar then this is also easily achieved in my code examples by changing or removing a single line.

For the line:

Code:
pattern = pattern -1    (for X1 version line is Pattern =  DCD w2 &#8211; 1)
Depending upon which &#8220;dot&#8221; (LED) is desired to be illuminated along the bar-graph then options are to:
1. remove the entire entirely (for all parts version) or
for the X1 parts just the -1 section of the line
(in this case the &#8220;dot&#8221; cold be higher along the bar-graph),

or

2. replace the &#8211; 1 part with >>2 or /2
(which would move the dot lower down the bar-graph if needed)

Use of an IF . . .THEN command and a variable or spare RAM location as a flag could allow the user to select bar-graph or point mode on the &#8220;fly&#8221; (from a menu or whatever) without reprogramming the PICAXE.


At the end of the day, it is a mathematical approach to driving a series of LED's that I seek to present in this thread but may not be the be alla nd end all for all PICAXE users.
 
Last edited:

westaust55

Moderator
untested version of my code which may be suitable for all PICAXE models
which has options/provision for selection/definition of:
1. bar-graph or dot (single LED) indication along a row of LEDs
2. bar or dot progressing left to right or right to left.

Unfortunately I will not get the opportunity to test or further enhance for a few days (long weekend starting here :))


Code:
; -----[ Constants ]----------------------------------------------------
;
      SYMBOL adc_ldr  = 6   ; light dependant resistor analogue input
;                             maybe some other device or source for value
;
      SYMBOL expand_0 = %01000000 ; %0100 = Chip ID, 000 = Addr - For LED's
;
;
      SYMBOL divisor  = 255 ; divisor=(2^NoLEDs)&#8211;1
                            ; based on value in the range 0 to 255, 
                            ; if say 10 bits and NoLEDs = 10 then divisor = 1023
;
; -----[ Variables ]----------------------------------------------------
;
      SYMBOL NoLEDs    = b1
      SYMBOL litelvl   = b2 ; change name throughout to suit source of
                             ; the value you are basing indication upon
      SYMBOL delaytime = b3
      SYMBOL directn   = b4  ; 0 = right to left, >0 = left to right
      SYMBOL indtype   = b5  ; 0 = bar graph, 1= for lower &#8216;level&#8217; dot posn
                             ; and >1 for higher &#8216;level&#8217; dot position
      SYMBOL pattern   = w3  ; b7 + b6 used here for up to 16 bits/LEDs
;
      SYMBOL pattnHi   = b7  ; for access to individual bytes of pattern
      SYMBOL pattnLo   = b6

      SYMBOL temp1     = w4  ; b9 and b8 used in maths below
      SYMBOL temp2     = b10 ; used in maths below
;
; -----[ Program Code ]----------------------------------------------------
;
Init: I2CSLAVE expand_0, i2cslow, i2cbyte ; for PCF8574 8-bit IO Expander
;
;  enable below 2 lines in lieu of above line for MCP23016      
;     i2SLAVE  expand_0, i2fast,  i2cbyte ; for MCP23016 16-bit IO Expander
;     WRITEI2C [expand_0], 6,($00,$00) ; set IRDIR0 & IRDIR1 direction to output
;
      directn         = 0   ; define display direction here or go get value
      indtype         = 0   ; define display type here or go get value
      delaytime       = 1500
      NoLEDs          = 8   ; define number of LED&#8217;s in display here
;

Main: READADC adc_ldr, litelvl  ; can be from some other source or variable
;              replace the READADC instruction with means to get your value      

      temp1 = litelvl * NoLEDs / divisor 
      pattern = 1  
      FOR temp2 = 1 TO  temp1
         pattern = pattern << 1
      NEXT
;
;     set pattern up for bar or dot according to definitions previously set
;
      IF indtype = 0 THEN       ; (0 = bar, otherwise = dot)
         pattern = pattern -1   ; sets all lower level LED&#8217;s on
      ELSEIF indtype = 1 THEN            
         Pattern = pattern / 2  ; if want single dot/LED at lower position)
      ENDIF                     ; nothing done if dot at higher pos&#8217;n okay
;
;
;    arrange pattern to move left or right as previously set above
;
     IF directn = 1 THEN        ; 0 = right to left, otherwise = left to right
         Pattern = pattern REV NoLEDs
      ENDIF                  ; nothing to do if left to right required
;
      GOSUB Pattn2led
      PAUSE delaytime
      GOTO Main
;      
;
; -----[ Subroutines ]-----------------------------------------------------
;
Pattn2led:
       IF NoLEDs = 8 THEN      ; here if there are only 8 LED&#8217;s
          WRITEI2C expand_0, (pattnLo)
       ELSE                   ; here if have an MCP23016  and > 8 LEDs
          WRITEI2C expand_0, (pattnHi, pattnLo)
       ENDIF
       RETURN
;==============================================
and here is a version for the X1 PICAXE parts . . .
Code:
; -----[ Constants ]----------------------------------------------------
;
      SYMBOL adc_ldr  = 6   ; light dependant resistor analogue input
;                             maybe some other device or source for value
;
      SYMBOL expand_0 = %01000000 ; %0100 = Chip ID, 000 = Addr - For LED's
;
;
      SYMBOL divisor  = 255 ; divisor=(2^NoLEDs)&#8211;1
                            ; based on value in the range 0 to 255, 
                            ; if say 10 bits and NoLEDs = 10 then divisor = 1023
;
; -----[ Variables ]----------------------------------------------------
;
      SYMBOL NoLEDs    = b1
      SYMBOL litelvl   = b2 ; change name throughout to suit source of
                             ; the value you are basing indication upon
      SYMBOL delaytime = b3
      SYMBOL directn   = b4  ; 0 = right to left, >0 = left to right
      SYMBOL indtype   = b5  ; 0 = bar graph, 1= for lower &#8216;level&#8217; dot posn
                             ; and >1 for higher &#8216;level&#8217; dot position
      SYMBOL pattern   = w3  ; b7 + b6 used here for up to 16 bits/LEDs
;
      SYMBOL pattnHi   = b7  ; for access to individual bytes of pattern
      SYMBOL pattnLo   = b6

      SYMBOL temp1     = w4  ; b9 and b8 used in maths below
      SYMBOL temp2     = b10 ; used in maths below
;
; -----[ Program Code ]----------------------------------------------------
;
Init: HI2CSETUP i2cmaster, expand_0, i2cslow, i2cbyte ; for PCF8574 8-bit IO Expander

;
;  enable below 2 lines in lieu of above line for MCP23016      
;     HI2CSETUP  expand_0, i2fast,  i2cbyte ; for MCP23016 16-bit IO Expander
;     HI2COUT   [expand_0], 6,($00,$00) ; set IRDIR0 & IRDIR1 direction to output
;
      directn         = 0   ; define display direction here or go get value
      indtype         = 1   ; define display type here or go get value
      delaytime       = 2500
      NoLEDs          = 8   ; define number of LED&#8217;s in display here
;

Main: READADC adc_ldr, litelvl  ; can be from some other source or variable
;              replace the READADC instruction with means to get your value      
      temp1 = litelvl * NoLEDs / divisor 
      pattern = 1  
      FOR temp2 = 1 TO  temp1
         pattern = pattern << 1
      NEXT
;
;     set pattern up for bar or dot according to definitions previously set
;
      IF indtype = 0 THEN       ; (0 = bar, otherwise = dot)
         Pattern =  DCD temp1 - 1  ; sets all lower level LED&#8217;s on
      ELSEIF indtype = 1 THEN            
         Pattern = DCD temp1 >> 1  ; if want single dot/LED at lower position)
      ENDIF                     ; nothing done if dot at higher pos&#8217;n okay
;
;
;    arrange pattern to move left or right as previously set above
;
     IF directn = 1 THEN        ;(0 = right to left, otherwise = left to right)
         Pattern = pattern REV NoLEDs
      ENDIF                  ; nothing to do if left to right required
;
      GOSUB Pattn2led
      PAUSE delaytime
      GOTO Main
;      
;
; -----[ Subroutines ]-----------------------------------------------------
;
Pattn2led:
       IF NoLEDs = 8 THEN     ; here if there are only 8 LED&#8217;s
          HI2COUT expand_0, (pattnLo)
       ELSE                   ; here if have an MCP23016  and > 8 LEDs
          HI2COUT expand_0, (pattnHi, pattnLo)
       ENDIF
       RETURN
&#8216;=====================================================================
The whole purpose of this thread started as a means to demonstrate ONE method of generating a bar-graph or similar indication using a row of LED's based upon mathmatical calculation methods.

As before I accept that:
1 there are other means of achieving the same results using IF...THEN and EEPROM lookup tables
2. that other means of achieving the same fuctionality may be faster

3. the code will need to be adjusted to suit the parameter/variable you wish to provide indication for
4. for X1 parts, same can be done using improved math functions (see my post 4)
5. those using 74HC595 shift registers or bit bash techniques to get data out of their PICAXE will need to rewrite the subroutine Pattn2led

This code is an example of what I have started to do for indication using a row of LED's on my "Experimenters Box".
I provide no warranty/garantee that the code given is fit for or the best possible code for your purpose/application
 
Last edited:

westaust55

Moderator
Hi myCroft,

No in fact I had not looked at the LM3914. Thanks for the link. Could be quite a useful IC and something to keep in mind for any future projects.

In my case my row of LED's is multifunction and includes:

1. use as a bar graph to reflect an analogue signal (as per the code I have shown in this thread, and

2. use as a form of decimal or hex to binary conversion display to show visually what a value looks like in binary (as 1's and zero's).
As I have a small 48 key keypad (with shift function giving almost the full alpha-numeric character set) I can enter a value as decimal or hex and display it in binary on the LED's.

Thats an interesting mental exercise, "Can i make a PICAXe do ..."
That to me is a good portion of the my purpose behind having the PICAXE as a hobby. I used to do such things in machine code a few decades ago before PC's became available. (age starting to show here :eek:)
 

Mycroft2152

Senior Member
That to me is a good portion of the my purpose behind having the PICAXE as a hobby. I used to do such things in machine code a few decades ago before PC's became available. (age starting to show here :eek:)
W,

I'm a member of the same club :)

I tend to use Occam's razor more and more lately.

Myc

We'll let the youngsters google that one.
 
Top