mac-rom-simm-programmer/drivers/mcp23s17.c

213 lines
6.6 KiB
C
Raw Normal View History

Break out code into a HAL, optimize flash operations This makes the code pretty easily portable to other architectures if someone wants to make a more modern SIMM programmer. I also was pretty careful to split responsibilities of the different components and give the existing components better names. I'm pretty happy with the organization of the code now. As part of this change I have also heavily optimized the code. In particular, the read and write cycle routines are very important to the overall performance of the programmer. In these routines I had to make some tradeoffs of code performance versus prettiness, but the overall result is much faster programming. Some of these performance changes are the result of what I discovered when I upgraded my AVR compiler. I discovered that it is smarter at looking at 32-bit variables when I use a union instead of bitwise operations. I also shaved off more CPU cycles by carefully making a few small tweaks. I added a bypass for the "program only some chips" mask, because it was adding unnecessary CPU cycles for a feature that is rarely used. I removed the verification feature from the write routine, because we can always verify the data after the write chunk is complete, which is more efficient. I also added assumptions about the initial/final state of the CS/OE/WE pins, which allowed me to remove more valuable CPU cycles from the read/write cycle routines. There are also a few enormous performance optimizations I should have done a long time ago: 1) The code was only handling one received byte per main loop iteration. Reading every byte available cut nearly a minute off of the 8 MB programming time. 2) The code wasn't taking advantage of the faster programming command available in the chips used on the 8 MB SIMM. The end result of all of these optimizations is I have programming time of the 8 MB SIMM down to 3:31 (it used to be 8:43). Another minor issue I fixed: the Micron SIMM chip identification wasn't working properly. It was outputting the manufacturer ID again instead of the device ID.
2020-11-18 05:03:32 +00:00
/*
* mcp23s17.c
*
* Created on: Nov 25, 2011
* Author: Doug
*
* Copyright (C) 2011-2020 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.
*
Break out code into a HAL, optimize flash operations This makes the code pretty easily portable to other architectures if someone wants to make a more modern SIMM programmer. I also was pretty careful to split responsibilities of the different components and give the existing components better names. I'm pretty happy with the organization of the code now. As part of this change I have also heavily optimized the code. In particular, the read and write cycle routines are very important to the overall performance of the programmer. In these routines I had to make some tradeoffs of code performance versus prettiness, but the overall result is much faster programming. Some of these performance changes are the result of what I discovered when I upgraded my AVR compiler. I discovered that it is smarter at looking at 32-bit variables when I use a union instead of bitwise operations. I also shaved off more CPU cycles by carefully making a few small tweaks. I added a bypass for the "program only some chips" mask, because it was adding unnecessary CPU cycles for a feature that is rarely used. I removed the verification feature from the write routine, because we can always verify the data after the write chunk is complete, which is more efficient. I also added assumptions about the initial/final state of the CS/OE/WE pins, which allowed me to remove more valuable CPU cycles from the read/write cycle routines. There are also a few enormous performance optimizations I should have done a long time ago: 1) The code was only handling one received byte per main loop iteration. Reading every byte available cut nearly a minute off of the 8 MB programming time. 2) The code wasn't taking advantage of the faster programming command available in the chips used on the 8 MB SIMM. The end result of all of these optimizations is I have programming time of the 8 MB SIMM down to 3:31 (it used to be 8:43). Another minor issue I fixed: the Micron SIMM chip identification wasn't working properly. It was outputting the manufacturer ID again instead of the device ID.
2020-11-18 05:03:32 +00:00
*/
#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;
}