M4 Express RS485 help

I am trying to connect an Arduino M4 Feather Express to a Sensirion SFC6000D mass flow controller using RS485.

Sensirion provides a framework (GitHub - Sensirion/arduino-uart-sfx6xxx: Arduino library to work with Sensirion's SFC6xxx mass flow controller or SFM6xxx sensor over UART) that i copied. I then added MartinL's code to set up RS485 on the M4 (SAMD51 RS485 Hardware driven TE pin).
Basically i've done nothing but meld these two.

Hardware wise, RX is connected to RX and TX is connected to TX. I am seeing data transmission both ways on the bus. If i terminate with the recommended 120 Ohm resistor, i think the M4 runs out of poop and data stops.

During operation, i get the "Error trying to execute readAveragedMeasuredValue(): Not enough space in buffer" error generated in the code. I do not see how i can increase the buffer size and i've never used an M4 or a SFC6000 before.

Here's the code:


```cpp
// Sensirion sourced
// Generator:     sensirion-driver-generator 0.23.0
// Product:       sfx6xxx
// Model-Version: 2.0.0

// FROM https://forum.arduino.cc/t/samd51-rs485-hardware-driven-te-pin/687055
//  NOTES FROM AUTHOR
  // The following code activates the TE (Tranmit Enable) on D10 (PA20) 
  // using an Adafruit Feather M4, with TX and RX on D1 and D0 respectively:


#include <Arduino.h>
#include <SensirionUartSfx6xxx.h>

// Adjust as needed for you Arduino board.
// [Serial, Serial1, Serial2, etc.]
#define SENSOR_SERIAL_INTERFACE Serial1

SensirionUartSfx6xxx sensor;

//static char errorMessage[64];
static char errorMessage[256];
static int16_t error;


// Convert Serial1 for RS485 operation on the Adafruit Feather M4
// this is per the second link
void setup() {
  Serial1.begin(115200);                        // Initialise Serial1 (on SERCOM5)
  while (!Serial1);                             // Wait for the console to open
  SERCOM5->USART.CTRLA.bit.ENABLE = 0x0;        // Disable SERCOM5
  while (SERCOM5->USART.SYNCBUSY.bit.ENABLE);   // Wait for synchronization
  PORT->Group[g_APinDescription[10].ulPort].PINCFG[g_APinDescription[10].ulPin].bit.PMUXEN = 1;   // Enable the TE output on D10
  PORT->Group[g_APinDescription[10].ulPort].PMUX[g_APinDescription[10].ulPin >> 1].reg |= PORT_PMUX_PMUXE(2);
  SERCOM5->USART.CTRLA.bit.TXPO = 0x3;          // Set TXPO to 3 to activate the TE output
  SERCOM5->USART.CTRLC.bit.GTIME = 0x2;         // Set the RS485 guard time
  SERCOM5->USART.CTRLA.bit.ENABLE = 0x1;        // Enable SERCOM5
  while (SERCOM5->USART.SYNCBUSY.bit.ENABLE);   // Wait for synchronization

 // initialize serial interface for logging-- THIS IS THE CONSOLE
  Serial.begin(115200);
  while (!Serial) {
    delay(100);
  }

  // initialize serial interface for sensor communication
  SENSOR_SERIAL_INTERFACE.begin(115200);
  while (!SENSOR_SERIAL_INTERFACE) {
    delay(100);
  }

    sensor.begin(SENSOR_SERIAL_INTERFACE);
error = sensor.deviceReset();
  if (error != NO_ERROR) {
    Serial.print("Error trying to execute deviceReset(): ");
    errorToString(error, errorMessage, sizeof errorMessage);
    Serial.println(errorMessage);
    return;
  }
  delay(2000);
  int8_t serialNumber[64] = {0};
  error = sensor.getSerialNumber(serialNumber, 64);
  if (error != NO_ERROR) {
    Serial.print("Error trying to execute getSerialNumber(): ");
    errorToString(error, errorMessage, sizeof errorMessage);
    Serial.println(errorMessage);
    return;
  }
  Serial.print("serialNumber: ");
  Serial.print((const char*)serialNumber);
  Serial.println();
  error = sensor.setSetpoint(2);
  if (error != NO_ERROR) {
    Serial.print("Error trying to execute setSetpoint(): ");
    errorToString(error, errorMessage, sizeof errorMessage);
    Serial.println(errorMessage);
    return;
  }
}

void loop() {

  float averagedMeasuredValue = 0.0;
  error = sensor.readAveragedMeasuredValue(50, averagedMeasuredValue);
  if (error != NO_ERROR) {
    Serial.print("Error trying to execute readAveragedMeasuredValue(): ");
    errorToString(error, errorMessage, sizeof errorMessage);
    Serial.println(errorMessage);
    if (error == STATUS_CODE_SENSOR_MEASURE_LOOP_NOT_RUNNING_ERROR) {
      Serial.println(
        "Most likely the valve was closed due to overheating "
        "protection.\nMake sure a flow is applied and restart the "
        "script.");
    }
    delay( 2500 );
    return;
  }
  Serial.print("averagedMeasuredValue: ");
  Serial.print(averagedMeasuredValue);
  Serial.println();
  delay( 2500 );
}

I went into HardwareSerial.cpp per another forum question, but was unsuccessful figuring out what i could change and even if it is the right place.

 I think I2C would be easier, but it doesn't fit in well with our environment.

That's not the right connection.
The correct ones are Rx-Tx and Tx-Rx.
Have you read the documentation you referenced?

|Converter|Cable Color|Arduino Pin|Comments  |
|TXD      |yellow     |RX         |cross over|
|RXD      |white      |TX         |cross over|

RS485 is a multidrop bus, unlike RS232 which is point-to-point and requires a crossover cable. There isn't really an "RX" or "TX", so i used incorrect nomenclature. I used names to match the naming of RX and TX (D1 and D0) of the M4 Express. I purchased the M4 because it was supposed to have RS485 capability, but i am concerned I'll need to add a 485 transceiver since the Arduino isn't true differential.

My question was regarding what to do about the serial buffer overflow (but i do appreciate other comments). I'm clueless since i discovered there isn't a native driver.

Okay, but I answered you according to the information shown in the link you attached because I can't know how much you know about RS485.

Regarding the buffer, I interpret that the error refers to the buffer created by the library, not the serial buffer.
But I could be wrong, of course. :wink:

Hi @richardsonpj

While the M4's USARTs support RS-485, this does not include the differential transceiver.

Instead the M4 provides a Transmit Enable (TE) pin that is driven high to activate the transceiver during data transmission. It also has the ability to set the guard time (transmit delay extention) after each byte has been tranmitted. Furthermore, the M4 facilitates collision detection for bidirectional transfers as well. These features require the configuration of the USART's registers that are detailed in the SAMD51 datasheet.

Since your sensor support I2C, why you prefer to connect with RS485?

I may have to. They are used to control MFC's for individual equipment in a large NOAA measurement site and the risk of I2C cable damage as the massive amount of cables and equipment is worked on/moved is high. I have also found quick disconnects that are impossible to connect wrong are highly preferred.

THANK YOU MartinL!
I didn't read enough in advance... the Adafruit documentation is not very detailed. All the feather M0 work has required reading the Cortex M0 specs, and i guess i should have done this first. We are in a massive hurry with a deadline and limited funds... what else is new in engineering?
I will see if i can find a PTH transceiver in my stock. I know i have SMT.

Do you think the M4 buffer size problem will be fixed once i get the right transceivers? The line is very short and though the signals look poor, i have been getting some data transfers as i occasionally see the CRC post errors.

I now have good looking RS485 transmit data after adding a driver/receiver.
However, i can't get a TXEN signal to release the bus. Pin 10 seems to be in the undriven floating state which results in TXN always being on (hence the ability to transmit).

Per MartinL's posting (referenced above), SAMD51 RS485 Hardware driven TE pin, his TXEN is on pin 10. I've looked at all pins with a scope and none appear to be changing in sync with the TX data stream.

When i run his code alone, it works. Mine doesn't. Does anyone see what might be wrong? I'm going to start digging into the Sensirion Uart library next.

TXEN stops happening when this is called

  // initialize serial interface for sensor communication
  // THIS IS WHAT CLOBBERS THE PIN 10 TXEN
  SENSOR_SERIAL_INTERFACE.begin(115200);
  while (!SENSOR_SERIAL_INTERFACE) {
    delay(100);
  }

```cpp
// Sensirion sourced
// Generator:     sensirion-driver-generator 0.23.0
// Product:       sfx6xxx
// Model-Version: 2.0.0

// FROM https://forum.arduino.cc/t/samd51-rs485-hardware-driven-te-pin/687055
//  NOTES FROM AUTHOR
  // The following code activates the TE (Tranmit Enable) on ** D6 per reasearch (author says D10 (PA20) )
  // using an Adafruit Feather M4, with TX and RX on D1 and D0 respectively:

// my notes
// Try MC34050 RS422 (not supposed to work, but this has an enable pin.
// Ard TX to pin 15 (DR1 in)
// Ard RX to pin 3 (REC1 out)
// cable white (D+) to chip pin 14 (sig+)
// cable black (D-) to chip pin 13 (sig-)
// Ard D10 goes to both pin 4 (DR1 EN) to pin 12 !(REC EN)
// Pin 1 (REC1 IN-) tied to Pin 13 (!DR1 OUT)
// pin 2 (REC1 IN+) tied to pin 14 (DR1 OUT)
// Pin 8 = GND
// Pin 16 = Vcc (+5V)
// tie pins 6 and 7 together to avoid RCV2 oscillation
// 120 Ohms across pins 13 and 14 if this works (termination)

#include <Arduino.h>
#include <SensirionUartSfx6xxx.h>

// // Adjust as needed for you Arduino board.
// // [Serial, Serial1, Serial2, etc.]
// #define SENSOR_SERIAL_INTERFACE Serial1

SensirionUartSfx6xxx sensor;

//static char errorMessage[64];
static char errorMessage[256];
static int16_t error;

// Convert Serial1 for RS485 operation on the Adafruit Feather M4
// this is per the second link
void setup() {
  
    // try configuring pin 10 as an output
// pinMode (6, OUTPUT); // didn't work- always low 

  Serial1.begin(115200);                        // Initialise Serial1 (on SERCOM5)
  while (!Serial1);                             // Wait for the console to open
  SERCOM5->USART.CTRLA.bit.ENABLE = 0x0;        // Disable SERCOM5
  while (SERCOM5->USART.SYNCBUSY.bit.ENABLE);   // Wait for synchronization
  PORT->Group[g_APinDescription[10].ulPort].PINCFG[g_APinDescription[10].ulPin].bit.PMUXEN = 1;   // Enable the TE output on D10
  PORT->Group[g_APinDescription[10].ulPort].PMUX[g_APinDescription[10].ulPin >> 1].reg |= PORT_PMUX_PMUXE(2);
  // below from data sheet Table 6-1
  // pad[0] TxD; PAD[1] = RxD, Trans enable should be PAD[2]
  // SERCOM PADS for M4 Express CHIP pin/Feather pins for SERCOM5: PAD[0] =  39 / TX_D1 JP1-2; PAD[1] = 40 / RX_D0 JP1-3; PAD[2] = 37 /  ; PAD[3] = 38
  SERCOM5->USART.CTRLA.bit.TXPO = 0x3;          // Set TXPO to 3 to activate the TE output and TxD: definition page 870 section 34.7
  SERCOM5->USART.CTRLC.bit.GTIME = 0x2;         // Set the RS485 guard time
  SERCOM5->USART.CTRLA.bit.ENABLE = 0x1;        // Enable SERCOM5
  


  while (SERCOM5->USART.SYNCBUSY.bit.ENABLE);   // Wait for synchronization

 // initialize serial interface for logging-- THIS IS THE CONSOLE
  Serial.begin(115200);
  while (!Serial) {
    delay(100);
  }


// Adjust as needed for you Arduino board.
// [Serial, Serial1, Serial2, etc.]
#define SENSOR_SERIAL_INTERFACE Serial1




  // initialize serial interface for sensor communication
  SENSOR_SERIAL_INTERFACE.begin(115200);
  while (!SENSOR_SERIAL_INTERFACE) {
    delay(100);
  }

  sensor.begin(SENSOR_SERIAL_INTERFACE);
  error = sensor.deviceReset();
  if (error != NO_ERROR) {
    Serial.print("Error trying to execute deviceReset(): ");
    errorToString(error, errorMessage, sizeof errorMessage);
    Serial.println(errorMessage);
    return;
  }
  delay(2000);
  int8_t serialNumber[64] = {0};
  error = sensor.getSerialNumber(serialNumber, 64);
  if (error != NO_ERROR) {
    Serial.print("Error trying to execute getSerialNumber(): ");
    errorToString(error, errorMessage, sizeof errorMessage);
    Serial.println(errorMessage);
    return;
  }
  Serial.print("serialNumber: ");
  Serial.print((const char*)serialNumber);
  Serial.println();
  error = sensor.setSetpoint(2);
  if (error != NO_ERROR) {
    Serial.print("Error trying to execute setSetpoint(): ");
    errorToString(error, errorMessage, sizeof errorMessage);
    Serial.println(errorMessage);
    return;
  }
}

void loop() {

  float averagedMeasuredValue = 0.0;
  error = sensor.readAveragedMeasuredValue(50, averagedMeasuredValue);
  if (error != NO_ERROR) {
    Serial.print("Error trying to execute readAveragedMeasuredValue(): ");
    errorToString(error, errorMessage, sizeof errorMessage);
    Serial.println(errorMessage);
    if (error == STATUS_CODE_SENSOR_MEASURE_LOOP_NOT_RUNNING_ERROR) {
      Serial.println(
        "Most likely the valve was closed due to overheating "
        "protection.\nMake sure a flow is applied and restart the "
        "script.");
    }
    delay( 2500 );
    return;
  }
  Serial.print("averagedMeasuredValue: ");
  Serial.print(averagedMeasuredValue);
  Serial.println();
  delay( 2500 );
}

Hi @richardsonpj

Looking at your code, it appears as though you're setting up Serial1 two times. Calling Serial1/SENSOR_SERIAL_INTERFACE begin() function a second time will disable the TE settings that were set previously.

Thank you. I think that is what is happening, too, but i don't have a clue how to fix it. Any ideas? I've been spinning my wheels.

And thank you so much for the original code i referenced. It's been a huge help. I've found the definition in variant.cpp

One other thing, the SENSOR_SERIAL_INTERFACE is bus for the RS485 device i'm trying to talk to.

Duh. I am redefining the port. Thank you, so obvious i missed it. Thank you MartinL!
Still having problems, but that solved my RS485 basic problem

Hi @richardsonpj

You should be able to just configure the Serial1 (SERCOM5) registers to enable the TE pin, then pass this (C++) object as a reference to the SensirionUartSfx6xxx sensor.begin() function.

In this way you'll get the TE pin to automatically operate on D10, but as this change has been made at the register level, Sensirion's sensor object should remain unaffected:

// Adjust as needed for you Arduino board.
// [Serial, Serial1, Serial2, etc.]
#define SENSOR_SERIAL_INTERFACE Serial1

  // initialize serial interface for sensor communication
  SENSOR_SERIAL_INTERFACE.begin(115200);
  while (!SENSOR_SERIAL_INTERFACE) {
    delay(100);
  }
  SERCOM5->USART.CTRLA.bit.ENABLE = 0x0;        // Disable SERCOM5
  while (SERCOM5->USART.SYNCBUSY.bit.ENABLE);   // Wait for synchronization
  PORT->Group[g_APinDescription[10].ulPort].PINCFG[g_APinDescription[10].ulPin].bit.PMUXEN = 1;   // Enable the TE output on D10
  PORT->Group[g_APinDescription[10].ulPort].PMUX[g_APinDescription[10].ulPin >> 1].reg |= PORT_PMUX_PMUXE(2);
  // below from data sheet Table 6-1
  // pad[0] TxD; PAD[1] = RxD, Trans enable should be PAD[2]
  // SERCOM PADS for M4 Express CHIP pin/Feather pins for SERCOM5: PAD[0] =  39 / TX_D1 JP1-2; PAD[1] = 40 / RX_D0 JP1-3; PAD[2] = 37 /  ; PAD[3] = 38
  SERCOM5->USART.CTRLA.bit.TXPO = 0x3;          // Set TXPO to 3 to activate the TE output and TxD: definition page 870 section 34.7
  SERCOM5->USART.CTRLC.bit.GTIME = 0x2;         // Set the RS485 guard time
  SERCOM5->USART.CTRLA.bit.ENABLE = 0x1;        // Enable SERCOM5
  while (SERCOM5->USART.SYNCBUSY.bit.ENABLE);   // Wait for synchronization

  sensor.begin(SENSOR_SERIAL_INTERFACE);

Works great. Now if i can only get the receiving device to respond...

This topic was automatically closed 180 days after the last reply. New replies are no longer allowed.