Thoughts on adding additional I/O

Mark.R

Member
Just been having a route about the forum and the picaxe website in the FAQ section and just wondered what fellow forumers (if that's a word) thoughts are on adding I/O.

There are the following options from what I can see:-

1. Use a bigger picaxe chip (probably the easiest thing to do)
2. MCP23017 I2C I/O expander
3. MCP23S17 SPI I/O expander
4. Using a second picaxe chip.

The last one, using a second picaxe I find quite interesting but from the looks of the example code given you can ether have the extra chip as an output or an input expansion and not a combination of the two.

Extra Outputs
Code:
        let dirsb = $FF
main:   serin C.0, N2400, b1
        let pinsb = b1
        goto main
Extra Inputs
Code:
        ; Input reading PICAXE
 main:  b1 = pinsC
        serout B.7, N2400, (b1)
        pause 10
        goto main
With all of these expansion options other than just using a larger picaxe how would you use these in a program as it doesn't look as simple as you cant just go "High C.1" to turn an output on and say "if pinB.1 then" for looking at an input?
 

Solar Mike

New Member
Depends somewhat on what extra IO are you wanting, Digital inputs\outputs or Analog and the required processing speed.
The port expanders you mentioned are digital logic only, if you require Analog readings then an analog multiplexer chip can be used, usually 4:16 or 3:8 are common.

If you are doing stuff with background PWM with the Picaxe, the I2C may interfere with the PWM, in which case I would use the SPI interface.

For fast signal processing, a bigger chip is probably easiest option; or distribute the processing load to a second cpu and use an interrupt scheme to tell the other when to do something with the data which can be sent serially between the two.

Cheers
Mike
 

premelec

Senior Member
@Mark.R you can add varying an analog input to indicate various states to the list - IF you can stand the extra time to "see" and process an analog pin state. An example is reading a 12 button matrix with one pin. There are caveats... ;-0
 

lbenson

Senior Member
As said, it depends on your requirements. Here's a thread about controlling 45 12V LEDs on a 24-hour timed schedule, with 256 possible turn-on times.

 

Mark.R

Member
Morning chaps, thanks for your replies to the thread, I will have a read through the posted links.

I wasn't looking at anything as complicated as analogue expansion (not yet anyway) just digital I/O. I was thinking that my 12 ish relays might be nicer fed from some sort of I/O expansion chip rather than just putting a bigger Picaxe in if I didn't need to. I have a 40X2 in stock which I may use as the main controller in my project to run all digital outputs and a few digital inputs, all the analogue temperature monitoring are done by separate Picaxe chips and sent to the pain one over serin/serout.

Mark
 

Aries

New Member
If you only need output from (and not input to) the Picaxe, you could consider using a series-to-parallel shift register like the 74HC595. This needs only 3 or 4 output pins on the Picaxe, and gives 8 digital outputs per shift register; they can be daisy-chained to any length, without needing any more Picaxe pins. If you are driving relays, you would then need suitable Darlington or transistor drivers as well (but you probably would anyway).
 

tmfkam

Senior Member
If you only need output from (and not input to) the Picaxe, you could consider using a series-to-parallel shift register like the 74HC595. This needs only 3 or 4 output pins on the Picaxe, and gives 8 digital outputs per shift register; they can be daisy-chained to any length, without needing any more Picaxe pins. If you are driving relays, you would then need suitable Darlington or transistor drivers as well (but you probably would anyway).
Or a TPIC high power output 595? Less available, and if memory serves, a different pin out to the standard '595, but a great option.
 

Mark.R

Member
Hi, question about the code I've written below.

It sets up two MCP23017's to be all outputs. I've used b0 to b3 so that I can use the "bits" in my program as if you would an output on a picaxe it's self, so instead of say High C.7 I can do High MCP1_A0 (bit0 of b0) which gets sent to the first of the two MCP23017 and port A output 0 turns on. Question is, am I only limited to only having two of these chips as I can only direct access 31 bits?

Code:
;BYTE VARIABLES SYMBOL LIST
Symbol MCP1_A    = b0        ;Used for bits for MCP23017_1 GPIO "A" outputs
Symbol MCP1_B    = b1        ;Used for bits for MCP23017_1 GPIO "B" outputs
Symbol MCP2_A    = b2        ;Used for bits for MCP23017_2 GPIO "A" outputs
Symbol MCP2_B    = b3        ;Used for bits for MCP23017_2 GPIO "B" outputs
  
   ;BIT VARIABLE SYMBOL LIST
Symbol MCP1_A0    = bit0    ;MCP23017_1 Output A0
Symbol MCP1_A1    = bit1    ;MCP23017_1 Output A1
Symbol MCP1_A2    = bit2    ;MCP23017_1 Output A2
Symbol MCP1_A3    = bit3    ;MCP23017_1 Output A3
Symbol MCP1_A4    = bit4    ;MCP23017_1 Output A4
Symbol MCP1_A5    = bit5    ;MCP23017_1 Output A5
Symbol MCP1_A6    = bit6    ;MCP23017_1 Output A6
Symbol MCP1_A7    = bit7    ;MCP23017_1 Output A7
Symbol MCP1_B0    = bit8    ;MCP23017_1 Output B0
Symbol MCP1_B1    = bit9    ;MCP23017_1 Output B1
Symbol MCP1_B2    = bit10    ;MCP23017_1 Output B2
Symbol MCP1_B3    = bit11    ;MCP23017_1 Output B3
Symbol MCP1_B4    = bit12    ;MCP23017_1 Output B4
Symbol MCP1_B5    = bit13    ;MCP23017_1 Output B5
Symbol MCP1_B6    = bit14    ;MCP23017_1 Output B6
Symbol MCP1_B7    = bit15    ;MCP23017_1 Output B7
Symbol MCP2_A0    = bit16    ;MCP23017_2 Output A0
Symbol MCP2_A1    = bit17    ;MCP23017_2 Output A1
Symbol MCP2_A2    = bit18    ;MCP23017_2 Output A2
Symbol MCP2_A3    = bit19    ;MCP23017_2 Output A3
Symbol MCP2_A4    = bit20    ;MCP23017_2 Output A4
Symbol MCP2_A5    = bit21    ;MCP23017_2 Output A5
Symbol MCP2_A6    = bit22    ;MCP23017_2 Output A6
Symbol MCP2_A7    = bit23    ;MCP23017_2 Output A7 
Symbol MCP2_B0    = bit24    ;MCP23017_2 Output B0
Symbol MCP2_B1    = bit25    ;MCP23017_2 Output B1
Symbol MCP2_B2    = bit26    ;MCP23017_2 Output B2
Symbol MCP2_B3    = bit27    ;MCP23017_2 Output B3
Symbol MCP2_B4    = bit28    ;MCP23017_2 Output B4
Symbol MCP2_B5    = bit29    ;MCP23017_2 Output B5
Symbol MCP2_B6    = bit30    ;MCP23017_2 Output B6
Symbol MCP2_B7    = bit31    ;MCP23017_2 Output B7

  ;I2C SYMBOL LIST
SYMBOL MCP23017_1    = %01000000    ;$0100 A2, A1, A0, R/W all connected to 0V
SYMBOL MCP23017_2 = %01000010    ;$0100 A2, A1,R/W low, A0 high
SYMBOL IODIRA    = $00        ;Port A IO Direction register DEFAULT = I/P
SYMBOL IODIRB    = $01        ;Port B IO Direction register DEFAULT = I/P
SYMBOL IOCON    = $0A        ;IO Expander config register - address $0B accesses the same register
SYMBOL GPIOA    = $12        ;Port A General purpose register
SYMBOL GPIOB    = $13        ;Port B General Purpose register
SYMBOL OLATA    = $14        ;Port A latch register
SYMBOL OLATB    = $15        ;Port B latch register


  ;PROGRAM
Init: 
Pause 1000
HI2CSETUP i2cmaster, MCP23017_1, i2cfast_4, i2cbyte
PAUSE 100
HI2COUT IODIRA, ($00) ;set all port A pins as outputs (0)
HI2COUT IODIRB, ($00) ;set all port B pins as outputs (0)
HI2COUT GPIOA,  ($00) ;all outputs in port A OFF
HI2COUT GPIOB,  ($00) ;all outputs in port B OFF
HI2CSETUP i2cmaster, MCP23017_2, i2cfast_4, i2cbyte
PAUSE 100
HI2COUT IODIRA, ($00) ;set all port A pins as outputs (0)
HI2COUT IODIRB, ($00) ;set all port B pins as outputs (0)
HI2COUT GPIOA,  ($00) ;all outputs in port A OFF
HI2COUT GPIOB,  ($00) ;all outputs in port B OFF

Do

   HI2CSETUP i2cmaster, MCP23017_1, i2cfast_4, i2cbyte
    PAUSE 100   
    HI2COUT GPIOA, (b0)
    HI2COUT GPIOB, (b1)
   HI2CSETUP i2cmaster, MCP23017_2, i2cfast_4, i2cbyte
    PAUSE 100   
    HI2COUT GPIOA, (b2)
    HI2COUT GPIOB, (b3)

Loop
 

westaust55

Moderator
What external device you use can also be influenced by the required speed.
On non X1 and D2 parts there is the need to bit-bang the data out for serial data such as to the 74HC595
With the X1 and X2 parts ther is the inbuilt SHIFTOUT command which is somewhat faster.
When using the MCP23017, the i2c command is much faster again.
If I recall correctly (from speed test long ago for an 8x8 RGB display) the i2c data transfer is around 4 times faster.
 

westaust55

Moderator
Since you have setup the MCP23017 to have the GPIO registers a consecutive addresses in your code above you can combine the transfer of the two bytes of data into a single Hi2COUT command.

the way you are using the bit variables then yes 32 bits and 2 MCP23017 would be the limit.
However if you hold the working data in other better variables (that is higher than b3) and just use one or more of the b0 to b3 as working registers transferring data from the higher byte variables to say b0, undertake the bit changes and transfer back to the stored byte variable the code will be more complex.
But then you can have up to the maximum of 8 of the MCP23017 IO expanders if necessary.
 

Buzby

Senior Member
You can access more pins on your IO chips if you use a subroutine to calculate the chip and pin number.

The code below is a rough idea of how to control 64 bits, which you then send to your MCPs. It can be improved by using indirection ( using @ ), but it's too late tonight for me to do that now.

The bytes 5-12 hold the 64 bits for the MCPs., the two subroutines set or clear a bit depending on MCbit value, 0-63.

Also, your idea of using HIGH and LOW won't work, these commands only apply to real PICAXE IO pins, not bits.

Code:
' Code to set bits 0 - 63

Symbol MCP1_A        = b5         ; 0 Used for bits for MCP23017_1 GPIO "A" outputs  0 -  7
Symbol MCP1_B        = b6         ; 1 Used for bits for MCP23017_1 GPIO "B" outputs  8 - 15
Symbol MCP2_A       = b7         ; 2 Used for bits for MCP23017_2 GPIO "A" outputs 16 - 23
Symbol MCP2_B        = b8         ; 3 Used for bits for MCP23017_2 GPIO "B" outputs 24 - 31
Symbol MCP3_A        = b9         ; 4 Used for bits for MCP23017_3 GPIO "A" outputs 32 - 39
Symbol MCP3_B        = b10        ; 5 Used for bits for MCP23017_3 GPIO "B" outputs 40 - 47
Symbol MCP4_A        = b11        ; 6 Used for bits for MCP23017_4 GPIO "A" outputs 48 - 55
Symbol MCP4_B        = b12        ; 7 Used for bits for MCP23017_4 GPIO "B" outputs 56 - 63

Symbol MCbit    = b13        ' Workspace, overall bit number ( 0 to 63 )
Symbol MC_bit    = b14        ' Workspace, individual bit number ( 0 to 7 )
Symbol MC_byte    = b15        ' Workspace, MC byte number ( 0 to 7 )

[code]

' Test code starts here
' ---------------------
do
    MCbit = 0 : gosub SetMCbit
    MCbit = 1 : gosub SetMCbit
    MCbit = 2 : gosub SetMCbit

    MCbit = 61 : gosub SetMCbit
    MCbit = 62 : gosub SetMCbit
    MCbit = 63 : gosub SetMCbit


    MCbit = 0 : gosub ClearMCbit
    MCbit = 1 : gosub ClearMCbit
    MCbit = 2 : gosub ClearMCbit

    MCbit = 61 : gosub ClearMCbit
    MCbit = 62 : gosub ClearMCbit
    MCbit = 63 : gosub ClearMCbit

loop

' Subroutines
' -----------
SetMCbit:
    MC_byte = mcbit / 8
    MC_bit  = mcbit // 8
    if MC_byte = 0 then setbit MCP1_A,MC_bit : return : endif
    if MC_byte = 1 then setbit MCP1_B,MC_bit : return : endif
    if MC_byte = 2 then setbit MCP2_A,MC_bit : return : endif
    if MC_byte = 3 then setbit MCP2_B,MC_bit : return : endif
    if MC_byte = 4 then setbit MCP3_A,MC_bit : return : endif
    if MC_byte = 5 then setbit MCP3_B,MC_bit : return : endif
    if MC_byte = 6 then setbit MCP4_A,MC_bit : return : endif
    if MC_byte = 7 then setbit MCP4_B,MC_bit : return : endif
return

ClearMCbit:
    MC_byte = mcbit / 8
    MC_bit  = mcbit // 8
    if MC_byte = 0 then clearbit MCP1_A,MC_bit : return : endif
    if MC_byte = 1 then clearbit MCP1_B,MC_bit : return : endif
    if MC_byte = 2 then clearbit MCP2_A,MC_bit : return : endif
    if MC_byte = 3 then clearbit MCP2_B,MC_bit : return : endif
    if MC_byte = 4 then clearbit MCP3_A,MC_bit : return : endif
    if MC_byte = 5 then clearbit MCP3_B,MC_bit : return : endif
    if MC_byte = 6 then clearbit MCP4_A,MC_bit : return : endif
    if MC_byte = 7 then clearbit MCP4_B,MC_bit : return : endif
return
 
Last edited:

AllyCat

Senior Member
Hi,

Note that SETBIT and CLEARBIT, etc. are X2-only instructions, but I don't think they're much more than "System Macro" commands as they use about 6 bytes of program space each. However, you can use the "bitwise" Logical operators to Set or Clear bits in ANY register, either directly or with a "mask" constant or variable. For example b7 = b7 OR $10 will Set bit 4 in b7 and b8 = b8 ANDNOT $20 will Clear bit 5 in b8. For a subroutine, you might shift a single bit (1) leftwards through a mask by repeated self-additions mask = mask + mask (which is marginally faster than mask = mask * 2) in any PICaxe. But I won't repeat all that I wrote in Post #3 (and posts # 11 and #17) of a thread on a similar topic just a few weeks ago.

Cheers, Alan.
 

erco

Senior Member
Adding a second Picaxe fully independent of the first one is easy and ideal if you cab split the job completely in two. But connecting them to communicate steadily back & forth to share data gets messy real fast. Programming two chips to talk is ten times more work than rewiring a bigger chip. Consumes your time, and the chips' memory & bandwidth.

Stretching a single chip to its limits is kinda fun sometimes if you have a sense of adventure and some time. Occasionally I'll multiplex an output using a simple decoder chip if I have multiple outputs but only one needs to be on at a time, such as chasing LEDs or similar. 3 picaxe outputs can select one of 8 output pins on a 74LS138 decoder, or 4 picaxe outputs can select one of 16 output pins on a 74LS154 decoder. Just hard wire all the enable pins and connect the picaxe to the decoder's address bus.


I used two 138 decoders (and 3 picaxe outputs each) to run these LEDs.

 
Last edited:

Mark.R

Member
This works in the PE6 simulator as an idea of turning a bit on and off from a picaxe input, this being part of b0 would the be sent to the MCP23017 and in theory output A0 would turn on.

Code:
if pinB.6 = 1 then let bit0 = 1 else let bit0 = 0
    endif
Also, your idea of using HIGH and LOW won't work, these commands only apply to real PICAXE IO pins, not bits.
I didn't think it would be that easy haha

However if you hold the working data in other better variables (that is higher than b3) and just use one or more of the b0 to b3 as working registers transferring data from the higher byte variables to say b0, undertake the bit changes and transfer back to the stored byte variable the code will be more complex.
I like this and have been thinking about it, will get the pencil and paper out I think.
 

AllyCat

Senior Member
Hi,
Code:
if pinB.6 = 1 then : bit0 = 1 : else : bit0 = 0
    endif
Can be written more compactly as:
Code:
bit0 = pinb.6
Personally, I nearly always reserve b1 (or b0) and/or w1 as "local" variables, to use whenever they're needed (e.g. to read or write bits in another variable). But if (e.g.) b0 is in use, you could still set any bit(s) in ANY variable using, for example:
Code:
swap b0 , anyvar     ; Or could use W0
bit0 = pinb.6
swap b0 , anyvar
However, SWAP is rather greedy of program bytes, so if necessary you can do a little better using a Macro:
Code:
#macro myswap(var)
var = var XOR b0
b0 = b0 XOR var
var = var XOR b0
#endmacro
;  Then used for Example as :
myswap(anyvar)
bit0 = pinb.6     ; Change any bit(s) as required
myswap(anyvar)
It's not "obvious" how the XORs work, but they do. ;)

Cheers, Alan.
 

Buzby

Senior Member
Here is what I hope will be the core of my future project. It needs to operate 64 outputs using 4 MCP23017 chips.

It's not an 8x8 LED array or anything like that, it needs to switch pins in sequences that are not preset.
Also, I will need to read some pins immediatley after setting some others, so the effect of a pin-write must be instant, I can't have a seperate routine handling all the I2C stuff at once.

The code uses two macros, one to turn a pin On, and the other to turn it Off. These macros are just to make the final code easier to read, I won't need lots of bit shuffling to get the values needed by the subroutines called by the macros.

The subroutines do all the clever stuff. They take the pin number (0-63) and use that to calculate the addresses for the bitmap, update the bitmap, calculate the chip and register addresses, then send using I2C to the relevant MCP register.

The code only needs 3 variables for calculations, and 8 bytes for the bitmaps.
NOTE : b5 is defined twice ( This makes the code easier to follow.)

I've not got any MCP chips yet, but it all looks fine in the simulator.

The PICAXE needs to be an X2, as it uses 'setbit' and 'newslave'.

Code:
' Code to set bits 0 - 63 in four MCP23017 chips

Symbol mcbit    = b4        ' MC bit number, 0-63  
Symbol bitnum    = b5        ' Bit number, 0-7, in bitmap : Same
symbol regaddr    = b5        ' Register address in slave  : byte
symbol chipaddr   = b6        ' Chip address of slave

' Bitmaps stored here
' b8        0 - 7        :
' b9        8 - 15    :
' b10        16 - 23    : Relevant Bitmap = MC bitnum / 8 + 8
' b11        24 - 31    :
' b12        32 - 39    :
' b13        40 - 47    :
' b14        48 - 55    :
' b15        56 - 63    :


' MCP addresses, set by hardwiring on A0,A1,A2
' %01000000    Slave 0    :
' %01000010    Slave 1    : chipaddr = slavenum * 2 + 64
' %01000100    Slave 2    :
' %01000110 Slave 3    :


' I2C control addresses
SYMBOL IODIRA    = $00        'Port A IO Direction register 
SYMBOL IODIRB    = $01        'Port B IO Direction register 
SYMBOL IOCON     = $0A        'IO Expander config register
SYMBOL GPIOA     = $12        'Port A IO register
SYMBOL GPIOB     = $13        'Port B IO register
SYMBOL OLATA     = $14        'Port A latch register
SYMBOL OLATB     = $15        'Port B latch register


' Macro definitions
' -----------------
#macro MC_On(mcpin)
    mcbit = mcpin
    gosub SetMCbit
#endmacro

#macro MC_Off(mcpin)
    mcbit = mcpin
    gosub ClearMCbit
#endmacro


' Test code starts here
' ---------------------

' Send filename to terminal,
sertxd(ppp_filename,cr,lf)  ' helps if you forget whats in your chip !

' Setup I2C master
HI2CSETUP i2cmaster, %01000000, i2cfast_4, i2cbyte ' Slave number irrelevant at this point

' Configure MCPs
for b0 = %01000000 to %01000110 step 2
    ' Configure IO pins
    HI2COUT [b0], IODIRA, ($00) ;set all port A pins as outputs (0)
    HI2COUT [b0], IODIRB, ($00) ;set all port B pins as outputs (0)
    HI2COUT [b0], GPIOA,  ($00) ;all outputs in port A OFF
    HI2COUT [b0], GPIOB,  ($00) ;all outputs in port B OFF
next

' Loop to set bits for testing
do
     ' Turn bits on
    MC_On(0)    ' First 3 bits of chip 0 reg A
    MC_On(1)
    MC_On(2)

    MC_On(8)    ' First 3 bits of chip 0 reg B
    MC_On(9)
    MC_On(10)
    
    MC_On(16)    ' First 3 bits of chip 1 reg A
    MC_On(17)
    MC_On(18)    
    
    MC_On(24)    ' First 3 bits of chip 1 reg B
    MC_On(25)
    MC_On(26)    
        
    MC_On(32)    ' First 3 bits of chip 2 reg A
    MC_On(33)
    MC_On(34)    
    
    MC_On(40)    ' First 3 bits of chip 2 reg B
    MC_On(41)
    MC_On(42)    
        
    MC_On(48)    ' First 3 bits of chip 3 reg A
    MC_On(49)
    MC_On(50)    
    
    MC_On(56)    ' First 3 bits of chip 3 reg B
    MC_On(57)
    MC_On(58)    

' ---------------------------
    ' Turn bits off
    MC_Off(0)    ' First 3 bits of chip 0 reg A
    MC_Off(1)
    MC_Off(2)

    MC_Off(8)    ' First 3 bits of chip 0 reg B
    MC_Off(9)
    MC_Off(10)
    
    MC_Off(16)    ' First 3 bits of chip 1 reg A
    MC_Off(17)
    MC_Off(18)    
    
    MC_Off(24)    ' First 3 bits of chip 1 reg B
    MC_Off(25)
    MC_Off(26)    
        
    MC_Off(32)    ' First 3 bits of chip 2 reg A
    MC_Off(33)
    MC_Off(34)    
    
    MC_Off(40)    ' First 3 bits of chip 2 reg B
    MC_Off(41)
    MC_Off(42)    
        
    MC_Off(48)    ' First 3 bits of chip 3 reg A
    MC_Off(49)
    MC_Off(50)    
    
    MC_Off(56)    ' First 3 bits of chip 3 reg B
    MC_Off(57)
    MC_Off(58)    

    ' Bits on fwd
    for b16 = 0 to 63
        MC_On(b16)
    next

    ' Bits off bwd
    for b16 = 63 to 0 step -1
        MC_Off(b16)
    next

loop    ' End of test code


' Subroutines
' -----------
SetMCbit:
    bptr = mcbit / 8 + 8        ' Point bptr to relevant bitmap
    bitnum = mcbit // 8         ' Make bit number 0-7
      setbit @bptr,bitnum        ' Set bit in bitmap
    chipaddr = mcbit / 8 * 2 + 64    ' Make chip addr  
    regaddr = bptr & $01 + $12    ' Make register address $12/$13 from odd/even bitmap address
    hi2cout [chipaddr],regaddr,(@bptr) ' Send to MCP
return    

ClearMCbit:
    bptr = mcbit / 8 + 8
    bitnum = mcbit // 8 
      clearbit @bptr,bitnum
    chipaddr = mcbit / 8 * 2 + %01000000
    regaddr = bptr & $01 + $12
    hi2cout [chipaddr],regaddr,(@bptr)
return
 

stan74

Senior Member
I've used port expander chips to run 2 stepper motors. 2 pins instead of 8
They're ok for some things but not for a glcd.
 

lbenson

Senior Member
If you can test it on a real set of MCPs I'd be really pleased. Mine have not arrived yet.
I'm sorry, but I'm not set up to do that. My Christmas tree of a setup with multiple MCP23017s & LEDs is 3200 kilometers away across a closed boarder. I have a couple of the MCP23017s, but not set up for different addresses and without attached LEDs. If I get time, I'll see what I can put together.
 

Buzby

Senior Member
I'm sorry, but I'm not set up to do that ...
No probs, I've ordered some, just waiting for delivery. As it is, I've got a couple of other projects to finish first, so I won't get to try before Christmas anyway.

Thanks,

Buzby
 
Top