Bidirectional SPI

cancel
Showing results for 
Show  only  | Search instead for 
Did you mean: 

Bidirectional SPI

2,752 Views
disdainfull
Contributor I

I am using the SLKS12 with option "C128", MC9S12 C128 MCU, 80 LQFP.  The accelerometer module that I am trying to communicate with is the HitachiH48C3 from Parallax.  It only has one data pin so I have no choice but to use bidirectional mode.  The datasheet is not very specific about the communication protocol but I found the data sheet for the ATD chip on the accelerometer module.  I have read a lot of threads concerning this mode but to no avail.  Hopefully Peg or BigMac can help with these short comings.  Here is the code I have been working with:

 

void main(void)
{
   float gforce[] = {0.0,0.0,0.0};                 //Calculated G-forces
                                                //0 is X axis
                                                //1 is Y axis
                                                //2 is Z axis
    SPIinit();
 
  //Forever loop
  for(;:smileywink:                                  
  {
       if( gforce[0]!=0.0 || gforce[1]!=0.0 || gforce[2]!=0.0)     //Test to see if motion
      PTT_PTT1 = 1;
    else
      PTT_PTT1 = 0;
       for (i=0 ; i<3 ; i++)
      gforce[i] = axis_acceleration(i);   //Interrogate accelerometer and calc G's
  }/* loop forever */
  /* please make sure that you never leave main */
}//end of main

 

 void SPIinit(void)
{
  SPICR1 = 0x70;                            //Load pattern for SPI control register 1
                                                //Interrupt disabled
                                                //System enabled
                                                //Trans interrupt enabled
                                                //Master mode
                                                //Clock polarity (idle low)
                                                //Clock phase (rising)
                                                //Slave select output disabled
                                                //MSB first
  SPICR2 = 0x03;                            //Load pattern for SPI control register 2
                                                //Mode fault disabled
                                                //Bidirectional mode(recieve)
                                                //Stop in wait mode
                                                //Serial pin control(bidirectional)
  SPIBR = 0x01;                             //Load pattern for SPI baud rate
                                                //Bus clock divide by 4
                                                //1MHz
                                                //500ns minimum clokc for accelerometer
                                                //2MHz clock maximum
}//SPI initialize subroutine

 

float axis_acceleration(int i)
{
  //Developed from example code "HitachiH48C3AxisAccelerometer.pdf"
  //Also referenced MCP3204/3208 datasheet from Microchip for SPI
  //Routine for reading accelerometer
  //To interogate accelerometer send 000110"axis"
    //000 are just fill bits for the 8bit SPIDR register
    //1 for start bit
    //1 for single ended operation of the ATD chip
    //0 is actually a don't care b/c there are only 4 ATD channels
    //"axis" is where the analog inputs are connected to the ATD
      //X axis is channel 00
      //Y axis is channel 01
      //Z axis is channel 10
      //Ref    is channel 11
  //Next series of words will contain 12bit channel conversion
    //First High-Z then "null" bit and then as MSB then LSB
    //high-z,null,11,10,9,8,7,6
    //5,4,3,2,1,0,1,2
    //3,4,5,6,7,8,9,10

   //11,0,0,0,0,0,0,0
    //then repeating nulls until interrogated again
 

 const int clkStall = 3;                    //Used to stall clock in communication with accelerometer                                                        
  const float G_convert =0.055000055;       //Constant used to convert ADC data to G-forces
                                       //4095 for max voltage on 12bit ADC
                                       //3.3V operational voltage
                                       //0.3663V = 1g output
                                       //G = (axis-vref)/4095*(3.3/0.3663)
                                       //G = (axis - vref)*0.0022000022
                                       //G/4 for shifting the data to align "0" bit
                                       //G*100 to make output unit 0.01g
  int j = 0;                        //Variable used for looping
  float gforce = 0.0;                 //Calculated G-forces
  word refCount = 0;                //Refernce data from accelerometer
  word axisCount = 0;               //Axis data from accelerometer
                                        //0 is X axis
                                        //1 is Y axis
                                        //2 is Z axis
  //Get reference conversion
  SPICR2_BIDIROE = 1;               //Set pin for ouput
  PTT_PTT0 = 0;                            //Turn accelerometer on
  PORTA = SPI_trans(3 + 24);        //Request reference
                                                                  //Reads erroneous data
  SPICR2_BIDIROE = 0;               //Set pin for input
  PORTA = SPI_trans(0xFF);          //Save MSB data
  PORTB = SPI_trans(0xFF);          //Save LSB data
  PTT_PTT0 = 1;                     //Turn accelerometer off
  refCount = PORTAB;                //Save reference count
 
  for( j=0 ; j<clkStall ; j++ ){}   //Delay loop for next accelerometer data
                                        //500ns minimum
                                        //3 * 250 = 750ns
  //Get Axis conversion
  SPICR2_BIDIROE = 1;               //Set pin for output
  PTT_PTT0 = 0;                             //Turn accelerometer on
  PORTA = SPI_trans(i + 24);        //Request axis
                                                                   //Reads erroneous data
  SPICR2_BIDIROE = 0;               //Set pin for input
  PORTA = SPI_trans(0xFF);          //Save MSB data
  PORTB = SPI_trans(0xFF);          //Save LSB data
  PTT_PTT0 = 1;                              //Turn accelerometer off
  axisCount = PORTAB;               //Save axis count

 

 //calculate g-forces
  gforce = (axisCount - refCount) * G_convert;
  return gforce;
}//axis acceleration subroutine

 

byte SPI_trans(byte val)
//SPI tranmit subroutine developed from
//https://community.freescale.com/message/35017#35017
{
  volatile byte garbage = 0;              //Variable used to collect "garbage"
  while (!SPISR_SPTEF);                   //Loops unitl Transmit Empty Flag is set
  garbage = SPISR;                        //First step to clear flags
  SPIDR = val;                            //Loads data and clears Flags
  while (!SPISR_SPIF);                    //Loops until data is transfered(8 clocks)
  garbage = SPISR;                        //First step to clear flags
  return SPIDR;                           //Returns data in SPI data register to caller and clears flags
}//SPI transmit subroutine

 

 

Thanks!

 

Labels (1)
0 Kudos
12 Replies

1,438 Views
bigmac
Specialist III

Hello, and welcome to the forum.

 

I will address the issue of communicating with the MCP3204 A/D converter using the SPI module.  Figure 6.1 of the converter datasheet shows a basic method (suitable for SPI mode 0,0) that would result in right-aligned 12-bit output data.  However, because you require to use bi-directional mode, this approach will need to be altered so that all configuration data is sent in the first SPI byte, and the 12-bit result is received exclusively within the next two bytes after the direction has been switched (and with the sending of "dummy" bytes).

 

The first send byte could be setup as follows:

Bit-7 = 0

Bit-6 = 1 (start bit)

Bit-5 = SGL/DIFF

Bit-4 = D2

Bit-3 = D1

Bit-2 = D0

Bit-1 = don't care (start conversion)

Bit-0 = don't care (null returned)

 

The master direction would then be switched to input mode, and the next two bytes would return the left-aligned 12-bit A/D data,  The low nybble of the second byte would repeat the bit sequence B1,B2,B3,B4, which would then usually be zeroed.

 

Regards,

Mac

 

0 Kudos

1,438 Views
disdainfull
Contributor I

Mac,

I will try your alignment protocol.  I apparently forgot to "zero" the top two bits of the upper byte before I shifted the word right.  The shift was in the Gconvert constant.  I do have a question though since I could not find this explicitly stated anywhere.  Does the SPI clock pause while the flags are set, in between bytes?

0 Kudos

1,438 Views
bigmac
Specialist III

Hello,


disdainfull@yahoo.com wrote:

I will try your alignment protocol.  I apparently forgot to "zero" the top two bits of the upper byte before I shifted the word right.  The shift was in the Gconvert constant.  I do have a question though since I could not find this explicitly stated anywhere.  Does the SPI clock pause while the flags are set, in between bytes?


When you initiate a transmission by sending a byte (dummy or otherwise) to the SPI data register, this will generate eight clock pulses only.  The next group of clock pulses will not commence until the next byte is written to the data register.  Any gap between bytes will be of no consequence to the A/D converter.

 

Regards,

Mac

 

0 Kudos

1,438 Views
kef
Specialist I

Ouch, please disregard my previous message. Now I see what's the purpose of start bit and how it enables bidirectional SPI with MCP3204. Thanks to Mac!

 

Regarding null bit (bit 0) in Mac's list. Since MCP3204 drives data line low soon after bit1 SCK falling edge, to prevent excessive current (on SPI data line) bit-1 and bit-0 sent to MCP3204 should be always 0. Current limiting resistor makes sense.

0 Kudos

1,438 Views
bigmac
Specialist III

Hello,

 

I had overlooked the ouput pin conflict.  If the accelerometer module has been conservatively designed, a current limiting series resistor may already be present.

 

An alternative way of avoiding the conflict issue would be to incorporate the null bit and the previous undefined bit within the first byte read by the SPI module.  The 16-bit result would then need to be shifted and masked to obtain the 12-bit data.  The final A/D value would be right-justified for unsigned int compatibility.

 

The modified send byte would be as follows:

Bit-7 = 0

Bit-6 = 0

Bit-5 = 0

Bit-4 = 1 (start bit)

Bit-3 = SGL/DIFF

Bit-2 = D2

Bit-1 = D1

Bit-0 = D0

 

Depending on what ultimately needs to be done with the acceleration data, it seems possible to avoid the use of floating point variables.  Assuming that the final acceleration result is a signed integer value in milli-G, the following integer calculation should give an accuracy that exceeds the accuracy of the sensor.

 

Xmg = (int)(((Xaxis - Ref)*1000L)/455);

 

 

Regards,

Mac

 

 

 

0 Kudos

1,438 Views
disdainfull
Contributor I

bigmac wrote:

Hello,

 

I had overlooked the ouput pin conflict.  If the accelerometer module has been conservatively designed, a current limiting series resistor may already be present.

 

An alternative way of avoiding the conflict issue would be to incorporate the null bit and the previous undefined bit within the first byte read by the SPI module.  The 16-bit result would then need to be shifted and masked to obtain the 12-bit data.  The final A/D value would be right-justified for unsigned int compatibility.

 

The modified send byte would be as follows:

Bit-7 = 0

Bit-6 = 0

Bit-5 = 0

Bit-4 = 1 (start bit)

Bit-3 = SGL/DIFF

Bit-2 = D2

Bit-1 = D1

Bit-0 = D0

 

Depending on what ultimately needs to be done with the acceleration data, it seems possible to avoid the use of floating point variables.  Assuming that the final acceleration result is a signed integer value in milli-G, the following integer calculation should give an accuracy that exceeds the accuracy of the sensor.

 

Xmg = (int)(((Xaxis - Ref)*1000L)/455);

 

 

Regards,

Mac

 

 

 


That is what I had assumed to start with.  I think the error was coming from not masking the upper two bits of the first read byte and the lower two bits of the second read before I shifted the combined value.  Reverting to the previous version and readding the syntax corrections and the unimplemented masks.

 

I am not the most experienced C programmer.  What is (int) in the beginning of the assignment?  Typecasting??

and what does the "L" stand for?

0 Kudos

1,438 Views
disdainfull
Contributor I

Okay, so all of the float variables are now int variables with the equation you provided.  I also added the masks.

  PORTA = SPI_trans(0xFF) & 0x3F;   //Save MSB data with upper bits reset
  PORTB = SPI_trans(0xFF) & 0xFC;   //Save LSB data with lower bits reset
Still nothing. 

0 Kudos

1,438 Views
bigmac
Specialist III

Hello,

 

PORTA and PORTB are hardware I/O registers.  Using them in place of a RAM variable can be problematic, especially if any of the I/O pins are configured as inputs.

 

The A/D readings have not been properly aligned for an integer value; there should be a shift to the right by two places.  It is probably cleaner to mask the 12-bit value after the shift has taken place.

 

Within main() you have the following statement, yet variable i does not appear to be defined.

PORTA = SPI_trans(i + 24);

 

 

Personally, my approach would be to create some additional functions that can be applied to any of the axes.  Maybe something like the following untested code example.

 

#define CS_ON  PTT_PTT0 = 0#define CS_OFF PTT_PTT0 = 1#define X_AXIS 0#define Y_AXIS 1#define Z_AXIS 2#define REF    4/*******************************************************************/// Global variables:int gforce[3];   // Calculated G-forces X, Y, Zint refval;/*******************************************************************/// Function prototypes:int get_accel( byte axis, int refcnt);int get_reading( byte chan);byte SPI_trans( byte val);void delay( byte n)void SPIinit(void)/*******************************************************************/void main( void){   byte i;      SPIinit();     // Initialise SPI module   for ( ; ; ) {      refval = get_reading( REF);      delay(3);      for (i = 0; i < 3; i++) {         gforce[i] = get_accel(i, refval); delay(3);      }      if (gforce[0] || gforce[1] || gforce[2])  // Test to see if motion         PTT_PTT1 = 1;      else         PTT_PTT1 = 0;   }}/*******************************************************************/
// Calculate axis acceleration
int get_accel( byte axis, int refcnt){   if (refcnt == 0) {      refcnt = get_reading( REF);      delay( 3);   }   return (int)(((get_reading( axis) - refcnt)*1000L)/455);}/*******************************************************************/// Get A/D reading

int get_reading( byte chan){   int result;      SPICR2_BIDIROE = 1;            // Set for ouput data from master   CS_ON;                         // Set CS active   (void)SPI_trans(0x18 + chan);  // Select required axis   SPICR2_BIDIROE = 0;            // Set for input data to master   result = SPI_trans(0xFF) << 8; // High data byte   result += SPI_trans(0xFF);     // Low data byte   CS_OFF;                        // Set CS inactive   result >>= 2;                  // Right align data   return (result & 0x0FFF);      // Mask 12-bit result}/*******************************************************************/// SPI transfer
byte SPI_trans( byte val){   while (!SPISR_SPTEF);          // Wait until send buffer is empty   SPIDR = val;                   // Send byte value   while (!SPISR_SPIF);           // Wait until transfer is complete   return SPIDR;                  // Also clears flag}/*******************************************************************/// Short delay functionvoid delay( byte n){   while (n)  n--;}

 Some further comment about the expression 

(int)(((get_reading( axis) - refcnt)*1000L)/455);

 

 

The use of the value 1000L indicates that this should be treated as a long (32-bit) type.  Otherwise there would be an integer overflow as a result of the multiplication process.  Then following the division, the result will again be within the range of a 16-bit value.  The cast provides conversion back to an int type.

 

I notice that your SPI_trans() function contained some additional reads of SPISR.  These are unnecessary (but will do no harm) since the testing of the flag bit will already meet the requirement for register read.

 

Regards,

Mac

 

0 Kudos

1,438 Views
disdainfull
Contributor I

Mac,

 

First I would like to thank you for all of your expertise!  I now have it working.  I did some more digging and found the datasheet for the accelerometer that parallax used.  It turns out that it detects gravity, Z axis acceleration.  I assumed that the module only detected changes in acceleration.  I changed the variables back to float and it is working like a champ.  Now on to the timer module to make my delays more suitable, not "poor man".:smileysad:  If you would like to see the full code let me know how to add the grey block like you did with your example code so that I am not character limited.  Thanks again!:smileyvery-happy:

0 Kudos

1,438 Views
Lundin
Senior Contributor IV

Some general C programming concerns:

 

 

if( gforce[0]!=0.0 || gforce[1]!=0.0 || gforce[2]!=0.0)

 

This is a bug, you cannot compare float numbers with the equality operators == or !=. This bug will make it seem as if the accelerometer is always giving a output even when it isn't. Read any C language FAQ for details, such as this one: http://c-faq.com/fp/fpequal.html

 

 

for( j=0 ; j<clkStall ; j++ ){}

 

This is a bug, the code will likely never be executed. If you insist on using a poor man's delay loop like this, then you must at least declare "j" as volatile. Use any of the 8 hardware timers or the RT clock of your MCU instead.

 

 

Also, note that 0.0 will be interpreted to "double" anywhere you use it. If you want to use numeric float constants in the code you must append an 'f' suffix to them, such as 0.0f. This may give you more efficient code and waste less flash memory.

0 Kudos

1,438 Views
disdainfull
Contributor I

Lundin,

Thanks for your comments on the programming syntax.  I am just now trying to use C++ instead of assymbly b/c it is a lot more readable.  I will implement the more appropriate timer once I get the SPI module operational.  One module at a time:smileyhappy:

0 Kudos

1,438 Views
kef
Specialist I

Parallax datasheet is almost useless. If they connected ADC serial in with ADC serial out, then they should tell that explicitly or provide full module schematic.

 

Since MCP3204 starts driving output pin sooner than 8 CLK periods after /CS, and since you can switch SPI direction only between transferring whole bytes, this module (with SPI in and out connected) is not SPI friendly. You should drive SPI pins manually (set clock pin high, set clock pin low, sample data pin etc), or maybe use wired or mode.

0 Kudos