CCS C Software and Maintenance Offers
FAQFAQ   FAQForum Help   FAQOfficial CCS Support   SearchSearch  RegisterRegister 

ProfileProfile   Log in to check your private messagesLog in to check your private messages   Log inLog in 

CCS does not monitor this forum on a regular basis.

Please do not post bug reports on this forum. Send them to CCS Technical Support

Serial communication between PIC and components
Goto page 1, 2, 3  Next
 
Post new topic   Reply to topic    CCS Forum Index -> General CCS C Discussion
View previous topic :: View next topic  
Author Message
vinniewryan



Joined: 29 Jul 2009
Posts: 154
Location: at work

View user's profile Send private message MSN Messenger

Serial communication between PIC and components
PostPosted: Thu Aug 27, 2009 5:33 pm     Reply with quote

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

View user's profile Send private message MSN Messenger

PostPosted: Fri Aug 28, 2009 4:06 am     Reply with quote

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

View user's profile Send private message Visit poster's website

PostPosted: Fri Aug 28, 2009 4:32 am     Reply with quote

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

View user's profile Send private message

PostPosted: Fri Aug 28, 2009 5:14 am     Reply with quote

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

View user's profile Send private message MSN Messenger

PostPosted: Fri Aug 28, 2009 5:43 am     Reply with quote

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

View user's profile Send private message

PostPosted: Fri Aug 28, 2009 6:00 am     Reply with quote

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

View user's profile Send private message Send e-mail

PostPosted: Fri Aug 28, 2009 8:33 am     Reply with quote

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

View user's profile Send private message MSN Messenger

PostPosted: Sat Aug 29, 2009 6:50 pm     Reply with quote

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

View user's profile Send private message Visit poster's website

PostPosted: Sat Aug 29, 2009 9:20 pm     Reply with quote

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

View user's profile Send private message MSN Messenger

PostPosted: Mon Aug 31, 2009 10:05 pm     Reply with quote

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

View user's profile Send private message Visit poster's website

PostPosted: Tue Sep 01, 2009 8:34 am     Reply with quote

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

View user's profile Send private message MSN Messenger

PostPosted: Tue Sep 01, 2009 3:46 pm     Reply with quote

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

View user's profile Send private message Visit poster's website

PostPosted: Wed Sep 02, 2009 8:11 am     Reply with quote

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

View user's profile Send private message MSN Messenger

PostPosted: Fri Sep 04, 2009 8:29 pm     Reply with quote

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

View user's profile Send private message Visit poster's website

PostPosted: Fri Sep 04, 2009 8:37 pm     Reply with quote

Take accel_reg_read out of the main function. C doesn't allow nested functions.
_________________
Andrew
Display posts from previous:   
Post new topic   Reply to topic    CCS Forum Index -> General CCS C Discussion All times are GMT - 6 Hours
Goto page 1, 2, 3  Next
Page 1 of 3

 
Jump to:  
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