1

I have configured two Arduino Micros as a pair of multiplexer/demultiplexers communicating via Tx and Rx.

Everything works great except for one little thing. If I reset the sender (multiplexer) the receiver (demultiplexer) goes into a bad state resulting in random low and high outputs.

Resetting the receiver brings it back to a normal state but I am hoping someone might have a better solution. Using the serial monitor I noticed that during the reset process on the sender there are some seemingly random bits being transmitted toward the end. Not sure if this has anything to do with it.

The code for the sender is:

byte PINBold;
byte PINDold;
byte PINFold; 

byte PBmask = B11111110;
byte PDmask = B11010011;
byte PFmask = B11110011;

void setup() {
  Serial1.begin(3704400); // Speed
  DDRB = DDRB & B00000001; // Sets pins to digital input: 11,10,9,8,MI,MO,SCK 
  DDRD = DDRD & B00101100; // Sets pins to digital input: 2,3,4,6,12                           
  DDRF = DDRF & B00001100; // Sets pins to digital input: A0,A1,A2,A3,A4,A5
}

void loop() {
  if (((PINB & PBmask) != PINBold) |((PIND & PDmask) != PINDold) | ((PINF & PFmask) != PINFold)) {
    Serial1.write(PINB);
    Serial1.write(PIND);
    Serial1.write(PINF);

    PINBold = PINB & PBmask;
    PINDold = PIND & PDmask;
    PINFold = PINF & PFmask;    
  }
}

The code fore the receiver is:

void setup() {
Serial1.begin(3704400); // Speed
DDRB = DDRB | B11111110; // Sets pins to digital output: 8,9,10,11,MI,MO,SCK
DDRD = DDRD | B11010011; // Sets pins to digital output: 2,3,4,6,12
DDRF = DDRF | B11110011; // Sets pins to digital output: A0,A1,A2,A3,A4,A5

PORTB = PORTB | B11111110;
PORTD = PORTD | B11010011;
PORTF = PORTF | B11110011; 
}

void loop() {
if (Serial1.available() > 2) {
PORTB = Serial1.read();
PORTD = Serial1.read();
PORTF = Serial1.read();
}
}
  • 1
    There is a bug in your code; in line ████ you should have written ████████ rather than ████████. Try changing it and it will work – frarugi87 Aug 8 '18 at 12:23
  • Make a protocol for the serial communcation, with a start byte, a checksum and a trailer byte. During reset the tx pin is set as input, thus it is high impedance and will pick up noise. Did you forget a pullup resistor? – Jot Aug 8 '18 at 13:39
  • @frarugi87 edited to include code. – Michael Aug 8 '18 at 14:28
  • @Jot I used pull down resistors, should still be okay correct? – Michael Aug 8 '18 at 14:29
  • @Michael there are some issues in your code; let me write a more complete answer to fix them – frarugi87 Aug 8 '18 at 14:37
0

Ok, there are some issues in your code. First of all, are you sure that a baudrate of 3.7Mbps is what you need? Can't you go a bit slower?

If you are dealing with mechanical parts (e.g. buttons), then a refresh rate of 100Hz cannot be noticed; to send 3 bytes you need 30 bits, and at 9600 bit/s this means 320 Hz of frequency. Reducing the speed increases the rejection to noise too, so I'd go with a lower speed (even a 38.4kbps is fine).

Then you are reading the PINx registers too many times, which can lead to inconsistency. What happens if the value changes after the if but before the updating of the value? It's better to store it in a variable.

These, however, are not the root cause of your problem. The main issue, as anticipated by Jot, is that you are not using a protocol to synchronize the two elements. This means that at the bootup the receiver does not know if it is at the beginning of the "frame" or in the middle. Moreover start conditions when the sender is not online are not ignored.

So a "protocol" must be agreed between the two. The simplest one I can think of revolves around the fact that you can reserve a bit for the flow control.

Looking at your port masks, I can see that if we split the PINB values in the two nibbles and then move the lowest one one bit to the left, we get that bit 3 is never used for transmitting a pin data:

PINB 76543210 -> 7654x321 (pin 0 was not used)

consequently we use this as a control bit. It has value 1 for the first bit in the frame, and 0 otherwise. On the receiver side, you look for a byte with this bit set, then record also the other two, then check if the other two have a 0 in this position and, if confirmed, you have your frame.

This solution has the highest throughput, but may be a bit fragile when some noise is present (false start of frames, possibly).

For this reason I suggest you to add another byte to the packet, thus having to transmit four bytes. Again, the first one has the bit set in position 3, and the other have zero. But in this case the first byte has a predefined value, and consequently the receiver has just to look for this specific value (and the robustness is increased.

Ok people, I can hear your question. Where is the code? Well, here it is. For the sender, you have to perform a bit of manipulation on the bytes to be sent. I chose the hex value 0x5A because it has a lot of transitions (difficult to replicate from a noise) and it has a 1 bit in position 3. I also changed the things said at the beginning (baud rate and reading PINx only once); feel free to revert the changes if you have different needs. Moreover I initialized the old variables, and I set the direction of the pins based on the mask rather than having it done manually (easier to maintain).

EDIT: I added the possibility to get the current status by sending a char on the serial interface (useful when the receiver boots up)

byte PINBold;
byte PINDold;
byte PINFold; 

const byte PBmask = B11111110;
const byte PDmask = B11010011;
const byte PFmask = B11110011;

const byte StartOfFrame = 0x5A; // bit 3 is always '1'

void setup()
{
    Serial1.begin(38400); // Speed
    DDRB = DDRB & (~PBmask); // Sets pins to digital input: 11,10,9,8,MI,MO,SCK 
    DDRD = DDRD & (~PDmask); // Sets pins to digital input: 2,3,4,6,12                           
    DDRF = DDRF & (~PFmask); // Sets pins to digital input: A0,A1,A2,A3,A4,A5

    PINBold = ~PBmask; // Force a 1 in a forbidden position -> force a sending at first round
    PINDold = ~PDmask;
    PINFold = ~PFmask;
}

void loop() {
    byte currPINB = PINB & PBmask;
    byte currPIND = PIND & PDmask;
    byte currPINF = PINF & PFmask;

    bool shouldSendData = false;

    if ((currPINB != PINBold) || (currPIND != PINDold) || (currPINF != PINFold))
    {
        PINBold = currPINB;
        PINDold = currPIND;
        PINFold = currPINF;
        shouldSendData = true;
    }

    while (Serial1.available())
    {
        Serial1.read();
        shouldSendData = true;
    }

    if (shouldSendData)
    {
        Serial1.write(StartOfFrame);
        Serial1.write((currPINB & 0xF0) | ((currPINB >> 1) & 0x07)); // send currPINB 7654_321, where _ is a '0'
        Serial1.write(currPIND); // bit 3 is always '0'
        Serial1.write(currPINF); // bit 3 is always '0'
    }
}

On the receiver side, what you need is to wait for the start of frame, then you have your data. I prefer to implement it as a very small state machine. I also added a bonus timeout feature to exit from the payload state after 10ms of inactivity.

EDIT: Added the sending of a char at the beginning, to trigger a resending of the status from the master (if available). Included also the functional code from the question, since the OP added the receiver

const byte StartOfFrame = 0x5A; // bit 3 is always '1'

const byte PBmask = B11111110;
const byte PDmask = B11010011;
const byte PFmask = B11110011;

// State machine states
const byte SM_WaitingForSOF = 1;
const byte SM_WaitingForPayload = 2;

byte SM_CurrentState;

bool receivedData;
byte receivedPortB;
byte receivedPortD;
byte receivedPortF;

uint8_t startOfTransmissionMillis;
const byte transmissionTimeout_ms = 10;

void setup()
{
    Serial1.begin(38400); // Speed

    DDRB = DDRB | PBmask; // Sets pins to digital output: 8,9,10,11,MI,MO,SCK
    DDRD = DDRD | PDmask; // Sets pins to digital output: 2,3,4,6,12
    DDRF = DDRF | PFmask; // Sets pins to digital output: A0,A1,A2,A3,A4,A5

    SM_CurrentState = SM_WaitingForSOF;
    receivedData = false;
    receivedPortB = 0;
    receivedPortD = 0;
    receivedPortF = 0;

    Serial1.write('H'); // Any byte is ok; it is used to trigger a sending of the status
}

void loop() {
    receivedData = false;

    switch (SM_CurrentState)
    {
        case SM_WaitingForSOF:
            while (Serial1.available() > 0) // can also be a "if"
            {
                if (Serial1.read() == StartOfFrame)
                {
                    SM_CurrentState = SM_WaitingForPayload;
                    startOfTransmissionMillis = (uint8_t)millis();
                    break;
                }
            }
            break;
        case SM_WaitingForPayload:
            if (Serial1.available() >= 3)
            {
                receivedPortB = Serial1.read();
                receivedPortD = Serial1.read();
                receivedPortF = Serial1.read();

                if (((receivedPortB & 0x08) | (receivedPortD & 0x08) | (receivedPortF & 0x08)) == 0)
                { // All the payload bits have '0' as bit 3, so communication was ok
                    receivedData = true;                
                }

                SM_CurrentState = SM_WaitingForSOF;
            }
            else if ((((uint8_t)millis()) - startOfTransmissionMillis) > transmissionTimeout_ms)
            {
                SM_CurrentState = SM_WaitingForSOF;
            }
            break;
        default:
            SM_CurrentState = SM_WaitingForSOF;
            break;
    }

    if (receivedData)
    {
        PORTB = (PORTB & (~PBmask)) | (receivedPortB & 0xF0) | ((receivedPortB & 0x07) << 1);
        PORTD = (PORTD & (~PDmask)) | receivedPortD;
        PORTF = (PORTF & (~PFmask)) | receivedPortF;
    }
  }
}

Beware: untested code. I'm confident that no major bugs are present, but some tweaking may be necessary

Your Answer

By clicking "Post Your Answer", you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.