Receive data (clock + signal) from PIC

I'm trying to use an Arduino to receive commands sent by PIC (18F524). The signal consists of a CLOCK pin at ~ 48Khz and a SIGNAL pin. Every 'command' consist of 18 * 4, 72 'bits' and is sent 6 times for redundancy.

Datasheet: Clock / Data

Datasheet: commands

One command, 72 bits.

On every rising edge of the clock, the data signal is read. So my first attempt to receive commands is by using the source clock as interrupt and do a digitalRead of the data pin on RISING.

Add all 72 reads in an array and compare this array to known commands.

A known command looks like "101000001100000000000000110001000100000000110001001000001001000010101100". It always start with '1010' and ends with '1100'.

Last received command looked = "000000100100000101000000000000000010000000000000000010000000000000000001" which doesn't come close to the sent signal.

I'm guessing the Arduino is too slow to do the digitalRead on time.. For testing i'm setting a test pin to high during the digitalRead and measuring this with a logic analyzer.

const int clockPin = 0; // digital pin 2
const int ledPin = 13;
const int dataPin = 8;
const int testPin = 11; 

int BjCmnd[72];
int i = 0;
int j = 0;
int q = 0;

int volX[72] = {  1,0,1,0,0,0,0,0,1,1,0,0,0,0,0,0,0,0,
                  0,0,0,0,0,0,1,1,0,0,0,1,0,0,0,1,0,0,
                  0,0,0,0,0,0,1,1,0,0,0,1,0,0,1,0,0,0,
                  0,0,1,0,0,1,0,0,0,0,1,0,1,0,1,1,0,0 };

void setup() {
  Serial.begin(9600); 
  pinMode(clockPin, INPUT);
  pinMode(dataPin, INPUT);
  pinMode(ledPin, OUTPUT);
  pinMode(testPin, OUTPUT);
  attachInterrupt(clockPin, pin2Interrupt, RISING);
}

void pin2Interrupt(void)
{
  digitalWrite(testPin, HIGH);
  int dataState = digitalRead(dataPin);
  digitalWrite(testPin, LOW);
  
  if (i == 72)
  {
     j++;
     i=0;  
  }
   
  BjCmnd[i] = dataState;
  i++;
}



void loop() {
  Serial.println(j);
  
  delay(5000);
  
  Serial.print("VJ VolX = \"");
  for (q=0; q<=71; q++)
  {  
     Serial.print(volX[q]);
  }
  Serial.println("\"");
  
  Serial.print("BJ Cmnd = \"");
  for (q=0; q<=71; q++)
  {  
     Serial.print(BjCmnd[q]);
  }
  Serial.println("\"");
}

result:


Depending on the 'delay' for setting a pin HIGH, it seems the delay is too much to read the signalpin on the clock rising.

I've tried using a second arduino to delay the clock, but so far no luck.

I think that you have way too much going on in your loop().
I thing think that it should be nothing but waiting for the interrupt event.

The interrupt routine should digitalRead the data/input pin, figure what it got (0/1) and what to do with that, and get back out.

[ Delay() doesn't work inside an interrupt. ]

Thanks, but even with an empty loop i'm seeing the same delay.

I thought it didn't matter what the loop() containts. An interrupt always has priority.

It still seems the digitalread is done too slow after the clock rise, missing the actual value.

The arduino simply just needs to:

  • sample 72 bits on clock rise
  • Include all 72 bits in one array
  • Compare this array to some known commands
  • Set a pin high/low of analogwrite a certain level based on the command.

If you use the Clock (48 kHz, 20usec, that's not ultra-fast) and set the Arduino input High (disconn'd from the PIC), does it report back with all 1's and if you set it Low then all 0's?

Yes. Tested by connecting the data pin to gnd or vcc.

The data pin is high for about half the clock time. 10us.

The clock rises in the middle of the data pin high. Which means the data pin in only high for 5us after the rise.

So it seems it mostly takes more then 5us for the interrupt event to be called + doing a digitalread()

You have:

void pin2Interrupt(void)
{
  digitalWrite(testPin, HIGH);
  int dataState = digitalRead(dataPin);
  digitalWrite(testPin, LOW);

pin2Interrupt is your “ISR”
It branches to that where

  • there’s a digitalWrite - which takes time
  • there’s a digitalRead - which takes time (OK)
  • there’s another digitalWrite - more time

If you just limit that to gobbling the data and filling the array and then dump out the result I think you’ll get have a better result outcome.

Thanks again!

I've added the digitalwrites after noticing something went wrong with capuring the data. Removing them doesn't resolve the issue.

Just tried something different, but it seems the CmndReceived() breaks the interrupt function. No errors, just no ouput.

const int clockPin = 0; // digital pin 2
const int dataPin = 8;

int BjCmnd[72];
int i = 0;
int j = 0;

void setup() {
  Serial.begin(9600); 
  pinMode(clockPin, INPUT);
  pinMode(dataPin, INPUT);
  attachInterrupt(clockPin, pin2Interrupt, RISING);
}

void pin2Interrupt(void)
{
  int dataState = digitalRead(dataPin);
  BjCmnd[i] = dataState;

  if (i == 71)
  {
    Serial.print("Command received:");
    i = 0;
    CmndReceived();
  }
  else
  {
    i++;  
  }
}


void loop() {
}


void CmndReceived() {
  for (j=0; j<= 71; j++)
  {
    Serial.print(BjCmnd[j]);
  }
  
  Serial.println(" ");
}
const int clockPin = 0; // digital pin 2
const int dataPin = 8;
byte dataState = 0;
byte BjCmnd[72];
int i = 0;
int j = 0;
volatile boolean clkDetect = false;  // Interrupt Note

void setup() 
{
  Serial.begin(9600); 
  pinMode(clockPin, INPUT);
  pinMode(dataPin, INPUT);
  attachInterrupt(clockPin, Activity, RISING);
}

void loop() 
{
  if (clkDetect == true)
  { 
    capture(); 
  }
}

void Activity()
{
  clkDetect = true;
}

void capture()
{
  if (digitalRead(dataPin) == HIGH)
  {  
    dataState = 1;
  }
  else
  {
    dataState = 0;
  }
  BjCmnd[i] = dataState;
  clkDetect = false;
  if (i == 71)
  {
    Serial.print("Command received:");
    i = 0;
    CmndReceived();
  }
  else
  {
    i++;  
  }
}

void CmndReceived() 
{
  for (j=0; j<= 71; j++)
  {
    Serial.print(BjCmnd[j]);
  }
  Serial.println(" ");
}

20uS bit spacing is pretty tight for pin change (and probably ICF) interrupts. Any reason you can't use the SPI feature of the CPU to read this stream?

Good suggestion

I’m not sure, SPI on arduino 8-bit, how it could read 72?
Probably. digitalRead/Write is the issue, reported performance for each command 4.7 - 5 usec, 3 of them would take almost 15 plus entering and leaving interrupt subroutine - and you missing data.
The only way I may think, is direct port readings, you can get bit in one clock cycle - 62.5 nsec, plus stuff its in a memory 2-3 clocks (pointers would improve speed, but may be you will get w/o). Time to dig into datasheet, there are samples :slight_smile:

unsigned char i;
...
/* Define pull-ups and set outputs high */
/* Define directions for port pins */
PORTB = (1<<PB7)|(1<<PB6)|(1<<PB1)|(1<<PB0);
DDRB = (1<<DDB3)|(1<<DDB2)|(1<<DDB1)|(1<<DDB0);
/* Insert nop for synchronization*/
__no_operation();
/* Read port pins */
i = PINB;
...

One of the biggest problems is that Timer0 takes 6uS to execute its handler every 1.024mS. That's pretty much a deal breaker right there. The processor is definitely capable of doing the job in assembly language with Timer0 disabled. It could probably be done in C as well, but it will be a little tight.

Magician:
The only way I may think, is direct port readings...

I wanted to suggest that, but I thought that the compiler would work it out like that anyway ( and I do not know.)

I wanted to suggest that, but I thought that the compiler would work it out like that anyway ( and I do not know.)

Of course, it would. BUT have a look at digitalRead internals and compare to i=PINB. You would get the idea where 62.5 nsec and 5 usec difference comes from. Inconvenience with i=PINB , you need to find a right bit associated to digital pin.

That's pretty much a deal breaker right there. The processor is definitely capable of doing the job in assembly language with Timer0 disabled.

I agree with that, especially if there is "delay" in code. It would make sense to should down any Timers interrupt , except it's really necessary. For example, including in "setup":

        TIMSK0 = 0x00;
        TIMSK1 = 0x00;
        TIMSK2 = 0x00;

Magician:

Inconvenience with i=PINB , you need to find a right bit associated to digital pin.

He's using D8 for the data_pin, that's bit 0 of PORTB.

if (portd & 1 == 1)

{
  // it's a 1
}
else
{
  it's a 0
}




Yes?

Hmmm, it turns out that idea of using SPI is the best approach I can find on i-net. There is a project of reading 24-bits ADC:
http://interface.khm.de/index.php/lab/experiments/connect-a-ltc2400-high-precision-24-bit-analog-to-digital-converter/
Just each byte needs to be managed before next one clock in, and it's shouldn't be an issue if 8 x 20 = 160 usec plenty of time

Thanks again all.

I'm not very familiar with SPI yet. After some reading i understand the basics and it seems i can't just hookup the clock and data pin to the spi clock and miso pins because the arduino is always the master thus controlling the clock. Could the MCP23S17 be of any help in this ?

I've also been thinking about manipulating the source before it enters the arduino, e.g. generating manchester (but the source doesn't seem to be suitable for this).

Using just a simple AND ttl ic might do the trick. The first bit is always 1 and 71 bits follow at a known clock rate. I could just wait for the 1 one and do a digitalread every 20us after that. However, this will be far less accurate.