|
|
View previous topic :: View next topic |
Author |
Message |
dklompar
Joined: 23 Feb 2008 Posts: 2
|
PIC and A/D communication via SPI |
Posted: Sun Feb 24, 2008 12:04 am |
|
|
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
|
|
Posted: Sun Feb 24, 2008 3:22 pm |
|
|
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
|
|
Posted: Mon Feb 25, 2008 1:43 pm |
|
|
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);
}
}
|
|
|
|
|
|
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
|