MCP23S17 I/O Expander with ATtiny 2313A - I/O unresponsive?

Go To Last Post
48 posts / 0 new
Author
Message
#1
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Good evening,

 

I'm trying to get an MCP23S17 16-bit I/O expander to work with an ATtiny 2313A over SPI. I have read and tried to understand the Microchip datasheet on the 23S17 and the Atmel datasheet on the 2313A and have verified that I am able to send the correct SPI serial sequences over the wire (checked using a serial protocol decoder on the signals). Nothing I send seems to evoke a response from the I/O expander. The 23S17 datasheet seems a little ambiguous in places with regard to the nitty-gritty of how to send control sequences to the device so I am asking if anyone has had success with simply turning outputs on and off, and exactly what the correct sequence of operations for that simple task is.

Below I show my code which seems to generate good (i.e. 'valid') serial data but which doesn't get the I/O expander to switch the state of I/O port GPIOA, as expected. If anyone can offer any advice as to what I might be doing wrong I'd be very grateful. My next step is to assume I have a dead 23S17...

 



#define F_CPU 4000000UL			// AVR clock rate is 4 MHz

// MCP23S17 - IOCON register
//
// The IOCON register has the following bits set at power on or reset:
//
// b7: BANK: 0 - Addresses are sequential
// b6: MIRROR: 0 - INT pins are associated with their ports, not linked
// b5: SEQOP: 0 - Sequential operation enabled; address pointer increments
// b4: DISSLW: 0 - Slew rate control enabled on SDA output
// b3: HAEN: 0 - Disables the MCP23S17 address pins
// b2: ODR: 0 - Configures INT pin as an active driver output (INTPOL bit sets the polarity)
// b1: INTPOL: 0 - Sets polarity of INT output pin to Active Low

// BANK = 0 and SEQOP = 0 mean the address pointer in the device automatically
// increments. When in this mode the device increments its address counter after each
// byte during data transfer. The Address Pointer automatically rolls over to 00h after
// accessing the last register.

// Only for BANK = 0

#define IODIRA 0x00
#define IODIRB 0x01
#define IPOLA 0x02
#define IPOLB 0x03
#define GPINTENA 0x04
#define GPINTENB 0x05
#define DEFVALA 0x06
#define DEFVALB 0x07
#define INTCONA 0x08
#define INTCONB 0x09
#define IOCON 0x0A
#define GPPUA 0x0C
#define GPPUB 0x0D
#define INTFA 0x0E
#define INTFB 0x0F
#define INTCAPA 0x10
#define INTCAPB 0x11
#define GPIOA 0x12
#define GPIOB 0x13
#define OLATA 0x14
#define OLATB 0x15

#define SPI_OPCODE_WRITE 0b01000000

#include <avr/io.h>
#include <util/delay.h>

unsigned char data;

void USART_INIT(void)
{
	// Set up Port D for SPI USART and other tasks

	// PORTD1 (pin 3) is USART 'TXD'; PORTD2 (pin 6) is USART 'XCK'; PORTD3 (pin 7) is 'CS'

	DDRD |= ((1 << PORTD1) | (1 << PORTD2) | (1 << PORTD3));

	// Set MSPI mode of operation and SPI data mode 0.
	//
	// UMSEL1 = 1 and UMSEL0 = 1 means MASTER SPI (MSPIM) mode
	// UCPOL and UCPHA should both be 0 for SPI MODE 0 operation

	UCSRC = (1 << UMSEL1) | (1 << UMSEL0);	// Set MASTER SPI mode
	UCSRC &= ~((1 << UCPOL) | (1 << UCPHA));

	// Set the transmitted word length to 8 bits

	// UCSZ2 = 0
	// UCSZ1 = 1
	// UCSZ0 = 1

	UCSRB &= ~(1 << UCSZ2);
	UCSRC |= ((1 << UCSZ1) | (1 << UCSZ0));

	// Set bit order in UCSRC, with UDORD, to 'MCB first'

	UCSRC &= ~(1 << UDORD);		// Clear bit for MSB FIRST operation

	// Enable the transmitter only for now.

	UCSRB = (1 << TXEN);

	// Set the baud rate.
	// IMPORTANT: The Baud Rate must be set after the transmitter is enabled
	// At 4.0 MHz, UBBR = 0 is a 250 kbps transmission rate.
	// UBRRL and UBRRH must be set independently.

	UBRRL = 0;
	UBRRH = 0;
}

void USART_SEND(unsigned char data)
{
	// Check for USART Data Register Empty condition before sending next character
	// IF UDRE is 1 the buffer is empty and more data can be written

	loop_until_bit_is_set(UCSRA, UDRE);

	// Send the data character

	UDR = data;

	// Wait until Transmit Complete bit is set

	loop_until_bit_is_set(UCSRA, TXC);

	// Clear the Transmit Complete bit by writing a '1' to TXC

	UCSRA |= _BV(TXC);
}

void CS_HIGH(void)
{
	PORTD |= _BV(PORTD3);		// Set bit 3 of Port D
}

void CS_LOW(void)
{
	PORTD &= ~(_BV(PORTD3));	// Clear bit 3 of Port D
}

//////////////////////////////////////////////////////////////////////////////////////

int main(void)
{
	CS_HIGH();		// Make sure CS\ is not asserted at the start
	USART_INIT();	// Initialize the USART for SPI Master use

	// Set up the IODIRA register so Port A is all Outputs

	CS_LOW();
		USART_SEND(SPI_OPCODE_WRITE);	// Write to device
		USART_SEND(IODIRA);				// Address IODIRA register
		USART_SEND(0x00);				// All pins on Port A are outputs
	CS_HIGH();

	// Write data to the GPIOA Output Latch Register, which sets up the data to write out

	CS_LOW();
	USART_SEND(SPI_OPCODE_WRITE);
	USART_SEND(GPIOA);
	USART_SEND(0b10101010);			// '1' is logic HIGH on the GPIO port
	CS_HIGH();

	// Write data to the OLATA Output Latch Register, which forces the latches to the states in GPIOA

	CS_LOW();
		USART_SEND(SPI_OPCODE_WRITE);
		USART_SEND(OLATA);
		USART_SEND(0b10101010);			// '1' is logic HIGH on the output latch
	CS_HIGH();

    while (1)
    {
		// Loop forever
    }
}

If it is helpful to see the SPI signals 'on the wire' I can post scope screengrabs.

 

Thank you for any assistance!

This topic has a solution.
Last Edited: Sun. Feb 5, 2017 - 04:59 AM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

I have only used the I2C version of the chip so maybe not much help.

 

What are you doing with the 3 device address pins A0-A2?

John Samperi

Ampertronics Pty. Ltd.

www.ampertronics.com.au

* Electronic Design * Custom Products * Contract Assembly

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

I have tied them all to GND.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Looking at the datasheet it seems that the only 2 registers with a POR value that isn't 0000 0000 are the two IODIRA and IODIRB registers which default to 1111 1111 so rather than try writing first maybe try a read of both a 0000 0000 and 1111 1111 register and see that you can see both.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Good idea. As you can see from my code, I have disabled the receiver in the 2313A because I wanted to keep things simple(?!) and just stick to writing to the device first. I have to say I don't think much of the Microchip datasheet on the 23S17 - a few pertinent examples in pseudocode would have been really helpful...

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

js, if you had success with this device (even in I2C) could you perhaps enlighten me on the proper method of setting output bits on a port? The datasheet is vague on the details. It seems that you:

 

* set the port direction register (IODIRA, if using port A)

* write the bit pattern you want to output to GPIOA

* write (something? what?) to the output latch OLATA and this causes the GPIOA pin(s) to change state in push-pull mode?

 

Am I missing anything as far as you know?

 

Thanks!

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Everything should be straightforward.

1.   setup the USART_MSPI

2.   treat it just like any other SPI  i.e. a single uint8_t spi(uint8_t tx) function and a macro/function to set/clear CS.

3.   follow the MCP23S17 instruction sequences.

4.   write to a register.   read it back to verify.

 

It will make no difference whether you use I2C or SPI version.

 

David.

 

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

I as well have used the I2C version of the device and it works quite well.  The SPI I would think a breeze to use. 

 

Hmmmm....I have a few here somewhere, maybe there is an SPI version in the drawer.

 

JIm

I would rather attempt something great and fail, than attempt nothing and succeed - Fortune Cookie

 

"The critical shortage here is not stuff, but time." - Johan Ekdahl

 

"Step N is required before you can do step N+1!" - ka7ehk

 

"If you want a career with a known path - become an undertaker. Dead people don't sue!" - Kartman

"Why is there a "Highway to Hell" and only a "Stairway to Heaven"? A prediction of the expected traffic load?"  - Lee "theusch"

 

Speak sweetly. It makes your words easier to digest when at a later date you have to eat them ;-)  - Source Unknown

Please Read: Code-of-Conduct

Atmel Studio6.2/AS7, DipTrace, Quartus, MPLAB, RSLogix user

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

In a hurry right now but I have the Mchip example code, maybe it will help.

Attachment(s): 

John Samperi

Ampertronics Pty. Ltd.

www.ampertronics.com.au

* Electronic Design * Custom Products * Contract Assembly

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

I was looking into those things, SPI version as well, and had a question.

 

I want to use lots of them with a paralleled /SS line, and use the hardware address pins to address them.  But by default, on powerup, in the SPI part, HAEN = 0, therefore if they're all slave-selected at once, they'll all react and try to reply to everything all at once.

 

My thought was that the very first initialization command sent would have to be "Set HAEN = 1" and then they would all accept that; and then be individually addressable by software only afterwards.

 

Does that work?

 

S.

 

PS - I have schematics and board layout for what I'm trying to do, but I've not actually had the board made yet.  Alterations are still possible...

 

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

So the chip I had in fact used in a project was the 8 bit version MCP23008, the 16 bit version was earmarked for another project but I think we ended up using a Beagle Bone black with lots of I/Os but that was 5 or more years ago and I can't remember what I had for breakfast if I did have breakfast.

 

This is the 8 chip bit init

 


	for (uint8_t Offset_adress = MCP23x08_MCP23x17_hi_address; Offset_adress > (MCP23x08_MCP23x17_hi_address-Glass_sense_n_boards);Offset_adress--)
		{
		Write23X08_17 (Device_adress_23X08_17 | (Offset_adress<<1), IOCON, (1<<SEQOP));	// Disable sequential reading
		Write23X08_17 (Device_adress_23X08_17 | (Offset_adress<<1), OLAT, 0xF0);	// Turn leds off
		Write23X08_17 (Device_adress_23X08_17 | (Offset_adress<<1), IODIR, 0x0F);	// Top 4 bits outputs, lower 4 bits input
		Write23X08_17 (Device_adress_23X08_17 | (Offset_adress<<1), GPPU, 0x0F);	// Lower 4 bits pullups
		}

and these are the driver functions, can't remember if I copied from somewhere or if I was clever enough to do them myself, most likely the first case. The drivers are meant to do both types of chips depending on the header file but never tried the SPI version.

 

// 23X08_17 driver

#include "MCP23008_MCP23017.h"
#include "twi.h"

/******************************************************************
   Function Name:  Write23X08_17                                         
   Return Value:                                              
   Parameters:     Register address, Data                    
   Description:    Writes to a 23X08_17 register. I2C or SPI is in
                   global byte     
******************************************************************/
void Write23X08_17(uint8_t sla, uint8_t reg, uint8_t data)
{
#if(Current_Mode == I2CMODE)	

	TWI_io_Buffer [0] = reg;
	TWI_io_Buffer [1] = data;
	twiWrite (sla, TWI_io_Buffer, 2);

#else											
	  SPIWriteByte(reg, data); 
#endif
}

/*****************************************************************
   Function Name:  Read23X08_17                                         
   Return Value:                                              
   Parameters:     Register address                    
   Description:    Reads a 23X08_17 register. I2C or SPI is in
                   global byte     
******************************************************************/
uint8_t Read23X08_17 (uint8_t sla, uint8_t reg)
{
	uint8_t num=0;

#if(Current_Mode == I2CMODE)	

	TWI_io_Buffer [0] = reg;				// Setup register to read
	twiWrite (sla, TWI_io_Buffer, 1);
	twiRead (sla, TWI_io_Buffer, 1);	// Read register
	num = TWI_io_Buffer [0];
#else

	num = SPIReadByte(reg);

#endif

	return(num);
}

header file

 

#include <avr/io.h>
#include "twi.h"

// Set mode of operation I2C or SPI
#define I2CMODE 1
#define SPIMODE 0
#define Current_Mode I2CMODE		// I2C for default startup

#define Device_adress_23X08_17 0x40

uint8_t Read23X08_17 (uint8_t sla, uint8_t reg);
void Write23X08_17 (uint8_t sla, uint8_t reg, uint8_t data);

/*************************************************************
MCP23X08 Definitions
**************************************************************/
#define IODIR		0x00	// I/O DIRECTION REGISTER
#define IPOL		0x01	// INPUT POLARITY REGISTER
#define GPINTEN		0x02	// INTERRUPT-ON-CHANGE CONTROL REGISTER
#define DEFVAL		0x03	// DEFAULT COMPARE REGISTER FOR INTERRUPT-ONCHANGE
#define INTCON		0x04	// INTERRUPT CONTROL REGISTER
#define IOCON		0x05	// CONFIGURATION REGISTER
#define GPPU		0x06	// PULL-UP RESISTOR CONFIGURATION REGISTER
#define INTF		0x07	// INTERRUPT FLAG (INTF)REGISTER
#define INTCAP		0x08	// INTERRUPT CAPTURE REGISTER
#define GPIO		0x09	// PORT REGISTER
#define OLAT		0x0A	// OUTPUT LATCH REGISTER

//IOCON bits
#define SEQOP 5
#define DISSLW 4
#define HAEN 3
#define ODR 2
#define INTPOL 1

/*************************************************************
MCP23X17 Definitions
**************************************************************/
#define IODIRA   	0x00
#define IODIRB   	0x01
#define IPOLA    	0x02
#define IPOLB    	0x03
#define GPINTENA 	0x04
#define GPINTENB 	0x05
#define DEFVALA  	0x06
#define DEFVALB  	0x07
#define INTCONA  	0x08
#define INTCONB  	0x09
#define IOCONA   	0x0A
#define IOCONB   	0x0B
#define GPPUA    	0x0C
#define GPPUB    	0x0D
#define INTFA    	0x0E
#define INTFB    	0x0F
#define INTCAPA  	0x010
#define INTCAPB  	0x011
#define GPIOA    	0x012
#define GPIOB    	0x013
#define OLATA    	0x014
#define OLATB    	0x015

 

John Samperi

Ampertronics Pty. Ltd.

www.ampertronics.com.au

* Electronic Design * Custom Products * Contract Assembly

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

can't remember if I copied from somewhere

I did, I did, I did! From the Microchip example I posted above. So If I could do it anyone can.

John Samperi

Ampertronics Pty. Ltd.

www.ampertronics.com.au

* Electronic Design * Custom Products * Contract Assembly

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

John,

 

As OP is trying to use SPI can you show your:

#else											
	  SPIWriteByte(reg, data); 
#endif

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Looking at FIGURE 2-5: SPI INPUT TIMING in the datasheet,   the device has the MISO pin in high-impedance when /CS becomes active.

 

Looking at FIGURE 2-6: SPI OUTPUT TIMING in the datasheet,   the device only drives the MISO pin when it has received a valid "read" command.

 

So all your Slaves will power up with HAEN=0 and will all listen to a Device Opcode of 0x40 from FIGURE 1-5: SPI ADDRESSING REGISTERS.

If you set HAEN=1 in your first communication after power-up,   subsequent SPI slaves will respond to their specific Device Opcode.

 

It looks as if the MCP23S17 is a perfectly well behaved SPI Slave.   i.e. it only drives MISO when specifically selected, addressed and is responding to an appropriate command.

 

I have not read the Microchip Application Note.   But they are generally well written with good example code.

 

I am mystified when people choose multiple Port Expanders.   Life is simpler if you just buy an AVR with more legs.  The MCP23S17 can only source/sink 25mA on a GPIO pin which is similar to an AVR.   You need a different chip if you intend high current drive.

 

David.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

As OP is trying to use SPI can you show your: 

Not there, as I said

The drivers are meant to do both types of chips depending on the header file but never tried the SPI version.

and that bit of code is never seen as the SPI is not enabled in the header file.

// Set mode of operation I2C or SPI
#define I2CMODE 1
#define SPIMODE 0
#define Current_Mode I2CMODE		// I2C for default startup

however the Microchip example that I posted above (and got my code from) shows

/*****************************************************************
     Function Name:    SPIWriteByte                                
     Return Value:                                             
     Parameters:       register address, and data.               
     Description:      This routine performs a byte write.         
*******************************************************************/
void SPIWriteByte(unsigned char reg, unsigned char data )
{
 
  CSL;  // Enable SPI Communication to MCP23S08/17
  while( WriteSPI(gControlByte | WrtCmd | gAddrPins) );
  while( WriteSPI(reg) );
  while( WriteSPI(data) );
  CSH;  // Disable SPI Communication to MCP23S08/17
}

/*****************************************************************
     Function Name:    SPIReadByte                                
     Return Value:     Data at register                                        
     Parameters:       Register
     Description:      This routine performs a sequential write.         
*******************************************************************/
unsigned char SPIReadByte(unsigned char reg)
{
  unsigned char n;
  
  CSL;  // Enable SPI Communication to  MCP23S08/17
  while( WriteSPI(gControlByte | RdCmd | gAddrPins) );
  while( WriteSPI(reg) );
  n = ReadSPI();
  CSH;  // Disable SPI Communication to  MCP23S08/17
  
  return n;
}

with, I guess, WriteSPI being a simple???

SPDR = c;
while (!(SPSR & (1<<SPIF)));
return SPDR;

 

John Samperi

Ampertronics Pty. Ltd.

www.ampertronics.com.au

* Electronic Design * Custom Products * Contract Assembly

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

with, I guess, WriteSPI being a simple???

SPDR = c;
while (!(SPSR & (1<<SPIF)));
return SPDR;

The OP is suing using the USART in synchronous mode so I am not sure that snippet will work.

 

JIm

I would rather attempt something great and fail, than attempt nothing and succeed - Fortune Cookie

 

"The critical shortage here is not stuff, but time." - Johan Ekdahl

 

"Step N is required before you can do step N+1!" - ka7ehk

 

"If you want a career with a known path - become an undertaker. Dead people don't sue!" - Kartman

"Why is there a "Highway to Hell" and only a "Stairway to Heaven"? A prediction of the expected traffic load?"  - Lee "theusch"

 

Speak sweetly. It makes your words easier to digest when at a later date you have to eat them ;-)  - Source Unknown

Please Read: Code-of-Conduct

Atmel Studio6.2/AS7, DipTrace, Quartus, MPLAB, RSLogix user

Last Edited: Wed. Feb 1, 2017 - 10:58 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Whether you use USART_MSPI,  SPI, USI, ... you still have a single function.      And it consists of a similar sequence to js's snippet. e.g.

UDR = c;
while (!(UCSRA & (1<<RXC)));
return UDR;

Since USART_MSPI has got better buffering,  you can achieve gap-less transfers.   When you are only sending 3 or 4 bytes,  it is not important.   Just use the simple 3-line sequence.

 

David.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

david.prentice wrote:

Looking at FIGURE 2-5: SPI INPUT TIMING in the datasheet,   the device has the MISO pin in high-impedance when /CS becomes active.

 

Looking at FIGURE 2-6: SPI OUTPUT TIMING in the datasheet,   the device only drives the MISO pin when it has received a valid "read" command.

 

So all your Slaves will power up with HAEN=0 and will all listen to a Device Opcode of 0x40 from FIGURE 1-5: SPI ADDRESSING REGISTERS.

If you set HAEN=1 in your first communication after power-up,   subsequent SPI slaves will respond to their specific Device Opcode.

 

It looks as if the MCP23S17 is a perfectly well behaved SPI Slave.   i.e. it only drives MISO when specifically selected, addressed and is responding to an appropriate command.

 

I have not read the Microchip Application Note.   But they are generally well written with good example code.

 

I am mystified when people choose multiple Port Expanders.   Life is simpler if you just buy an AVR with more legs.  The MCP23S17 can only source/sink 25mA on a GPIO pin which is similar to an AVR.   You need a different chip if you intend high current drive.

 

David.

 

Thank you!  I agree with your reading of the specs, and it does "look" like it should work, I was wondering if anyone tried it.  Tellyawhat, I'll make the board and try it and report back!  (Sorry folks, the sample code might be in assembler.  ;-)  )

 

And as far as using an AVR with more feet?  I have a handful of 44-pin 8535s in PLCC packages (which I socket), but they don't have the "toggle PORTx on PINx Write" feature I need.  There exist (I think up to 100 pin) more modern AVRs, but they're fine-pitch SMD and I am an old cat whose hands shake.  The 23S (and the mega168 I intend to use with them) are all big lumpy friendly  0.1 / 0.3 " DIP chips.  The board does have some 1206 caps on it, so I'm not a _complete_ luddite.  (It also struck me that by using addressed expanders to control the chip selects on 16 strings of eight more expanders each, and then doing that again for every available pin could get you more than 240,000 individually addressable pins from one tiny2313 (hee hee).  You will want to buffer your SPI signals for that).

 

S.

 

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

I am mystified when people choose multiple Port Expanders. 

In my case I have up to 8 external boards so it was either an I2C port expander or another micro on each board to read sensors and light up LEDs.

 

More legs would not have helped.

John Samperi

Ampertronics Pty. Ltd.

www.ampertronics.com.au

* Electronic Design * Custom Products * Contract Assembly

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

js wrote:
I am mystified when people choose multiple Port Expanders. In my case I have up to 8 external boards so it was either an I2C port expander or another micro on each board to read sensors and light up LEDs. More legs would not have helped.

Not to drag this thread off topic, but I used them as a buffer of sorts in an industrial application.  I have the input circuitry with all teh usual protection devices in place, but in a couple of situations the customer wanted an extra measure so the expander reads all external I/O rather than the AVR directly.  The idea is that the expander takes the hit before the AVR.  I have had to repair a couple of boards where this in fact did happen because of a huge surge.

 

May not be pretty, but it works.  Customer happy....check clears, me happy.

 

JIm

I would rather attempt something great and fail, than attempt nothing and succeed - Fortune Cookie

 

"The critical shortage here is not stuff, but time." - Johan Ekdahl

 

"Step N is required before you can do step N+1!" - ka7ehk

 

"If you want a career with a known path - become an undertaker. Dead people don't sue!" - Kartman

"Why is there a "Highway to Hell" and only a "Stairway to Heaven"? A prediction of the expected traffic load?"  - Lee "theusch"

 

Speak sweetly. It makes your words easier to digest when at a later date you have to eat them ;-)  - Source Unknown

Please Read: Code-of-Conduct

Atmel Studio6.2/AS7, DipTrace, Quartus, MPLAB, RSLogix user

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

I haven't had much time so far this week to revisit this, but I did just spend some time and switched from an MCP23S17 to an MCP23S08, on the basis that it is slightly simpler with respect to initial configuration. I also re-enabled the USART Receiver and adjusted the register addresses. The new configuration failed to respond either. I'm obviously missing some key thing here.

 

Below are my writes to the device, which as I said results in no change of output state.

 

	CS_HIGH();		// Make sure CS\ is not asserted at the start
	USART_INIT();	// Initialize the USART for SPI Master use

	// Set up the IODIRA register so Port A is all Outputs

	CS_LOW();
		USART_SEND_RECEIVE(SPI_OPCODE_WRITE);	// Write to device
		USART_SEND_RECEIVE(IODIR);				// Address IODIR register
		USART_SEND_RECEIVE(0x00);				// All pins on Port are outputs
	CS_HIGH();

	// Write data to the GPIO Output Latch Register, which sets up the data to write out

	CS_LOW();
		USART_SEND_RECEIVE(SPI_OPCODE_WRITE);
		USART_SEND_RECEIVE(GPIO);
		USART_SEND_RECEIVE(0b10101010);			// '1' is logic HIGH on the GPIO port
	CS_HIGH();

	// Write data to the OLATA Output Latch Register, which forces the latches to the states in GPIO

	CS_LOW();
		USART_SEND_RECEIVE(SPI_OPCODE_WRITE);
		USART_SEND_RECEIVE(OLAT);
		USART_SEND_RECEIVE(0b10101010);			// '1' is logic HIGH on the output latch
	CS_HIGH();

This is driving me mad. sad

 

NB: MCP23S08 A0, A1 tied to GND. RESET to 5V via 10K pull-up. Vdd is 5V. IOCON is not modified from the POR value. Serial SPI data on SCK, SI, SO and CS/ appear correct according to the program in the 2313A - I can post screen-grabs from the scope if it's of interest.

Last Edited: Thu. Feb 2, 2017 - 05:42 AM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Unfortunately I don't have a SPI device to play with.

 

One thing I notice is that I have a 10ms reset pulse to the chip before talking to it, this is on top of SUT delays etc. Maybe you are trying to communicate with the chip before it has time to fully wake up.

 

You say that you have RESET to 5V via 10K pull-up, but what is ensuring that the chip is getting a proper power on reset? Maybe change the resistor to 100K and add a 100nF to GND to ensure some power on reset. I had a dedicated reset pin.

 

edit ok so the above may be nonsense as the chip has a POR circuit internally. However I would still put some delay before starting to set up the chip, this is the same for any external device. Make sure that everything is fully waken up, the micro may start talking to half woken devices.

John Samperi

Ampertronics Pty. Ltd.

www.ampertronics.com.au

* Electronic Design * Custom Products * Contract Assembly

Last Edited: Thu. Feb 2, 2017 - 06:54 AM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 1

@Swulf:

I looked and I not only have some Tiny2313's on hand, I also have the MCP23S17's as well....

 

ZIP up your project, and either post it here, or PM it to me if you would prefer.  I am in the middle of writing a big proposal for a job, but I will try and see if I can get your project to work on my rig.  Might take me a day so patience is a virtue.

 

 

Best I can offer.

 

JIm

 

 

I would rather attempt something great and fail, than attempt nothing and succeed - Fortune Cookie

 

"The critical shortage here is not stuff, but time." - Johan Ekdahl

 

"Step N is required before you can do step N+1!" - ka7ehk

 

"If you want a career with a known path - become an undertaker. Dead people don't sue!" - Kartman

"Why is there a "Highway to Hell" and only a "Stairway to Heaven"? A prediction of the expected traffic load?"  - Lee "theusch"

 

Speak sweetly. It makes your words easier to digest when at a later date you have to eat them ;-)  - Source Unknown

Please Read: Code-of-Conduct

Atmel Studio6.2/AS7, DipTrace, Quartus, MPLAB, RSLogix user

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

@Jim,

 

Thank you very much for the help!

 

I attach below the ZIPped Studio 7 project as well as the schematic and a grab from the oscilloscope showing the decoded first two command sequences.

 

EDIT: I see you were likely expecting the 16-bit I/O version of the project. Let me know if you want me to send that. As I said I changed to the 8-bit device because it seemed simpler to configure - still didn't work with my code though. Perhaps not surprising as whatever I am doing wrong I seem to be doing it in both versions of the project.

Attachment(s): 

Last Edited: Fri. Feb 3, 2017 - 04:44 AM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

...and I lost my reply....

 

Do you really NOT have any supply bypass caps on either chips? surprise If that's the case then ADD 100nF bypass caps to the chip's supply pins and close to them.

 

Replace C1 with a 100nF cap.

 

C4 maybe OK but make sure that you add some delay in your start up or you may be trying to talk to a chip that is still in reset mode.

John Samperi

Ampertronics Pty. Ltd.

www.ampertronics.com.au

* Electronic Design * Custom Products * Contract Assembly

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

@js,

 

Sorry, there are 100 nF bypass at each device (omitted from the schematic as I typically put power supply details on another sheet and did the same omission this time, even tho there is no 'other sheet'!)

 

I'll try what you suggest and see what happens - thanks for the suggestions. =D

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Your code "looks" ok.   You have selected SPI mode#2.   I would choose SPI mode#0.   I do not have a MCP23S17.

 

I Simulated in AS7.0.1188.   I note that AS7.0.1006 does not support bit7 in UCSRC.

 

I would write the functions more concisely.   e.g. init() in 5 lines.   one line for each SFR.   Using = and never |=

I would use a single primitive SPI function.

I would just use RXC flag and not TXC flag.

 

I would write a single helper function that takes your opcode, cmd, arg and returns a uint8_t.

 

Please let us know how you get on.

 

David.

Last Edited: Fri. Feb 3, 2017 - 11:01 AM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Swulf wrote:
EDIT: I see you were likely expecting the 16-bit I/O version of the project. Let me know if you want me to send that.

YEs as that is the title of the thread, and what I assumed you were working on.  I grabbed the 8 bit anyway, but if you could post the 16 bit project that would be great as I do not have any of the 8 bit expanders

 

JIm

 

EDIT:

I opened your project in AS7 and it builds, but Naggy is complaining about these items:

loop_until_bit_is_set(UCSRA, UDRE);
loop_until_bit_is_set(UCSRA, TXC);
loop_until_bit_is_set(UCSRA, RXC);
UCSRA |= _BV(TXC);

 

I get what they are doing, but why Naggy is complaining I am not sure.  I am guessing the loop_until.... is a C++ thing

 

I also just noticed that I have Tiny2313's and not 2313A's, but I do have mega48's and from the datasheets it looks like I can use your code on one to test and the USARTS are the same...we shall see wink

I would rather attempt something great and fail, than attempt nothing and succeed - Fortune Cookie

 

"The critical shortage here is not stuff, but time." - Johan Ekdahl

 

"Step N is required before you can do step N+1!" - ka7ehk

 

"If you want a career with a known path - become an undertaker. Dead people don't sue!" - Kartman

"Why is there a "Highway to Hell" and only a "Stairway to Heaven"? A prediction of the expected traffic load?"  - Lee "theusch"

 

Speak sweetly. It makes your words easier to digest when at a later date you have to eat them ;-)  - Source Unknown

Please Read: Code-of-Conduct

Atmel Studio6.2/AS7, DipTrace, Quartus, MPLAB, RSLogix user

Last Edited: Fri. Feb 3, 2017 - 12:51 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

@Jim, Try SPI mode #0.

Last Edited: Fri. Feb 3, 2017 - 01:09 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

david,

I will. 

 

JIm
 

I would rather attempt something great and fail, than attempt nothing and succeed - Fortune Cookie

 

"The critical shortage here is not stuff, but time." - Johan Ekdahl

 

"Step N is required before you can do step N+1!" - ka7ehk

 

"If you want a career with a known path - become an undertaker. Dead people don't sue!" - Kartman

"Why is there a "Highway to Hell" and only a "Stairway to Heaven"? A prediction of the expected traffic load?"  - Lee "theusch"

 

Speak sweetly. It makes your words easier to digest when at a later date you have to eat them ;-)  - Source Unknown

Please Read: Code-of-Conduct

Atmel Studio6.2/AS7, DipTrace, Quartus, MPLAB, RSLogix user

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

I wus bored.   This is completely untested.   I just showed my "way of doing things":

/*
* IO Expander - MCP23S017_kbv.cpp
*/

#define F_CPU 4000000UL			// AVR clock rate is 4 MHz

#define IODIR   0x00
#define IPOL    0x01
#define GPINTEN 0x02
#define DEFVAL  0x03
#define INTCON  0x04
#define IOCON   0x05
#define GPPU    0x06
#define INTF    0x07
#define INTCAP  0x08
#define GPIO    0x09
#define OLAT    0x0A

#define SPI_OPCODE_WRITE 0b01000000
#define SPI_OPCODE_READ  0b01000001

#include <avr/io.h>
#include <util/delay.h>

uint8_t spi(uint8_t txdata)
{
    UDR = txdata;
    while((UCSRA & (1<<RXC)) == 0);  //wait for shift register to complete
    return UDR;
}

void CS_HIGH(void)
{
    PORTD |= _BV(PORTD3);		// Set bit 3 of Port D
}

void CS_LOW(void)
{
    PORTD &= ~(_BV(PORTD3));	// Clear bit 3 of Port D
}

void USART_INIT(void)
{
    CS_HIGH();
    DDRD |= (1<<1)|(1<<2)|(1<<3);   //MOSI, SCK, CS
    UCSRC = (3<<UMSEL0)|(0<<UCPHA)|(0<<UCPOL);  //MSPI mode #0
    UCSRB = (1<<RXEN)|(1<<TXEN);
    UCSRA = (1<<TXC);               //clear flag
    UBRRL = 0;
    UBRRH = 0;
}

uint8_t MCP23S017(uint8_t opcode, uint8_t cmd, uint8_t val)
{
    CS_LOW();
    spi(opcode);
    spi(cmd);
    uint8_t ret = spi(val);
    CS_HIGH();
    return ret;
}

int main(void)
{
    USART_INIT();	// Initialize the USART for SPI Master use

    MCP23S017(SPI_OPCODE_WRITE, IODIR, 0x00);   // Set up the IODIR register so Port is all Outputs
    MCP23S017(SPI_OPCODE_WRITE, GPIO, 0b10101010);   // Write data to the GPIO Output Latch Register
    MCP23S017(SPI_OPCODE_WRITE, OLAT, 0b10101010);   // '1' is logic HIGH on the output latch

    DDRB = 0xFF;    // connect LEDs to PORTB
    while (1)
    {
        PORTB = MCP23S017(SPI_OPCODE_READ, GPIO, 0);
    }
}

Note that the data sheet has different names for Bank 0 and Bank 1 registers.

 

David.

Last Edited: Fri. Feb 3, 2017 - 02:55 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

@Jim,

 

The lines you mention

 

loop_until_bit_is_set(UCSRA, UDRE);
loop_until_bit_is_set(UCSRA, TXC);
loop_until_bit_is_set(UCSRA, RXC);
UCSRA |= _BV(TXC);

are bit manipulation and testing macros from here:

 

http://www.nongnu.org/avr-libc/user-manual/group__avr__sfr.html

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Yeah I remembered that after I looked it up.

 

Can you post your project for the device in the title?

 

I see David has posted a rather nice solution already

 

JIm

I would rather attempt something great and fail, than attempt nothing and succeed - Fortune Cookie

 

"The critical shortage here is not stuff, but time." - Johan Ekdahl

 

"Step N is required before you can do step N+1!" - ka7ehk

 

"If you want a career with a known path - become an undertaker. Dead people don't sue!" - Kartman

"Why is there a "Highway to Hell" and only a "Stairway to Heaven"? A prediction of the expected traffic load?"  - Lee "theusch"

 

Speak sweetly. It makes your words easier to digest when at a later date you have to eat them ;-)  - Source Unknown

Please Read: Code-of-Conduct

Atmel Studio6.2/AS7, DipTrace, Quartus, MPLAB, RSLogix user

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

@Jim,

 

16-bit project attached. I will have to send the schematic later, but here is the Atmel 7 project.

Attachment(s): 

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 1

OK, I got it working on a mega48 to set GPIOA port to alternating 1's and 0's

 

I changed the bit names to work with the Tiny2313A and it builds without errors, but I have no Tiny2313A parts to test.

 

I commented out the stuff that you did not need so you can see what I did.  I think David pretty much hit the nail on the head with regards to setting MODE0

 

Here is the .CPP file:

 

/*
 * IO Expander - 16 bit.cpp
 *
 * Created: 1/24/2017 8:25:28 PM
 */ 

#define F_CPU 4000000UL			// AVR clock rate is 4 MHz

// MCP23S17 - IOCON register
//
// The IOCON register has the following bits set at power on or reset:
//
// b7: BANK: 0 - Addresses are sequential
// b6: MIRROR: 0 - INT pins are associated with their ports, not linked
// b5: SEQOP: 0 - Sequential operation enabled; address pointer increments
// b4: DISSLW: 0 - Slew rate control enabled on SDA output
// b3: HAEN: 0 - Disables the MCP23S17 address pins
// b2: ODR: 0 - Configures INT pin as an active driver output (INTPOL bit sets the polarity)
// b1: INTPOL: 0 - Sets polarity of INT output pin to Active Low

// BANK = 0 and SEQOP = 0 mean the address pointer in the device automatically
// increments. When in this mode the device increments its address counter after each
// byte during data transfer. The Address Pointer automatically rolls over to 00h after
// accessing the last register.

// Only for BANK = 0

#define IODIRA 0x00
#define IODIRB 0x01
#define IPOLA 0x02
#define IPOLB 0x03
#define GPINTENA 0x04
#define GPINTENB 0x05
#define DEFVALA 0x06
#define DEFVALB 0x07
#define INTCONA 0x08
#define INTCONB 0x09
#define IOCON 0x0A
#define GPPUA 0x0C
#define GPPUB 0x0D
#define INTFA 0x0E
#define INTFB 0x0F
#define INTCAPA 0x10
#define INTCAPB 0x11
#define GPIOA 0x12
#define GPIOB 0x13
#define OLATA 0x14
#define OLATB 0x15

#define SPI_OPCODE_WRITE 0b01000000
#define SPI_OPCODE_READ  0b01000001


#include <avr/io.h>
#include <util/delay.h>

unsigned char data, rxdata, txdata;

void USART_INIT(void)
{	
	// Set up Port D for SPI USART and other tasks

	// PORTD1 (pin 3) is USART 'TXD'; PORTD2 (pin 6) is USART 'XCK'; PORTD3 (pin 7) is 'CS'
	
	DDRD |= ((1 << PORTD1) | (1 << PORTD2) | (1 << PORTD3));
	
	// Set MSPI mode of operation and SPI data mode 0.
	//
	// UMSEL1 = 1 and UMSEL0 = 1 means MASTER SPI (MSPIM) mode
	// UCPOL and UCPHA should both be 0 for SPI MODE 0 operation
	
	UCSRC = (1 << UMSEL1) | (1 << UMSEL0);	// Set MASTER SPI mode
	//UCSRC &= ~((1 << UCPOL) | (1 << UCPHA));
	
	// Set the transmitted word length to 8 bits
	
	// UCSZ2 = 0
	// UCSZ1 = 1
	// UCSZ0 = 1
	
	//UCSRB &= ~(1 << UCSZ2);
	//UCSRC |= ((1 << UCSZ1) | (1 << UCSZ0));
	
	// Set bit order in UCSRC, with UDORD, to 'MCB first'
	
	UCSRC &= ~(1 << UDORD);		// Clear bit for MSB FIRST operation
	
	// Enable the transmitter only for now.
	
	//UCSRB = (1 << TXEN);
	UCSRB |= ((1 << TXEN) | (1 << RXEN));
	
	// Set the baud rate.
	// IMPORTANT: The Baud Rate must be set after the transmitter is enabled
	// At 4.0 MHz, UBBR = 0 is a 250 kbps transmission rate.
	// UBRRL and UBRRH must be set independently.
	
	UBRRL = 0;
	UBRRH = 0;
}

unsigned char USART_SEND_RECEIVE(unsigned char txdata)
{
	// TRANSMIT DATA
	//
	// Check for USART Data Register Empty condition before sending next character
	// IF UDRE is 1 the buffer is empty and more data can be written
	
	loop_until_bit_is_set(UCSRA, UDRE);
	
	// Send the data character
	
	UDR = txdata;
	
	// Wait until Transmit Complete bit is set
	
	loop_until_bit_is_set(UCSRA, TXC);
	
	// Clear the Transmit Complete bit by writing a '1' to TXC
	
	UCSRA |= _BV(TXC);
	
	// RECEIVE DATA
	//
	// Loop until the RXC bit is set, indicating there is unread data in the receive buffer
	
	loop_until_bit_is_set(UCSRA, RXC);
	
	// Received data is automatically put into UDR register
	
	return UDR;
}

void USART_SEND(unsigned char data)
{
	// Check for USART Data Register Empty condition before sending next character
	// IF UDRE is 1 the buffer is empty and more data can be written
	
	loop_until_bit_is_set(UCSRA, UDRE);
	
	// Send the data character
	
	UDR = data;
	
	// Wait until Transmit Complete bit is set
	
	loop_until_bit_is_set(UCSRA, TXC);
	
	// Clear the Transmit Complete bit by writing a '1' to TXC
	
	UCSRA |= _BV(TXC);
}

void CS_HIGH(void)
{
	PORTD |= _BV(PORTD3);		// Set bit 3 of Port D
}

void CS_LOW(void)
{
	PORTD &= ~(_BV(PORTD3));	// Clear bit 3 of Port D
}

//////////////////////////////////////////////////////////////////////////////////////

int main(void)
{
	CS_HIGH();		// Make sure CS\ is not asserted at the start
	USART_INIT();	// Initialize the USART for SPI Master use
	
	// Set up the IODIRA register so Port A is all Outputs
	
	CS_LOW();
		USART_SEND_RECEIVE(SPI_OPCODE_WRITE);	// Write to device
		USART_SEND_RECEIVE(IODIRA);				// Address IODIRA register
		USART_SEND_RECEIVE(0x00);				// All pins on Port are outputs
	CS_HIGH();
		
	// Write data to the GPIOA Register, which sets up the data to write out

	CS_LOW();
		USART_SEND_RECEIVE(SPI_OPCODE_WRITE);
		USART_SEND_RECEIVE(GPIOA);
		USART_SEND_RECEIVE(0b10101010);			// '1' is logic HIGH on the GPIOA port
	CS_HIGH();
	
	// Write data to the OLATA Output Latch Register, which forces the latches to the states in GPIOA

	CS_LOW();
		USART_SEND_RECEIVE(SPI_OPCODE_WRITE);
		USART_SEND_RECEIVE(OLATA);
		USART_SEND_RECEIVE(0b10101010);			// '1' is logic HIGH on the output latch
	CS_HIGH();

    while (1) 
    {
		// Loop forever
    }
}

This simply writes to the unit, configures the GPIO port as outputs and then sets all teh pins on GPIOA to alternating 1's and 0's.

 

Jim

I would rather attempt something great and fail, than attempt nothing and succeed - Fortune Cookie

 

"The critical shortage here is not stuff, but time." - Johan Ekdahl

 

"Step N is required before you can do step N+1!" - ka7ehk

 

"If you want a career with a known path - become an undertaker. Dead people don't sue!" - Kartman

"Why is there a "Highway to Hell" and only a "Stairway to Heaven"? A prediction of the expected traffic load?"  - Lee "theusch"

 

Speak sweetly. It makes your words easier to digest when at a later date you have to eat them ;-)  - Source Unknown

Please Read: Code-of-Conduct

Atmel Studio6.2/AS7, DipTrace, Quartus, MPLAB, RSLogix user

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

...and you are back among the living... wink

John Samperi

Ampertronics Pty. Ltd.

www.ampertronics.com.au

* Electronic Design * Custom Products * Contract Assembly

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Ok, I played some more and now have something that reads GPIOB, and puts it on GPIOA.  Again I tested it on a Mega48, then changed the bits for the Tiny2313A.

 

It's sloppy, but you get teh idea:

/*
 * IO Expander - 16 bit.cpp
 *
 * Created: 1/24/2017 8:25:28 PM
 */ 

#define F_CPU 4000000UL			// AVR clock rate is 4 MHz

// MCP23S17 - IOCON register
//
// The IOCON register has the following bits set at power on or reset:
//
// b7: BANK: 0 - Addresses are sequential
// b6: MIRROR: 0 - INT pins are associated with their ports, not linked
// b5: SEQOP: 0 - Sequential operation enabled; address pointer increments
// b4: DISSLW: 0 - Slew rate control enabled on SDA output
// b3: HAEN: 0 - Disables the MCP23S17 address pins
// b2: ODR: 0 - Configures INT pin as an active driver output (INTPOL bit sets the polarity)
// b1: INTPOL: 0 - Sets polarity of INT output pin to Active Low

// BANK = 0 and SEQOP = 0 mean the address pointer in the device automatically
// increments. When in this mode the device increments its address counter after each
// byte during data transfer. The Address Pointer automatically rolls over to 00h after
// accessing the last register.

// Only for BANK = 0

#define IODIRA 0x00
#define IODIRB 0x01
#define IPOLA 0x02
#define IPOLB 0x03
#define GPINTENA 0x04
#define GPINTENB 0x05
#define DEFVALA 0x06
#define DEFVALB 0x07
#define INTCONA 0x08
#define INTCONB 0x09
#define IOCON 0x0A
#define GPPUA 0x0C
#define GPPUB 0x0D
#define INTFA 0x0E
#define INTFB 0x0F
#define INTCAPA 0x10
#define INTCAPB 0x11
#define GPIOA 0x12
#define GPIOB 0x13
#define OLATA 0x14
#define OLATB 0x15

#define SPI_OPCODE_WRITE 0b01000000
#define SPI_OPCODE_READ  0b01000001


#include <avr/io.h>
#include <util/delay.h>

unsigned char data, rxdata, txdata;

void USART_INIT(void)
{	
	// Set up Port D for SPI USART and other tasks

	// PORTD1 (pin 3) is USART 'TXD'; PORTD2 (pin 6) is USART 'XCK'; PORTD3 (pin 7) is 'CS'
	
	DDRD |= ((1 << PORTD1) | (1 << PORTD2) | (1 << PORTD3));
	
	// Set MSPI mode of operation and SPI data mode 0.
	//
	// UMSEL1 = 1 and UMSEL0 = 1 means MASTER SPI (MSPIM) mode
	// UCPOL and UCPHA should both be 0 for SPI MODE 0 operation
	
	UCSRC = (1 << UMSEL1) | (1 << UMSEL0);	// Set MASTER SPI mode
	//UCSRC &= ~((1 << UCPOL) | (1 << UCPHA));
	
	// Set the transmitted word length to 8 bits
	
	// UCSZ2 = 0
	// UCSZ1 = 1
	// UCSZ0 = 1
	
	//UCSRB &= ~(1 << UCSZ2);
	//UCSRC |= ((1 << UCSZ1) | (1 << UCSZ0));
	
	// Set bit order in UCSRC, with UDORD, to 'MCB first'
	
	UCSRC &= ~(1 << UDORD);		// Clear bit for MSB FIRST operation
	
	// Enable the transmitter only for now.
	
	//UCSRB = (1 << TXEN);
	UCSRB |= ((1 << TXEN) | (1 << RXEN));
	
	// Set the baud rate.
	// IMPORTANT: The Baud Rate must be set after the transmitter is enabled
	// At 4.0 MHz, UBBR = 0 is a 250 kbps transmission rate.
	// UBRRL and UBRRH must be set independently.
	
	UBRRL = 0;
	UBRRH = 0;
}

unsigned char USART_SEND_RECEIVE(unsigned char txdata)
{
	// TRANSMIT DATA
	//
	// Check for USART Data Register Empty condition before sending next character
	// IF UDRE is 1 the buffer is empty and more data can be written
	
	loop_until_bit_is_set(UCSRA, UDRE);
	
	// Send the data character
	
	UDR = txdata;
	
	// Wait until Transmit Complete bit is set
	
	loop_until_bit_is_set(UCSRA, TXC);
	
	// Clear the Transmit Complete bit by writing a '1' to TXC
	
	UCSRA |= _BV(TXC);
	
	// RECEIVE DATA
	//
	// Loop until the RXC bit is set, indicating there is unread data in the receive buffer
	
	loop_until_bit_is_set(UCSRA, RXC);
	
	// Received data is automatically put into UDR register
	
	return UDR;
}

void USART_SEND(unsigned char data)
{
	// Check for USART Data Register Empty condition before sending next character
	// IF UDRE is 1 the buffer is empty and more data can be written
	
	loop_until_bit_is_set(UCSRA, UDRE);
	
	// Send the data character
	
	UDR = data;
	
	// Wait until Transmit Complete bit is set
	
	loop_until_bit_is_set(UCSRA, TXC);
	
	// Clear the Transmit Complete bit by writing a '1' to TXC
	
	UCSRA |= _BV(TXC);
}

void CS_HIGH(void)
{
	PORTD |= _BV(PORTD3);		// Set bit 3 of Port D
}

void CS_LOW(void)
{
	PORTD &= ~(_BV(PORTD3));	// Clear bit 3 of Port D
}

//////////////////////////////////////////////////////////////////////////////////////

int main(void)
{
	CS_HIGH();		// Make sure CS\ is not asserted at the start
	USART_INIT();	// Initialize the USART for SPI Master use
	
	// Set up the IODIRA register so Port A is all Outputs
	
	CS_LOW();
		USART_SEND_RECEIVE(SPI_OPCODE_WRITE);	// Write to device
		USART_SEND_RECEIVE(IODIRA);				// Address IODIRA register
		USART_SEND_RECEIVE(0x00);				// All pins on Port are outputs
	CS_HIGH();
		

    while (1) 
    {
		//Read GPIOB and store it
		uint8_t temp;
		CS_LOW();
		USART_SEND_RECEIVE(SPI_OPCODE_READ);
		USART_SEND_RECEIVE(GPIOB);
		temp = USART_SEND_RECEIVE(0xF0);			//Read GPIO transmit  Dummy Byte - Can be anything
		CS_HIGH();
		
			
		//Now pass this to GPIOA
		CS_LOW();
		USART_SEND_RECEIVE(SPI_OPCODE_WRITE);
		USART_SEND_RECEIVE(GPIOA);
		USART_SEND_RECEIVE(temp);			// Place read of GPIOA on GPIOB
		CS_HIGH();	
		
		//Wait a few milliseconds then rinse and repeat
		_delay_ms(50);
    }
}

 

Untested, but confident.

 

Jim

 

PS.:

js wrote:

...and you are back among the living... wink

Yeah...Zombie now though devil

I would rather attempt something great and fail, than attempt nothing and succeed - Fortune Cookie

 

"The critical shortage here is not stuff, but time." - Johan Ekdahl

 

"Step N is required before you can do step N+1!" - ka7ehk

 

"If you want a career with a known path - become an undertaker. Dead people don't sue!" - Kartman

"Why is there a "Highway to Hell" and only a "Stairway to Heaven"? A prediction of the expected traffic load?"  - Lee "theusch"

 

Speak sweetly. It makes your words easier to digest when at a later date you have to eat them ;-)  - Source Unknown

Please Read: Code-of-Conduct

Atmel Studio6.2/AS7, DipTrace, Quartus, MPLAB, RSLogix user

This reply has been marked as the solution. 
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

All,

 

Success!! I have the ATtiny 2313A controlling the MCP23S08 and doing a simple periodic write of bit patterns to the port. My final, working code is below and the Studio 7 project is attached. I want to really thank you all for taking the time and the patience to help me with this, in particular Jim ('jgmdesign') for actually verifying things on his hardware.

 

For others who may be in the same boat at some time, here's what I learned.

 

  • Set your AVR to operate in the SPI mode 'MPSIM' and configure it to use 'Mode 0'. The I/O expander datasheet isn't up-front about this - you have to interpret it from the timing diagrams.
  • Set your 'bit order' in the microcontroller SPI configuration to 'MSB first'
  • Verify, and verify again, your connections. At one point I made the (stupid) mistake of monitoring the CS\ line at the microcontroller and not directly at the I/O expander. You can guess where it had become disconnected...
  • BEWARE the fact that certain 2313A register functions change depending on whether the device is in USART mode (section 14 of the datasheet) or MSPIM mode (section 15 of the datasheet). Compare, for example, the bit functions of register UCSRC in Section 14.10.4 (USART mode) and 15.8.4 (MSPIM mode). The datasheet does mention this, but it is easy to overlook it, and I did.
  • To 'just write bits' to a port, it's only necessary to write to the I/O expander's IODIR register once (to set the port direction as 'output') and subsequently to the OLAT register, to send whatever bit pattern you want to appear on the port pins. It would seem that you would write to the GPIO register, but you don't if you are just outputting bits.
  • An oscilloscope with serial protocol decoding, or some sort of logic analyser is invaluable. Kudos to the manufacturers who have made such things affordable to the hobbyist.

 

Thanks again to all!

 

/*
 * IO Expander - 8 bit.cpp
 */ 

#define F_CPU 4000000UL			// AVR clock rate is 4 MHz. External crystal used.

// Register definitions for the MCP23S08 Port Expander

#define IODIR 0x00
#define IPOL 0x01
#define GPINTEN 0x02
#define DEFVAL 0x03
#define INTCON 0x04
#define IOCON 0x05
#define GPPU 0x06
#define INTF 0x07
#define INTCAP 0x08
#define GPIO 0x09
#define OLAT 0x0A

#define SPI_OPCODE_WRITE 0b01000000

#include <avr/io.h>
#include <util/delay.h>

unsigned char txdata;

void USART_INIT(void)
{
	// Set up Port D for SPI USART and other tasks

	// PORTD1 (pin 3) is USART 'TXD'; PORTD2 (pin 6) is USART 'XCK'; PORTD3 (pin 7) is 'CS'

	DDRD |= ((1 << PORTD1) | (1 << PORTD2) | (1 << PORTD3));

	// Set MSPI mode of operation and SPI data mode 0.
	//
	// UMSEL1 = 1 and UMSEL0 = 1 means MASTER SPI (MSPIM) mode
	// UCPOL and UCPHA should both be 0 for SPI MODE 0 operation

	UCSRC |= (1 << UMSEL1) | (1 << UMSEL0);	// Set MASTER SPI mode (MSPIM)

	// Set SPI MODE 0

	UCSRC &= ~((1 << UCPOL) | (1 << UCPHA));

	// Set bit order in UCSRC, with UDORD, to 'MSB first'

	UCSRC &= ~(1 << UDORD);		// Clear bit for MSB FIRST operation

	// Enable the transmitter and receiver. Note only the transmitter is strictly necessary in this example.

	UCSRB |= ((1 << TXEN) | (1 << RXEN));

	// Set the baud rate.
	// IMPORTANT: The Baud Rate must be set after the transmitter is enabled
	// At 4.0 MHz, UBBR = 0 is a 250 kbps transmission rate.
	// UBRRL and UBRRH must be set independently.

	UBRRL = 0;
	UBRRH = 0;
}

unsigned char USART_SEND_RECEIVE(unsigned char txdata)
{
	// TRANSMIT & RECEIVE DATA
	//
	// Check for USART Data Register Empty condition before sending next character
	// IF UDRE is 1 the buffer is empty and more data can be written

	loop_until_bit_is_set(UCSRA, UDRE);

	// Send the txdata character

	UDR = txdata;

	// Wait until Transmit Complete bit is set

	loop_until_bit_is_set(UCSRA, TXC);

	// Clear the Transmit Complete bit by writing a '1' to TXC

	UCSRA |= _BV(TXC);

	// RECEIVE DATA
	//
	// Loop until the RXC bit is set, indicating there is unread data in the receive buffer

	loop_until_bit_is_set(UCSRA, RXC);

	// Received data is automatically put into UDR register

	return UDR;
}

void CS_HIGH(void)
{
	PORTD |= _BV(PORTD3);		// Set bit 3 of Port D
}

void CS_LOW(void)
{
	PORTD &= ~(_BV(PORTD3));	// Clear bit 3 of Port D
}

int main(void)
{
	USART_INIT();	// Initialize the USART for SPI Master use
	CS_HIGH();		// Make sure CS\ is not asserted at the start

	// Set up the IODIR register so Port is all outputs

	CS_LOW();
		USART_SEND_RECEIVE(SPI_OPCODE_WRITE);	// Write to device
		USART_SEND_RECEIVE(IODIR);				// Address IODIR register
		USART_SEND_RECEIVE(0x00);				// All pins on Port are outputs
	CS_HIGH();

    while (1)
    {
		// Write data to the OLATA Output Latch Register, which sets the output bit states

		CS_LOW();
		USART_SEND_RECEIVE(SPI_OPCODE_WRITE);
		USART_SEND_RECEIVE(OLAT);
		USART_SEND_RECEIVE(0b10101010);			// '1' is logic HIGH on the output latch
		CS_HIGH();

		_delay_ms(10);

		CS_LOW();
		USART_SEND_RECEIVE(SPI_OPCODE_WRITE);
		USART_SEND_RECEIVE(OLAT);
		USART_SEND_RECEIVE(0b01010101);			// '1' is logic HIGH on the output latch
		CS_HIGH();

		_delay_ms(10);
    }
}

 

Attachment(s): 

Last Edited: Sat. Feb 4, 2017 - 09:43 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Swulf wrote:
Set your AVR to operate in the SPI mode 'MPSIM' and configure it to use 'Mode 0'. The I/O expander datasheet isn't up-front about this - you have to interpret it from the timing diagrams.

Page 28 and 29 of the datasheet mention this in the timing diagrams....David Prentice told you to set for Mode 0 in [ost #27 and #29

 

Swulf wrote:
BEWARE the fact that certain 2313A register functions change depending on whether the device is in USART mode (section 14 of the datasheet) or MSPIM mode (section 15 of the datasheet). Compare, for example, the bit functions of register UCSRC in Section 14.10.4 (USART mode) and 15.8.4 (MSPIM mode). The datasheet does mention this, but it is easy to overlook it, and I did.

The datasheet is quite clear as to the register functions based on the mode you are using them in.  If you look at the USART and USART-as-SPI master it is very evident.

 

Swulf wrote:
To 'just write bits' to a port, it's only necessary to write to the I/O expander's IODIR register once (to set the port direction as 'output') and subsequently to the OLAT register, to send whatever bit pattern you want to appear on the port pins. It would seem that you would write to the GPIO register, but you don't if you are just outputting bits.

Ummm Take a look at the two examples I used.  I wrote to the GPIOA.  In case #2 I am reading GPIOB and writing the pattern to GPIOA.  Can you elaborate on what your saying?

 

Swulf wrote:
An oscilloscope with serial protocol decoding, or some sort of logic analyser is invaluable. Kudos to the manufacturers who have made such things affordable to the hobbyist.

Yes, compared to the monster units I used back in the 90's that weighed 50 or more pounds and cost tens of thousands of dollars.  My little Saleae Logic8 is more than what I need.

 

JIm

I would rather attempt something great and fail, than attempt nothing and succeed - Fortune Cookie

 

"The critical shortage here is not stuff, but time." - Johan Ekdahl

 

"Step N is required before you can do step N+1!" - ka7ehk

 

"If you want a career with a known path - become an undertaker. Dead people don't sue!" - Kartman

"Why is there a "Highway to Hell" and only a "Stairway to Heaven"? A prediction of the expected traffic load?"  - Lee "theusch"

 

Speak sweetly. It makes your words easier to digest when at a later date you have to eat them ;-)  - Source Unknown

Please Read: Code-of-Conduct

Atmel Studio6.2/AS7, DipTrace, Quartus, MPLAB, RSLogix user

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Style is a very personal thing.   My eyes can't focus on a lengthy sequence.   The AVR is pretty simple.   Most peripherals can be initialised in 4 or 5 statements.    If you can rely on starting with power-up,   you can often cut it down to 2 or 3 statements.

 

Likewise,   the I2C helper function might be:

uint8_t MCP23xx(uint8_t opcode, uint8_t cmd, uint8_t val)   //I2C variant
{
    uint8_t ret;
    i2c_start(opcode & ~1);    //use the SLAVE_W opcode
    i2c_write(cmd);            //select the Cmd register
    if (opcode & 1) {          //? do we want SLAVE_R
        i2c_start(opcode);     //yes, SLAVE_R
        ret = i2c_readNak();   //read the cmd register
    }
    else i2c_write(val);       //no, we writing to cmd reg
    i2c_stop();                //release Bus
    return ret;
}

uint8_t MCP23Sxx(uint8_t opcode, uint8_t cmd, uint8_t val)   //SPI variant
{
    CS_LOW();
    spi(opcode);
    spi(cmd);
    uint8_t ret = spi(val);
    CS_HIGH();
    return ret;
}

Untested.   It was typed straight into the Browser.    You can use the same Fleury calls on the bit-banged 2313A as you would on a TWI Mega.

I have not looked at the Microchip App Notes.   But I would be fairly certain that they would have a similar approach.  i.e. simple init(),  universal helper function.

 

I doubt if you need a Logic Analyser for such trivial sequences but like Jim,   I find a Saleae Logic-8 useful on more complex peripherals.

 

David.

Last Edited: Sat. Feb 4, 2017 - 10:43 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

 

configure it to use 'Mode 0'.......you have to interpret it from the timing diagrams.

.

.

Set your 'bit order' in the microcontroller SPI configuration to 'MSB first'

You mean this timing diagram? It also seems to indicate that the chip will work in mode 1,1 so while everything is in working order you may want to verify this so that someone else will have the full pictur

 

 

as I default when working with SPI devices I think of mode 0,0 with MSB first (then investigate if things don't work.......), the early SPIs from when I used to have hair where pretty simple.

John Samperi

Ampertronics Pty. Ltd.

www.ampertronics.com.au

* Electronic Design * Custom Products * Contract Assembly

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Hi Jim,

 

'Page 28 and 29 of the datasheet mention this in the timing diagrams....David Prentice told you to set for Mode 0 in [ost #27 and #29'

 

Indeed, the timing diagrams refer to 'Mode 0,0' and 'Mode 1,1'. I didn't know how that mapped into the Mode settings in the AVR SPI implementation (Table 15-2 in the AVR datasheet) however. AVR SPI modes are referred to as 0-3, not '0,0' or '1,1'. One could argue that it's 'obvious' that '0,0' refers to SPI polarity and phase settings I suppose, but I found it ambiguous and I expect other inexperienced people might also.

 

'The datasheet is quite clear as to the register functions based on the mode you are using them in.  If you look at the USART and USART-as-SPI master it is very evident.'

 

Again, I was just pointing out my misunderstanding as a warning to others.

 

'Can you elaborate on what your saying?'

 

In the code I posted above, which I also have running on hardware, when I want to change the output port bit state I write only to OLAT and it changes the pin states. I do this because in the MCP23S08 datasheet under the OLAT register it states 'A write to this register modifies the output latches that modify the pins configured as outputs.'

 

Since then I have tested writing only to the GPIO register (instead of OLAT) and it also causes the data written to it to be written to the port bits. However, from what is written in the data sheet it was not clear to me whether - in order to change the status of bits in a port configured as an output - one should write to GPIO, or OLAT, or both, or ? Again, my misunderstanding. The datasheet says, under the GPIO register, 'Writing to this register modifies the Output Latch (OLAT) register.' so maybe again it is 'obvious' that this would change the port bit states. But I could not see the point of having both GPIO and OLAT writes causing bits to be set in the output-confgured port. Maybe I was over (or under) thinking the issue. Remember that at the time I had never had this device working and I was scrambling to understand the correct way to get it working. Once one has had experience and been able to experiment with a device that is not obviously dead, it is easier to test and learn. But when sequence after sequence of serial data results in nothing you start questioning every little thing.

 

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Hi John,

 

So what does '0,0' and '1,1' mean?

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Swulf wrote:

Hi John,

 

So what does '0,0' and '1,1' mean?

Page 147 of the Tiny2313A datasheet:

 

 

 

JIm

I would rather attempt something great and fail, than attempt nothing and succeed - Fortune Cookie

 

"The critical shortage here is not stuff, but time." - Johan Ekdahl

 

"Step N is required before you can do step N+1!" - ka7ehk

 

"If you want a career with a known path - become an undertaker. Dead people don't sue!" - Kartman

"Why is there a "Highway to Hell" and only a "Stairway to Heaven"? A prediction of the expected traffic load?"  - Lee "theusch"

 

Speak sweetly. It makes your words easier to digest when at a later date you have to eat them ;-)  - Source Unknown

Please Read: Code-of-Conduct

Atmel Studio6.2/AS7, DipTrace, Quartus, MPLAB, RSLogix user

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

So the first digit is to always be interpreted as polarity and the second digit is to always be interpreted as phase, with '0,0' meaning 'Mode 0'? Again, I was not aware of this convention and I did not see it mentioned anywhere. My mistake.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 1

That I cannot accurately answer, but IME usually the manufacturer tells you point blank what the MODE is for their device, and then you have to look at the micro you are using to determine what bits in the control register need to be set/cleared to set up for that mode.

 

COmes down to you learn from experience and judgement...both of which come from bad experiences, and bad judgement.  (credit joeymorin)

 

JIm

I would rather attempt something great and fail, than attempt nothing and succeed - Fortune Cookie

 

"The critical shortage here is not stuff, but time." - Johan Ekdahl

 

"Step N is required before you can do step N+1!" - ka7ehk

 

"If you want a career with a known path - become an undertaker. Dead people don't sue!" - Kartman

"Why is there a "Highway to Hell" and only a "Stairway to Heaven"? A prediction of the expected traffic load?"  - Lee "theusch"

 

Speak sweetly. It makes your words easier to digest when at a later date you have to eat them ;-)  - Source Unknown

Please Read: Code-of-Conduct

Atmel Studio6.2/AS7, DipTrace, Quartus, MPLAB, RSLogix user

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

And since Jim has already replied I'll add something from a MSP430 chip

 

 

and another one from Microchip

 

 

 

John Samperi

Ampertronics Pty. Ltd.

www.ampertronics.com.au

* Electronic Design * Custom Products * Contract Assembly

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

In my experience, most SPI chips use mode#0. Many accept #0 and #3 e.g. LSI Display Controllers.
.
AVR and most Atmel microcontrollers use #0 for In Circuit Serial Programming.
However the AT89S8253 uses mode #2
.
I have never seen or heard of any hardware chip that uses mode #1.
.
So I would always start with mode #0 but most importantly, I would initialise the USART in a straightforward way. i.e. using the MSPI bit names.
.
YMMV
.
David.