Sunteți pe pagina 1din 21

Using Serial Peripheral Interface (SPI) Master and Slave

with Atmel AVR Microcontroller


1une 25, 2009 by rwb, under Microcontroller.



Sometimes we need to extend or add more I/O ports to our microcontroller based project.
Because usually we only have a limited I/O port leIt than the logical choice is to use the serial
data transIer method; which usually only requires Irom one up to Iour ports Ior doing the data
transIer. Currently there are Iew types oI modern embedded system serial data transIer
interIace widely supported by most oI the chip`s manuIactures such as I2C (read as I square
C), SPI (Serial Peripheral InterIace), 1-Wire (One Wire), Controller Area Network (CAN),
USB (Universal Serial Bus) and the RS-232 Iamilies (RS-423, RS-422 and RS-485). The last
three interIace types is used Ior long connection between the microcontroller and the devices,
up to 1200 meters Ior the RS-485 speciIication, while the Iirst three is used Ior short range
connection.
Among these serial data transIer interIace types, SPI is considered the Iastest synchronous
with Iull duplex serial data transIer interIace and can be clocked up to 10 MHz; that is why it
is widely used as the interIace method to the high speed demand peripheral such as the
Microchip Ethernet controller ENC28J60, Multi Media Card (MMC) Flash Memory,
Microchip SPI I/O MCP23S17, Microchip 128K SPI EEPROM 25AA128, ADC, sensors,
etc.
In this tutorial we will learn how to utilize the Atmel AVR ATMega168 SPI peripheral to
expand the ATMega168 I/O ports and to communicate between two microcontrollers with the
SPI peripheral where one microcontroller is conIigured as a master and other as a slave. The
principal we learn here could be applied to other types oI microcontroller Iamilies.
Serial Peripheral Interface (SPI)
The standard Serial Peripheral InterIace uses a minimum oI three line ports Ior
communicating with a single SPI device (SPI slave), with the chip select pin (CS) is being
always connected to the ground (enable). II more the one SPI devices is connected to the
same bus, then we need Iour ports and use the Iourth port (SS pin on the ATMega168
microcontroller) to select the target SPI device beIore starting to communicate with it.


II more then three SPI slave devices, then it is better to use Irom three to eight channels
decoder chip such as 74HC138 Iamilies. Since the SPI protocol uses Iull duplex synchronous
serial data transIer method, it could transIer the data and at the same time receiving the slave
data using its internal shiIt register.

From the SPI master and slave interconnection diagram above you could see that the SPI
peripheral use the shiIt register to transIer and receive the data, Ior example the master want
to transIer 0b10001101 (0x8E) to the slave and at the same time the slave device also want to
transIer the 0b00110010 (032) data to the master. By activating the CS (chip select) pin on
the slave device, now the slave is ready to receive the data. On the Iirst clock cycle both
master and slave shiIt register will shiIt their registers content one bit to the leIt; the SPI slave
will receive the Iirst bit Irom the master on its LSB register while at he same time the SPI
master will receive its Iirst data Irom slave on its LSB register.

Continuously using the same principal Ior each bit, the complete data transIer between master
and slave will be done in 8 clock cycle. By using the highest possible clock allowed such as
the Microchip MCP23S17 SPI slave I/O device (10 MHz) than the complete data transIer
between the microcontroller and this SPI I/O port could be achieve in 0.8 us. As you
understand how the SPI principal works, now its time to implement it with the Atmel AVR
ATMega168 microcontroller.
The Iollowing is the list oI hardware and soItware used in this project:
O 74HC595, 8-bit shiIt registers with output latch
O Microchip MCP23S17 16-bit SPI I/O Expander
O Resistor: 330 Ohm (8), 10K (1)
O Eight 3 mm blue LED
O One micro switch
O AVRJazz Mega168 board Irom ermicro which base on the AVR ATmega168
microcontroller (board schema).
O WinAVR Ior the GNU`s C compiler
O Atmel AVR Studio 4 Ior the coding and debugging environment
O STK500 programmer Irom AVR Studio 4, using the AVRJazz Mega168 board
STK500 v2.0 bootloader Iacility.
Expanding Output Port with 74HC595 8-bit Shift Registers
Because the basic operation oI SPI peripheral is a shiIt register, then we could simply use the
8-bit shiIt register with output latch to expand the output port. The 16 pins 74HC595 Iamilies
could be use to serve this purpose.

The 74HC595 device has 8-bit serial-in, parallel-out shiIt register that Ieeds directly to the 8-
bit D-type storage register. The 8-bit serial-in shiIt register has its own input clock pin named
SCK, while the D-Latch 8-bit registers use pin named RCK Ior transIerring (latching) the 8-
bit shiIt registers output to D-Latch output registers.
In normal operation according to the truth table above the 74HC595 shiIt registers clear pin
(SCLR) should be put on logical high and the 8-bit D-Latch buIIer output enable pin (G)
should be put on logical low. By Ieeding the serial input pin (SER) with AVR ATMega168
master out slave in pin (MOSI) and connecting the master synchronous clock (SCK) to the
74HC595 shiIt registers clock (SCK), we could simply use the 74HC595 as the SPI slave
device. Optionally we could connect the 74HC595 Q`H output pin (shiIt registers MSB bit)
to the master in slave out pin (MISO); this optional connection will simply returns the
previous value oI the shiIt registers to the SPI master register.


Now let`s take a look to the C code Ior sending simple chaser LED display to the 74HC595
output:
/

// File Name : avrspi.c
// Version : 1.0
// Description : SPI I/J Using 74HC595 8-bit shift registers
// with output latch
// Author : RWB
// Target : AVRJazz Mega168 Board
// Compiler : AVR-GCC 4.3.0; avr-libc 1.6.2 (WinAVR 20080610)
// IDE : Atmel AVR Studio 4.14
// Programmer : AVRJazz Mega168 STK500 v2.0 Bootloader
// : AVR Visual Studio 4.14, STK500 programmer
// Last Updated : 28 May 2009

/
#include <avr/io.h
#include <util/delay.h
#define SPI_PJRT PJRTB
#define SPI_DDR DDRB
#define SPI_CS PB2
unsigned char SPI_WriteRead(unsigned char dataout)
,
unsigned char datain;
// Start transmission (MJSI)
SPDR = dataout;
// Wait for transmission complete
while(!(SPSR & (1<<SPIF)));
// Get return Value;
datain = SPDR;
// Latch the Jutput using rising pulse to the RCK Pin
SPI_PJRT |= (1<<SPI_CS);
_delay_us(1); // Hold pulse for 1 micro second
// Disable Latch
SPI_PJRT &= ~(1<<SPI_CS);
// Return Serial In Value (MISJ)
return datain;
,
int main(void)
,
unsigned char cnt;
// Set the PJRTD as Jutput:
DDRD=0xFF;
PJRTD=0x00;

// Initial the AVR ATMega168 SPI Peripheral
// Set MJSI and SCK as output, others as input
SPI_DDR = (1<<PB3)|(1<<PB5)|(1<<PB2);
// Latch Disable (RCK Low)
SPI_PJRT &= ~(1<<SPI_CS);
// Enable SPI, Master, set clock rate fck/2 (maximum)
SPCR = (1<<SPE)|(1<<MSTR);
SPSR = (1<<SPI2X);
// Reset the 74HC595 register
cnt=SPI_WriteRead(0);

for(;;) ,
cnt=1;
while(cnt) ,
cnt=cnt<<1;
PJRTD=SPI_WriteRead(cnt);
_delay_ms(100);
,
cnt=0x80;
while(cnt) ,
cnt=cnt1;
PJRTD=SPI_WriteRead(cnt);
_delay_ms(100);
,
,
return 0;
,
/ EJF: avrspi.c /
AVR Serial Peripheral Interface
The principal operation oI the SPI is simple but rather then to create our own bit-bang
algorithm to send the data, the build in SPI peripheral inside the Atmel AVR ATMega168
microcontroller make the SPI programming become easier as we just passing our data to the
SPI data register (SPDR) and let the AVR ATMega168 SPI peripheral do the job to send and
read the data Irom the SPI slave device. To initialize the SPI peripheral inside the
ATMega168 microcontroller we need to enable this device Ior SPI master and set the master
clock Irequency using the SPI control register (SPCR) and SPI status register (SPST), Ior
more inIormation please reIer to the AVR ATMega168 datasheet.

The Iirst thing beIore we use the SPI peripheral is to set the SPI port Ior SPI master
operation; MOSI (PB3) and SCK (PB5) as output port and MISO (PB4) is the input port,
while the SS can be any port Ior SPI master operation but on this tutorial we will use the PB2
to select the SPI slave device. The Iollowing C code is used to set these SPI ports.
#define SPI_PJRT PJRTB
...
// Set MJSI and SCK as output, others as input
SPI_DDR = (1<<PB3)|(1<<PB5)|(1<<PB2);
AIter initializing the ports now we have to enable the SPI by setting the SPE (SPI enable) bit
to logical '1 and selecting the SPI master operation by setting the MSTR bit to logical '1
in the SPCR register. For all other bits we just use its deIault value (logical '0'); such as the
data order (DORD) bit Ior Iirst transIerring MSB, using the rising clock Ior the master clock
on clock polarity (CPOL) bit and sampled the data on leading edge clock phase (CPHA) bit.
Because the 74HC595 shiIt register can receive up to 30 Mhz clock rate, then I use the Iastest
clock that can be generated by the ATMega168 microcontroller SPI peripheral which is Isc/2
(the AVRJazz Mega168 board using 11.059200 MHz); thereIore the maximum clock
generated by the SPI master will be 5.5296 MHz. This Irequency can be achieved by setting
the SPR10 and SPR00 in the SPCR register and SPI2X1 in the SPSR register.
// Enable SPI, Master, set clock rate fck/2 (maximum)
SPCR = (1<<SPE)|(1<<MSTR);
SPSR = (1<<SPI2X);
Sending and reading the data is using the SPI data register (SPDT) and when the SPI Master-
Slave data transIer is completed than SPIF (SPI interrupt Ilag) in the SPSR register will be
set to logical '1'; thereIore by examining this bit status we could ensure that the transmission
between SPI master and slave is completed.
// Start transmission (MJSI)
SPDR = dataout;
// Wait for transmission complete
while(!(SPSR & (1<<SPIF)));
// Get return Value;
datain = SPDR;
AIter reading the data Irom the SPDT register; we have to send the latch clock to the
74HC595 D-Latch register (RCK pin) to latch the 8-bit shiIt registers data to its output.
#define SPI_PJRT PJRTB
#define SPI_DDR DDRB
#define SPI_CS PB2
...
// Latch the Jutput using rising pulse to the RCK Pin
SPI_PJRT |= (1<<SPI_CS);
_delay_us(1); // Hold pulse for 1 micro second
// Disable Latch
SPI_PJRT &= ~(1<<SPI_CS);
Microchip MCP23S17 SPI I/O Expander
For more advance input and output capability SPI device you could use the Microchip
MCP23S17 SPI I/O Expander chip, which provide additional 16-bit I/O Ior your
microcontroller`s based project.

With the addressable pins conIiguration Ieature (A2,A1 and A0), practically you could attach
up to 8 oI MCP23S17 SPI I/O expander devices which gives you 128 I/O ports using just
Iour oI your AVR ATMega168 microcontroller`s ports (MOSI, MISO, SCK and SS).

All the MCP23S17 16-bit general purpose I/O (GPIO) ports can be conIigured both as input
or output by setting the MCP23S17 IODIRA and IODIRB I/O direction register. Each oI the
MCP23S17 general I/O pins could be conIigured to generate interrupt when the ports pin
changes its state (Ior more inIormation please reIers to Microchip MCP23S17 datasheet).
For the purpose oI this tutorial we will use the Microchip MCP23S17 just as the ordinary
input and output expander Ior the AVR ATMega168 microcontroller.


The MCP23S17 is conIigured to use address 000 (address pins A0,A1 and A2 are connected
to the ground) and the push button switch connected to GPB0 port will be use as the toggle
button to start and stop the chaser LED display attached to the GPA0 to GPA7 ports. The
Iollowing is the C code to achieve these tasks.
/

// File Name : mcp23s17.c
// Version : 1.0
// Description : SPI I/J Using Microchip MCP23S17 16-Bit I/J Expander
// Author : RWB
// Target : AVRJazz Mega168 Board
// Compiler : AVR-GCC 4.3.0; avr-libc 1.6.2 (WinAVR 20080610)
// IDE : Atmel AVR Studio 4.14
// Programmer : AVRJazz Mega168 STK500 v2.0 Bootloader
// : AVR Visual Studio 4.14, STK500 programmer
// Last Updated : 28 May 2009

/
#include <avr/io.h
#include <util/delay.h
#define SPI_PJRT PJRTB
#define SPI_DDR DDRB
#define SPI_CS PB2
// MCP23S17 SPI Slave Device
#define SPI_SLAVE_ID 0x40
#define SPI_SLAVE_ADDR 0x00 // A2=0,A1=0,A0=0
#define SPI_SLAVE_WRITE 0x00
#define SPI_SLAVE_READ 0x01
// MCP23S17 Registers Definition for BANK=0 (default)
#define IJDIRA 0x00
#define IJDIRB 0x01
#define IJCJNA 0x0A
#define GPPUA 0x0C
#define GPPUB 0x0D
#define GPIJA 0x12
#define GPIJB 0x13
// Define MCP23S17 Slave Emulation Mode:
//
// 0 - Real MCP23S17:
// Jnly works on real Microchip MCP23S17 SPI I/J
// 1 - ATMega168 SPI Slave (MCP23S17 Emulation):
// Both works on real MCP23S17 and ATMega168 Slave Mode
//
// ATMega168 Crystal Frequency: 11059200 Hz
//
#define MCP23S17_EMULATIJN 1
void SPI_Write(unsigned char addr,unsigned char data)
,
// Activate the CS pin
SPI_PJRT &= ~(1<<SPI_CS);
// Start MCP23S17 JpCode transmission
SPDR = SPI_SLAVE_ID | ((SPI_SLAVE_ADDR << 1) & 0x0E)| SPI_SLAVE_WRITE;
// Wait for transmission complete
while(!(SPSR & (1<<SPIF)));
// Start MCP23S17 Register Address transmission
SPDR = addr;
// Wait for transmission complete
while(!(SPSR & (1<<SPIF)));

// Start Data transmission
SPDR = data;
// Wait for transmission complete
while(!(SPSR & (1<<SPIF)));
// CS pin is not active
SPI_PJRT |= (1<<SPI_CS);
,
unsigned char SPI_Read(unsigned char addr)
,
// Activate the CS pin
SPI_PJRT &= ~(1<<SPI_CS);
// Start MCP23S17 JpCode transmission
SPDR = SPI_SLAVE_ID | ((SPI_SLAVE_ADDR << 1) & 0x0E)| SPI_SLAVE_READ;
// Wait for transmission complete
while(!(SPSR & (1<<SPIF)));
#if MCP23S17_EMULATIJN
_delay_us(1);
#endif
// Start MCP23S17 Address transmission
SPDR = addr;
// Wait for transmission complete
while(!(SPSR & (1<<SPIF)));

#if MCP23S17_EMULATIJN
_delay_us(1);
#endif
// Send Dummy transmission for reading the data
SPDR = 0x00;
// Wait for transmission complete
while(!(SPSR & (1<<SPIF)));

// CS pin is not active
SPI_PJRT |= (1<<SPI_CS);
return(SPDR);
,
int main(void)
,
unsigned char cnt,togbutton,inp;
unsigned int idelay;
char pattern32,= ,0b00000001,
0b00000011,
0b00000110,
0b00001100,
0b00011001,
0b00110011,
0b01100110,
0b11001100,
0b10011000,
0b00110000,
0b01100000,
0b11000000,
0b10000000,
0b00000000,
0b00000000,
0b00000000,
0b10000000,
0b11000000,
0b01100000,
0b00110000,
0b10011000,
0b11001100,
0b01100110,
0b00110011,
0b00011001,
0b00001100,
0b00000110,
0b00000011,
0b00000001,
0b00000000,
0b00000000,
0b00000000,
,;
// Set the PJRTD as Jutput:
DDRD=0xFF;
PJRTD=0x00;
// Initial ATMega168 ADC Peripheral for User's Trimpot Input on PC0
DDRC &= ~(1<<PC0);

ADCSRA = (1<<ADEN) | (1<<ADPS2) | (1<<ADPS1);
// Free running Mode
ADCSRB = 0x00;
// Disable digital input on ADC0 (PC0)
DIDR0 = 0x01;
ADMUX=0x00; // Select Channel 0 (PC0)

// Initial the AVR ATMega168 SPI Peripheral
// Set MJSI (PB3),SCK (PB5) and PB2 (SS) as output, others as input
SPI_DDR = (1<<PB3)|(1<<PB5)|(1<<PB2);
// CS pin is not active
SPI_PJRT |= (1<<SPI_CS);
#if MCP23S17_EMULATIJN
// Enable SPI, Master, set clock rate fck/64
SPCR = (1<<SPE)|(1<<MSTR)|(1<<SPR1);
#else
// Enable SPI, Master, set clock rate fck/2
SPCR = (1<<SPE)|(1<<MSTR);
SPSR |= (1<<SPI2X);
#endif
// Initial the MCP23S17 SPI I/J Expander
SPI_Write(IJCJNA,0x28); // I/J Control Register: BANK=0, SEQJP=1,
HAEN=1 (Enable Addressing)
SPI_Write(IJDIRA,0x00); // GPIJA As Jutput
SPI_Write(IJDIRB,0xFF); // GPIJB As Input
SPI_Write(GPPUB,0xFF); // Enable Pull-up Resistor on GPIJB
SPI_Write(GPIJA,0x00); // Reset Jutput on GPIJA
togbutton=0; // Toggle Button
cnt=0;
idelay=100; // Default Delay;

for(;;) ,
inp=SPI_Read(GPIJB); // Read from GPIJB
if (inp == 0xFE) , // Button is pressed
_delay_ms(1);

inp=SPI_Read(GPIJB); // Read from GPIJB, for simple debounce
if (inp == 0xFE) togbutton^=0x01;
if (togbutton == 0x00) ,
SPI_Write(GPIJA,0x00); // Write to MCP23S17 GPIJA
PJRTD=0x00; // Write to PJRTD
cnt=0;
,
,

if (togbutton) ,
// Start conversion by setting ADSC on ADCSRA Register
ADCSRA |= (1<<ADSC);
// wait until convertion complete ADSC=0 - Complete
while (ADCSRA & (1<<ADSC));
// Get the ADC Result
idelay = ADCW;
SPI_Write(GPIJA,patterncnt,); // Write to MCP23S17 GPIJA
PJRTD=patterncnt++,; // Write to PJRTD
if (cnt = 32) cnt=0;
,
_delay_ms(idelay);
,
return 0;
,
/ EJF: mcp23s17.c /
Unlike the 74HC595 8-bit shiIt register above, writing and reading to and Irom the
MCP23S17 SPI slave must be done in three SPI master writing operation cycles. First the SPI
master has to send the MCP23S17 SPI slave ID with its physical address (set by A2, A1 and
A0 pins) and the read or write instruction to the MCP23S17. Secondly the SPI master has to
tell MCP23S17 which one oI the MCP23S17 control registers address we want to use, and
the last one we send or read the actual data.

From the MCP23S17 SPI addressing diagram above you could see that at least we need to
perIorm three SPI master writing to send or read the data to or Irom the MCP23S17 SPI slave
I/O expander. Using the same principal we`ve learned Irom the 74HC595 to send the data we
simply supply the correct data to the ATMega168 microcontroller SPDR register while keep
the SS (PB2) pin low to enable the MCP23S17 SPI slave device. AIter all data has been sent
or read then we raise (logical '1') the SS (PB2) pin to deactivate the SPI slave device.
The SPI_Write() and the SPI_Read() Iunction are used to do the write and read to and Irom
the MCP23S17 SPI slave. As you`ve seen Irom the SPI_Read() Iunction code above because
the SPI slave could not initiate its own data transIer thereIore the SPI master has to start the
data transIer by sending the dummy data 000 to the SPI slave in order to read the SPI slave
data.
...
// Send Dummy transmission for reading the data
SPDR = 0x00;
// Wait for transmission complete
while(!(SPSR & (1<<SPIF)));

// CS pin is not active
SPI_PJRT |= (1<<SPI_CS);
return(SPDR);
The MCP23S17 SPI I/O expander has two general I/O ports named GPIOA and GPIOB.
The deIault power-up condition oI MCP23S17 is all I/O ports conIigured as an input port. By
changing each oI the I/O direction registers (IODIRA and IODIRB) we could change this
port behavior; this pretty much the same as we set the data direction register on the
ATMega168 microcontroller I/O ports. The Iollowing code shows how to initialize the
MCP23S17 SPI I/O expander.
// Initial the MCP23S17 SPI I/J Expander
SPI_Write(IJCJNA,0x28); // I/J Control Register: BANK=0, SEQJP=1, HAEN=1
(Enable Addressing)
SPI_Write(IJDIRA,0x00); // GPIJA As Jutput
SPI_Write(IJDIRB,0xFF); // GPIJB As Input
SPI_Write(GPPUB,0xFF); // Enable Pull-up Resistor on GPIJB
SPI_Write(GPIJA,0x00); // Reset Jutput on GPIJA
By assigning the IODIRA with 000 and IODIRB with 0xFF, we tell MCP23S17 to assign
its GPIOA ports as output and GPIOB as input. Remember this setup is diIIerent compare to
the ATMega168 microcontroller data direction register, which use 0xFF Ior output and 000
Ior input. To make sure all the GPIOB input ports are on known state (all ports high), we
activate the MCP23S17 pull-up resistors by assigning 0xFF to the GPPUB register.
To enable the MCP23S17 addressing mode, we have to enable (logical '1') the HAEN bit on
the IOCONA register. The IOCONA and IOCONB registers actually shared the same bit;
thereIore you could change either the IOCONA or IOCONB.

Assigning 028 to the IOCON register means we used the BANK 0 addressing mode,
disable the sequential address increment and we enable the address bit control on A2, A1 and
A0 pins oI MCP23S17 (current implementation A20, A10, and A00).
Inside the Infinite Loop
AIter conIigure the MCP23S17 registers, we entering the inIinite loop which simple read the
MCP23S17 GPIOB input port and iI the switch is pressed then start sending the LED display
patterns to the MCP23S17 GPIOA output port and to the ATMega168 PORTD which is also
conIigured as the output port.
The LED display delay is controlled by the user`s trimport on the AVRJazz Mega168 board
which connected to the ADC channel 0 (PC0), Ior Iurther inIormation about using the
ATMega168 microcontroller ADC peripheral please reIer to my previous posted blog Analog
to Digital Converter AVR C Programming.
AVR ATMega168 microcontroller as the SPI Slave Device
In this last AVR SPI tutorial we will transIorm a second AVR ATMega168 to the SPI slave
I/O device; in order not to change the SPI master demo program shown on the MCP23S17
section above, I decided to emulate some Iunctions oI the Microchip MCP23S17 SPI I/O
expander using the Atmel AVR ATMega168 microcontroller and at the same time it will give
us a good example oI how to program the AVR ATMega168 microcontroller as the SPI slave
device.


The SPI slave device practically is a passive device which means that the SPI device could
not initiate the data transIer to the SPI Master. Its rely only on the SPI Master to Iirst transIer
its data then at the same time the SPI slave could transIer its own data to the SPI master. The
C code bellows show how the Atmel AVR ATMega168 microcontroller being transIormed to
the Microchip MCP23S17 SPI I/O expander:
/

// File Name : avrslave.c
// Version : 1.0
// Description : SPI I/J Using ATMega168 as the SPI Slave device
// Partial Emulation of MCP23S17 SPI I/J Expander
// PJRTD - GPIJA, PJRTB.PB0 - GPIJB.GPB0
// Author : RWB
// Target : AVRJazz Mega168 Board
// Compiler : AVR-GCC 4.3.0; avr-libc 1.6.2 (WinAVR 20080610)
// IDE : Atmel AVR Studio 4.14
// Programmer : AVRJazz Mega168 STK500 v2.0 Bootloader
// : AVR Visual Studio 4.14, STK500 programmer
// Last Updated : 28 May 2009

/
#include <avr/io.h
#include <util/delay.h
#define SPI_PJRT PJRTB
#define SPI_DDR DDRB
#define SPI_CS PB2
// MCP23S17 Emulation Read & Write JpCode
#define MCP23S17_Write 0x40
#define MCP23S17_Read 0x41
#define IJDIRA 0x00
#define IJDIRB 0x01
#define IJCJNA 0x0A
#define GPPUA 0x0C
#define GPPUB 0x0D
#define GPIJA 0x12
#define GPIJB 0x13
unsigned char SPI_WriteRead(unsigned char dataout)
,
// Put Slave Data Jn SPDR
SPDR=dataout;
// Wait for transmission complete
while(!(SPSR & (1<<SPIF)));
// Return Serial In Value (MISJ)
return SPDR;
,
int main(void)
,
unsigned char state,rwstat;
unsigned char datain,dataout,slavereg;
// MCP23S17 Registers Emulation Variables
unsigned char iodirb;
unsigned char gpiob;
unsigned char iocona;
unsigned char gppua;
unsigned char gppub;

// Initial the AVR ATMega168 SPI Slave Peripheral
// Set MISJ (PB4) as output, others as input
SPI_DDR |= (1<<PB4);
// Enable SPI as Slave
SPCR = (1<<SPE);
state=0; // State: 0-ID and Address, 1-Register, 2-Data
rwstat=0; // 0-Read, 1-Write
dataout=0;
slavereg=0;

// Reset All the MCP23S17 Slave Emulation Registers
iocona=0;
gppua=0;
gppub=0;
iodirb=0;
gpiob=0;

for(;;) ,
// Poll the SPI Data
datain=SPI_WriteRead(dataout);

// Examining the state here
switch(state) ,
case 0:
// Check for MCP23S17 Slave Emulation JpCode
if (datain == MCP23S17_Write) ,
state=1;
rwstat=1; // Write
,

if (datain == MCP23S17_Read) ,
state=1;
rwstat=0; // Read
,
break;
case 1:
// For MCP23S17 implemented register, change the state
if (datain == IJDIRA || datain == IJDIRB || datain == IJCJNA ||
datain == GPPUA ||
datain == GPPUB || datain == GPIJA || datain == GPIJB) ,

slavereg=datain;
state=2;
// For Read Condition then prepare for returning value on the
next master cycle
if (!rwstat) ,
dataout=0x00;

switch(slavereg) ,
case IJDIRA:
dataout=~DDRD;
break;
case IJDIRB:
dataout=iodirb;
break;
case GPIJA:
dataout=PIND;
break;
case GPIJB:
if (bit_is_clear(PINB, PB0)) ,
dataout=0xFE;
,
break;
,
,
,
break;
case 2:
if (rwstat) ,
// Write
switch(slavereg) ,
case IJDIRA:
// Complement Data, ATMega168 DDR use "1" for Jutput Data
Direction
DDRD=~datain;
break;
case IJDIRB:
iodirb=datain & 0x01; // Just put it on variable
if (iodirb == 0x01) ,
// Jnly for input PB0, ignore output and other ports
DDRB &= ~(1 << PB0);
,
break;
case IJCJNA:
iocona=datain; // Just put it on variable
break;
case GPPUA:
gppua=datain; // Just put it on variable
break;
case GPPUB:
gppub=datain; // Just put it on variable
break;
case GPIJA:
PJRTD=datain; // Passing data to the real port
break;
case GPIJB:
gpiob=datain; // Passing data to the emulation register
break;
,
dataout=0;
,
state=0;
rwstat=0;
break;
,
,
return 0;
,
/ EJF: avrslave.c /
The ATMega168 microcontroller SPI slave initiation can be done by simply enabling the
MISO port (PB4) as the output port and enabling the SPI peripheral bit SPE (logical '1') in
the SPCR register. By polling the SPDR register using the SPI_WriteRead() Iunction we
could examine the data send by the SPI Master using the state condition algorithm to emulate
the Microchip MCP23S17 SPI I/O expander partial Iunctionality as shown inside the inIinite
loop code.

O The Iirst state cycle checks iI the SPI master is sending the MCP23S17 ID (0100) and
the address (currently implemented as 000) and examines iI this is READ (1) or
WRITE (0) operation code.
O The second state cycle check Ior implemented MCP23S17 control registers (IODIRA
,IODIRB,IOCONA,GPPUA,GPPUB,GPIOA, and GPIOB) and at the same time
prepares the data to be sent to the SPI Master on the next SPI master cycle iI this is
the READ operation
O The third state cycle (Iinal state) send the SPI slave data to the SPI master, executes
the WRITE operation using the last data sent by the SPI master to the MCP23S17
emulated registers and resets the state cycle variables Ior the next new SPI master
command.
II you notice in the SPI master code above (MCP23S17 section), I include the define
MCP23S17_EMULATION 1 compiler directive; which give slightly diIIerent code when it
turn oII (define MCP23S17_EMULATION 0).
#if MCP23S17_EMULATIJN
// Enable SPI, Master, set clock rate fck/64
SPCR = (1<<SPE)|(1<<MSTR)|(1<<SPR1);
#else
// Enable SPI, Master, set clock rate fck/2
SPCR = (1<<SPE)|(1<<MSTR);
SPSR |= (1<<SPI2X);
#endif
When you turn oII this compiler directive the SPI master clock setting will use the Iastest
possible clock Isck/2 (5.5296 MHz), but this clock rate will not work on the ATMega168 SPI
slave, it only work on real Microchip MCP23S17 SPI I/O expander; thereIore Ior the
ATMega168 SPI slave we use the Ick/64 (172.800 KHz) with 11.059200 MHz crystal clock
on the AVRJazz Mega168 board.
Now you could enjoy the Iollowing video showing all the experiments we`ve done in this
tutorial:
The Final Though
Currently the Serial Peripheral InterIace (SPI) is lack oI the standard speciIication, its also
called de facto standard, rather than one standard protocol speciIication agreed by the
international committee. This lack oI the standardization lead to the wide SPI protocol option
implemented by various manuIactures; Ior example as shown on the SPI data transIer to the
74HC595 8-bit shiIt register tutorial above, we use diIIerent chip select logical signal to latch
the 74HC595 D-Latch register compared to the real SPI slave I/O device such as Microchip
MCP23S17.
Nevertheless the SPI is out there; it`s still considered as one oI the best and Iastest embedded
systems serial data transIers available today and it`s supported by most oI the IC (integrated
circuit) leading manuIactures.

S-ar putea să vă placă și