mac-rom-simm-programmer/drivers/mcp23s17.c
Doug Brown 2e4e303c0c Change license to GPLv3
I can't use GPLv2 as soon as I need to start using the Nuvoton sample
code which is licensed with an Apache 2.0 license.
2023-06-25 11:41:18 -07:00

212 lines
6.4 KiB
C

/*
* mcp23s17.c
*
* Created on: Nov 25, 2011
* Author: Doug
*
* Copyright (C) 2011-2023 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 3 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, see <https://www.gnu.org/licenses/>.
*
*/
#include "mcp23s17.h"
#include "hardware.h"
/// Maximum SPI clock rate = 10 MHz
#define MCP23S17_MAX_CLOCK 10000000UL
static void MCP23S17_WriteBothRegs(MCP23S17 *mcp, uint8_t addrA, uint16_t value);
static uint16_t MCP23S17_ReadBothRegs(MCP23S17 *mcp, uint8_t addrA);
/** Initializes an MCP23S17 object
*
* @param mcp The MCP23S17 object to initialize
* @param resetPin The GPIO pin hooked to the reset input
*/
void MCP23S17_Init(MCP23S17 *mcp, GPIOPin resetPin)
{
SPI_InitDevice(&mcp->spi, MCP23S17_MAX_CLOCK, SPI_MODE_0);
// Do a reset pulse if we need to. No need to save the reset pin
// after that.
if (!GPIO_IsNull(resetPin))
{
GPIO_SetDirection(resetPin, true);
GPIO_SetOff(resetPin);
DelayUS(1);
GPIO_SetOn(resetPin);
DelayUS(1);
}
}
/** Begins a set of transactions with the MCP23S17.
*
* @param mcp The MCP23S17 to talk with
*
* You have to call this before using any of the MCP23S17 functions other than
* init. This takes control of the SPI bus. If the MCP23S17 is the only device
* on the bus, you can just call this once in the program. Otherwise, you should
* release it as soon as you're done so other SPI devices can use the bus instead.
*/
void MCP23S17_Begin(MCP23S17 *mcp)
{
SPI_RequestBus(&mcp->spi);
}
/** Ends a set of transactions with the MCP23S17.
*
* @param mcp The MCP23S17 to release
*
* You should call this when you're done talking to the MCP23S17 to free up the
* SPI bus for other devices.
*/
void MCP23S17_End(MCP23S17 *mcp)
{
SPI_ReleaseBus(&mcp->spi);
}
/** Sets the data direction register in the MCP23S17
*
* @param mcp The MCP23S17
* @param ddr Bitmask representing direction of the 16 GPIO pins (1 = output, 0 = input)
*/
void MCP23S17_SetDDR(MCP23S17 *mcp, uint16_t ddr)
{
// The MCP23S17's DDR is backwards from most other chips. I like dealing
// with it so it behaves like other MCUs, 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 most MCUs. I value the consistency more.
MCP23S17_WriteBothRegs(mcp, MCP23S17_IODIRA, ~ddr);
}
/** Reads the data direction register in the MCP23S17
*
* @param mcp The MCP23S17
* @return Bitmask representing direction of the 16 GPIO pins (1 = output, 0 = input)
*/
uint16_t MCP23S17_DDR(MCP23S17 *mcp)
{
// As I mentioned above, DDR bits are inverted from what the MCP23S17's
// datasheet says, but consistent with most MCUs
return ~MCP23S17_ReadBothRegs(mcp, MCP23S17_IODIRA);
}
/** Sets the output values in the MCP23S17
*
* @param mcp The MCP23S17
* @param data Bitmask representing output state of the 16 GPIO pins (1 = high, 0 = low)
*/
void MCP23S17_SetOutputs(MCP23S17 *mcp, uint16_t data)
{
MCP23S17_WriteBothRegs(mcp, MCP23S17_GPIOA, data);
}
/** Gets the current output values in the MCP23S17
*
* @param mcp The MCP23S17
* @return Bitmask representing output state of the 16 GPIO pins (1 = high, 0 = low)
*/
uint16_t MCP23S17_Outputs(MCP23S17 *mcp)
{
return MCP23S17_ReadBothRegs(mcp, MCP23S17_OLATA);
}
/** Reads the current input values in the MCP23S17
*
* @param mcp The MCP23S17
* @return Bitmask representing input state of the 16 GPIO pins (1 = high, 0 = low)
*/
uint16_t MCP23S17_ReadInputs(MCP23S17 *mcp)
{
return MCP23S17_ReadBothRegs(mcp, MCP23S17_GPIOA);
}
/** Enables/disables pullups in the MCP23S17
*
* @param mcp The MCP23S17
* @param pullups Bitmask representing which input pins should have pullups enabled.
*/
void MCP23S17_SetPullups(MCP23S17 *mcp, uint16_t pullups)
{
MCP23S17_WriteBothRegs(mcp, MCP23S17_GPPUA, pullups);
}
/** Reads the current pullup state in the MCP23S17
*
* @param mcp The MCP23S17
* return Bitmask representing which input pins have pullups enabled.
*/
uint16_t MCP23S17_Pullups(MCP23S17 *mcp)
{
return MCP23S17_ReadBothRegs(mcp, MCP23S17_GPPUA);
}
/** Helper function that writes two consecutive registers in the MCP23S17
*
* @param mcp The MCP23S17
* @param addrA The address of the first ("A") register
* @param value The value to write to the A and B registers. High byte = A, low byte = B
*/
static void MCP23S17_WriteBothRegs(MCP23S17 *mcp, uint8_t addrA, uint16_t value)
{
// addrA should contain the address of the "A" register.
// the chip should also be in "same bank" mode.
SPI_Assert(&mcp->spi);
// Start off the communication by telling the MCP23S17 that we are writing to a register
SPI_RWByte(&mcp->spi, MCP23S17_CONTROL_WRITE(0));
// Tell it the first register we're writing to (the "A" register)
SPI_RWByte(&mcp->spi, addrA);
// Write the first byte of the register
SPI_RWByte(&mcp->spi, (uint8_t)((value >> 8) & 0xFF));
// It should auto-increment to the "B" register, now write that
SPI_RWByte(&mcp->spi, (uint8_t)(value & 0xFF));
SPI_Deassert(&mcp->spi);
}
/** Helper function that reads two consecutive registers in the MCP23S17
*
* @param mcp The MCP23S17
* @param addrA The address of the first ("A") register
* @return The value read back from the A and B registers. High byte = A, low byte = B
*/
static uint16_t MCP23S17_ReadBothRegs(MCP23S17 *mcp, uint8_t addrA)
{
uint16_t returnVal;
SPI_Assert(&mcp->spi);
// Start off the communication by telling the MCP23S17 that we are reading from a register
SPI_RWByte(&mcp->spi, MCP23S17_CONTROL_READ(0));
// Tell it which register we're reading from (the "A" register)
SPI_RWByte(&mcp->spi, addrA);
// Read the first byte of the register
returnVal = (((uint16_t)SPI_RWByte(&mcp->spi, 0)) << 8);
// It should auto-increment to the "B" register, now read that
returnVal |= SPI_RWByte(&mcp->spi, 0);
SPI_Deassert(&mcp->spi);
return returnVal;
}