MCP23S17 - 16-Bit I/O Expander with SPI Interface

caganjan

New Member
Hi all.
I needed try to communicate via SPI. I choosed 16-Bit I/O Expander with SPI Interface for my HalloWorld. So, there is a simple example, how to use MCP23S17 via SPI. Circuit is connected according to Figure 1.
I am hoping, that it will be usefull for other people, which want to start communicate via SPI, or which need expand their ports.
Have a nice day ;)

Ps:
If it will read some PICAXE GURU, which will find any bug or big inefficiency, i will very happy if he tells me a new idea or corrections. If it will read english native speaker, and he will find some big grammar mistake, i will happy for correction too. :)

Program code:
Code:
;**************************************************
;AUTHOR: CAGI, DATE: 25.5.2012
;**************************************************
;PROGRAM DESCRIPTION:
;This program offer simple example, how to use
;16-Bit I/O Expander with SPI Interface. There
;are two MCP23S17 controled by 40X2. First of them
;(via cable select CS1) is set like an input. Second
;(via cable select CS2) is set like an output.
;Program transfer states from intput expander
;to output expander only. It help 

;**************************************************
;PROCESSOR SETTING
	#PICAXE 40X2

;**************************************************
;CLOCK SETTING
;8MHz external oscilator (32MHz after PPL multiplying)
	setfreq em32	

;**************************************************
;IO SETTING
;bits order:       %01234567;1=out, 0=in
	let dirsA =  %00000100
	let dirsD =  %00000000

;**************************************************
;DEFINITIONS
	symbol LED=A.5
	symbol CS1=D.3
	symbol CS2=D.2
	symbol IOCON=$0A
	symbol IODIRA=$00
	symbol IODIRB=$01
	symbol GPPUA=$0C
	symbol GPPUB=$0D
	symbol GPIOA=$12
	symbol GPIOB=$13
	symbol SPIwrite=%01000000	;bit7: write->R/W=0,read->R/W=1
	symbol SPIread=%01000001	;MCP23S17 address pins disabled
	symbol SPIdata=b0

;**************************************************
;SPI SETTING
	HSPISETUP spimode00, spifast
;Disabling sequential operation (automatic address inceremnting)
	low CS1
	hspiout (SPIwrite,IOCON)
	hspiout (%00100000)	
	high CS1

	low CS2
	hspiout (SPIwrite,IOCON)
	hspiout (%00100000)	
	high CS2
;Setting expander 1 like an input
	low CS1				;enable chip select
	hspiout (SPIwrite,IODIRA)	;choosing register to write
	hspiout (%11111111)		;data are clocked in to expander	
	high CS1				;disable chip select
	
	low CS1
	hspiout (SPIwrite,IODIRB)
	hspiout (%11111111)	
	high CS1
;Setting expander 2 like an output
	low CS2
	hspiout (SPIwrite,IODIRA)
	hspiout (%00000000)	
	high CS2	

	low CS2
	hspiout (SPIwrite,IODIRB)
	hspiout (%00000000)
	high CS2
;Enabling PullUp resistors at expamder 1
	low CS1
	hspiout (SPIwrite,GPPUA)
	hspiout (%11111111)	
	high CS1

	low CS1
	hspiout (SPIwrite,GPPUB)
	hspiout (%11111111)	
	high CS1

;**************************************************
;MAIN LOOP

main: 
	toggle LED

	low CS1				;enable chip select
	hspiout (SPIread,GPIOA)		;choosing register to read	
	hspiin (SPIdata)			;clocked out data from expander
	high CS1				;disable chip select

	low CS2
	hspiout (SPIwrite,GPIOA)
	hspiout (SPIdata)
	high CS2	

	low CS1
	hspiout (SPIread,GPIOB)
	hspiin (b1)
	high CS1

	low CS2
	hspiout (SPIwrite,GPIOB)
	hspiout (b1)	
	high CS2	
		
	pause 100
	debug
	
	goto main

;**************************************************
;END
Figure 1.JPG
 

nick12ab

Senior Member
Welcome to the PICAXE Forum.

Ps:
If it will read some PICAXE GURU, which will find any bug or big inefficiency, i will very happy if he tells me a new idea or corrections.
...
View attachment 11331
In the code, for the 'SPI SETTING' part, an alternative way of doing it would be to use a FOR : NEXT loop and use lookups (or table memory) to determine which number to send via SPI. I don't think your setup code is long enough for doing this to create a significant program memory saving though, but it would make the code visually smaller and it would allow easier expansion if you needed to add extra setup parts in the future.

For the circuit itself, it is a good idea to include decoupling capacitors.

If it will read english native speaker, and he will find some big grammar mistake, i will happy for correction too. :)
Your English isn't terrible, mostly just missing out the word "the" and not using the "ing" ending where necessary. It is clear enough for native speakers to know exactly what you're saying, with the possible exception of "If it will read".

As you asked for a correction, this is my correction of what you said:
"Hi all.
I needed to communicate via SPI. I chose a 16-Bit I/O Expander with SPI Interface for my HelloWorld. So, here is a simple example of how to use the MCP23S17 via SPI. The circuit is connected according to Figure 1.
I am hoping, that it will be useful for other people who want to start communicating via SPI or expand their ports.
Have a nice day ;)

Ps:
If this is read by a PICAXE guru, who finds bugs or big inefficiencies, I will be very happy if he tells me a new idea or corrections. If this is read by an English native speaker, and he finds some big grammar mistake, I will happy for some corrections too. :)
"
 

westaust55

Moderator
Well done on creating your project using the MCP23S17.

Many here have used the similar MCP23017 (i2c version) but I do not recall many posts for the MCP23S17 (SPI version).

Your code is well documented and well formatted with code indented/tabulated leaving only the labels in the first column. :)
 

caganjan

New Member
Welcome to the PICAXE Forum.

In the code, for the 'SPI SETTING' part, an alternative way of doing it would be to use a FOR : NEXT loop and use lookups (or table memory) to determine which number to send via SPI. I don't think your setup code is long enough for doing this to create a significant program memory saving though, but it would make the code visually smaller and it would allow easier expansion if you needed to add extra setup parts in the future.

For the circuit itself, it is a good idea to include decoupling capacitors.

Your English isn't terrible, mostly just missing out the word "the" and not using the "ing" ending where necessary. It is clear enough for native speakers to know exactly what you're saying, with the possible exception of "If it will read".

As you asked for a correction, this is my correction of what you said:
"Hi all.
I needed to communicate via SPI. I chose a 16-Bit I/O Expander with SPI Interface for my HelloWorld. So, here is a simple example of how to use the MCP23S17 via SPI. The circuit is connected according to Figure 1.
I am hoping, that it will be useful for other people who want to start communicating via SPI or expand their ports.
Have a nice day ;)

Ps:
If this is read by a PICAXE guru, who finds bugs or big inefficiencies, I will be very happy if he tells me a new idea or corrections. If this is read by an English native speaker, and he finds some big grammar mistake, I will happy for some corrections too. :)
"
Thank you nick12ab for your corrections.

I have already tried to use the table for better arrangement of the registers and their values.
Did I use TABLE according to your idea?
The setting loops can be realised like one loop with switching of cable select, but it does not matter in this example I think.
Here can be used sequential writing too, i think. But this speciality i am leaving for now.

Here is the program code:

Code:
;**************************************************
;AUTHOR: CAGI, DATE: 25.5.2012
;**************************************************
;PROGRAM DESCRIPTION:
;This program offer simple example, how to use
;16-Bit I/O Expander with SPI Interface. There
;are two MCP23S17 controled by 40X2. First of them
;(via cable select CS1) is set like an input. Second
;(via cable select CS2) is set like an output.
;Program transfer states from intput expander
;to output expander only. Here is used TABLE
;for well-arranged organization of setting registers
;and their values.

;**************************************************
;PROCESSOR SETTING
	#PICAXE 40X2

;**************************************************
;CLOCK SETTING
;8MHz external oscilator (32MHz after PPL multiplying)
	setfreq em32	

;**************************************************
;IO SETTING
;bits order:       %01234567;1=out, 0=in
	let dirsA =  %00000100
	let dirsD =  %00000000

;**************************************************
;DEFINITIONS & TABLE
	symbol LED=A.5
	symbol CS1=D.3
	symbol CS2=D.2
	symbol IOCON=$0A
	symbol IODIRA=$00
	symbol IODIRB=$01
	symbol GPPUA=$0C
	symbol GPPUB=$0D
	symbol GPIOA=$12
	symbol GPIOB=$13
	symbol IPOLA=$02
	symbol IPOLB=$03
	symbol SPIwrite=%01000000	;bit7: write->R/W=0,read->R/W=1
	symbol SPIread=%01000001	;MCP23S17 address pins disabled
	symbol SPIdata=b0

;Individual (and well-arranged) setting for expander 1
TABLE (IOCON, %00100000);IOCON: !!FIRST OF ALL!!: Disabling sequential operation (automatic address inceremnting)
TABLE (IODIRA,%11111111);IODIRA:all pins are inputs
TABLE (IODIRB,%11111111);IODIRB:all pins are inputs
TABLE (GPPUA, %11111111);GPPUA: all pins have PullUp res.
TABLE (GPPUB, %11111111);GPPUB: all pins have PullUp res.
TABLE (IPOLA, %01010101);1=opposite logic state,0=same logic state
TABLE (IPOLB, %10101010);
;Individual (and well-arranged) setting for expander 2
TABLE (IOCON, %00100000);IOCON: !!FIRST OF ALL!!: Disabling sequential operation (automatic address inceremnting)
TABLE (IODIRA,%00000000);IODIRA:all pins are outputs
TABLE (IODIRB,%00000000);IODIRB:all pins are outputs
TABLE (GPPUA, %00000000);GPPUA: without PullUp res.
TABLE (GPPUB, %00000000);GPPUB: without PullUp res.
TABLE (IPOLA, %00000000);1=opposite logic state,0=same logic state
TABLE (IPOLB, %00000000);

;**************************************************
;SPI SETTING
	HSPISETUP spimode00, spifast

;setting loop for expander 1

	for b1=0 to 13
	low CS1				;enable chip select
	readtable b1, SPIdata		;mining from table
	hspiout (SPIwrite,SPIdata)	;choosing register to write	
	inc b1				;table shift
	readtable b1, SPIdata
	hspiout (SPIdata)			;write to choosed register
	high CS1				;disable chip select
	next b1

;setting loop for expander 2
	for b1=b1 to 27
	low CS2				;enable chip select
	readtable b1, SPIdata
	hspiout (SPIwrite,SPIdata)	;choosing register to write	
	inc b1
	readtable b1, SPIdata
	hspiout (SPIdata)
	high CS2				;disable chip select
	next b1

;**************************************************
;MAIN LOOP

main: 
	toggle LED

	low CS1				;enable chip select
	hspiout (SPIread,GPIOA)		;choosing register to read	
	hspiin (SPIdata)			;clocked out data from expander
	high CS1				;disable chip select

	low CS2
	hspiout (SPIwrite,GPIOA)
	hspiout (SPIdata)
	high CS2	

	low CS1
	hspiout (SPIread,GPIOB)
	hspiin (SPIdata)
	high CS1

	low CS2
	hspiout (SPIwrite,GPIOB)
	hspiout (SPIdata)	
	high CS2	
		
	pause 100
	debug
	
	goto main

;**************************************************
;END
Now, if i am equipped with know-how about SPI and TABLE, i will prepare example for the AD7710 (very precise 24 bit AD converter via SPI).

Thank you for your grammar corrections too. I try to improve my english every day :rolleyes:
 

caganjan

New Member
Thank you for kompliment westaust55.
I am glad i can be into this forum. I tried register before 1,5 year (even on old server picaxe.com), but i did not receive permission from here.
Our Czech PICAXE forum is almost death. I am not feeling alone now :cool:
 

nick12ab

Senior Member
I have already tried to use the table for better arrangement of the registers and their values.
Did I use TABLE according to your idea?
Code:
TABLE (IOCON, %00100000);IOCON: !!FIRST OF ALL!!: Disabling sequential operation (automatic address inceremnting)
TABLE (IODIRA,%11111111);IODIRA:all pins are inputs
TABLE (IODIRB,%11111111);IODIRB:all pins are inputs
TABLE (GPPUA, %11111111);GPPUA: all pins have PullUp res.
TABLE (GPPUB, %11111111);GPPUB: all pins have PullUp res.
TABLE (IPOLA, %01010101);1=opposite logic state,0=same logic state
TABLE (IPOLB, %10101010);
;Individual (and well-arranged) setting for expander 2
TABLE (IOCON, %00100000);IOCON: !!FIRST OF ALL!!: Disabling sequential operation (automatic address inceremnting)
TABLE (IODIRA,%00000000);IODIRA:all pins are outputs
TABLE (IODIRB,%00000000);IODIRB:all pins are outputs
TABLE (GPPUA, %00000000);GPPUA: without PullUp res.
TABLE (GPPUB, %00000000);GPPUB: without PullUp res.
TABLE (IPOLA, %00000000);1=opposite logic state,0=same logic state
TABLE (IPOLB, %00000000);

;**************************************************
;SPI SETTING
    HSPISETUP spimode00, spifast

;setting loop for expander 1

    for b1=0 to 13
    low CS1                ;enable chip select
    readtable b1, SPIdata        ;mining from table
    hspiout (SPIwrite,SPIdata)    ;choosing register to write    
    inc b1                ;table shift
    readtable b1, SPIdata
    hspiout (SPIdata)            ;write to choosed register
    high CS1                ;disable chip select
    next b1

;setting loop for expander 2
    for b1=b1 to 27
    low CS2                ;enable chip select
    readtable b1, SPIdata
    hspiout (SPIwrite,SPIdata)    ;choosing register to write    
    inc b1
    readtable b1, SPIdata
    hspiout (SPIdata)
    high CS2                ;disable chip select
    next b1
Not quite. I would use a single loop by using an IF statement to change the chip select pins half way through rather than have two loops.

Thank you for your grammar corrections too. I try to improve my english every day :rolleyes:
You're welcome. "Compliment" is spelt with a C.
Thank you for kompliment westaust55.
To add to WestAust's compliment:
Your code is well documented and well formatted with code indented/tabulated leaving only the labels in the first column.
It's also good that you actually used [code][/code] tags as newbies tend to never do that despite the 'Read Me First' sticky saying that you're supposed to use them!
 

caganjan

New Member
Not quite. I would use a single loop by using an IF statement to change the chip select pins half way through rather than have two loops.
Ok, sorry, i am slothful sometimes :rolleyes:
I have already replaced two loop for one loop. I used the pointer at CSs here, because two IFs in loop would be inefficient. It is allright with respect to efficiency?
Here is my code:
Code:
;**************************************************
;AUTHOR: CAGI, DATE: 25.5.2012
;**************************************************
;PROGRAM DESCRIPTION:
;This program offer simple example, how to use
;16-Bit I/O Expander with SPI Interface. There
;are two MCP23S17 controled by 40X2. First of them
;(via cable select CS1) is set like an input. Second
;(via cable select CS2) is set like an output.
;Program transfer states from intput expander
;to output expander only. Here is used TABLE
;for well-arranged organization of setting registers
;and their values.

;**************************************************
;PROCESSOR SETTING
	#PICAXE 40X2

;**************************************************
;CLOCK SETTING
;8MHz external oscilator (32MHz after PPL multiplying)
	setfreq em32	

;**************************************************
;IO SETTING
;bits order:       %01234567;1=out, 0=in
	let dirsA =  %00000100
	let dirsD =  %00000000

;**************************************************
;DEFINITIONS & TABLE
	symbol LED=A.5
	symbol CS1=D.3
	symbol CS2=D.2
	symbol IOCON=$0A
	symbol IODIRA=$00
	symbol IODIRB=$01
	symbol GPPUA=$0C
	symbol GPPUB=$0D
	symbol GPIOA=$12
	symbol GPIOB=$13
	symbol IPOLA=$02
	symbol IPOLB=$03
	symbol SPIwrite=%01000000	;bit7: write->R/W=0,read->R/W=1
	symbol SPIread=%01000001	;MCP23S17 address pins disabled
	symbol SPIdata=b0

;Individual (and well-arranged) setting for expander 1
TABLE (IOCON, %00100000);IOCON: !!FIRST OF ALL!!: Disabling sequential operation (automatic address inceremnting)
TABLE (IODIRA,%11111111);IODIRA:all pins are inputs
TABLE (IODIRB,%11111111);IODIRB:all pins are inputs
TABLE (GPPUA, %11111111);GPPUA: all pins have PullUp res.
TABLE (GPPUB, %11111111);GPPUB: all pins have PullUp res.
TABLE (IPOLA, %01010101);1=opposite logic state,0=same logic state
TABLE (IPOLB, %10101010);
;Individual (and well-arranged) setting for expander 2
TABLE (IOCON, %00100000);IOCON: !!FIRST OF ALL!!: Disabling sequential operation (automatic address inceremnting)
TABLE (IODIRA,%00000000);IODIRA:all pins are outputs
TABLE (IODIRB,%00000000);IODIRB:all pins are outputs
TABLE (GPPUA, %00000000);GPPUA: without PullUp res.
TABLE (GPPUB, %00000000);GPPUB: without PullUp res.
TABLE (IPOLA, %00000000);1=opposite logic state,0=same logic state
TABLE (IPOLB, %00000000);

;**************************************************
;SPI SETTING
	HSPISETUP spimode00, spifast

;setting loop for expanders
	for b1=0 to 27
	if b1 < 14 then {@ptr=CS1} else {@ptr=CS2} endif
	low @ptr				;enable chip select
	readtable b1, SPIdata		;mining from table
	hspiout (SPIwrite,SPIdata)	;choosing register to write	
	inc b1				;table shift
	readtable b1, SPIdata
	hspiout (SPIdata)			;write to choosed register
	high @ptr				;disable chip select
	next b1

;**************************************************
;MAIN LOOP

main: 
	toggle LED

	low CS1				;enable chip select
	hspiout (SPIread,GPIOA)		;choosing register to read	
	hspiin (SPIdata)			;clocked out data from expander
	high CS1				;disable chip select

	low CS2
	hspiout (SPIwrite,GPIOA)
	hspiout (SPIdata)
	high CS2	

	low CS1
	hspiout (SPIread,GPIOB)
	hspiin (SPIdata)
	high CS1

	low CS2
	hspiout (SPIwrite,GPIOB)
	hspiout (SPIdata)	
	high CS2	
		
	pause 100
	debug
	
	goto main

;**************************************************
;END
 

caganjan

New Member
Code:
;setting loop for expanders
	for b1=0 to 27
	if b1 < 14 then {@ptr=CS1} else {@ptr=CS2} endif
	low @ptr				;enable chip select
	readtable b1, SPIdata		;mining from table
	hspiout (SPIwrite,SPIdata)	;choosing register to write	
	inc b1				;table shift
	readtable b1, SPIdata
	hspiout (SPIdata)			;write to choosed register
	high @ptr				;disable chip select
	next b1
It is rubbish i think. I so sorry. It was working only due to default values at expanders i think.
PTR is usable only for scratchpad... My novice mistake.
Here is not possiple use pointer to arbitrary place (for example to CS1 and CS2 in my case).
It is true?

So, here is "worse" loop with two IFs.

Code:
;setting loop for expanders
	for b1=0 to 27
	if b1 < 14 then {low CS1} else {low CS2} endif         ;enable chip select
	readtable b1, SPIdata                   		        ;mining from table
	hspiout (SPIwrite,SPIdata)                     	        ;choosing register to write	
	inc b1		                        	        	;table shift
	readtable b1, SPIdata
	hspiout (SPIdata)	                                                ;write to choosed register
	if b1 < 14 then {high CS1} else {high CS2} endif	;disable chip select
	next b1
 

techElder

Well-known member
The "curly" braces ( "{}" ) in the lines similar to this one:

Code:
if b1 < 14 then {low CS1} else {low CS2} endif         ;enable chip select
are not needed, although it visually looks better that way. The compiler ignores them.

The braces are usually used in the manuals to designate optional parameters.
 

westaust55

Moderator
Within the Programming Editor, the braces are used for expanding and collapsing blocks of code for V5.0.5 onwards.

if b1 < 14 then {low CS1} else {low CS2} endif

would be better as:

if b1 < 14 then : low CS1 : else : low CS2 : endif

to visually delineate each Line/statement although the PE will still accept the code without the colons (:)
 

Mark.R

Member
Evening all, what would I need to do to get this code to run on an 18M2 instead of the 40X2 shown? I've built the circuit on my AXE091 project board but I only have an 18M2 installed. Any help much appreciated as always.
 

AllyCat

Senior Member
Hi,
.. what would I need to do to get this code to run on an 18M2 ...
The simple answer is ...... an X2 . :)

Which code? There appears to be 6 versions above. :(

But it will be almost certainly necessary for you to provide us with more details of your application requirement. The 18M2 does NOT have the necessary SPI interface hardware, so it's not going to be a "simple" conversion (if it's possible at all).

Cheers, Alan.
 

Mark.R

Member
The simple answer is ...... an X2 . :)
There's the first thing to do then, I'll replace the M2 chips with X2 ones and go from there.

Which code? There appears to be 6 versions above. :(
I'd only really looked at this first one if I'm honest.

Application wise at the moment I just wanted to do a bit of experimenting with what could be done with using these chips for extra I/O.
 

Mark.R

Member

AllyCat

Senior Member
Hi,

There is no "18X2" so you either need to use a 20X2 (or higher) or bit-bang the SPI on the 18M2, for example as shown in Examples 2 and 3 for the SHIFTOUT command.

But IMHO SPI is not really a "bus type", it's just an ad hoc method of sending data to a few simple devices. Generally a separate Enable signal is required for each device and "daisy-chaining" the data for devices of different types might be problematic. The code shown in post #1 above seems quite elementary and I would have thought that it could be easily bit-banged with a simple subroutine or macro to replace the HSPIOUT commands. That can use any available PICaxe pins, leaving the I2C Hardware pins (often shared with the SPI) available for a "Real" data bus. :)

Cheers, Alan.
 

kranenborg

Senior Member
Hi Mark, I think that in general I2C-variants of devices are better supported through commands for all Picaxe types. Direct SPI support is limited to the X2 variants only.

Comparing the two protocols, I tend to compare them as I2C = flexible vs. SPI = Fast.
The flexibility of I2C stems from the fact that the device address is part of the message, and in that way a single two-wire physical network is sufficient to which all devices can be tied (and often these devices have external pins to adjust their I2C-address, like for example the MCP23017 has, allowing many devices - including identical types - to be connected to the same I2C network). However, since the device address is part of the data, it needs to be decoded as well, and that likely sets a data speed limit.
SPI on the other hand is basically a very simple way of "clocking in or out" data (as Allycat explained) and can be done very fast due to its simplicity. However, in general the device address is not part of the data, therefore an external CS (Chip Select) line is needed to address the particular chip, thus increasing hardware complexity when multiple SPI-chips are used.
In my opinion the use of I2C by RevEd as the standard protocol for device communication is a wise decision, since in a Picaxe system speed is generally not the key requirement, and flexibility is always welcome.

Note that there is a nice detail to the SPI versions MCP23S08 and MCP23S17, they actually do have external address pins to use (like the I2C variants) so you can use a single CS line for multiple devices of this type (each configured with different addresses of course). I have not seen this address decode approach and support on any other SPI-device though (but am glad to be proven wrong) so that will only work for the mentioned types.

/Jurjen
https://www.kranenborg.org/electronics
 
Last edited:

Mark.R

Member
Hi guys, thanks as always for your comments and conversations. It looks like the best thing to do is to move away from the SPI and focus/do more learning on the I2C as looks to be better supported.
 
Last edited:
Top