[SOLVED] Help with software SPI code problem

I have been developing my own software SPI library but testing it I am having problems with receiving MISO data.
The .transfer function is sending the byte data okay and the SRAM chip I'm testing it on is returning the expected result when looked at using a logic analyser but I am not getting what I expected when reading the MISO port & pin.
Could some have a look and point out where I'm going wrong please.
The library is using direct port manipulation to try and speed things up and when initialized it grabs and stores the port & mask for the supplied arduino pin numbers.
I have attached a logic screendump to show the SRAM is responding to a read memory request with the correct data.

The test sketch

#include "SPIsoft.h"

#define cs 13
#define miso 6
#define mosi 5
#define clk 7

#define WRITE 2
#define READ  3

SPIsoft sSPI(miso, mosi, clk);

const uint32_t ramSize = 0x1FFFF;           // 128K x 8 bit

char buffer1[] = {"Hello"};
char buffer2[sizeof(buffer1)];

void setup(){
  Serial.begin(115200);
  sSPI.begin();
  delay(2000);
  Serial.println("Setup Stage 1");
  digitalWrite(cs,HIGH);
  pinMode(cs,OUTPUT);
  //sSPI.setBitOrder(LSBFIRST);
  Serial.println("\r\nFill Memory with Buffer.");
  digitalWrite(cs,LOW);
  sRam_writeBuffer(0, buffer1, sizeof(buffer1) - 1);
  digitalWrite(cs,HIGH);
}

void loop(){
  delay(2000);
  Serial.println("\r\nRead Buffer.");
  digitalWrite(cs,LOW);
  sRam_readBuffer(0,buffer2,sizeof(buffer2) - 1);
  digitalWrite(cs,HIGH);
  for(byte i = 0; i < sizeof(buffer2) - 1; i++){
    Serial.print((byte)buffer1[i],BIN);
    Serial.print("\t");
    Serial.println((byte)buffer2[i],BIN);
  }
  Serial.println(buffer1);
  Serial.println(buffer2);
}

void sRam_readBuffer(uint32_t address, char *buffer, uint32_t length) {
  setAddressMode(address, READ);
  byte x;
  for (uint32_t i = 0; i < length; i++) buffer[i] = sSPI.transfer(0xFF);
}

void sRam_writeBuffer(uint32_t address, char *buffer, uint32_t length) {
  setAddressMode(address, WRITE);
  for (uint32_t i = 0; i < length; i++) sSPI.transfer(buffer[i]);
}

void setAddressMode(uint32_t address, byte mode) {
  sSPI.transfer(mode);
  sSPI.transfer((byte)(address >> 16));
  sSPI.transfer((byte)(address >> 8));
  sSPI.transfer((byte)address);
}

Library header

#ifndef _SPIsoft_H
  #define _SPIsoft_H
  
  #if ARDUINO < 100
    #error Not supported on IDE's < v1.0
  #else
    #include <Arduino.h>
  #endif
  
  // Copied from SPI.h (Dangerous)
  #define SPI_CLOCK_DIV4 0x00
  #define SPI_CLOCK_DIV16 0x01
  #define SPI_CLOCK_DIV64 0x02
  #define SPI_CLOCK_DIV128 0x03
  #define SPI_CLOCK_DIV2 0x04
  #define SPI_CLOCK_DIV8 0x05
  #define SPI_CLOCK_DIV32 0x06
  
  #define SPI_MODE0 0x00
  #define SPI_MODE1 0x04
  #define SPI_MODE2 0x08
  #define SPI_MODE3 0x0C
  
  
  
  class SPIsoft {
    public:
    SPIsoft(uint8_t, uint8_t, uint8_t);
    void begin();
    void end();
    byte transfer(byte _data);
    void setBitOrder(uint8_t);
    void setDataMode(uint8_t);
    void setClockDivider(uint8_t);
    
    private:
    volatile uint8_t  *_misoPort;
    uint8_t           _misoMask;
    volatile uint8_t  *_mosiPort;
    uint8_t           _mosiMask;
    volatile uint8_t  *_clkPort;
    uint8_t           _clkMask;
    uint8_t           _bitOrder;
    uint8_t           _clkPin;
    uint8_t           _mosiPin;
    uint8_t           _misoPin;
  };
  
#endif

Library core

#include "pins_arduino.h"
#include <SPIsoft.h>

SPIsoft::SPIsoft(uint8_t misoPin, uint8_t mosiPin, uint8_t clkPin){
  uint8_t port;
  // Store the pins
  _misoPin = misoPin;
  _mosiPin = mosiPin;
  _clkPin = clkPin;
  // Break down the pin numbers into port & mask values for direct port manipulation
  port = digitalPinToPort(misoPin);
  _misoPort = portInputRegister(port);
  _misoMask = digitalPinToBitMask(misoPin);
  
  port = digitalPinToPort(mosiPin);
  _mosiPort = portOutputRegister(port);
  _mosiMask = digitalPinToBitMask(mosiPin);
  
  port = digitalPinToPort(clkPin);
  _clkPort = portOutputRegister(port);
  _clkMask = digitalPinToBitMask(clkPin);
  
  // Set default bit order, mode & speed
  setBitOrder(MSBFIRST);
  //setClockDivider(SPI_CLOCK_DIV64);
  //setDataMode(SPI_MODE0);
}

void SPIsoft::begin(){
  digitalWrite(_misoPin,LOW);                   // *** Change if datamode implemented
  pinMode(_misoPin,INPUT);                      // Really needed?
  pinMode(_mosiPin,OUTPUT);
  digitalWrite(_clkPin,LOW);                    // *** Change if datamode implemented
  pinMode(_clkPin,OUTPUT);
}

void SPIsoft::end(){
  // Do nothing
}

byte SPIsoft::transfer(byte _data){
  uint8_t i, tmpByte;
  
  if(_bitOrder == MSBFIRST) {                   // Need to swap bit order?
    for(i = 0;i < 8;i++){
      tmpByte |= ((_data >> i) & 1) << (7 - i);
    }
    _data = tmpByte;
  }
  
  uint8_t oldSREG = SREG;                       // Backup register
  tmpByte = 0;
  cli();                                        // turn off interrupts to transfer
  
  for (i = 0; i <8; i++){                       // 8 bits to transfer
    
    if (_data & (1 << i)){
      *_mosiPort |= _mosiMask;                  // Set mosi pin
    }
    else{
      *_mosiPort &= ~_mosiMask;                 // Clear mosi pin
    }
    *_clkPort |= _clkMask;                      // Set clock pin
    
    delayMicroseconds(1);                       // Delay (* needs to be a better way)
    
    *_clkPort &= ~_clkMask;                     // Clear clock pin
    
    delayMicroseconds(3);                       // Delay to allow slave time to load bit

// *********** Suspect problem is here    
    if (*_misoPort & _misoMask){                // Read the miso port bit
      tmpByte &= ~(1 << i);
    }
    else{
      tmpByte |= (1 << i);
    }
  }
  
  SREG = oldSREG;                               // Restore register (turn interrupts back on)
  *_mosiPort &= ~_mosiMask;                     // Ensure MOSI is cleared *** Change if mode implemented

  _data = tmpByte;
  // Reconstruct the return value bit order if needed
  if(_bitOrder == MSBFIRST) {                   // Need to swap bit order?
    for(i = 0;i < 8;i++){
      tmpByte |= ((_data >> i) & 1) << (7 - i);
    }
    }
  
  return tmpByte;                               // Return with read data
}

void SPIsoft::setBitOrder(uint8_t bitOrder){
  _bitOrder = bitOrder;
}

void SPIsoft::setDataMode(uint8_t dataMode){
  // Not implemented yet
  //#error Not_Yet_Implemented
}

void SPIsoft::setClockDivider(uint8_t clockDivider){
  // Not implemented yet
  //#error Not_Yet_Implemented
}

Riva:
I have been developing my own software SPI library but testing it I am having problems with receiving MISO data.

Here's a software SPI code that I use for a DS3234 clock chip:

// transfer one byte via SPI Mode 3
uint8_t DS3234::_spi_transfer (uint8_t data)
{
    uint8_t x = 0b10000000;

    while (x) {
        digitalWrite (_sck_pin, LOW);
        digitalWrite (_mosi_pin, data & x ? HIGH : LOW);
        digitalWrite (_sck_pin, HIGH);
        digitalRead (_miso_pin) ? data |= x : data &= ~x;
        x >>= 1;
    }

    return data;
}

You can easily modify it for Mode 0,1 or 2 by changing the bit order (i.e. start with 0b00000001 and shift left) or change the SCK low and high order).

Here's a piece of code I use to get "Arduino to actual chip" addresses (if you need speed and don't want to use "digitalWrite" and "digitalRead":

void portToAddr (uint8_t pin, uint16_t *rd, uint16_t *wr, uint16_t *ddr, uint8_t *bitval)
{
    *rd = pgm_read_word (port_to_input_PGM + pgm_read_byte (digital_pin_to_port_PGM + pin));
    *wr = pgm_read_word (port_to_output_PGM + pgm_read_byte (digital_pin_to_port_PGM + pin));
    *ddr = pgm_read_word (port_to_mode_PGM + pgm_read_byte (digital_pin_to_port_PGM + pin));
    *bitval = pgm_read_byte (digital_pin_to_bit_mask_PGM + pin);
}

Call it like this:

uint8_t pin = A0; // arduino analog in 0
uint16_t rd; // AVR PINx pin
uint16_t wr; // AVR PORTx pin
uint16_t ddr; // AVR DDRx pin
uint8_t bitval; // hex value of bit (i.e. bit 5 returns 0x20)

portToAddr (pin, &rd, &wr, &ddr, &bitval);

Lastly, here's my SPI code that I use with my VFD (vacuum florescent display) driver:

/////////////////////////////////////////////////////////////////////////////////////////
// The following code is the driver for either serial SPI mode or parallel mode.
/////////////////////////////////////////////////////////////////////////////////////////
// if using serial SPI mode
#if GUU_MODE == 1

  #define CS1 _BV (2) // control port (C_PORT) bits
  #define CS2 _BV (1)
  #define RST _BV (0)

  #if ((defined(__AVR_ATmega2560__) || defined(__AVR_ATmega2561__)))
    // control port on a MEGA
    #define C_PORT PORTF
    #define C_DDR DDRF
	// SPI port on a MEGA
    #define SPI_PORT PORTB
    #define SPI_DDR DDRB
    #define SS_PIN _BV (0)
    #define MOSI_PIN _BV (2)
    #define MISO_PIN _BV (3)
    #define SCK_PIN _BV (1)
  #else
    // control port on an UNO
    #define C_PORT PORTB // for UNO SPI mode
    #define C_DDR DDRB
	// SPI port on an UNO
    #define SPI_PORT PORTB
    #define SPI_DDR DDRB
    #define SS_PIN _BV (2)
    #define MOSI_PIN _BV (3)
    #define MISO_PIN _BV (4)
    #define SCK_PIN _BV (5)
  #endif

// if using parallel mode (MEGA only 'cause UNO
// doesn't have enough pins for parallel mode)
#else

  #define RS _BV (7) // control port (C_PORT) bits
  #define RW _BV (6)
  #define EN _BV (5)
  #define CS1 _BV (4)
  #define CS2 _BV (3)
  #define RST _BV (2)
  #define C_PORT PORTC // control port
  #define C_DDR DDRC

#endif

void Noritake_VFD_GUU100::initPort (void)
{
#if GUU_MODE == 1
	// set RESET, CS1 and CS2 as outputs
	C_PORT = C_PORT | RST | CS1 | CS2;
	C_DDR = C_DDR | RST | CS1 | CS2;
	// setup SPI pins
	SPI_PORT = SPI_PORT | SS_PIN | MOSI_PIN | SCK_PIN;
	SPI_DDR = SPI_DDR | SS_PIN | MOSI_PIN | SCK_PIN;
	// setup SPI mode (master, mode 3, clk/2 speed, msb first)
	SPCR = 0 | _BV (SPE) | _BV (MSTR) | _BV (CPHA) | _BV (CPOL);
	SPSR = 0 | _BV (SPI2X);
#else
	// setup control port pins
	C_PORT = 0 | RS | RW | EN | CS1 | CS2 | RST;
	C_DDR = 0 | RS | RW | EN | CS1 | CS2 | RST;
#endif
}

void Noritake_VFD_GUU100::hardReset (void)
{
	_delay_ms (250); // time delay after powerup (GU128X64E manual pg. 17)
	C_PORT &= ~RST;
	_delay_ms (10);
	C_PORT |= RST;
	_delay_ms (100); // required delay (GU128X64E manual pg. 10)
}

uint8_t Noritake_VFD_GUU100::readPort (uint8_t rs, uint8_t chip)
{
#if GUU_MODE == 1
	C_PORT |= CS1 | CS2;
	(chip) ? C_PORT &= ~CS2 : C_PORT &= ~CS1;
	spiTransfer (0xFC + (rs << 1)); // send read command
	return spiTransfer (0); // return read data
#else
	C_PORT = (chip ? CS2 : CS1) | (rs ? RS : 0) | RW | EN | RST);
	DDRA = 0b00000000; // DDR=input
	C_PORT &= ~EN;
	C_PORT |= EN;
	asm volatile (// 300 ns delay (GU128X64E manual pg. 10)
		" nop\n" // 62.5 ns each
		" nop\n"
		" nop\n"
		" nop\n"
		" nop\n"
	);
	return PINA;
#endif
}

void Noritake_VFD_GUU100::writePort (uint8_t data, uint8_t rs, uint8_t chip)
{
#if GUU_MODE == 1
	C_PORT |= CS1 | CS2;
	(chip) ? C_PORT &= ~CS2 : C_PORT &= ~CS1;
	spiTransfer (0xF8 + (rs << 1)); // send write command
	spiTransfer (data); // send data
#else
	C_PORT = (chip ? CS2 : CS1) | (rs ? RS : 0) | EN | RST);
	DDRA = 0b11111111; // DDR=output
	PORTA = data;
	C_PORT &= ~EN;
	C_PORT |= EN;
#endif
}

#if GUU_MODE == 1
uint8_t Noritake_VFD_GUU100::spiTransfer (uint8_t data)
{
	SPDR = data;
	while (! (SPSR & _BV (SPIF)));
	return SPDR;
}

#endif

Hope you can make use of some of this.

-- Roger

Riva:

void SPIsoft::begin(){

digitalWrite(_misoPin,LOW);                   // *** Change if datamode implemented
  pinMode(_misoPin,INPUT);                      // Really needed?
  pinMode(_mosiPin,OUTPUT);
  digitalWrite(_clkPin,LOW);                    // *** Change if datamode implemented
  pinMode(_clkPin,OUTPUT);
}

The "pinMode INPUT" for the MISO pin is NOT needed if you use the built in AVR SPI, it IS needed if you use a bit-banger driver. Otherwise, if the MISO pin happens to be set as an output, the SPI slave device won't be able to drive the pin logic level (i.e. bus contention).

Krupski:
The "pinMode INPUT" for the MISO pin is NOT needed if you use the built in AVR SPI, it IS needed if you use a bit-banger driver. Otherwise, if the MISO pin happens to be set as an output, the SPI slave device won't be able to drive the pin logic level (i.e. bus contention).

I had originally questioned my decision to set pin as input as it's the default state when SPIsoft is initialized on Arduino bootup but left it in as by the time the .begin call was made it could have been set to output by the user.
Thanks for the code examples. It's the direct register equivalent of your "digitalRead (_miso_pin) ? data |= x : data &= ~x;" I am having problems with, the digitalWrite equivalent works fine as can be seen on the logic analyser traces. I send the MOSI data and get the correct MISO reply but somewhere I have screwed up and the code is not reading the pin correctly. This could be I'm not reading the correct port, not reading the correct bit or not leaving enough time after setting CLK low before reading the port. The latter is less likely as increasing the delay does not help.

<sigh.> why is it I can spend hours trying to chase down a bug without success and within an hour of posting on here for help I find it.
I will not bother posting a working sketch as I intend on inflicting this site with the library when I'm happy with it. Suffice to say I was reading data on the wrong clock edge.