12-bit DAC output

The DACs seems to work fine with the revised SPI speed.
Think I should do the same on the Tx side with this chip?
MCP3208

I'm also trying to pass on data from this rotary encoder & quad encoder. Puts out clock pulse & direction line
LS7184
Bourns ECW1J
100K bias resistor seems to provide ~80uS wide pulse.

I gotta get to bed - more tomorrow.

Thanks guys.
Robert

MCP3204 MCP3208 4, 8 channel 12 bit DAC DIP.pdf (722 KB)

ECW1J Bourns Quad Encoder.pdf (263 KB)

LS7183_LS7184.pdf (190 KB)

Okay, I rewrote my code to use interrupts to make the quadrature encoders (4 of them) more responsive.
Not sure I have INT0 & INT1 or PCINT20 and PCINT21 quite right - couple of lines won't compile (IDE 1.0) in setup, see <<<<<<<<

/* Sketch for Remote Control Transmitter board.
 Set up to read from 8-channel ADC and send int values to the Receive board over the serial interface every 10mS.
 2 INTs & 2 PCINT's used to detect pulses from four Quadrature Encoders and send out over the RS232 as well with direction.
 Also send the state of 3 push buttons.
 Octal ADC MCP3208
Quad encoder chip LS72184

 Rewired:
 D0 - RX - no change
 D1 - Tx - no change
 D2 - INT0, Optical encoder 0 clk via LS7184
 D3 - INT1, Optical encoder 1 clk
 D4 - PCINT20, mechanical encoder 0 clk
 D5 - PCINT21, mechanical encoder 1 clk
 D6 - Optical encoder 0 Up/Down 
 D7 - Optical encoder 1 Up/Down
 D8 - S3 status from Rx card - no change
 D9 - Sync status from Rx card - no change
 D10 - ADC_SS - no change
 D11 - MOSI - no change
 D12 - MISO - no change
 D13 - SCK - no change
 D14 - mechanical encoder 0 Up/Down
 D15 - mechanical encoder 1 Up/Down
 D16 - S0 switch input - no change
 D17 - S1 switch input - no change
 D18 - S2 switch input - no change
 D19 - not used
 */

// call libraries

#include <SPI.h>

// declare pins used

// Port B
byte ADC_SS = 10; // SPI SS for ADC
// D11,12,13 - SPI for ADC
byte Syncpin = 9; // Sync output
byte S3pin = 8; // Switch2 output, Hi/Lo received from Rx

// Port C
byte D19 = 19; // not used
byte S2pin = 18; // Switch2 input, Hi/Lo
byte S1pin = 17; // Switch1 input, Hi/Lo
byte S0pin = 16; // Switch0 input, Hi/Lo

byte updown3 = 15;
byte updown2 = 14;

// Port D
byte updown1 = 7;
byte updown0 = 6;
byte enc_clk3 = 5;
byte enc_clk2 = 4;
byte enc_clk1 = 3;
byte enc_clk0 = 2;
// D1, D0 used by Serial

// declare variables

byte ADCsyncbyte = 0x33; // b00110011 sync byte when send ADC data
byte Encsyncbyte = 0xAA; // B10101010 sync byte when send quadrature encoder data

int ADCaddress = 0x0600;     // 600, 640, 680, 6C0, 700, 740, 780, 7C0

byte dummyADC = 0;
byte highADC = 0;
byte lowADC = 0;
int x= 0;

byte quadEncoder = 0;
byte dir0;
byte dir1;
byte dir2;
byte dir3;

byte p;
byte oldPortD;

unsigned long previousMillis;

byte address_count;

void setup(){

  /******************************
   * // setup the I/O pins
   ******************************/
  pinMode (ADC_SS, OUTPUT);
  digitalWrite (ADC_SS, HIGH);

  pinMode(enc_clk0, INPUT);
  digitalWrite (enc_clk0, HIGH); // enable pullup resistor
  pinMode(enc_clk1, INPUT);
  digitalWrite (enc_clk1, HIGH); // enable pullup resistor
  pinMode(enc_clk2, INPUT);
  digitalWrite (enc_clk2, HIGH); // enable pullup resistor
  pinMode(enc_clk3, INPUT);
  digitalWrite (enc_clk3, HIGH); // enable pullup resistor

  pinMode(updown0, INPUT);
  digitalWrite (updown0, HIGH); // enable pullup resistor
  pinMode(updown1, INPUT);
  digitalWrite (updown1, HIGH); // enable pullup resistor
  pinMode(updown2, INPUT);
  digitalWrite (updown2, HIGH); // enable pullup resistor
  pinMode(updown3, INPUT);
  digitalWrite (updown3, HIGH); // enable pullup resistor

  pinMode(S0pin, INPUT);
  digitalWrite (S0pin, HIGH); // enable pullup resistor
  pinMode(S1pin, INPUT); 
  digitalWrite (S1pin, HIGH); // enable pullup resistor
  pinMode(S2pin, INPUT);
  digitalWrite (S2pin, HIGH);  // enable pullup resistor
  pinMode(S3pin, OUTPUT);
  digitalWrite (S3pin, LOW);  // add new wiring to drive LED anode High when S3 on receiver is closed 
  pinMode(Syncpin, INPUT);
  digitalWrite (Syncpin, LOW);  // add new wiring to drive LED anode High when Sync message from receiver is returned 

  // free pin  
  pinMode (D19, INPUT);
  digitalWrite (D19, HIGH);  

  PCMSK2 = _BV (PCINT4) | _BV (PCINT5); // want pins 20, 21  >> PCMSK2 is port D

  PCIFR = _BV (PCIF4) |_BV (PCIF5) ;  // Clear any existing interrupts                                      <<<<<<

  PCICR |= _BV (PCIE4) | _BV (PCIE5); // enable pin change interrupts for PCINT20,21  on port D  <<<<<<<

  // Open Serial interface
  Serial.begin (115200);
  // Open SPI interface
  SPI.begin (); // leave blank, we are master

}  // end void setup
/*
 >  1  Reset 
 >  2  External Interrupt Request 0  (pin D2)          (INT0_vect)
 >  3  External Interrupt Request 1  (pin D3)          (INT1_vect)
 >  6  Pin Change Interrupt Request 2 (pins D0 to D7)  (PCINT2_vect)
 */

// ISRs for Hardware interrupts

/* quadEncoder bit assigments:
 BIT7 - optical encoder 1 up/down direction
 BIT6 - optical encoder 0 up/down direction
 BIT5 - mechanical encoder 1 pulse
 BIT4 - mechanical encoder 0 pulse
 BIT3 - optical encoder 1 pulse
 BIT2 - optical encoder 0 pulse
 BIT1 - mechanical encoder 1 up/down direction
 BIT0 - mechanical encoder 0 up/down direction
 
 Works out well for masking off & mixing in results!
 */

// Hardware interrupt 0

ISR (INT0_vect) // right way to call INT0?
{
  // optical encoder 0 pulse
  dir0 = PIND & B01000000;  // 40
  quadEncoder = quadEncoder  | B00000100 | dir0; // 04
}

// Hardware interrupt 1

ISR (INT1_vect) // right way to call INT1?
{
  // optical encoder 1 pulse
  dir1 = PIND & B10000000; // 80
  quadEncoder = quadEncoder | B0001000 | dir1; // 08
}

// PCINTs for mechanical encoders 0 & 1
ISR (PCINT2_vect)  // PCINT2 for port D
{
  byte p = PIND;
  // mechanical encoder 0 pulse
  if ((p & 0x10) != (oldPortD & 0x10))  
  {
    // portD4, PCINT20, has changed
    dir2 = PINC & B00000001; // 01
    quadEncoder = quadEncoder | B00010000 | dir2; // 10
  }
  // mechanical encoder 0 pulse
  if ((p & 0x20) != (oldPortD & 0x020))
  {
    // portD5, PCINT21, has changed
    dir3 = PINC & B00000010; // 02
    quadEncoder = quadEncoder | B00100000 | dir3; // 20
  }

  // remember for next time
  oldPortD = p;

} // end of PCINT2_vect

/*
Every 5mS, read a Pot via SPI, send it via RS232 and send the state of the 3 switches.
 The rest of the time, be watching for an encoder clock pulse, if a pulse occurs then capture that in the ISR with the direction, and at the top of loop set the bits/direction for any of the four that occurred and RS232 message.
 
 On the receive side, set the output bits accordingly & clear.
 Change the bias resistor on the LS7184s to shorten up the pulses that create the interrupt.
 
 Every 5mS, there could be some delay for the SPI read of a DAC channel, the rest of the time should be available for the INTs.
 */
void loop(){
  if (millis()>=previousMillis){

    previousMillis = previousMillis + 5; // set for next 5 mS interval

    // read ADC with 3 byte transfer
    // digitalWrite (ADC_SS, LOW);
    PORTB = PORTB & B11111011;  // is this faster?

    // ADCaddress = 0x0600, 640, 680, 6C0, 700, 740, 780, 7C0
    // How is Rx to know which ADC data is coming?
    dummyADC = SPI.transfer (highByte(ADCaddress));  // 0x06, 0x07 out, read in dummy data
    highADC = SPI.transfer (lowByte(ADCaddress));    // 0x00, 0x40, 0x80, 0xC0, read in upper 4 bits
    lowADC = SPI.transfer (0);              // dummy 0 byte out , read in lower 8 bits

    // digitalWrite (ADC_SS, HIGH);
    PORTB = PORTB & B11111111; // is this faster?

    // Have to worry about interrupts during these 5 writes?

    Serial.write (ADCsyncbyte);      // syncbyte = B00110011, 0x33
    Serial.write (PINC & B00011100); // 3 switches on Port C. PINC = direct port read?
    Serial.write (address_count);    // find way to put this in top nibble of next command?
    Serial.write (highADC & 0x0F);   // send it out
    Serial.write (lowADC);           // send it out

    address_count = address_count +1;  // 0 to 7, pass to Rx for DAC sync - how?
    ADCaddress = ADCaddress + (address_count * 0x40); // update for next pass

    if (address_count == 8){ 
      address_count = 0;
    }  // reset if hit all 8

  } // end 5mS test

  // send out quad encoder data if any occured
  if (quadEncoder !=0){
    Serial.write (Encsyncbyte); // syncbyte = B10101010, 0xAA
    Serial.write (quadEncoder);
    quadEncoder = 0;
  }

  /* Not tested yet.  S3 not wired on receiver yet.   
   if (Serial.available()>0){                         // 
   digitalWrite (S3pin, (Serial.read() & B00010000)); // mask for S3 status
   digitalWrite (Syncpin, HIGH);                    // show receive comimg back
   }
   else {
   digitalWrite (Syncpin, LOW);
   }
   */
} // end void loop

Looks like there isn't a PCIF4. Check which one you want from here:

PCIFR

Bit 2 – PCIF2: Pin Change Interrupt Flag 1

When a logic change on any PCINT23:16 pin triggers an interrupt request, PCIF2 becomes set (one). If the I-bit in SREG and the PCIE2 bit in PCICR are set (one), the MCU will jump to the corresponding Interrupt Vector. The flag is cleared when the interrupt routine is executed. Alternatively, the flag can be cleared by writing a logical one to it.

Bit 1 – PCIF1: Pin Change Interrupt Flag 1

When a logic change on any PCINT15:8 pin triggers an interrupt request, PCIF1 becomes set (one). If the I-bit in SREG and the PCIE1 bit in PCICR are set (one), the MCU will jump to the corresponding Interrupt Vector. The flag is cleared when the interrupt routine is executed. Alternatively, the flag can be cleared by writing a logical one to it.

Bit 0 – PCIF0: Pin Change Interrupt Flag 0

When a logic change on any PCINT7:0 pin triggers an interrupt request, PCIF0 becomes set (one). If the I-bit in SREG and the PCIE0 bit in PCICR are set (one), the MCU will jump to the corresponding Interrupt Vector. The flag is cleared when the interrupt routine is executed. Alternatively, the flag can be cleared by writing a logical one to it.

So it looks like I need to change both of these

From:
PCIFR = _BV (PCIF4) |_BV (PCIF5) ; // Clear any existing interrupts
PCICR |= _BV (PCIE4) | _BV (PCIE5); // enable pin change interrupts for PCINT20,21 on port D

To:
PCIFR = _BV (PCIF2); // Clear any existing interrupts
PCICR |= _BV (PCIE2); // enable pin change interrupts for PCINT20,21 on port D

I think logic flow wise, I still need some understanding of when to clear interrupts.

I have never had a reason to clear interrupts, except by responding to them via an ISR. Essentially, doing so in the setup() routine just starts you off with a clean slate. Most likely harmless.

Also, I noticed this:

    // digitalWrite (ADC_SS, HIGH);
    PORTB = PORTB & B11111111; // is this faster?

That does nothing to PORTB. The "PORTB & 0xFF" bit there just unmasks all the existing bits and reassigns the old value. You need an OR operation instead:

    PORTB = PORTB | B00000100; // this IS faster!

Thanks on the PortB, cut & paste error there. Hazard of late night programming!

I agree with SirNickity - don't get too hung up on this point. I had it in my example code because under certain circumstances you might turn interrupts on, and have them fire immediately, from a pin change 20 minutes ago.

But for something like a rotary encoder, well perhaps keep it simple for now.

Ok, got the transmitter side fixed up. Thanks for the help so far. Now to rewrite the receiver side and see if its actually working.

/* Sketch for Remote Control Transmitter board.
 Set up to read from 4-channel ADC and send int values to the Receive board over the serial interface every 10mS.
 4 PCINT's used to detect pulses from four Quadrature Encoders and send out over the RS232 as well with direction.
 Also send the state of 3 push buttons.
 Also show sync LED.
 */
/*
 Revision to use interrupts
 Rewire board some:
 D0 - RX - no change
 D1 - Tx - no change
 D2 - INT0, Optical encoder 0 clk via LS7184
 D3 - INT1, Optical encoder 1 clk
 D4 - PCINT20, mechanical encoder 0 clk
 D5 - PCINT21, mechanical encoder 1 clk
 D6 - Optical encoder 0 Up/Down 
 D7 - Optical encoder 1 Up/Down
 D8 - S3 status from Rx card - no change
 D9 - Sync status from Rx card - no change
 D10 - ADC_SS - no change
 D11 - MOSI - no change
 D12 - MISO - no change
 D13 - SCK - no change
 D14 - mechanical encoder 0 Up/Down
 D15 - mechanical encoder 1 Up/Down
 D16 - S0 switch input - no change
 D17 - S1 switch input - no change
 D18 - S2 switch input - no change
 D19 - not used
 */

// call libraries

#include <SPI.h>

/**********************************************
 * // The pins
 ***********************************************/
// declare pins used

// Port B
byte ADC_SS = 10; // SPI SS for ADC
// D11,12,13 - SPI for ADC
byte Syncpin = 9; // Sync output
byte S3pin = 8; // Switch2 output, Hi/Lo received from Serial

// Port C
byte D19 = 19; // not used
byte S2pin = 18; // Switch2 input, Hi/Lo
byte S1pin = 17; // Switch1 input, Hi/Lo
byte S0pin = 16; // Switch0 input, Hi/Lo

byte updown3 = 15;
byte updown2 = 14;

// Port D
byte updown1 = 7;
byte updown0 = 6;
byte enc_clk3 = 5;
byte enc_clk2 = 4;
byte enc_clk1 = 3;
byte enc_clk0 = 2;
// D1, D0 used by Serial

// declare variables

byte ADCsyncbyte = 0x33; // b00110011 sync byte when send ADC data
byte Encsyncbyte = 0xAA; // B10101010 sync byte when send quadrature encoder data

int ADCaddress = 0x0600;     // 600, 640, 680, 6C0, 700, 740, 780, 7C0

byte dummyADC = 0;
byte highADC = 0;
byte lowADC = 0;
int x= 0;

byte quadEncoder = 0;
byte dir0;
byte dir1;
byte dir2;
byte dir3;

byte p;
byte oldPortD;

unsigned long previousMillis;

byte address_count;

void setup(){

  /******************************
   * // setup the I/O pins
   ******************************/
  pinMode (ADC_SS, OUTPUT);
  digitalWrite (ADC_SS, HIGH);

  pinMode(enc_clk0, INPUT);
  digitalWrite (enc_clk0, HIGH); // enable pullup resistor
  pinMode(enc_clk1, INPUT);
  digitalWrite (enc_clk1, HIGH); // enable pullup resistor
  pinMode(enc_clk2, INPUT);
  digitalWrite (enc_clk2, HIGH); // enable pullup resistor
  pinMode(enc_clk3, INPUT);
  digitalWrite (enc_clk3, HIGH); // enable pullup resistor

  pinMode(updown0, INPUT);
  digitalWrite (updown0, HIGH); // enable pullup resistor
  pinMode(updown1, INPUT);
  digitalWrite (updown1, HIGH); // enable pullup resistor
  pinMode(updown2, INPUT);
  digitalWrite (updown2, HIGH); // enable pullup resistor
  pinMode(updown3, INPUT);
  digitalWrite (updown3, HIGH); // enable pullup resistor

  pinMode(S0pin, INPUT);
  digitalWrite (S0pin, HIGH); // enable pullup resistor
  pinMode(S1pin, INPUT); 
  digitalWrite (S1pin, HIGH); // enable pullup resistor
  pinMode(S2pin, INPUT);
  digitalWrite (S2pin, HIGH);  // enable pullup resistor
  pinMode(S3pin, OUTPUT);
  digitalWrite (S3pin, LOW);  // add new wiring to drive LED anode High when S3 on receiver is closed 
  pinMode(Syncpin, INPUT);
  digitalWrite (Syncpin, LOW);  // add new wiring to drive LED anode High when Sync message from receiver is returned 

  // free pin  
  pinMode (D19, INPUT);
  digitalWrite (D19, HIGH);  

  PCMSK2 = _BV (PCINT4) | _BV (PCINT5); // want pins 20, 21  >> PCMSK2 is port D

  PCIFR = _BV (PCIF2) ;  // Clear any existing interrupts

  PCICR |= _BV (PCIE2) ; // enable pin change interrupts for PCINT20,21  on port D

  // Open Serial interface
  Serial.begin (115200);
  // Open SPI interface
  SPI.begin (); // leave blank, we are master

}  // end void setup
/*
 >  1  Reset 
 >  2  External Interrupt Request 0  (pin D2)          (INT0_vect)
 >  3  External Interrupt Request 1  (pin D3)          (INT1_vect)
 >  6  Pin Change Interrupt Request 2 (pins D0 to D7)  (PCINT2_vect)
 */

// ISRs for Hardware interrupts

/* quadEncoder bit assigments:
 BIT7 - optical encoder 1 up/down direction
 BIT6 - optical encoder 0 up/down direction
 BIT5 - mechanical encoder 1 pulse
 BIT4 - mechanical encoder 0 pulse
 BIT3 - optical encoder 1 pulse
 BIT2 - optical encoder 0 pulse
 BIT1 - mechanical encoder 1 up/down direction
 BIT0 - mechanical encoder 0 up/down direction
 
 Works out well for masking off & mixing in results!
 */

// Hardware interrupt 0

ISR (INT0_vect) // right way to call INT0?
{
  // optical encoder 0 pulse
  dir0 = PIND & B01000000;  // 40
  quadEncoder = quadEncoder  | B00000100 | dir0; // 04
}

// Hardware interrupt 1

ISR (INT1_vect) // right way to call INT1?
{
  // optical encoder 1 pulse
  dir1 = PIND & B10000000; // 80
  quadEncoder = quadEncoder | B0001000 | dir1; // 08
}

// PCINTs for mechanical encoders 0 & 1
ISR (PCINT2_vect)  // PCINT2 for port D
{
  byte p = PIND;
  // mechanical encoder 0 pulse
  if ((p & 0x10) != (oldPortD & 0x10))  
  {
    // portD4, PCINT20, has changed
    dir2 = PINC & B00000001; // 01
    quadEncoder = quadEncoder | B00010000 | dir2; // 10
  }
  // mechanical encoder 0 pulse
  if ((p & 0x20) != (oldPortD & 0x020))
  {
    // portD5, PCINT21, has changed
    dir3 = PINC & B00000010; // 02
    quadEncoder = quadEncoder | B00100000 | dir3; // 20
  }

  // remember for next time
  oldPortD = p;

} // end of PCINT2_vect

/*
Every 5mS, read a Pot via SPI, send it via RS232 and send the state of the 3 switches.
 The rest of the time, be watching for an encoder clock pulse, if a pulse occurs then capture that in the ISR with the direction, and at the top of loop set the bits/direction for any of the four that occurred and RS232 message.
 
 On the receive side, set the output bits accordingly & clear.
 Change the bias resistor on the LS7184s to shorten up the pulses that create the interrupt.
 
 Every 5mS, there could be some delay for the SPI read of a DAC channel, the rest of the time should be available for the INTs.
 */
void loop(){
  if (millis()>=previousMillis){

    previousMillis = previousMillis + 5; // set for next 5 mS interval

    // read ADC with 3 byte transfer
    // digitalWrite (ADC_SS, LOW);
    PORTB = PORTB & B11111011;  // is this faster?

    // ADCaddress = 0x0600, 640, 680, 6C0, 700, 740, 780, 7C0
    // How is Rx to know which ADC data is coming?
    dummyADC = SPI.transfer (highByte(ADCaddress));  // 0x06, 0x07 out, read in dummy data
    highADC = SPI.transfer (lowByte(ADCaddress));    // 0x00, 0x40, 0x80, 0xC0, read in upper 4 bits
    lowADC = SPI.transfer (0);              // dummy 0 byte out , read in lower 8 bits

    // digitalWrite (ADC_SS, HIGH);
    PORTB = PORTB | B00000100; // is this faster?

    // Have to worry about interrupts during these 5 writes?

    Serial.write (ADCsyncbyte);      // syncbyte = B00110011, 0x33
    Serial.write (PINC & B00011100); // 3 switches on Port C. PINC = direct port read?
    Serial.write (address_count);    // find way to put this in top nibble of next command?
    Serial.write (highADC & 0x0F);   // send it out
    Serial.write (lowADC);           // send it out

    address_count = address_count +1;  // 0 to 7, pass to Rx for DAC sync - how?
    ADCaddress = ADCaddress + (address_count * 0x40); // update for next pass

    if (address_count == 8){ 
      address_count = 0;
    }  // reset if hit all 8

  } // end 5mS test

  // send out quad encoder data if any occured
  if (quadEncoder !=0){
    Serial.write (Encsyncbyte); // syncbyte = B10101010, 0xAA
    Serial.write (quadEncoder);
    quadEncoder = 0;
  }

  /* Not tested yet.  S3 not wired on receiver yet.   
   if (Serial.available()>0){                         // 
   digitalWrite (S3pin, (Serial.read() & B00010000)); // mask for S3 status
   digitalWrite (Syncpin, HIGH);                    // show receive comimg back
   }
   else {
   digitalWrite (Syncpin, LOW);
   }
   */
} // end void loop

Cool! Does all the rotary encoding work?

Don't know yet, still doing the Rx code ...

Hey Nick,
I'm having a brainfart and need to simplify this section. I want to take in a byte that spans 2 bits from portC and 6 from portD.
4 of the bits are direction lines, and 4 represent the encoder pulses that need to go low and then right back high until the next serial transfer comes in.
I can make the pulses come in as a high or low in the ISR in the transmit code.

if (transfer_type == 0xAA){ // get in sync, B10101010
/* quadEncoder bit assigments:
BIT7 - optical encoder 1 up/down direction >> PD:7
BIT6 - optical encoder 0 up/down direction >> PD:6
BIT5 - mechanical encoder 1 pulse >> PD:5
BIT4 - mechanical encoder 0 pulse >> PD:4
BIT3 - optical encoder 1 pulse >> PD:3
BIT2 - optical encoder 0 pulse >> PD:2
BIT1 - mechanical encoder 1 up/down direction >> PC:1
BIT0 - mechanical encoder 0 up/down direction >> PC:0
*/
// Take pulse bits low if low, direction bits just follow.
// Then take pulse bits back high
// Could be 1,2,3, or 4 pulses & directions changing
encoder_data = Serial.read(); // D:7 Dir, D:6 Dir, D:5 Pulse, D:4 Pulse, D:3 Pulse, D:2 Pulse, C:1 Dir , C:0 Dir
PORTC = PORTC | (encoder_data & B00000011); // port C direction bits, can be high or low
PORTD = PORTD | (encoder_data & B11000011); // port D direction bits, can be high or low, leaveRx/Tx alone
// so now PORTD has xx1111xx for direction on left & RX/TX on the right.
PORTD = ; // how pull in low pulses without impacting the DIR bits?
PORTD = PORTD | B00111100; // take pulses back high, leave directions alone
}

Code tags?

Well for one thing, to read the port it is PIND. To write to it, it is PORTD.

If you are doing serial reads, speed is hardly the issue, so it would be simpler just to do digitalWrite and digitalRead. All this port stuff is confusing unless you are going after ultra speed.

Here's the entire Rx code - its the section at the end that needs something.
I am going for ultrafast. Serial read at 115200, write the DACs and encoder states, be ready for next byte coming in.

/* Sketch for Remote Control Receiver board.
 Set up to read from Serial port & 
 send int values to the ports to simulate 4 Quadrature Encoders and 2 switches,
 send to 4 DUAL 12-BIT DACs, MCP4922
 send back status of a switch over the RS232.
 Also show the state of 3 push buttons.
 
 */

// call libraries

#include <SPI.h>

// declare pins used
// D0, D1 used by Serial.begin

// D11,12,13 - SPI for SS for ADC
byte SlaveSel3 = 10; // SPI SS for ADC 0/1
byte SlaveSel2 = 9;  // SPI SS for ADC 2/3
byte Slavesel1 = 8;  // SPI SS for ADC 4/5
byte Slavesel0 = 19; // SPI SS for ADC 6/7

byte S3 = 18; // Switch2 output, Hi/Lo
byte S1 = 17; // Switch1 output, Hi/Lo
byte S0 = 16; // Switch0 output, Hi/Lo

// need to change these around
byte enc_clk3 = 15; // now optical encoder1 updown
byte enc_ud3 = 14;  // now optical encoder2 updown
byte enc_clk2 = 7;  // now mechanical encoder1 pulse
byte enc_ud2 = 6;   // now mechanical encoder0 pulse
byte enc_clk1 = 5;  // now optical encoder1 pulse
byte enc_ud1 = 4;   // now optical encoder0 pulse
byte enc_clk0 = 3;  // now mechanical encoder1 updown
byte enc_ud0 = 2;   // now mechanical encoder0 updown

// D1, D0 used by Serial

// declare variables - most not actually used now with Direct Port Access ??

byte enc_clk0_state = 1; // the clock pins 
byte enc_clk1_state = 1; // the clock pins 
byte enc_clk2_state = 1; // the clock pins 
byte enc_ud0_state = 1; // the up/down pins 
byte enc_ud1_state = 1; // the up/down pins 
byte enc_ud2_state = 1; // the up/down pins

byte enc_clk3_state = 1; // the clock pins 
byte enc_ud3_state = 1; // the up/down pins 

byte ADCsyncbyte = 0x33; // b00110011
byte Encsyncbyte = 0xAA; // b10101010

byte transfer_type;
byte encoder_data;

byte x = 0;

// use as upper 4 bits of highADC = 0/1 for A/B, 1 for buffered, 1 for gain x1, 1 for active (not shutdown)
byte DAC_A_mode = B01110000; 
byte DAC_B_mode = B11110000;

void setup(){

  // setup the I/O pins

  pinMode (DAC_SS[6], OUTPUT);
  digitalWrite (DAC_SS[6], HIGH);
  pinMode (DAC_SS[4], OUTPUT);
  digitalWrite (DAC_SS[4], HIGH);
  pinMode (DAC_SS[2], OUTPUT);
  digitalWrite (DAC_SS[2], HIGH);
  pinMode (DAC_SS[0], OUTPUT);
  digitalWrite (DAC_SS[0], HIGH);


  pinMode(enc_clk0, OUTPUT);

  pinMode(enc_clk1, OUTPUT);

  pinMode(enc_clk2, OUTPUT);

  pinMode(enc_clk3, OUTPUT);


  pinMode(enc_ud0, OUTPUT);

  pinMode(enc_ud1, OUTPUT);

  pinMode(enc_ud2, OUTPUT);

  pinMode(enc_ud3, OUTPUT);


  pinMode(S0, OUTPUT);
  digitalWrite (S0, LOW); 
  pinMode(S1, OUTPUT);
  digitalWrite (S1, LOW); 
  pinMode(S3, OUTPUT);
  digitalWrite (S3, LOW);  

  // Open Serial interface
  Serial.begin (115200);
  // Open SPI interface
  SPI.begin (); // leave blank, we are master
  SPI.setClockDivider( SPI_CLOCK_DIV2 ); // faster clocking for DACs

}

void loop(){
  /* The Flow:
   receive Sync Byte
   if = 0x33:
   read serial, Direct write switch to Port C
   read serial, write to DAC
   if = 0xAA:
   read serial, split & send to PortD, PortC
   read S3 state, send back over serial
   */

  if (Serial.available()>0){  

    transfer_type = Serial.read();    
    if (transfer_type == 0x33){ // switches + a DAC
      /*
       How data is created in Tx code:
       Serial.write (PINC & B00011100); // 3 switches on Port C. PINC = direct port read?
       Serial.write (address_count);    // find way to put this in top nibble of next command?
       Serial.write (highADC & 0x0F);   // send it out
       Serial.write (lowADC);           // send it out
       */
      if (Serial.available() >3 ){
        PORTC = PORTC | Serial.read() ; // should be b000xxx00, switch data
        switch (Serial.read() ) { // should be 0 to 7
        case 0:
          // read serial UART & SPI.transfer it out
          //digitalWrite (DAC_SS[0], LOW);
          PORTB = PORTB & B11111011; // PB:2
          SPI.transfer (Serial.read() | DAC_A_mode); // read highADC nibble & mix with control bits, send it out
          SPI.transfer (Serial.read());        // read lowADC byte, send it out
          // digitalWrite (DAC_SS[0], HIGH);
          PORTB = PORTB & B00000100; // PB:2
          break;

        case 1:
          //digitalWrite (DAC_SS[1], LOW); 
          PORTB = PORTB & B11111011; // PB:2
          SPI.transfer (Serial.read() | DAC_B_mode); // read highADC nibble & mix with control bits, send it out
          SPI.transfer (Serial.read());        // read lowADC byte, send it out
          //digitalWrite (DAC_SS[1], HIGH);
          PORTB = PORTB & B00000100; // PB:2
          break;
        case 2:
          //  digitalWrite (DAC_SS[2], LOW); 
          PORTB = PORTB & B11111101; // PB1
          SPI.transfer (Serial.read() | DAC_A_mode); // read highADC nibble & mix with control bits, send it out
          SPI.transfer (Serial.read());        // read lowADC byte, send it out
          //digitalWrite (DAC_SS[2], HIGH);
          PORTB = PORTB & B00000010; // PB:1
          break;

        case 3:    
          //digitalWrite (DAC_SS[3], LOW); 
          PORTB = PORTB & B11111101; // PB:1
          SPI.transfer (Serial.read() | DAC_B_mode); // read highADC nibble & mix with control bits, send it out
          SPI.transfer (Serial.read());        // read lowADC byte, send it out
          //digitalWrite (DAC_SS[3], HIGH);
          PORTB = PORTB & B00000010; // PB1
          break;

        case 4:
          //digitalWrite (DAC_SS[4], LOW);
          PORTB = PORTB & B11111110; // PB:0
          SPI.transfer (Serial.read() | DAC_A_mode); // read highADC nibble & mix with control bits, send it out
          SPI.transfer (Serial.read());        // read lowADC byte, send it out
          //digitalWrite (DAC_SS[4], HIGH);
          PORTB = PORTB & B00000001; // PB:0
          break;
        case 5:
          //digitalWrite (DAC_SS[5], LOW); 
          PORTB = PORTB & B11111110; // PB0
          SPI.transfer (Serial.read() | DAC_B_mode); // read highADC nibble & mix with control bits, send it out
          SPI.transfer (Serial.read());        // read lowADC byte, send it out
          //digitalWrite (DAC_SS[5], HIGH);
          PORTB = PORTB & B00000001; // PB:0
          break;
        case 6:

          //digitalWrite (DAC_SS[6], LOW); 
          PORTC = PORTC & B11011111; // PC:5
          SPI.transfer (Serial.read() | DAC_A_mode); // read highADC nibble & mix with control bits, send it out
          SPI.transfer (Serial.read());        // read lowADC byte, send it out
          //digitalWrite (DAC_SS[6], HIGH);
          PORTC= PORTC & B00100000; // PC:5
          break;

        case 7:

          //digitalWrite (DAC_SS[7], LOW); 
          PORTC = PORTC & B11011111; // PC:5
          SPI.transfer (Serial.read() | DAC_B_mode); // read highADC nibble & mix with control bits, send it out
          SPI.transfer (Serial.read());        // read lowADC byte, send it out
          //digitalWrite (DAC_SS[7], HIGH);
          PORTC= PORTC & B00100000; // PC:5
          break;
        }
        /*
    Serial.write (B00000010);  // send back sync bit
         */
      }     
      if (transfer_type == 0xAA){  // get in sync, B10101010
        /* quadEncoder bit assigments:
         BIT7 - optical encoder 1 up/down direction >> PD:7
         BIT6 - optical encoder 0 up/down direction >> PD:6
         BIT5 - mechanical encoder 1 pulse >> PD:5
         BIT4 - mechanical encoder 0 pulse >> PD:4
         BIT3 - optical encoder 1 pulse >> PD:3
         BIT2 - optical encoder 0 pulse >> PD:2
         BIT1 - mechanical encoder 1 up/down direction >> PC:1
         BIT0 - mechanical encoder 0 up/down direction >> PC:0
         */
        // Take pulse bits low if low, direction bits follow the pulse
        // Then take pulse bits back high
        encoder_data = Serial.read(); // D:7 Dir, D:6 D, D:5 Pulse, D:4 P, D:3 P, D:2 P, C:1 D , C:0 D
        PORTC = PORTC | (encoder_data & B00000011); // port C direction bits, can be high or low
        PORTD = PORTD | (encoder_data & B11000011); // port D direction bits, can be high or low, leaveRx/Tx alone
        PORTD = PORTD  ; // ???            // pull in low pulses
        PORTD = PORTD | B00111100; // take pulses back high, leave directions alone
      }
      /*
    Serial.write (B00010010);  // send back an ack
       */
    }
  }
}
  if (Serial.available()>0){  

    transfer_type = Serial.read();    
...
      if (Serial.available() >3 ){

This almost certainly won't happen. Serial isn't that fast. I would just make the first check for > 4 if that is what is required.


          //digitalWrite (DAC_SS[7], HIGH);
          PORTC= PORTC & B00100000; // PC:5

Assuming you are trying to do what the commented-out line does, you need to or it:

          PORTC |= B00100000; // PC:5

        PORTC = PORTC | (encoder_data & B00000011); // port C direction bits, can be high or low
        PORTD = PORTD | (encoder_data & B11000011); // port D direction bits, can be high or low, leaveRx/Tx alone

They aren't the direction bits. That's DDRC and DDRD.


        PORTD = PORTD  ; // ???            // pull in low pulses

I'm not sure what your intention is here. Are you reading the port?

Ok, I see your point on some things, and you are missing my context on others.

if (Serial.available()>0){  

    transfer_type = Serial.read();    
...
      if (Serial.available() >3 ){

I am checking for 2 situations here.
One set of data will be:
0x33
switch_data
DAC selection byte
DAC upper data
DAC lower data
so I guess once I see the 0x33, I know 4 more bytes are coming, I can put some delayMicroseconds in there to give the rest of the bytes a chance to come in, or a while loop to give Serial.available a chance to build up the count, or similar. Or look for 2 bytes all the time, if the first is a 0x33 then I know the next is the DAC select, then can wait for Serial.available to get up to 2 again for the DAC data. And if the first is 0xAA, I'll execute next part.

Setting & clearing the DAC slave select bits:

          //digitalWrite (DAC_SS[0], LOW);
          PORTB = PORTB & B11111011; // PB:2
:
:
          //digitalWrite (DAC_SS[0], HIGH);
          PORTB = PORTB & B00000100; // PB:2

yeah, should have been this to go back high. Will fix all of those.
PORTB = PORTB | B00000100; // PB:2

Now, the other message type, that's where I really want the help.

I have 2 bytes coming:
0xAA, will tell me its the quadrature encoder data coming in.
Next byte is the quadrature encoder data, I suppose I can put some delayMicroseconds there too to give the 2nd byte a chance to arrive, or a while or something as discussed above. At 115200, and the Tx sending the 2 bytes back to back, should be quick.
Can be data for 1 encoder, can be 4 encoders.
Each encoder has an up/down bit (which I see I confusingly called direction, leading to the DDRx comment)
and the pulse bit, which are created by the LS7184 chip. The LS7184 sees the actual encoder A/B output high low sequences, and makes the pulses that indicates how fast the knob is being turned, and the up/down line to show clockwise/counterclockwise rotation.

On the transmitter side, these pulses create an interrupt, the ISR sets (or clears) a bit in the byte for a pulse, A 2nd bit is set/cleared indicating up/down (direction) for the knob twist.
That gets sent to the Rx card, which I am trying to mimic here.

So what I want to do:
Receive the pulse/up-down byte.
2 bits of up/down info need to go out as PortC bits 1 & 0. Change the bit(s) that changed while leaving the rest of PortC alone.

2 bits of up/down info need to go out as PortD bits 7 & 6. Change the bit(s) that changed while leaving the rest of PortD alone, and
4 bits of pulse information need to go out as PortD bits 5,4,3,2. Change the bit(s) indicated to be low, and then back high again.

The pulse information can be set as 1 or 0 in the ISR on the Tx side, which ever is quicker to manipulate on the receive side into a 0.

So the bit manipulation is where I was struggling logic wise.
1,2,3 or 4 updown bits that may or may not change state.
1,2,3, or 4 pulse bits, however many got set while the interrupts fired during the Tx sending out the DAC data, need to go low and then back high again.

Thanks
Bob

Just throwing this out there... Your serial read loop seems kinda fragile. I think I'd set it up to read the entire "command sequence" first, then branch off to a function to handle the reads and writes for that particular event. Something like:

uint8_t data[MAX_LEN];
uint8_t pos = 0;

void loop() {

if (Serial.available()) {
  inbyte = Serial.read();
  
  // Look for preamble
  if (inbyte == 0x33) {
    pos = 0;
    
    while (1) {
      if (Serial.available()) {
        data[pos] = Serial.read();
        pos++;
      }

      if (pos >= LENGTH_OF_MSG_33) break;
    }

    doMsg33(data);

  } else if (inbyte == 0xAA) {
    // Same thing as above...
  }

}

At least then, your port manipulation and serial acks are all contained within functions. Makes it easier to read, and less likely to break when you've met some condition (like reading 0x33), but haven't waited long enough to receive all the serial data yet, or whatever...

Anyway, just an idea. :slight_smile:

Yeah, I think that's whats I was heading towards in my most ramblings, which definitely look like hardware guy thinking out loud :grin:
I'm going for speed here, so minimal branches & jumping in/out of functions.

void loop(){
  /* The Flow:
   receive Sync Byte
   {
   if = 0x33:
   read serial, Direct write switch to Port C
   read DAC address
   read upper DAC
   read lower DAC
   write upper DAC
   write lower DAC
   }

   if = 0xAA:
   {
   read serial, split & send to PortD, PortC
  }
   
   read S3 state, send back over serial
   */

  if (Serial.available()>1){ // got at least 2 bytes, minimum needed for quad encoder message  

// read the first byte
    transfer_type = Serial.read();   
 
    if (transfer_type == 0x33){ // switches + a DAC
      /*
       How data is created in Tx code:
       Serial.write (PINC & B00011100); // 3 switches on Port C. PINC = direct port read?
       Serial.write (address_count);    // find way to put this in top nibble of next command?
       Serial.write (highADC & 0x0F);   // send it out
       Serial.write (lowADC);           // send it out
       */
// doing switches & DACs, wait for more bytes

while (Serial.available() < 3)  // waiting for more data
      { // got the switch * DAC bytes, proceed
        PORTC = PORTC | Serial.read() ; // byte1: should be b000xxx00, make the outputs reflect the 3 bits, do not touch the others
        // PORTC not really a variable tho ??
        // maybe PORTC = PINC & B11100011 to clear the bits, 
        // then PORTC = PINC | Serial.read(); ??

        switch (Serial.read() ) { // byte 2, DAC selection data, should be 0 to 7
        case 0:
          // read serial UART & SPI.transfer it out
          PORTB = PORTB & B11111011; // PB:2  slave select low
          SPI.transfer (Serial.read() | DAC_A_mode); // read highADC nibble & mix with control bits, send it out
          SPI.transfer (Serial.read());        // read lowADC byte, send it out
          PORTB = PORTB ? B00000100; // PB:2   slave select high
          break;

        case 1:
          etc.
      } // end switch:case

// now the quadrature encoder byte
     if (transfer_type == 0xAA){ //
    // already know 2nd byte is available, read it in
    encoder_data = Serial.read(); // D:7 Up/down, D:6 Up/down, D:5 Pulse, D:4 Pulse, D:3 Pulse, D:2 Pulse, C:1 Up/down , C:0 Up/down

   // Tx code not set in stone, so Pulse bits can be sent over as 0 to show pulse occurred, or as 1 

   // the part I haven't quite figured out, follow PortC/PinC logic above, assuming that's correct
 
   // PORTC: keep bits 7:2 the same, let 1:0 follow the new up/down data
   portC_updown_data = PINC & b11111100; / clear the lower 2 bits
   PORTC = PINC | ( (encoder_data & b00000011) | portC_updown_data ); // mask for the lower 2 bits, OR it into the existing data

   // PORTD: keep bits 5:2 the same, let 7:6 follow the new up/down data
   portD_updown_data = PIND & b00111111; / clear the upper 2 bits
   PORTD = PIND | (encoder_data & b00111111); // keep bits 5:0 the same, let 7:6 follow the new up/down data

   pulse_data = PIND & b11000011; // clear the old pulse
   PORTD = PIND | (encoder_data & b00111100; // make bits 5:2 low if the pulse data is low
   PORTD = PIND | b00111100; // pulse data is over, make bits 5:2 high stretch the low out if pulse is too short for the end device

    } // end of quadrature data check

  } // end check for 2 bytes
    // send ack message back, not defined yet

} // end void loop

fencing class, gotta run ...

I'll have a look in more detail shortly.

At 115200, and the Tx sending the 2 bytes back to back, should be quick.

It's still 173 uS, so you have a bit of time on your hands. For incoming serial data there is a limiting speed (about 87 uS per byte at 115200) so the code may as well be robust. In fact "I want it to be fast" and "I'll throw in a few delays to make it work" are pretty much mutually exclusive.

The thing is that incoming serial bytes are put in a buffer by interrupts, so just waiting for Serial.available () >= 5 (when you expect 5 bytes) is going to be much faster. Because while the interrupts are doing that, you can be doing other things like setting up your DAC gadgets. Whereas read one byte, delay, read another, delay is actually the slower way of doing it.

I'll try to make up a cut-down version of what you are doing. Just to clarify... the idea is that you have:

a) A transmitter with 4 rotary encoders. Depending on the readings of those encoders, you transmit the 4 positions to the receiver.

b) The receiver reads the 4 positions. It then adjusts some DACs to reflect the encoder position.

Is that it?

Could you post your transmitter code? Sometimes the fastest way of doing this is not the obvious one.

Conceptually it sounds a bit like my remote controlled car:

The transmitter reads a Wii nunchuk, and sends the position to the car, which reads that to change directions.

Code in next post.

I have 2 sets of things I am passing across.
The output of the Octal ADC, one at a time every 5mS or so.

The outputs of 4 rotary encoders. Two are optical on the hardware interrupts, two are mechanical on the PCINTs.

The receiver takes the ADC data and outputs the data via DACs.
The receiver also outputs the quadrature encoder pulses and up/down direction lines.
The optical encoders are expected to have higher pulse rates than the mechanical encoders for better position resolution.