|
|
View previous topic :: View next topic |
Author |
Message |
vinniewryan
Joined: 29 Jul 2009 Posts: 154 Location: at work
|
Serial communication between PIC and components |
Posted: Thu Aug 27, 2009 5:33 pm |
|
|
For the last week I've been looking for a tutorial on working with serial data between microcontrollers and serial components. If anyone knows of a good tutorial that spells out the process of working with serial communications with microcontrollers, please enlighten me. I would love to learn everything about serial data exchange, but for now I need to learn what I can to get past my problem.
Basically I have an ADXL345Z digital accelerometer that communicates with a microcontroller via serial data. The data sheet for the accelerometer can be found here: http://www.analog.com/static/imported-files/data_sheets/ADXL345.pdf
The PINOUT for the accelerometer as mounted on the EVAL board is as follows:
1 VIO - I/O Supply Voltage
2 GND - Ground
3 CS - Chip Select
4 VS - Supply Voltage (3.0V)
5 SCL - Serial Clock
6 SDA/SDI - Serial Data Input
7 SDO - Serial Data Output
8 INT1 - Interrupt 1
9 INT2 - Interrupt 2
Instead of analog X Y Z pins, I have to retrieve the X Y Z values through SDI and SDO serial I/O pins. This is all fine except I'm not too entirely familiar with how to do this. I understand there is a process for working with serial data that goes something like this:
• Initiate clock between PIC and device
• Send X Y Z values to registers
• Read data from registers to the PIC
• Convert data to numeric values
• Repeat multiple times per second
This is where I'm stumped. I've tried to find help online, and I've had some success with learning about registers but I don't know how to do what I need to do with the knowledge I have. If anyone here has the knowledge to help me get started, I would be very grateful. Starting with how to set a pin on my PIC as a serial data input pin, and another as a serial output pin. I'm using a 16F684 14 pin 8 bit PIC.
Thanks! |
|
|
vinniewryan
Joined: 29 Jul 2009 Posts: 154 Location: at work
|
|
Posted: Fri Aug 28, 2009 4:06 am |
|
|
I think I'm on the right track:
Code: | #define RS232_XMIT PIN_A1
#define RS232_RCV PIN_A0
#use rs232(baud=9600, xmit=RS232_XMIT, rcv=RS232_RCV) |
These lines of code should ready my PIC for transcieving data through pins A0 and A1, but I still need to setup the clock so the data can be read at the fall of each clock pulse. Would this be as simple as sending constant pulses to another pin? Such as:
Code: | output_high(pin_A2);
delay_us(20);
output_low(pin_A2);
delay_us(20); |
Or am I completely off? |
|
|
andrewg
Joined: 17 Aug 2005 Posts: 316 Location: Perth, Western Australia
|
|
Posted: Fri Aug 28, 2009 4:32 am |
|
|
Your accelerometer is not an RS232 serial device, but an SPI serial device. Look up "#use spi" in your manual... _________________ Andrew |
|
|
ckielstra
Joined: 18 Mar 2004 Posts: 3680 Location: The Netherlands
|
|
Posted: Fri Aug 28, 2009 5:14 am |
|
|
As an additional source of info study the SPI article on Wikipedia.
A confusing part in the SPI communication is that there are 4 different possible configurations but Motorola (Freescale) never defined this very well resulting in each hardware vendor using their own names (license issues also have to do with this).
This is the translation table I always use: Code: | // SPI Mode | MOTOROLA | MICROCHIP | CCS
//----------------------------------------------------------------
// | CPOL CPHA| CKP CKE |
// 0 | 0 0 | 0 1 | SPI_L_TO_H | SPI_XMIT_L_TO_H
// 1 | 0 1 | 0 0 | SPI_L_TO_H
// 2 | 1 0 | 1 1 | SPI_H_TO_L
// 3 | 1 1 | 1 0 | SPI_H_TO_L | SPI_XMIT_L_TO_H
//
#define SPI_MODE_0 (SPI_L_TO_H | SPI_XMIT_L_TO_H)
#define SPI_MODE_1 (SPI_L_TO_H)
#define SPI_MODE_2 (SPI_H_TO_L)
#define SPI_MODE_3 (SPI_H_TO_L | SPI_XMIT_L_TO_H) |
According to the ADXL345 datasheet: clock polarity (CPOL) = 1 and clock phase (CPHA) = 1.
This is SPI Mode 3
Code: | setup_spi(SPI_MASTER | SPI_MODE_3 | SPI_CLK_DIV_4 ); |
|
|
|
vinniewryan
Joined: 29 Jul 2009 Posts: 154 Location: at work
|
|
Posted: Fri Aug 28, 2009 5:43 am |
|
|
Wow that cleared so many things up! Thanks for the reply. I just finished going over the SPI functions in the manual, as well as the examples. I think I have it figured out except for setting the input pin to A0. Which is correct?
Code: | setup_spi(spi_slave | spi_l_to_h | DI=(pin_A0) ); |
or
Code: | ( !spi_data_is_in() && input(PIN_A0) ); |
EDIT: Also thanks to the most recent post, I'll check out that article right away. And your table will help me a lot. I kind of have the structure down, there are just a few gaps that I need to fill. I'll check out that article and check back tomorrow.
Thanks again! |
|
|
ckielstra
Joined: 18 Mar 2004 Posts: 3680 Location: The Netherlands
|
|
Posted: Fri Aug 28, 2009 6:00 am |
|
|
Code: | setup_spi(spi_slave | spi_l_to_h | DI=(pin_A0) ); | Your PIC is the SPI Master, not a Slave.
I just checked the datasheet of your PIC16F684 and noticed it has no hardware SPI module. This changes things a bit as the setup_spi() function only works on the hardware SPI module.
A workaround is to use the '#use SPI' directive introduced in the v4 CCS compiler which can generate a software based SPI. I don't know the actual status of this functionality right now. The first v4 compilers had some problems with it. |
|
|
mkuang
Joined: 14 Dec 2007 Posts: 257
|
|
Posted: Fri Aug 28, 2009 8:33 am |
|
|
In your drivers directory see whether you see a file called 9356.c. It shows you how to write to the 9356 series eeprom which use spi, on a PIC that does not have hardware spi.
Basically you have to clock the data out yourself, pretty tedious but not difficult. Personally I wouldn't pick an device nowadays without hardware spi (or uart for that matter). |
|
|
vinniewryan
Joined: 29 Jul 2009 Posts: 154 Location: at work
|
|
Posted: Sat Aug 29, 2009 6:50 pm |
|
|
This is actually a lot more simple than I thought. I've been reading and messing around with the test codes some more and I came up with what I think will be a working program, though when I compile it, I get these errors:
"Undefined identifier -- setup_spi"
"Undefined identifier -- spi_read"
Code: |
#include "C:\.....\MAIN.h"
#use delay(clock=4000000) //4 MHz OSC
#use spi(DI=PIN_A0, DO=PIN_A1, CLK=PIN_A2, ENABLE=PIN_C0)
float data;
void main()
{
setup_spi(SPI_MASTER | SPI_H_TO_L | SPI_CLK_DIV_16);
output_low(PIN_A0); //SPI DATA INPUT
output_low(PIN_A1); //SPI DATA OUTPUT
output_low(PIN_A2); //SPI CLOCK
output_low(PIN_C0); //SPI LED
output_low(PIN_C1); //SPI CHIP SELECT
while(1)
{
if ((pin_C2)==1) //button is pressed; not setup yet
if ( spi_data_is_in() )
data=spi_read();
}//while
} //main
|
Am I missing something? I've looked over the example programs and they don't have any identifiers that pertain to SPI, so I'm stumped. |
|
|
andrewg
Joined: 17 Aug 2005 Posts: 316 Location: Perth, Western Australia
|
|
Posted: Sat Aug 29, 2009 9:20 pm |
|
|
setup_spi, spi_data_is_in and spi_read are all only used with hardware SPI, which your PIC doesn't have.
In your case #use spi will setup software SPI support, and then you just use the function spi_xfer to transfer data.
Typical interaction with an SPI device involves setting the chip select to select the device, performing the comms, then deselecting the device. This last can be important as some devices won't act on commands sent to them until they've been deselected.
You may not even need the output_low calls to setup the input, output and clock pins. I think the compiler generates code to do that. You'll need to keep the LED and chip select setups for sure, though. If you inspect the compiler listing, you could probably confirm the code generated for the #use spi is setting up the outputs. _________________ Andrew |
|
|
vinniewryan
Joined: 29 Jul 2009 Posts: 154 Location: at work
|
|
Posted: Mon Aug 31, 2009 10:05 pm |
|
|
I'm close, I think. Thanks again for helping me through this. I don't know what I would have done without it your aid.
The only thing I'm having trouble with now is reading/ writing to and from the registers. I've written the following sample program that doesn't work, and I'm sure it's obvious why, but I can't figure out the proper way to do read or write from the accelerometer via SPI. The only thing required by the accelerometer is that I bring it out of standby mode by setting the FIFO bit. I'm not sure what to set it to but the data sheet also said that 3 is xyz measure mode, which is what I want. So I tried this:
spi_xfer(3), (0x38); //FIFO CTRL bit, bit 3 = xyz measure mode bit
And I failed. Details below, this is the program:
Code: |
#include "16f684.h"
#use delay(clock=20000000)
#Fuses FCMEN,RC,PROTECT,MCLR,BROWNOUT,CPD,WDT,NOPUT,IESO,NOPROTECT
#use spi(DI=pin_a0, DO=PIN_A1, CLK=PIN_A2, MODE=2)
int8 data;
int8 time;
void main()
{
while(1)
{
output_low(pin_c1); //chip select
output_low(pin_c0); //LED
delay_ms(time);
output_high(pin_c0); //LED
delay_ms(time);
//X, Y, Z registers = 8 bit
//X 0x32 DATA0, ox33 DATA1
//Y 0x34 DATA0, 0x35 DATA1
//Z 0x36 DATA0, 0x37 DATA1
//0x38 = FIFO set bit. Modes = FIFO, STREAM, TRIGGER, BYPASS
spi_xfer(3), (0x38); //FIFO CTRL bit, bit 3 = xyz measure mode bit
data=spi_xfer(0x33);
time=data; //LED will blink slower as X axis is closer to 0
output_high(pin_c1); //chip select, return high at end of transmission
delay_ms(10);
}
}
|
When I test the program on my circuit, the LED blinks only while the accelerometer is being moved. When the accelerometer is still, the LED stays solid. The odd thing is that the LED consistantly blinks about once every 100ms, the speed of the blinking never varies.
I know it's a problem with register 0x38... I just don't know how to correct it. |
|
|
andrewg
Joined: 17 Aug 2005 Posts: 316 Location: Perth, Western Australia
|
|
Posted: Tue Sep 01, 2009 8:34 am |
|
|
Code: | spi_xfer(3), (0x38); | You'll need to explain that a bit more. It's outputting the byte 3 to the SPI bus. the (0x38) is doing nothing.
I'd start by writing two functions - one to read a register and another to write to a register. I think this will be correct: Code: | int8 accel_reg_read(int8 addr)
{
int8 ret;
output_low(pin_c1);
spi_xfer(addr|0x80);
ret = spi_xfer(0);
output_high(pin_c1);
return ret;
}
void accel_reg_write(int8 addr, int8 data)
{
output_low(pin_c1);
spi_xfer(addr);
spi_xfer(data);
output_high(pin_c1);
} | I then might build on that to produce higher-level functions that use those two.
To set bit 3 in register 0x38, the code is: Code: | accel_reg_write(0x38, accel_reg_read(0x38) | 0x04); // read first, OR in bit, write it back | However, on reading the data sheet, register 0x38 is the FIFO control register, and bit 3 is just one of 5 bits in the samples field. Double-check which register and bits you should be using. _________________ Andrew |
|
|
vinniewryan
Joined: 29 Jul 2009 Posts: 154 Location: at work
|
|
Posted: Tue Sep 01, 2009 3:46 pm |
|
|
andrewg wrote: | Code: | spi_xfer(3), (0x38); | You'll need to explain that a bit more. It's outputting the byte 3 to the SPI bus. the (0x38) is doing nothing. |
The 0x38 part was a typo, sorry.
I tried the code as is, adding the line "time=data;", and the LED stayed lit solid with a very slight flicker once every 100 ms or so. But I must pick apart the code to better understand it so I know what each function is doing. I'll just copy your code and add notes.
Code: |
int8 accel_reg_read(int8 addr)
{
int8 ret;
output_low(pin_c1); //begin transmission
spi_xfer(addr|0x80); //transfer value of addr and 0x80 over SPI, why 0x80?
ret = spi_xfer(0); // variable ret (return) = data coming in from SPI
output_high(pin_c1); //end of transmission
return ret; //What is the purpose of returning 'ret'? For verification?
}
void accel_reg_write(int8 addr, int8 data)
{
output_low(pin_c1); //begin transmission
spi_xfer(addr); //transfer value of addr over SPI
spi_xfer(data); //transfer value of data over SPI
output_high(pin_c1); //end of transmission
}
|
So the read section of this code seems to be sending 0x80 to the accelerometer, then returning a value to ret. The write section seems to be sending values of data and addr to the accelerometer? Or are they reading from the accelerometer, to addr and data variables?
**********************
"After VS is applied, the device enters standby mode, where power
consumption is minimized and the device waits for VDD I/O to be
applied and for the command to enter measurement mode to be
received. (This command can be initiated by setting the measure
bit in the POWER_CTL register (Address 0x2D).)"
--This is what I wanted to accomplish with Code: | accel_reg_write(0x38, accel_reg_read(0x38) | 0x04); |
but by mistake I referred to 0x38 instead of the correct 0x2D register. |
|
|
andrewg
Joined: 17 Aug 2005 Posts: 316 Location: Perth, Western Australia
|
|
Posted: Wed Sep 02, 2009 8:11 am |
|
|
accel_reg_read is reading the value of a register in the accelerometer and returning that value so your app can doing something with it.
The "|0x80" part is setting bit 7 in the first byte sent to the accelerometer. If you read the data sheet (page 9), you'll see there's a bit marked R/W with a line over the W. What that means is when that bit is 1, the command is a Read and when it's 0, it's a Write.
The next bit is "MB" which is "Multi-Byte". I've ignored that and left it zero (single-byte transfers). The rest of the bits are address and data arguments.
To switch the accelerometer into measure mode, the code would be: Code: | accel_reg_write(0x2D, accel_reg_read(0x2D) | 0x08); // read first, OR in bit, write it back | That reads the current state of register 0x2D, sets bit 3 (Measure) to 1 and leaves the rest unchanged, then writes the new value back.
Note that in my previous post I couldn't count, and had 0x04 for bit 3, when it should have been 0x08, as I've just written. _________________ Andrew |
|
|
vinniewryan
Joined: 29 Jul 2009 Posts: 154 Location: at work
|
|
Posted: Fri Sep 04, 2009 8:29 pm |
|
|
I tried compiling the code you provided, and when the compiler asked for a main function I re-formatted the code to implement one. Now the LED isn't even coming on. I've tried messing around with it a little bit, but I can't seem to get the accelerometer to read anything. I'm not sure what the problem is...
Code: |
#include "16f684.h"
#use delay(clock=20000000)
#Fuses FCMEN,RC,PROTECT,MCLR,BROWNOUT,CPD,WDT,NOPUT,IESO,NOPROTECT
#use spi(DI=pin_a0, DO=PIN_A1, CLK=PIN_A2, MODE=3)
int8 time;
void accel_reg_write(int8 addr, int8 data)
{
time=data;
output_low(pin_C0); //LED
delay_ms(time); //LED DELAY
output_low(pin_c1);
spi_xfer(addr);
spi_xfer(data);
output_high(pin_c1);
output_high(pin_C0); //LED
}
void main()
{
int8 accel_reg_read(int8 addr)
{
int8 ret;
output_low(pin_c1);
spi_xfer(addr|0x80);
ret = spi_xfer(0);
output_high(pin_c1);
return ret;
//accel_reg_write(0x2D, accel_reg_read(0x2D) | 0x08); //this causes error: undefined identifier accel_reg_read
// I tried adding accel_reg_write(); here, but I get error: a numeric expression should appear between parentheses
}
}
|
|
|
|
andrewg
Joined: 17 Aug 2005 Posts: 316 Location: Perth, Western Australia
|
|
Posted: Fri Sep 04, 2009 8:37 pm |
|
|
Take accel_reg_read out of the main function. C doesn't allow nested functions. _________________ Andrew |
|
|
|
|
You cannot post new topics in this forum You cannot reply to topics in this forum You cannot edit your posts in this forum You cannot delete your posts in this forum You cannot vote in polls in this forum
|
Powered by phpBB © 2001, 2005 phpBB Group
|