mac-rom-simm-programmer/mcp23s17.c

214 lines
6.1 KiB
C

/*
* mcp23s17.c
*
* Created on: Nov 25, 2011
* Author: Doug
*
* Copyright (C) 2011-2012 Doug Brown
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
*/
#include "mcp23s17.h"
#include <avr/io.h>
#include <util/delay.h>
#include <stdbool.h>
static bool MCP23S17_Inited = false;
// Pin definitions on PORTB
#define SPI_CS (1 << 0)
#define SPI_SCK (1 << 1)
#define SPI_MOSI (1 << 2)
#define SPI_MISO (1 << 3)
#define MCP23S17_RESET (1 << 7)
#define ASSERT_CS() PORTB &= ~SPI_CS
#define DEASSERT_CS() PORTB |= SPI_CS;
// A few register defines
#define MCP23S17_CONTROL_WRITE(address) (0x40 | (address << 1))
#define MCP23S17_CONTROL_READ(address) (0x40 | (address << 1) | 1)
#define MCP23S17_IODIRA 0x00
#define MCP23S17_IODIRB 0x01
#define MCP23S17_IPOLA 0x02
#define MCP23S17_IPOLB 0x03
#define MCP23S17_GPINTENA 0x04
#define MCP23S17_GPINTENB 0x05
#define MCP23S17_DEFVALA 0x06
#define MCP23S17_DEFVALB 0x07
#define MCP23S17_INTCONA 0x08
#define MCP23S17_INTCONB 0x09
#define MCP23S17_IOCON 0x0A
#define MCP23S17_IOCON_AGAIN 0x0B
#define MCP23S17_GPPUA 0x0C
#define MCP23S17_GPPUB 0x0D
#define MCP23S17_INTFA 0x0E
#define MCP23S17_INTFB 0x0F
#define MCP23S17_INTCAPA 0x10
#define MCP23S17_INTCAPB 0x11
#define MCP23S17_GPIOA 0x12
#define MCP23S17_GPIOB 0x13
#define MCP23S17_OLATA 0x14
#define MCP23S17_OLATB 0x15
// Private functions
void MCP23S17_WriteBothRegs(uint8_t addrA, uint16_t value);
uint16_t MCP23S17_ReadBothRegs(uint8_t addrA);
uint8_t MCP23S17_ByteRW(uint8_t b);
void MCP23S17_Init()
{
// If it has already been initialized, no need to do it again.
if (MCP23S17_Inited)
{
return;
}
// Initialize the SPI pins
// Set MOSI, SCLK, and CS as outputs, MISO as input
// (Also, set the MCP23S17 reset line as an output)
DDRB |= SPI_CS | SPI_SCK | SPI_MOSI | MCP23S17_RESET;
DDRB &= ~SPI_MISO;
// Initialize the SPI peripheral
// We can run it at 8 MHz (divider of 2 from 16 MHz system clock -- maximum speed of MCP23S17 10 MHz)
#if ((F_CPU / 2) > 10000000)
#error This code assumes that the CPU clock divided by 2 is less than or equal to the MCP23S17's maximum speed of 10 MHz, and in this case, it's not.
#endif
SPCR = (0 << SPIE) | // No SPI interrupts
(1 << SPE) | // Enable SPI
(0 << DORD) | // MSB first
(1 << MSTR) | // Master mode
(0 << CPOL) | // SPI mode 0,0
(0 << CPHA) |
(0 << SPR0); // SCK frequency = F_CPU/2 (because of SPI2X being set below
SPSR = (1 << SPI2X); // Double the SPI clock rate -- allows /2 instead of /4
// Leave CS deasserted
DEASSERT_CS();
// Pull the MCP23S17 out of reset (it's pulled down to GND on the board with a 100k pulldown
// so it won't activate during AVR ISP programming)
PORTB |= MCP23S17_RESET;
_delay_ms(50);
// All done!
MCP23S17_Inited = true;
}
void MCP23S17_SetDDR(uint16_t ddr)
{
// The MCP23S17's DDR is backwards from the AVR's.
// I like dealing with it so it behaves like the AVR's,
// so I invert any DDR values in this driver.
// In other words, when you set or get the DDR through
// this driver, the 1s and 0s are backwards from what
// the MCP23S17's datasheet says, but they are
// consistent with the AVR. I value the consistency more.
MCP23S17_WriteBothRegs(MCP23S17_IODIRA, ~ddr);
}
void MCP23S17_SetPins(uint16_t data)
{
MCP23S17_WriteBothRegs(MCP23S17_GPIOA, data);
}
uint16_t MCP23S17_ReadPins(void)
{
return MCP23S17_ReadBothRegs(MCP23S17_GPIOA);
}
void MCP23S17_SetPullups(uint16_t pullups)
{
MCP23S17_WriteBothRegs(MCP23S17_GPPUA, pullups);
}
// Determines the output values of output pins without reading any input pins
uint16_t MCP23S17_GetOutputs(void)
{
return MCP23S17_ReadBothRegs(MCP23S17_OLATA);
}
uint16_t MCP23S17_GetDDR(void)
{
// As I mentioned above, DDR bits are inverted from
// what the MCP23S17's datasheet says, but
// consistent with what the AVR's datasheet says
return ~MCP23S17_ReadBothRegs(MCP23S17_IODIRA);
}
uint16_t MCP23S17_GetPullups(void)
{
return MCP23S17_ReadBothRegs(MCP23S17_GPPUA);
}
uint8_t MCP23S17_ByteRW(uint8_t b)
{
SPDR = b;
while ((SPSR & (1 << SPIF)) == 0);
return SPDR;
}
void MCP23S17_WriteBothRegs(uint8_t addrA, uint16_t value)
{
// addrA should contain the address of the "A" register.
// the chip should also be in "same bank" mode.
ASSERT_CS();
// Start off the communication by telling the MCP23S17 that we are writing to a register
MCP23S17_ByteRW(MCP23S17_CONTROL_WRITE(0));
// Tell it the first register we're writing to (the "A" register)
MCP23S17_ByteRW(addrA);
// Write the first byte of the register
MCP23S17_ByteRW((uint8_t)((value >> 8) & 0xFF));
// It should auto-increment to the "B" register, now write that
MCP23S17_ByteRW((uint8_t)(value & 0xFF));
DEASSERT_CS();
}
uint16_t MCP23S17_ReadBothRegs(uint8_t addrA)
{
uint16_t returnVal;
ASSERT_CS();
// Start off the communication by telling the MCP23S17 that we are reading from a register
MCP23S17_ByteRW(MCP23S17_CONTROL_READ(0));
// Tell it which register we're reading from (the "A" register)
MCP23S17_ByteRW(addrA);
// Read the first byte of the register
returnVal = (((uint16_t)MCP23S17_ByteRW(0)) << 8);
// It should auto-increment to the "B" register, now read that
returnVal |= MCP23S17_ByteRW(0);
DEASSERT_CS();
return returnVal;
}