Setting Serial config parameters individually

How do I set the data bits, parity & stop bits individually for Serial. My use case would be to read user input for these settings and initialize each parameter separately. But the Serial.begin(baud, config) function does not breakout these parameters. I do not understand the rationale behind cramming 3 parameters into a single macro. Now I need to have an additional step to parse the input and reconstruct the macro. Either that or I overwhelm the user with a single setting with every permutation of these 3 parameters.

Any help would be appreciated and why was this design decision made ?

You haven't explained your "use case". Since the Serial device can't be used until all the parameters are set, what's the point of setting them individually?

begin() is not a macro, it's a function of the HardwareSerial class. And, there are only 2 parameters, not 3. The second parameter is optional and defaults to SERIAL_8N1.

No, you can query the user for each parameter individually and save the answers. Then, when you have the answer for both parameters (there are only 2), use them in your call to Serial.begin().

You also haven't explained how you intend to query the user with out the Serial connection up and running.

I'm running a Modbus RTU and expose the settings for baud_rate, data_bits, parity and stop_bits as holding registers so that the user can change these settings during runtime. Another use case would be to persist these settings into EEPROM and use it to initialize Serial on startup.

Anyway I found this and ended up with this kludge:

byte data_bits_conf[4] = {0x00, 0x02, 0x04, 0x06};
byte parity_conf[3] = {0x00, 0x20, 0x30};
byte stop_bits_conf[2] = {0x00, 0x08};

byte ser_config(byte data_bits, byte parity, byte stop_bits) {
  return data_bits_conf[data_bits - 5] | parity_conf[parity] | stop_bits_conf[stop_bits - 1];
}

Serial.begin(9600, ser_config(8, 0, 1)); // Serial.begin(9600, SERIAL_8N1)

This works assuming that party is {0 = NONE, 1 = EVEN, 2 = ODD}. I can see that the macro approach would prevent people from using wrong values.

You have to notice how SERIAL_8N1 is defined.
When you include "Serial.h" then "HardwareSerial.h" will be included.

namespace arduino {

// XXX: Those constants should be defined as const int / enums?
// XXX: shall we use namespaces too?
#define SERIAL_PARITY_EVEN   (0x1ul)
#define SERIAL_PARITY_ODD    (0x2ul)
#define SERIAL_PARITY_NONE   (0x3ul)
#define SERIAL_PARITY_MARK   (0x4ul)
#define SERIAL_PARITY_SPACE  (0x5ul)
#define SERIAL_PARITY_MASK   (0xFul)

#define SERIAL_STOP_BIT_1    (0x10ul)
#define SERIAL_STOP_BIT_1_5  (0x20ul)
#define SERIAL_STOP_BIT_2    (0x30ul)
#define SERIAL_STOP_BIT_MASK (0xF0ul)

#define SERIAL_DATA_5        (0x100ul)
#define SERIAL_DATA_6        (0x200ul)
#define SERIAL_DATA_7        (0x300ul)
#define SERIAL_DATA_8        (0x400ul)
#define SERIAL_DATA_MASK     (0xF00ul)

#define SERIAL_5N1           (SERIAL_STOP_BIT_1 | SERIAL_PARITY_NONE  | SERIAL_DATA_5)
#define SERIAL_6N1           (SERIAL_STOP_BIT_1 | SERIAL_PARITY_NONE  | SERIAL_DATA_6)
#define SERIAL_7N1           (SERIAL_STOP_BIT_1 | SERIAL_PARITY_NONE  | SERIAL_DATA_7)
#define SERIAL_8N1           (SERIAL_STOP_BIT_1 | SERIAL_PARITY_NONE  | SERIAL_DATA_8)
#define SERIAL_5N2           (SERIAL_STOP_BIT_2 | SERIAL_PARITY_NONE  | SERIAL_DATA_5)
#define SERIAL_6N2           (SERIAL_STOP_BIT_2 | SERIAL_PARITY_NONE  | SERIAL_DATA_6)
#define SERIAL_7N2           (SERIAL_STOP_BIT_2 | SERIAL_PARITY_NONE  | SERIAL_DATA_7)
#define SERIAL_8N2           (SERIAL_STOP_BIT_2 | SERIAL_PARITY_NONE  | SERIAL_DATA_8)
#define SERIAL_5E1           (SERIAL_STOP_BIT_1 | SERIAL_PARITY_EVEN  | SERIAL_DATA_5)
#define SERIAL_6E1           (SERIAL_STOP_BIT_1 | SERIAL_PARITY_EVEN  | SERIAL_DATA_6)
#define SERIAL_7E1           (SERIAL_STOP_BIT_1 | SERIAL_PARITY_EVEN  | SERIAL_DATA_7)
#define SERIAL_8E1           (SERIAL_STOP_BIT_1 | SERIAL_PARITY_EVEN  | SERIAL_DATA_8)
#define SERIAL_5E2           (SERIAL_STOP_BIT_2 | SERIAL_PARITY_EVEN  | SERIAL_DATA_5)
#define SERIAL_6E2           (SERIAL_STOP_BIT_2 | SERIAL_PARITY_EVEN  | SERIAL_DATA_6)
#define SERIAL_7E2           (SERIAL_STOP_BIT_2 | SERIAL_PARITY_EVEN  | SERIAL_DATA_7)
#define SERIAL_8E2           (SERIAL_STOP_BIT_2 | SERIAL_PARITY_EVEN  | SERIAL_DATA_8)
#define SERIAL_5O1           (SERIAL_STOP_BIT_1 | SERIAL_PARITY_ODD   | SERIAL_DATA_5)
#define SERIAL_6O1           (SERIAL_STOP_BIT_1 | SERIAL_PARITY_ODD   | SERIAL_DATA_6)
#define SERIAL_7O1           (SERIAL_STOP_BIT_1 | SERIAL_PARITY_ODD   | SERIAL_DATA_7)
#define SERIAL_8O1           (SERIAL_STOP_BIT_1 | SERIAL_PARITY_ODD   | SERIAL_DATA_8)
#define SERIAL_5O2           (SERIAL_STOP_BIT_2 | SERIAL_PARITY_ODD   | SERIAL_DATA_5)
#define SERIAL_6O2           (SERIAL_STOP_BIT_2 | SERIAL_PARITY_ODD   | SERIAL_DATA_6)
#define SERIAL_7O2           (SERIAL_STOP_BIT_2 | SERIAL_PARITY_ODD   | SERIAL_DATA_7)
#define SERIAL_8O2           (SERIAL_STOP_BIT_2 | SERIAL_PARITY_ODD   | SERIAL_DATA_8)
#define SERIAL_5M1           (SERIAL_STOP_BIT_1 | SERIAL_PARITY_MARK  | SERIAL_DATA_5)
#define SERIAL_6M1           (SERIAL_STOP_BIT_1 | SERIAL_PARITY_MARK  | SERIAL_DATA_6)
#define SERIAL_7M1           (SERIAL_STOP_BIT_1 | SERIAL_PARITY_MARK  | SERIAL_DATA_7)
#define SERIAL_8M1           (SERIAL_STOP_BIT_1 | SERIAL_PARITY_MARK  | SERIAL_DATA_8)
#define SERIAL_5M2           (SERIAL_STOP_BIT_2 | SERIAL_PARITY_MARK  | SERIAL_DATA_5)
#define SERIAL_6M2           (SERIAL_STOP_BIT_2 | SERIAL_PARITY_MARK  | SERIAL_DATA_6)
#define SERIAL_7M2           (SERIAL_STOP_BIT_2 | SERIAL_PARITY_MARK  | SERIAL_DATA_7)
#define SERIAL_8M2           (SERIAL_STOP_BIT_2 | SERIAL_PARITY_MARK  | SERIAL_DATA_8)
#define SERIAL_5S1           (SERIAL_STOP_BIT_1 | SERIAL_PARITY_SPACE | SERIAL_DATA_5)
#define SERIAL_6S1           (SERIAL_STOP_BIT_1 | SERIAL_PARITY_SPACE | SERIAL_DATA_6)
#define SERIAL_7S1           (SERIAL_STOP_BIT_1 | SERIAL_PARITY_SPACE | SERIAL_DATA_7)
#define SERIAL_8S1           (SERIAL_STOP_BIT_1 | SERIAL_PARITY_SPACE | SERIAL_DATA_8)
#define SERIAL_5S2           (SERIAL_STOP_BIT_2 | SERIAL_PARITY_SPACE | SERIAL_DATA_5)
#define SERIAL_6S2           (SERIAL_STOP_BIT_2 | SERIAL_PARITY_SPACE | SERIAL_DATA_6)
#define SERIAL_7S2           (SERIAL_STOP_BIT_2 | SERIAL_PARITY_SPACE | SERIAL_DATA_7)
#define SERIAL_8S2           (SERIAL_STOP_BIT_2 | SERIAL_PARITY_SPACE | SERIAL_DATA_8)

class HardwareSerial : public Stream
{
  public:
    virtual void begin(unsigned long) = 0;
    virtual void begin(unsigned long baudrate, uint16_t config) = 0;
    virtual void end() = 0;
    virtual int available(void) = 0;
    virtual int peek(void) = 0;
    virtual int read(void) = 0;
    virtual void flush(void) = 0;
    virtual size_t write(uint8_t) = 0;
    using Print::write; // pull in write(str) and write(buf, size) from Print
    virtual operator bool() = 0;
};

// XXX: Are we keeping the serialEvent API?
extern void serialEventRun(void) __attribute__((weak));

}

Nothing you said there precludes having your code hold on to the parameters and calling Serial.begin() when it has a complete set. Again, the Serial object is unusable until you set all the parameters (at the same time).

@embeddedkiddie Thanks for pointing me to the source.

@gfvalvo Here's a minimal example of my use-case. The ellipses ... would represent some modification of config. I'm currently using the workaround from my previous comment in place of ???. The config in Serial.begin() is neither broken out separately nor does it have any setters.

#include <EEPROM.h>

enum Parity { NONE, EVEN, ODD };

struct Config {
  byte data_bits, parity, stop_bits, commit;
} config = {8, NONE, 1, 0};

void setup() {
  EEPROM.get(0, config);
  Serial.begin(9600, ???); // What do I put here ?
}

void loop() {
  ...
  if (config.commit) {
    config.commit = 0;
    EEPROM.put(0, config);
  }
}

Why do you call a perfectly good function a kludge?

All it does it what you have to do. Justput it somewhere it won't hurt your eyes and forget all about whatever it is you can't like about it.

a7

I guess I simply have to turn a blind eye to it. A simple setter would be pretty handy though.

You could just set the registers individually, but that would make your code platform specific

Write a wrapper function (compiles but untested):

#include <EEPROM.h>

enum Parity { NONE, EVEN, ODD };

struct Config {
  uint32_t baud;
  uint8_t dataBits;
  Parity parity;
  uint8_t stopBits;
  bool commit;
};

void configSerial(const Config &parameters, HardwareSerial &port = Serial);

Config config {115200, 8, NONE, 1, false};

void setup() {
  EEPROM.get(0, config);
  configSerial(config);
}

void loop() {
  //...
  if (config.commit) {
    config.commit = false;
    EEPROM.put(0, config);
  }
}

void configSerial(const Config &parameters, HardwareSerial &port) {
  uint8_t serialSettings {6};
  switch (parameters.dataBits) {
    case 5:
      serialSettings = 0;
      break;

    case 6:
      serialSettings = 2;
      break;

    case 7:
      serialSettings = 4;
      break;

    case 8:
    default:
      break;
  }

  switch (parameters.parity) {
    case EVEN:
      serialSettings |= 0x20;
      break;

    case ODD:
      serialSettings |= 0x30;
      break;

    case NONE:
    default:
      break;
  }

  if (parameters.stopBits == 2) {
    serialSettings |= 0x8;
  }

  port.end();
  port.begin(parameters.baud, serialSettings);
}

1 Like