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(;
{
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!
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
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?
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
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.
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
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?
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.
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
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". 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!
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.
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
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.