Forum Rule: Always post complete source code & details to reproduce any issue!
Results 1 to 2 of 2

Thread: UART and SPI conflict

  1. #1
    Junior Member
    Join Date
    Dec 2020
    Posts
    4

    UART and SPI conflict

    Hello. For my current project I have a teensy 4 that needs to communicate with Arduino Nano via Serial1.
    So i connected both modules and stream the data from Arduino to Teensy with success. . Unfortunately, when trying to add SPI dac 4822 and initialising instance from MCP48xx.h library teensy starts to receive wrong data from my Arduino. Looks like some kind of conflict with both protocols here. Here is a code:

    Teensy (receiver):

    Code:
    #define CV_IN_1 1
    #define CV_IN_2 2
    byte data;
    bool calculate_CV = false;
    byte dataArr[2];
    byte counter = 0;
    unsigned int combinedData[2];
    
    #include <MCP48xx.h>
    #include <SPI.h>
    MCP4822 dac(9);
    
    void setup() {
      pinMode(9, OUTPUT);
      Serial.begin(9600);
      Serial1.begin(9600);
    }
    
    void loop() {
      checkSerial();
    
    }
    void checkSerial () {
      if (Serial1.available()) {
        if (calculate_CV) {
          dataArr[counter] = Serial1.read();
          counter++;
          if (counter > 1) {
            counter = 0;
            combinedData[data - 1] =  (dataArr[1] << 8) | (dataArr[0]);
            calculate_CV = 0;
          }
        }
        else {
          data = Serial1.read();
          if (data == CV_IN_1) {
            calculate_CV = true;
          }
          if (data == CV_IN_2) {
            calculate_CV = true;
          }
        }
      }
    }
    Arduino (sender):


    Code:
    // include the library code:
    #include <SPI.h>
    
    // Set Constants
    const int adcChipSelectPin = 10; 
    #define CV1 1
    #define ENC_TIC_BTN 7
    bool sendData = 0;
    
    // Start setup function:
    void setup() {
      delay(1000);
      pinMode(ENC_TIC_BTN, INPUT_PULLUP);
      pinMode (adcChipSelectPin, OUTPUT);
      // set the ChipSelectPins high initially:
      digitalWrite(adcChipSelectPin, HIGH);
      // initialise SPI:
      SPI.begin();
      SPI.setBitOrder(MSBFIRST);         // Not strictly needed but just to be sure.
      SPI.setDataMode(SPI_MODE0);        // Not strictly needed but just to be sure.
      // Set SPI clock divider to 16, therfore a 1 MhZ signal due to the maximum frequency of the ADC.
      SPI.setClockDivider(SPI_CLOCK_DIV16);  // В даташиті mcp3204 максимальна частота клок 2Mhz того можна і на 8 поділити. Нижній поріг для клок 10khz, верхній 2 mhz.
      Serial.begin(9600);
      //myTransfer.begin(Serial);
    } // End setup function.
    
    // Start loop function:
    void loop() {
      unsigned int number = readAdc(0) * 1000;
      byte b1 = lowByte(number);
      byte b2 = highByte(number);
      byte btn = digitalRead(ENC_TIC_BTN);
      if (btn) {
        sendData = 1;
    
      }
      if (sendData) {      /// Sending Data to Teensy 4
        Serial.write(CV1);
        Serial.write(b1);
        Serial.write(b2);
      }
    
    }
    
    //Function to read the ADC, accepts the channel to be read.
    float readAdc(int channel) {
      byte adcPrimaryRegister = 0b00000110;      // Sets default Primary ADC Address register B00000110, This is a default address setting, the third LSB is set to one to start the ADC, the second LSB is to set the ADC to single ended mode, the LSB is for D2 address bit, for this ADC its a "Don't Care" bit.
      byte adcPrimaryRegisterMask = 0b00000111;  // b00000111 Isolates the three LSB.
      byte adcPrimaryByteMask = 0b00001111;      // b00001111 isolates the 4 LSB for the value returned.
      byte adcPrimaryConfig = adcPrimaryRegister & adcPrimaryRegisterMask; // ensures the adc register is limited to the mask and assembles the configuration byte to send to ADC.
      byte adcSecondaryConfig = channel << 6;
      noInterrupts(); // disable interupts to prepare to send address data to the ADC.
      digitalWrite(adcChipSelectPin, LOW); // take the Chip Select pin low to select the ADC.
      SPI.transfer(adcPrimaryConfig); //  send in the primary configuration address byte to the ADC.
      byte adcPrimaryByte = SPI.transfer(adcSecondaryConfig); // read the primary byte, also sending in the secondary address byte.
      byte adcSecondaryByte = SPI.transfer(0x00); // read the secondary byte, also sending 0 as this doesn't matter.
      digitalWrite(adcChipSelectPin, HIGH); // take the Chip Select pin high to de-select the ADC.
      interrupts(); // Enable interupts.
      adcPrimaryByte &= adcPrimaryByteMask; // Limits the value of the primary byte to the 4 LSB:
      int digitalValue = (adcPrimaryByte << 8) | adcSecondaryByte; // Shifts the 4 LSB of the primary byte to become the 4 MSB of the 12 bit digital value, this is then ORed to the secondary byte value that holds the 8 LSB of the digital value.
      float value = (float(digitalValue) * 5.000) / 4096.000; // The digital value is converted to an analogue voltage using a VREF of 2.048V. У нас VREF 5V
      return value; // Returns the value from the function
    }

  2. #2
    Senior Member PaulStoffregen's Avatar
    Join Date
    Nov 2012
    Posts
    26,574
    I don't see how the MCP4822 could interfere. Looks like your program only creates an instance but never actually uses it. I'm not familiar with this MCP48xx library, but if you give a link to its source code, maybe a quick look could check whether it's doing something very unusual just by having the unused instance created.

    If you were using the DAC and it seemed to be consuming too much CPU time, I would recommend using Serial1.addMemoryForRead() to increase the receive buffer. Details here:

    https://www.pjrc.com/teensy/td_uart.html

    But that's unlikely to be the real problem.

    My best guess about the problem is a matter of sync with the 3 byte messages, or the bytes themselves within the bit stream (more on that in a moment).

    I only read your code quickly, but my impression is the transmitting sends a 3 byte message starting with the number 1, followed by 2 bytes for actual data. The receiving code seems to look for the number 1 or 2, and then 2 data bytes. But there is nothing on the transmitting side which prevents those special numbers 1 and 2 from occurring in the data bytes. So if transmitting is already sending and the receiver restarts (like when you upload new code to Teensy without stopping the Arduino board from transmitting) and the data happens to contain 1 or 2 on a sustained basis, the receiver could mistake a data byte at the start of message indicator and use the wrong bytes as received data.

    Normally this sort of communication is done with code that prevents the start byte number from ever occurring within the data. Often that means transmitting putting only 4, 6 or 7 bits per byte and transmitting a longer message. Usually that's a good trade-off for an iron-clad guarantee your data can't ever be mistaken for the start-of-message byte.

    However, there is yet another problem which might occur if the transmitting board is able to read its sensor quick enough that it always transmits 100% serial bandwidth utilization, and the receiver starts "in the middle" of an already started stream. At 100% bandwidth utilization, Teensy receives a never ending stream of bits. There is no guarantee the first high to low transition is sees is actually a start bit. It could be any 1 to 0 data bit change. With 100% bandwidth usage, it's possible for Teensy (or any board) to forever parse the wrong groups of 8 bits into bytes.

    This problem has come up several times. Here's a prior thread with explanation and a solution.

    https://forum.pjrc.com/threads/67454...054#post281054

    This 100% bandwidth utilization problem comes up when a transmitting board runs simple code where the serial baud rate paces the overall speed. It's a fundamental limitation of the way asynchronous serial communication works. If the receiver might ever start "in the middle" of a bitstream, you must have serial idle times of at least 9-10 bits to allow the receiver to get properly in sync with the incoming bytes.

    It's easy to mistake this problem for other issues, because the receiver can get into sync and remain in sync as long as the transmitting starts after the receiving is already listening. When you experience your code working because you started the transmitter after the receiver, and then you add something else like SPI communication to a DAC chip on the receiver side, and suddenly the serial reception no longer works because you restarted the receiving while the transmitter kept running, it's easy to conclude the stuff you just added caused the problem.
    Last edited by PaulStoffregen; 07-05-2022 at 02:42 PM.

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •