 |
 |
View previous topic :: View next topic |
Author |
Message |
songdaegon
Joined: 17 Jun 2025 Posts: 8
|
Failed to read SPI register on PIC16F15356 |
Posted: Wed Jul 16, 2025 6:50 pm |
|
|
Hello,
I hope this message finds you well.
We are currently experiencing an issue with SPI communication between the PIC16F15356 and the ADS8689.
Although we are receiving raw ADC values, the readings appear to be incorrect. For example, with a 4mA / 1V input, we are seeing approximately 25,000 as the raw value at 8mA input.
We initially thought adjusting the ADS8689 register settings would resolve the issue, but it seems the register values are not being updated at all.
Below is the code we’re using to attempt the register write:
Code: |
ADS8689_WriteRegister(0x10, 0x7D08); // DATAOUT_CTL_REG
delay_ms(2);
ADS8689_WriteRegister(0x14, 0x000B); // RANGE_SEL_REG (0~5.12V)
delay_ms(2);
|
When reading register 0x14 of the ADS8689, the value keeps alternating like this:
0x0008
0x0009
0x0008
0x0009
It continuously changes in this pattern.
main.c
Code: |
#include "main.h"
#include "ads8689.h"
#include "usart.h"
#include "modbus.h"
// ---------------- Timer ISR ----------------
#INT_TIMER0
void TIM0_ISR(void)
{
CLEAR_INTERRUPT(INT_TIMER0); // Prevent repeated interrupts
SET_TIMER0(TIM0_1ms); // Set timer to trigger again after 1ms
Sys_ms++; // System time counter in milliseconds
UART_Rx.ms++; // Used to check UART receive timeout
}
void PORT_Init(void)
{
OUTPUT_LOW(PIN_C3); // Set CLK pin LOW
OUTPUT_LOW(PIN_C5); // Set MOSI pin LOW
INPUT(PIN_C4); // Set MISO pin as input
delay_ms(10);
// 2. RST LOW → HIGH (Release reset)
output_low(ADS8689_RST);
delay_ms(2);
output_high(ADS8689_RST);
delay_ms(10);
PORT_C_PULLUPS(0xC0); // Enable pull-ups for RC6 (RX) and RC7 (TX)
}
// System initialization
void MCU_Init(void)
{
PORT_Init();
UART_Init();
// Use internal clock, divide by 2, use 8-bit timer
// SET_TIMER0() will be used to maintain 1ms timing
SETUP_TIMER_0(RTCC_INTERNAL | RTCC_DIV_2 | RTCC_8_BIT);
ENABLE_INTERRUPTS(INT_TIMER0); // Enable Timer0 interrupt
ENABLE_INTERRUPTS(PERIPH | GLOBAL); // Enable peripheral and global interrupts
}
// Modbus 0x0100, 0x0200 command branch handler
void DoCommand() {
Modbus_Branch_Process();
}
void main()
{
MCU_Init(); // Initialize MCU and peripherals
ADS8689_Init(); // Initialize ADS8689 ADC
modbus_init_slave(1); // Initialize Modbus slave with ID 1
ADC_Coeff_Init(); // Initialize ADC coefficient (e.g. gain, offset)
while (TRUE)
{
ADC_Scan(); // Perform ADC scan
DoCommand(); // Handle Modbus commands
//! uint16 reg = ADS8689_ReadRegister(0x14);
//! printf("REG[0x14] = 0x%04X\r\n", reg);
//! delay_ms(250);
}
}
|
ads8689.h
Code: |
#define CH_MAX 8 // Number of channels
// ADC to uA
// #define ADC_to_uA 0.0003125 // (5.120V / 65536 resolution / 250Ω)
#define ADC_to_uA 0.3125 // Simplified scale factor (5.120V / 65536 / 250Ω)
// uA to ADC
#define uA_to_ADC 3.2768 // 65536 max ADC value / 20000 uA (20mA max current)
uint16 ref_buff[2][CH_MAX]; // Calibration reference values: stores expected ADC values for 4mA and 20mA
uint16 meas_buff[2][CH_MAX]; // Calibration measured values: actual ADC readings for 4mA and 20mA
uint16 FBuff_mA[CH_MAX]; // Calibrated current values in uA (for Modbus transmission)
uint16 FBuff_ADC[CH_MAX]; // Converted 0–65535 ADC scale values (Modbus-compatible)
float Coeff_uA[CH_MAX]; // Calibration gain coefficients
float Offset_uA[CH_MAX]; // Calibration offsets
/////////////////////////////// test ///////////////////////////////////////////
uint16 ADS8689_ReadRegister(uint8 reg_addr)
{
uint8 cmd1, cmd2;
uint8 rx[4];
uint16 reg_value;
// Frame 1: Send READ command
cmd1 = 0xC0 | ((reg_addr & 0x3F) >> 1); // READ + high bits of address
cmd2 = (reg_addr << 7); // low bits of address
output_low(ADS8689_CS);
spi_write(cmd1);
spi_write(cmd2);
spi_write(0x00);
spi_write(0x00);
output_high(ADS8689_CS);
delay_us(2); // Wait for device response
// Frame 2: Dummy write
output_low(ADS8689_CS);
spi_write(0x00);
spi_write(0x00);
spi_write(0x00);
spi_write(0x00);
output_high(ADS8689_CS);
delay_us(2);
// Frame 3: Read actual response
output_low(ADS8689_CS);
rx[0] = spi_read(0x00);
rx[1] = spi_read(0x00);
rx[2] = spi_read(0x00);
rx[3] = spi_read(0x00);
output_high(ADS8689_CS);
reg_value = ((uint16)rx[0] << 8) | rx[1]; // First 2 bytes are valid data
return reg_value;
}
void ADS8689_Reset(void)
{
// Toggle RST pin: LOW → delay → HIGH
output_low(ADS8689_RST); // Begin ADS8689 reset
delay_ms(2); // Minimum delay 1ms (per TI datasheet)
output_high(ADS8689_RST); // Release reset
delay_ms(10); // Wait for stabilization (a few µs minimum, safely using 10ms)
}
// MUX channel select function (0~7)
void MUX36S08_Select(uint8 ch)
{
output_bit(MUX_A0, bit_test(ch, 0)); // Set A0
output_bit(MUX_A1, bit_test(ch, 1)); // Set A1
output_bit(MUX_A2, bit_test(ch, 2)); // Set A2
}
uint16 ADS8689_ReadADC(void)
{
uint8 rx[4];
uint32 value;
// 1. Frame 1: Send dummy READ command (starts conversion)
output_low(ADS8689_CS);
spi_write(0xC0); // READ command
spi_write(0x00); // address (0x00 or ignored)
spi_write(0x00); // dummy
spi_write(0x00); // dummy
output_high(ADS8689_CS);
delay_us(2); // Short delay
// 2. Frame 2: Receive 4-byte data
output_low(ADS8689_CS);
rx[0] = spi_read(0x00);
rx[1] = spi_read(0x00);
rx[2] = spi_read(0x00);
rx[3] = spi_read(0x00);
output_high(ADS8689_CS);
// Return top 16 bits
value = ((uint32)rx[0] << 24);
value |= ((uint32)rx[1] << 16);
value |= ((uint32)rx[2] << 8);
value |= ((uint32)rx[3]);
return (uint16)(value >> 8); // Equivalent to ((uint16)rx[1] << 8) | rx[2]
}
void ADS8689_WriteRegister(uint8 reg_addr, uint16 value)
{
uint8 cmd1, cmd2, data_h, data_l;
uint8 dummy;
cmd1 = 0xD0 | ((reg_addr & 0x3F) >> 1); // WRITE HWORD command
cmd2 = (reg_addr << 7);
data_h = (uint8)(value >> 8);
data_l = (uint8)(value & 0xFF);
output_low(ADS8689_CS); delay_us(1);
spi_write(cmd1);
spi_write(cmd2);
spi_write(data_h);
spi_write(data_l);
delay_us(1);
output_high(ADS8689_CS);
delay_us(2);
// Dummy Read Frame
output_low(ADS8689_CS); delay_us(1);
dummy = spi_read(0x00);
dummy = spi_read(0x00);
dummy = spi_read(0x00);
dummy = spi_read(0x00);
delay_us(1);
output_high(ADS8689_CS);
delay_us(2);
}
void ADC_Coeff_Init()
{
uint16 ch; // Channel index (0–7)
uint16 ref0, ref1, meas0, meas1;
float coeff, offset;
for(ch = 0; ch < CH_MAX; ch++)
{
ref0 = ref_buff[0][ch];
ref1 = ref_buff[1][ch];
meas0 = meas_buff[0][ch];
meas1 = meas_buff[1][ch];
Coeff_uA[ch] = 1.0;
Offset_uA[ch] = 0.0;
if((ref1 > ref0) && (meas1 > meas0))
{
coeff = (float)(ref1 - ref0) / (float)(meas1 - meas0);
offset = (float)ref0 - (float)meas0 * coeff;
Coeff_uA[ch] = coeff;
Offset_uA[ch] = offset;
}
}
}
void ADS8689_Init(void)
{
MUX36S08_Select(0);
delay_ms(10); // Wait for power stabilization (important)
// 1. Dummy Frame (NOP)
output_low(ADS8689_CS);
spi_write(0x00);
spi_write(0x00);
spi_write(0x00);
spi_write(0x00);
output_high(ADS8689_CS);
delay_ms(2);
// Write registers using exact 16-bit format
ADS8689_WriteRegister(0x10, 0x7D08); // DATAOUT_CTL_REG
delay_ms(2);
ADS8689_WriteRegister(0x14, 0x000B); // RANGE_SEL_REG (0–5.12V range)
delay_ms(2);
uint16 reg = ADS8689_ReadRegister(0x14); // Confirm written value
printf("REG[0x14] = 0x%04X\r\n", reg); // Print to terminal
}
void Convert_ADC_to_mA(uint8 ch, uint16 adc_val)
{
float uA = 0, val = 0;
if (adc_val < 8000) // Disconnected or invalid input
uA = 0;
else
{
val = (float)adc_val * ADC_to_uA;
uA = val * Coeff_uA[ch] + Offset_uA[ch];
// uA = (float)adc_val * COEF_ADC2uA_512V; // Alternative without calibration
}
FBuff_mA[ch] = (uint16)uA;
val = uA * uA_to_ADC;
if (val > 65535) val = 65535;
FBuff_ADC[ch] = (uint16)val;
}
void ADC_Scan(void)
{
static uint16 prev_ms = 0;
static uint8 ch = 0;
static uint8 scan_count = 0; // Track full scan cycles
if(Sys_ms - prev_ms < 125) return; // Sample each channel every 125ms
prev_ms = Sys_ms;
MUX36S08_Select(ch);
delay_ms(2);
ADS8689_ReadADC(); // Dummy read for conversion delay
delay_ms(2);
uint16 adc_val = ADS8689_ReadADC();
Convert_ADC_to_mA(ch, adc_val);
ch++;
if (ch >= CH_MAX) {
ch = 0;
scan_count++;
if (scan_count >= 5) { // After 5 full scans
scan_count = 0;
// Blink LED6
output_low(PIN_C1); // LED ON
delay_ms(10);
output_high(PIN_C1); // LED OFF
}
}
delay_ms(10);
}
|
|
|
 |
songdaegon
Joined: 17 Jun 2025 Posts: 8
|
Regarding the SPI hardware: |
Posted: Wed Jul 16, 2025 6:52 pm |
|
|
The pin configuration and wiring have all been checked. |
|
 |
Ttelmah
Joined: 11 Mar 2010 Posts: 19928
|
|
Posted: Thu Jul 17, 2025 5:41 am |
|
|
First, you should raise CS during the INIT. Otherwise it may not be high
on the first command.
However the big problem is your bytes being sent are wrong.
You are oring 7 bits of the register address withe the first byte sent, then
just sending the bottom bit of this as the top bit of the second byte.
Pretty much 100% wrong!.....
The write expects as it's first byte 0xD0, D2 or D4, with just the top bit of
a 9 bit address put into the bottom bit.
In fact it uses an 8 bit address, since the top bit is always zero.
So Your cmd1, for 16 bit write data is just 0xD0. Your OR there is creating
an invalid command, for anything but 0 as the register address. Wrong.
Then the reg_addr, does not want to be rotated to generate cmd1. That
if throwing away the top seven bits of the address, and just leaving the
low bit as the top bit in the address written. Again wrong.
cmd1, just wants the register address. No rotation involved at all.....
On your rotations for byte combination, just use make32. Faster, and
easier.
Then your data you send to the data out_ctl register is wrong. You
send 7D04, this would tell the converter to output all zeros. Why?.
100 for the low three bits.
 |
|
 |
songdaegon
Joined: 17 Jun 2025 Posts: 8
|
|
Posted: Sun Jul 20, 2025 9:30 pm |
|
|
I'll try applying it and provide feedback.
Thank you. |
|
 |
|
|
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
|