SPI.transfer() always returns zeros from ST Micro shield

Hi, I am developing the Arduino sketch, library code and shield code for an STM8 based shield that connects to the Arduino Uno.

I am able to reliably send commands over the SPI bus to the STM8 shield. On the shield, there is firmware to process the command set being sent over the SPI bus.

For instance, the command JEManalogRead requests that the STM8 shield perform an ADC reading and return the data to the Uno master over the SPI bus.

Using the STM debugger, I can follow the code pretty completely and I see the STM8 interrupt handler putting data into the SPI1->DR register on the slave side and I can trace the subsequent dummy write from the Uno to clock the data in from the slave. I can also see activity on the MISO line when I issue a write from the Arduino library. But, the data I get back is always zero.

It's almost as if the MISO line is not configured as an input on the Arduino board.

You would make our job much easier if you provide a link to every piece of hardware you're using (except original Arduino products) as well as all libraries not included in the IDE.

In your case my guess is an error in your code or wiring. As you didn't post your code verifying this is not possible. Could you give us the favor to provide the information necessary to help you? Thank you.

It's almost as if the MISO line is not configured as an input on the Arduino board.

Correctly initialized the MISO line is an input, I talk to serveral devices over the SPI interface and that works flawlessly.

Is there any way I can read the ATMega registers from the Arduino environment?

Also, there is this excerpt from the data sheet:

When the SPI is configured as a Master (MSTR in SPCR is set), the user
can determine
the direction of the SS pin.

If SS is configured as an output, the pin is a general output pin which
does not affect the
SPI system. Typically, the pin will be driving the SS pin of the SPI Slave.

If SS is configured as an input, it must be held high to ensure Master
SPI operation. If
the SS pin is driven low by peripheral circuitry when the SPI is
configured as a Master
with the SS pin defined as an input, the SPI system interprets this as
another Master
selecting the SPI as a Slave and starting to send data to it. To avoid
bus contention, the
SPI system takes the following actions:

  1. The MSTR bit in SPCR is cleared and the SPI system becomes a Slave. As a
    result of the SPI becoming a Slave, the MOSI and SCK pins become inputs.

  2. The SPIF Flag in SPSR is set, and if the SPI interrupt is enabled,
    and the I-bit in
    SREG is set, the interrupt routine will be executed.

Thus, when interrupt-driven SPI transmission is used in Master mode, and
there exists a
possibility that SS is driven low, the interrupt should always check
that the MSTR bit is
still set. If the MSTR bit has been cleared by a Slave Select, it must
be set by the user to
re-enable SPI Master mode.

========================================

Is there any point where the Arduino code leaves the SS pin as an input?

Yes.

Tim, yes means it gets left as an input ?

I was also wondering is there any way a sketch can read Atmel registers?

Thanks much, B

I was not going to post any detail if you were going to ignore my post like you did pylon's post. You should respond to pylon's requests. He asked the right questions. Post links to your hardware and your test code that fails.

Is the Arduino the master SPI device? If so, why do you feel it is necessary to read the internal Atmel registers?

I'm sorry, I didn't ignore pylon. I simply didn't know how to respond to him/her. The shield board is our own design so it's not out there in the public domain. And there is probably a bug in our configuration. I am basically asking for advice on where to look next.

In our configuration, the Atmel chip is the master and the STM8 is the slave. We are trying to send bytes bidirectionally between the two. Writes from the master to the slave work and reads always return zero.

It would be nice to be able to read Atmel registers because there have been bugs in the Arduino codebase in the past, notably w/respect to the spi.end() and spi.begin() commands.

BTW, you treat new people like complete morons. If you don't want to help me, just say so.

When someone tells me to, "check my wiring and software," that is not help.

BTW, you treat new people like complete morons. If you don't want to help me, just say so.

No, SurferTim doesn't treat you like a moron. You didn't even try to answer my question but started to open a completely other field. I tried my best to be not rude and asked questions, maybe they wasn't sound enough, so please excuse, English is not my mother tongue.

When someone tells me to, "check my wiring and software," that is not help.

If you don't show your wiring to us, we are not able to check it, so it's you who's left to check it.
Although we cannot guarantee that there are no bugs in the SPI library, it's very improbable that your problem lies in one. The SPI library is quite slick, so don't look there for the error, it's either in your code or in your wiring.

Pylon, I am sorry if I seem like a trouble maker. I am working on getting an up to date schematic and the code excerpts. If you can look at them, that would be great.

Once, I was in Alsace-Lorraine and I asked directions to the WC. I was so tired that I got a droit and a gauche mixed up.

Hello, here are what I consider the important code excerpts from our shield project.

The STM8-based LCD Shield is designed to provide the capabilities of an LCD
screen while using only three SPI pins on the Arduino.  Also, the LCD
Shield provides GPIO pins, buttons, an A/D converter, a buzzer, etc.

====================================================================

Here is the code from the Arduino sketch loop.  It is trying to read a byte
from the STM8 shield and display on the STM8 shield LCD screen.  We use
pin 8 to select the STM8 slave.

DigitLCD lcd(8);

void loop() {

  //
  // Try to read a byte from STM8 shield and echo it back
  //

  x = lcd.test() ;
  delay(500) ;
  lcd.setSegments(1, x) ;     // Write value x to LCD position 1
  delay(500) ;

}

=====================================================================

Here is the code for the lcd-test() function from our LCDShield.cpp library:

byte DigitLCD::test(void) {
  byte value;
  
  configSpi() ;
  SPI.begin() ;
  
  digitalWrite(_pin,LOW);             // Assert CS To ST Micro
  delayMicroseconds(SPI_PACING);
  
  SPI.transfer(VMS_TEST_INTERFACE);   // Send Test Command
  delayMicroseconds(SPI_PACING);

  value= SPI.transfer(0x00);          // Read Response
  delayMicroseconds(SPI_PACING);
  
  digitalWrite(_pin,HIGH);
  SPI.end() ;

  return value;
}

=====================================================================

Here is the interrupt handler from the STM8 code.  It handles both
reads and writes.  There is a delay function in it because STM8
doesn't allow reads of its status register while in the interrupt
handler.  spiRxBuffer is a buffer for incoming commands and data.
spiTxBuffer is a buffer for responses to the UNO.


INTERRUPT_HANDLER(SPI_XMIT_COMPLETE, 26) {

   unsigned char x ;
	 unsigned int y ;

	     x = SPI1-> DR ;

             //
			 //  If there are no bytes needing to be sent out, this
			 //   must be a regular, incoming command or data byte.
			 //

		   if (!spi_poll_write()) {
				 
               spiRxBuffer[spiRxIn] = x ;
   		       spiRxIn = (spiRxIn + 1) & 0x0F ;
				 
		   }

             //
			 // Otherwise, send the next available byte.
			 //

       else {

                 for (y = 0; y < 1000; y++) ;
				 SPI1-> DR = spiTxBuffer[spiTxOut] ;
				 spiTxOut = (spiTxOut + 1) & 0x0F ;
				 
		   }
			
}

digit shield.pdf (95.8 KB)

BTW, you treat new people like complete morons. If you don't want to help me, just say so.

If that is aimed at me, that is completely untrue. I did treat you like a bordering-on-rude newbie, which is how you acted when you ignored pylon. If you did not know how to answer him, you should have said so. He would have explained to you what he wanted. We don't get paid for this. We do this out of a desire to help others, at least I do.

You should post your entire sketch.

You need run SPI.begin() only once, and that should be in the setup() function.

Do you have a logic analyzer or an o-scope? That would really help.

Is your STM8 set for MSBFIRST and SPI mode 0?

Criticism accepted. I misread pylon. Thanks for not giving me the boot.

I have a 150 MHz oscope but am primarily a software guy. I have a h/w tech who can help
me with probing.

I need to break this message up into sections because apparently there is a 9000 character limit.

==============================================

Here is how the STM8 part is initialized:

        //
	// SPI_CR1 (SPI Control Register 1)
	//
	// Bit 7:  0 = MSB Transmitted First
	// Bit 6:  1 = Peripheral Enabled (Wait To Do This Later)
	// Bits [5:3] = Baud Rate is SYSCLK / 16
	// Bit 2:  0 = Slave
	// Bit 1:  0 = Clock Goes To 0 When Idle
	// Bit 0:  0 = First CLK Transition Is Data Capture Phase
	
	
	SPI1->CR1 = 0x58 ;  // Switch To 0x58 To Enable SPI Peripheral

=============================

Here is the .INO sketch with unnecessary calls deleted to save space:

//
// LCDAPITEST.INO
//
// Copyright (c) 2014, JEM Innovation ... www.jeminnovation.com
//
// Revision History
//
// 0.01 -- 01 NOV 2014 Initial release
//




#include <DigitLCD.h>
#include <SPI.h>

DigitLCD lcd(8);

//
// void setup() ;
//
// This is the one-time initialization code.  All we want to do is give the
// VMShield processor a head start by a few seconds.
//
// The SPI bus initialization is done in the VMShield library that accompanies
// this file.

void setup()
{
  delay(5000);     // Let VMShield Wake Up First
}

//
// void loop() ;
//
// This is the code that runs continuously on the Arduino board.  In this example,
// we show you how to control the various functions of the VMShield board.
//

void loop() 
{
  
  unsigned int x ;

  delay(200) ;

StartOver:

  x = lcd.test() ;
  lcd.setDigit(1, x) ;
  delay(100) ;
  lcd.setDigit(2, x) ;
  delay(100) ;
  lcd.setDigit(3, x) ;
  delay(100) ;
  lcd.setDigit(4, x) ;
  delay(100) ;

goto StartOver ;

}

Continuation:
Here is the LCD Shield library code. For now, the setSegments(), setIcons(), and setDigits() functions seem to work 100%. From the read side, I am working on the ::test() function and the ::analogRead() function. The functions other than these may or may not have been tested.

I hit the 9000 character limit again, so I deleted omitted the body of some unused functions from the .CPP file.

#include "DigitLCD.h"

#ifndef Arduino_h
#include "arduino.h"
#endif

#include <SPI.h>

static const int SPI_PACING= 50;

//
//  These are the commands from the Arduino to the VMShield device
//

#define VMS_WAIT_FOR_RESPONSE            0x00
#define VMS_TEST_INTERFACE               0x03
#define VMS_SET_SEGMENTS                 0x10
#define VMS_SET_DIGIT                    0x11
#define VMS_SET_ICONS                    0x12
#define VMS_DISPLAY_NUMBER               0x13
#define VMS_DIGITAL_READ                 0x14
#define VMS_DIGITAL_WRITE                0x15
#define VMS_SET_OUTPUT_PIN               0x16
#define VMS_ANALOG_READ                  0x17
#define VMS_BYTE_WRITE                   0x18
#define VMS_SET_DECIMALS                 0x1D
#define VMS_SET_LEDS                     0x20
#define VMS_SET_BACKLIGHT                0x21
#define VMS_BEEP                         0x30
#define VMS_HANDLE_BUTTONS               0x40

// constructor used by spi driver

DigitLCD::DigitLCD(byte pin) {
  configSpi() ;
  SPI.begin();

  if (pin) {
     pinMode(pin, OUTPUT);
     digitalWrite(pin,HIGH);
  }
  
  _pin= pin;
  SPI.end() ;
}

void DigitLCD::waitForResponse(void) {
  int cnt= 0;
  while(cnt < 1000) {
    delayMicroseconds(SPI_PACING);
    if(SPI.transfer(VMS_WAIT_FOR_RESPONSE) == 0x55) return;
    cnt++;
  }
  Serial.println("SPI TIMEOUT");
}

void DigitLCD::SendAByte(unsigned char SomeNum) {

   configSpi() ;
   SPI.begin() ;
     
   digitalWrite(_pin, LOW);
   delayMicroseconds(SPI_PACING) ;
   SPI.transfer(SomeNum) ;
   delayMicroseconds(SPI_PACING) ;
   digitalWrite(_pin, HIGH) ;
   delayMicroseconds(SPI_PACING) ;
   
   SPI.end() ;
   
}


void DigitLCD::configSpi(void) {
  SPI.setBitOrder(MSBFIRST);
  SPI.setDataMode(SPI_MODE1);
  SPI.setClockDivider(SPI_CLOCK_DIV16);
}

void DigitLCD::setDecimals(byte position) {

  configSpi() ;
  SPI.begin() ;

  if (_pin) digitalWrite(_pin,LOW);
  delayMicroseconds(SPI_PACING) ;
  SPI.transfer(VMS_SET_DECIMALS) ;
  delayMicroseconds(SPI_PACING) ;
  SPI.transfer(position) ;
  delayMicroseconds(SPI_PACING) ;
  digitalWrite(_pin,HIGH);

  SPI.end() ;
  
}

void DigitLCD::setBacklight(byte level) {

  configSpi() ;
  SPI.begin() ;

  digitalWrite(_pin,LOW);
  delayMicroseconds(SPI_PACING) ;
  SPI.transfer(VMS_SET_BACKLIGHT) ;
  delayMicroseconds(SPI_PACING) ;
  SPI.transfer(level) ;
  digitalWrite(_pin,HIGH);

  SPI.end() ;
  
}
  

void DigitLCD::setSegments(byte position, byte segments) {

  configSpi() ;
  SPI.begin() ;

  if (_pin) digitalWrite(_pin,LOW);
  delayMicroseconds(SPI_PACING);
  SPI.transfer(VMS_SET_SEGMENTS);
  delayMicroseconds(SPI_PACING);
  SPI.transfer(position);
  delayMicroseconds(SPI_PACING);
  SPI.transfer(segments);
  delayMicroseconds(SPI_PACING) ;
  if (_pin) digitalWrite(_pin,HIGH);  
  delayMicroseconds(SPI_PACING);
  
  SPI.end() ;
  
}

void DigitLCD::setDigit(byte position, byte value) {

  configSpi() ;
  SPI.begin() ;

  if (_pin) digitalWrite(_pin,LOW);
  delayMicroseconds(SPI_PACING);
  SPI.transfer(VMS_SET_DIGIT);
  delayMicroseconds(SPI_PACING);
  SPI.transfer(position);
  delayMicroseconds(SPI_PACING);
  SPI.transfer(value);
  delayMicroseconds(SPI_PACING) ;
  if (_pin) digitalWrite(_pin,HIGH);
  delayMicroseconds(SPI_PACING);
  
  SPI.end() ;
  
}

// this routine both clears and sets icons based on the two integers sent
void DigitLCD::setIcons(unsigned int set) {

  configSpi() ;
  SPI.begin() ;

  if (_pin) digitalWrite(_pin,LOW);

  delayMicroseconds(SPI_PACING);
  SPI.transfer(VMS_SET_ICONS);

  delayMicroseconds(SPI_PACING);
  SPI.transfer(set >> 8);

  delayMicroseconds(SPI_PACING);
  SPI.transfer(set & 0xFF);

  delayMicroseconds(SPI_PACING);
  if (_pin) digitalWrite(_pin,HIGH);
 
  SPI.end() ;
  
}

void DigitLCD::setRGB(byte red, byte green, byte blue) {
}

void DigitLCD::beep(byte pitch, byte duration) {
}

byte DigitLCD::buttons(byte *currentPtr) {
  return events;
}

byte DigitLCD::test(void) {
  byte value;
  
  configSpi() ;
  SPI.begin() ;
  
  digitalWrite(_pin,LOW);             // Assert CS To ST Micro
  delayMicroseconds(SPI_PACING);
  
  SPI.transfer(VMS_TEST_INTERFACE);   // Send Test Command
  delayMicroseconds(SPI_PACING);

  value= SPI.transfer(0x00);          // Read Response
  delayMicroseconds(SPI_PACING);
  
  digitalWrite(_pin,HIGH);
  SPI.end() ;

  return value;
}

byte DigitLCD::JEMdigitalRead(byte pin) {
}


void DigitLCD::JEMdigitalWrite(byte pin, byte onoff) {
}


void DigitLCD::JEMpinMode(byte pin, byte hilow) {
}

//
// DigitLCD::JEMwrite(byte data) ;
//
// General purpose byte write from Arduino to VMS device
//

void DigitLCD::JEMwrite(byte data) {
}


unsigned int DigitLCD::JEManalogRead(byte ADCpin) {
}


byte DigitLCD::JEMread(void) {
}

byte DigitLCD::JEMavailable(void) {
}


void DigitLCD::SetBitRate(unsigned long bitrate) {
}  

void DigitLCD::JEM_init(void) {
}

Here is the #include file for the .CPP library code

/*
*/
#ifndef DIGIT_LCD_H
#define DIGIT_LCD_H

#ifndef Arduino_h
#include "arduino.h"
#endif

 //
 //  The following are the icons available on the VMShield
 //   Not including the typical 7 segment alphanumerics.
 //

#define SET_ICON      0x0001
#define ALARM_ICON    0x0002
#define AMPS_ICON     0x0004
#define VOLTS_ICON    0x0008
#define LOW_ICON      0x0010
#define BATTERY_ICON  0x0020
#define START_ICON    0x0040
#define STOP_ICON     0x0080
#define DEGREE_ICON   0x0100
#define COLON_ICON    0x0200 
 
class DigitLCD {
public:
  DigitLCD(byte pin);
  void setSegments(byte position, byte segments);
  void setDigit(byte position, byte value);
  void setIcons(unsigned int set);
  void setDecimals(byte position) ;
  void display(int value, byte field= 0, byte format= 0);
  void setRGB(byte red, byte green, byte blue);
  void beep(byte pitch, byte duration);
  void setBacklight(byte level);
  byte JEMdigitalRead(byte pin);   
  void JEMdigitalWrite(byte pin, byte onoff);  
  void JEMpinMode(byte pin, byte onoff);       
  void JEMwrite(byte data);
  byte JEMread(void);
  byte JEMavailable(void);  
  void SetBitRate(unsigned long bitrate);
  void JEM_init(void);
  void SendAByte(unsigned char SomeNum) ;  
    
  byte buttons(byte *currentPtr = NULL);
  unsigned int JEManalogRead(byte ADCpin); 
  byte test(void); 
  
private:
  void waitForResponse(void);
  void configSpi(void);
  int _pin;
};

#endif

OK. I would recommend starting out with a test sketch in the Arduino and working from there. I don't know what commands your STM8 is expecting, so you should change the spiCommand variable to something that will send an expected output on the MISO line.

Connect one lead of the o-scope to the SCK line, and one to the MISO line on the Arduino. Your hardware person should know how to setup the rest of the o-scope settings. The SCK clock should be running at about 4MHz.

With any luck, you will see two sets of 8 pulses (low-to-high-to-low) on the SCK line every 1/10th second, and the response from the STM8 on the MOSI MISO line during those pulses.

#include <SPI.h>

byte spiCommand = 0;

void setup() {
  Serial.begin(9600);

  pinMode(8,OUTPUT);
  digitalWrite(8,HIGH);

  SPI.begin();
}

void loop() {

  byte rtnVal;

  // set slave select LOW
  digitalWrite(8,LOW);
  delay(1);

  // send command
  SPI.transfer(spiCommand);

  // receive response
  rtnVal = SPI.transfer(0);

  // set slave select HIGH
  digitalWrite(8,HIGH);

  // print the response
  Serial.println(rtnVal);

  // wait one tenth second
  delay(100);

}

Is that what you see?

In the past, I have checked the MISO line with a resistor to see if it is an input. If you want to test the MISO line on your Arduino, disconnect the STM8 and use a 1K resistor with one end connected to the MISO pin and the other connected to 5v or GND during the test above. If the MISO line is receiving data (input), it should show 255 when the resistor is connected to 5 volts, and 0 when connected to GND.

I understand what you are asking me to do. It may take a couple of days if you don't mind.

No problem. Post here if you would like more help.

Hi Surfer Tim, I did your test with the pullup resistor and your suspicion was correct, MISO is configured as in input. I get FF when the 1K resistor is connected to 5V and 00 when it is connected to ground.

I went and hooked up a serial port driver board to the Uno, now I can use the Serial.println() function. It works fine. I wanted to eliminate echoing characters back to our LCD shield. I do the following:

x = LCD.test() ;
Serial.print("Test returned: ") ;
Serial.println(x) ;

and, sure enough I get zeroes in the terminal program on the PC.

I also removed those extraneous calls to spi.begin() and spi.end() and that made no difference.

So, it is starting to smell like a hardware problem and my friend said he could help me look at the signaling later today. My oscope only has one probe, so I couldn't trigger on anything.

thanks, B

So, we looked at the SPI bus with a scope. The data being returned to the Arduino from the STM8 shield is definitely all zeros. We checked MISO at the STM8 device and it is never wiggling, reason unknown.

I am deliberately stuffing the STM data out register with a known pattern (e.g. 0x99, 0x55, etc) and the MISO line never changes at the STM8 part, so it seems like we must have a configuration problem on the STM8 device. Dunno, still baffled.

This problem is, at long last, solved.

Good news for the Arduino crowd, it has nothing to do with your platform.

Three observations:

  1. If you are using SPI, definitely upgrade to the news Arduino code. This way you can avoid the spi.begin()/spi.end() problem that was in rev 1.55(??) of the Arduino libraries.

  2. If you are using SPI on an STM8 microcontroller, you CANNOT read the status of the SPI hardware while in a transmit complete interrupt. It causes their clock tree to reset.

  3. You MUST set the MISO bit to be push-pull in the STM8 microcontroller. Otherwise, it defaults to an input. This was the primary problem. Even though we configured the PBx pins to be the SPI bus, an additional step (making the MISO bit a push-pull) is required. The MOSI bit requires no configuration on the STM8 part.

thousand pardons for my shortness when I originally posted this issue. It was very frustrating and could have been cured by a one sentence statement in the SPI section of their reference manual.