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 support@ccsinfo.com

PIC and A/D communication via SPI

 
Post new topic   Reply to topic    CCS Forum Index -> General CCS C Discussion
View previous topic :: View next topic  
Author Message
dklompar



Joined: 23 Feb 2008
Posts: 2

View user's profile Send private message

PIC and A/D communication via SPI
PostPosted: Sun Feb 24, 2008 12:04 am     Reply with quote

Hello,

I've been having some trouble getting the PIC18F8722 to communicate via SPI with an A/D.

The problem is: when I try to read or write to the A/D through the serial interface, the program hangs. It's waiting for the serial operation to complete.

I narrowed down the problem to the fact that the PIC never sends a clock pulse to the A/D. The A/D is controlled by the PIC's clock, so this step is vital. How can I ensure that the PIC sends a clock pulse with its read and write operations?

I'm also not exactly sure I have my spi_setup parameters right.

Here's the code I'm using:
Code:

#include <18F6722.h>

#device adc=8
#fuses NOWDT, HS, NOPROTECT

#use delay(clock=14745600)
#use rs232(baud = 9600, parity = N, xmit = PIN_C6, rcv = PIN_C7, bits = 8)
#use fixed_io(A_outputs = pin_A5, pin_A3)
#use fixed_io(C_outputs = pin_C1, pin_C2)

// ADC data in
#define AD_DIN PIN_C5

// ADC clock
#define AD_DCLK PIN_C3

// Mag Set-Reset
#define SET_RESET_PIN PIN_A3

// Gyro and temp
#define CHIP_SELECT_1 PIN_C2

// Accelerometer and magnetometer
#define CHIP_SELECT_2 PIN_C1

// A/D channels
#define CHANNEL_0 0x8F
#define CHANNEL_1 0xCF
#define CHANNEL_2 0x9F
#define CHANNEL_3 0xDF
#define CHANNEL_4 0xAF
#define CHANNEL_5 0xEF
#define CHANNEL_6 0xBF
#define CHANNEL_7 0xFF

BYTE read_from_spi(BYTE channel)
{
   BYTE data;

   output_low(CHIP_SELECT_2); // Select the A/D (it's active low)

   printf("Writing...\n\r"); // Request which channel of the A/D to get
   spi_write(channel);

   printf("Reading...\n\r"); // Get the info from the A/D
   data = spi_read(0);

   output_high(CHIP_SELECT_2); // Unselect the A/D

   printf("Returning...\n\r");
   return data;
}

void main()
{
   printf("Starting...\n\r");

   SET_TRIS_C(0b10010001);
   // C6 C5 C3 C2 C1 are outputs
   // C7 C4 C0 are inputs

   output_high(CHIP_SELECT_1); // Start out with both A/Ds not selected
   output_high(CHIP_SELECT_2);
   output_low(AD_DIN);
   output_low(AD_DCLK);

   setup_spi(SPI_MASTER | SPI_XMIT_L_TO_H | SPI_L_TO_H);
   printf("SPI setup complete...\n\r");

   while(true)
   {
      printf("At the beginning of the loop...\n\r");
      output_high(SET_RESET_PIN); delay_ms(5);
      output_low(SET_RESET_PIN); delay_ms(5);
      printf("SR toggled...\n\r");

      printf("CS = 2, Channel 0 = %u\n\r", read_from_spi(CHANNEL_0));
      printf("CS = 2, Channel 1 = %u\n\r", read_from_spi(CHANNEL_1));
      printf("CS = 2, Channel 2 = %u\n\r", read_from_spi(CHANNEL_2));
      printf("CS = 2, Channel 3 = %u\n\r", read_from_spi(CHANNEL_3));
      printf("CS = 2, Channel 4 = %u\n\r", read_from_spi(CHANNEL_4));
      printf("CS = 2, Channel 5 = %u\n\r", read_from_spi(CHANNEL_5));
      printf("CS = 2, Channel 6 = %u\n\r", read_from_spi(CHANNEL_6));
      printf("CS = 2, Channel 7 = %u\n\r", read_from_spi(CHANNEL_7));

      delay_ms(1000);
   }

}


Here are the data sheets for the PIC and the A/D.

Any help would be much appreciated!

-- Dylan
PCM programmer



Joined: 06 Sep 2003
Posts: 21708

View user's profile Send private message

PostPosted: Sun Feb 24, 2008 3:22 pm     Reply with quote

There are several issues.

1. You need the NOLVP fuse. Without it, if the PGM pin floats to a
high level, your PIC will go into program mode and it will appear to
"randomly lock up". Use the NOLVP fuse in all your programs.

2. The next issue is with the SPI setup. The ADS8345 data sheet shows
on page 3 that the maximum external clock frequency is 2.4 MHz.
But here, in your setup_spi() statement, you have left out the clock
divisor. In that case, it defaults to a divisor of Fosc/4.
Quote:
setup_spi(SPI_MASTER | SPI_XMIT_L_TO_H | SPI_L_TO_H);

But your Fosc is 14.7456 MHz, and dividing it by 4 gives 3.7 MHz.
That's way beyond the allowable maximum of 2.4 MHz. You need
to use the next higher divisor, and specify it with SPI_CLK_DIV_16.

3. Your channel select values are not correct. Here are the bit
definitions for the control byte in the data sheet:
Code:

BIT7   BIT6   BIT5   BIT4   BIT3   BIT2   BIT1   BIT0
 S      A2     A1     A0     -     SGL    PD1    PD0

Here's your code:
Quote:
// A/D channels
#define CHANNEL_0 0x8F
#define CHANNEL_1 0xCF
#define CHANNEL_2 0x9F
#define CHANNEL_3 0xDF
#define CHANNEL_4 0xAF
#define CHANNEL_5 0xEF
#define CHANNEL_6 0xBF
#define CHANNEL_7 0xFF

The only channels that are correct out of all those, are channels 0 and 7.
Look at channel 1, for example. You've got 0xCF. That is 1100 1111.
The channel bitfield is set to 100. But that's channel 4. It's wrong.
It should be 001. Combined back into the whole byte, it would be 0x9F.
Re-write the constants in binary format (using 0bnnnnnnnn) instead of
hex, to make it easier to see this. Look at Table III on page 13.

Also, you've got Bit 3 set = 1 in all of them. I would have left it as 0
but since they don't specify it, it's probably OK.


4. The read_from_spi() function is not correct. According to the data
sheet, after the 8-bit control byte is shifted out, they want 1 clock for
the busy bit and then 16 clocks for the data. You've only got 8 clocks
in your routine. The data sheet suggests that you do 24 clocks to
to get the data, and then presumably do a right-shift by 7 bits, to right
justify the data in a 16-bit word.
One other possibility, I believe proposed by Ttelmah or someone else,
is to manually toggle the first single bit, using output_high() and
output_low() functions, and then use the hardware SPI to shift in the
16 bits of data.. But I don't want to go search for that sample code
right now. I think it's easier for now, just to load the data into an int32
and right-shift it, and then return an int16.

5. Finally, there's no real reason to use fixed i/o and set the TRIS
yourself. It adds complexity, and all your communication is fairly low
speed. There is no need to optimize for minimum clock cycles. Further,
this PIC has 64K of instruction words. You're not running out of ROM.
I can't think of any reason not to let the compiler handle the TRIS.

6. Maybe one more thing. You should rename the constant for the
A/D chip select to "AD_CS" or something like that. Then it's apparent
what it's used for. Calling it "CHIP_SELECT_2" leaves it as a mystery.
It's better to have code that is self-documenting.
dklompar



Joined: 23 Feb 2008
Posts: 2

View user's profile Send private message

PostPosted: Mon Feb 25, 2008 1:43 pm     Reply with quote

PCM programmer, thank you for your wonderful suggestions! After modifying my code to include your suggestions, it started working!

For anyone who may have a similar problem in the future, here's the working code.

Code:

#include <18F6722.h>

#device adc=8
#fuses NOWDT, HS, NOPROTECT, NOLVP

#use delay(clock=14745600)
#use rs232(baud = 9600, parity = N, xmit = PIN_C6, rcv = PIN_C7, bits = 8)

// ADC data in
#define AD_DIN PIN_C5

// ADC clock
#define AD_DCLK PIN_C3

// Mag Set-Reset
#define SET_RESET_PIN PIN_A3

// Gyro and temp
#define AD_CS_1 PIN_C2

// Accelerometer and magnetometer
#define AD_CS_2 PIN_C1

// A/D channels
#define CHANNEL_0 0b10000111
#define CHANNEL_1 0b10010111
#define CHANNEL_2 0b10100111
#define CHANNEL_3 0b10110111
#define CHANNEL_4 0b11000111
#define CHANNEL_5 0b11010111
#define CHANNEL_6 0b11100111
#define CHANNEL_7 0b11110111

int16 read_from_spi(BYTE channel)
{
   BYTE data;
   int16 result = 0;

   output_low(AD_CS_2); // Select the A/D (it's active low)

   spi_write(channel); // Request which channel of the A/D to get
   result = spi_read(0);
   result = result << 8;
   data = spi_read(0);
   result = result | data;
   result = result << 1;
   data = spi_read(0);
   result = result | data;

   output_high(AD_CS_2); // Unselect the A/D

   return result;
}

void main()
{
   output_high(AD_CS_1); // Start out with both A/Ds not selected
   output_high(AD_CS_2);
   output_low(AD_DIN);
   output_low(AD_DCLK);

   setup_spi(SPI_MASTER | SPI_XMIT_L_TO_H | SPI_L_TO_H | SPI_CLK_DIV_16);
   printf("SPI setup complete...\n\r");

   while(true)
   {
      output_high(SET_RESET_PIN); delay_ms(5);
      output_low(SET_RESET_PIN); delay_ms(5);

      printf("CS = 2, Channel 0 = %lu\n\r", read_from_spi(CHANNEL_0));
      printf("CS = 2, Channel 1 = %lu\n\r", read_from_spi(CHANNEL_1));
      printf("CS = 2, Channel 2 = %lu\n\r", read_from_spi(CHANNEL_2));
      printf("CS = 2, Channel 3 = %lu\n\r", read_from_spi(CHANNEL_3));
      printf("CS = 2, Channel 4 = %lu\n\r", read_from_spi(CHANNEL_4));
      printf("CS = 2, Channel 5 = %lu\n\r", read_from_spi(CHANNEL_5));
      printf("CS = 2, Channel 6 = %lu\n\r", read_from_spi(CHANNEL_6));
      printf("CS = 2, Channel 7 = %lu\n\r", read_from_spi(CHANNEL_7));

      delay_ms(1000);
   }
}
Display posts from previous:   
Post new topic   Reply to topic    CCS Forum Index -> General CCS C Discussion All times are GMT - 6 Hours
Page 1 of 1

 
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