9-Bit UART is built in to the new RA4M1 chips!

DO NOT ATTEMPT IF YOU ARE NOT SURE YOU KNOW WHAT YOU ARE DOING

Yes, true 9-bit works!

Okay, so let me lay it all out for you, as there is a tiny snag. You need to do two tiny tweaks to your core files, or the folks who run the repos for the core and API need to merge my pull requests.

So, let me start by saying that everything is already turned on, hooked up, and ready to use except the actual defined setting to turn it on. I also see this is an issue with 7-bit, but that is not in my scope here.

To get 9-bit available, we need to go into the core API/HardwareSerial.h and Serial.cpp files and add the settings. These files on my machine (Win11) are found:

C:\Users\<my_username>\AppData\Local\Arduino15\packages\arduino\hardware\renesas_uno\1.2.2\cores\arduino

Add these lines to api/HardwareSerial.h:

#define SERIAL_DATA_9        (0x500ul)
#define SERIAL_9N1           (SERIAL_STOP_BIT_1 | SERIAL_PARITY_NONE  | SERIAL_DATA_9)

Here is a snippet of the file to show where these lines go:

#define SERIAL_DATA_6        (0x200ul)
#define SERIAL_DATA_7        (0x300ul)
#define SERIAL_DATA_8        (0x400ul)
#define SERIAL_DATA_9        (0x500ul) // 9-bit support
#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_9N1           (SERIAL_STOP_BIT_1 | SERIAL_PARITY_NONE  | SERIAL_DATA_9) // 9-bit support
#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)

Add these lines to Serial.cpp:

 case SERIAL_9N1:
           uart_cfg.data_bits = UART_DATA_BITS_9;
           uart_cfg.parity = UART_PARITY_OFF;
           uart_cfg.stop_bits = UART_STOP_BITS_1;
           break;

Here is a snippet of the file to show where these lines go:

    switch(config){
      case SERIAL_8N1:
          uart_cfg.data_bits = UART_DATA_BITS_8;
          uart_cfg.parity = UART_PARITY_OFF;
          uart_cfg.stop_bits = UART_STOP_BITS_1;
          break;
      case SERIAL_8N2:
          uart_cfg.data_bits = UART_DATA_BITS_8;
          uart_cfg.parity = UART_PARITY_OFF;
          uart_cfg.stop_bits = UART_STOP_BITS_2;
          break;
      case SERIAL_8E1:
          uart_cfg.data_bits = UART_DATA_BITS_8;
          uart_cfg.parity = UART_PARITY_EVEN;
          uart_cfg.stop_bits = UART_STOP_BITS_1;
          break;
      case SERIAL_8E2:
          uart_cfg.data_bits = UART_DATA_BITS_8;
          uart_cfg.parity = UART_PARITY_EVEN;
          uart_cfg.stop_bits = UART_STOP_BITS_2;
          break;
      case SERIAL_8O1:
          uart_cfg.data_bits = UART_DATA_BITS_8;
          uart_cfg.parity = UART_PARITY_ODD;
          uart_cfg.stop_bits = UART_STOP_BITS_1;
          break;
      case SERIAL_8O2:
          uart_cfg.data_bits = UART_DATA_BITS_8;
          uart_cfg.parity = UART_PARITY_ODD;
          uart_cfg.stop_bits = UART_STOP_BITS_2;
          break;
      case SERIAL_9N1: // 9-bit support
          uart_cfg.data_bits = UART_DATA_BITS_9;
          uart_cfg.parity = UART_PARITY_OFF;
          uart_cfg.stop_bits = UART_STOP_BITS_1;
          break;
    }

Now that you have those code changes in place, you can turn on your UART port with the normal Serial1.begin(19200) command, but now, to allow for 9-bit, you just added the ability to add SERIAL_9N1 to it. So to open a 9bit UART port, just run this command instead:

Serial1.begin(19200, SERIAL_9N1)
You can change the baud rate as you see fit.

Now that your port is open, how do you use it?

I would say use it as usual with one exception: the write command is a bit more picky. It requires byte arrays, and there is a very good reason for this.

Serial.write(val) \\ Does not work with 9bit enabled
Serial.write(str) \\ Does not work with 9bit enabled
Serial.write(buf, len) \\ Use this one

Although the write command natively supports 8-bit bytes, it also supports casting from 16-bit to 8-bit by sending an array of casted 16-bit bytes. I'm unsure I said that correctly or used the correct terms, but let me show you what I mean.

uint16_t d[] = {0xFF01, 0xFC8A, 0xFC00, 0xFC23};
Serial1.write((uint8_t *)d, sizeof(d));

So how is this working, you ask? Good question :wink:

So, your array of 16-bit bytes is being broken down and sent to write one 8-bit byte at a time, but hidden deep inside the write command, deep inside the Reneses chips FSP layer, there is this code.

if (2U == p_ctrl->data_bytes)
        {
            p_ctrl->p_reg->FTDRHL = *((uint16_t *) (p_src)) | (uint16_t) ~(SCI_UART_FIFO_DAT_MASK);
        }
        else
        {
            p_ctrl->p_reg->TDR = *(p_src);
        }

This turns your 8-bit bytes back into 16-bit bytes by mashing two 8-bits back together (Casting) and sending them to the FTDRHL register to be sent as a 9-bit serial.

So why 16bit for 9bit comms well the FTDRHL register is a 16bit register.

Transmit FIFO Data Register HL (FTDRHL)

| b15 | b14 | b13 | b12 | b11 | b10 | b9 | b8 | b7 | b6 | b5 | b4 | b3 | b2 | b1 | b0 |

b8 to b0 Serial Transmit Data Serial transmit data
b9 MPBT Multi-Processor Transfer Bit
b15 to b10 — Reserved The write value should be 1

uint16_t d[] = {0xFF01, 0xFC8A, 0xFC00, 0xFC23};
Serial1.write((uint8_t *)d, sizeof(d));

So, in my 16-bit array, I have to send a machine address, first 01, and then the data packet of 8A, 00, 23. However, my address 01 needs to have what they call the wake bit set. The wake bit means the 9th bit needs to be set to 1. That tells the machine listening for address 01 to wake up and pay attention.

So, to structure my bits according to the framework laid out in the RA4M1 manual, we would also set the Multi-Processor bit to 1 to tell the register this is an address bit.

So our bit steam to the FTDRHL register will look like something like this:

1111 1111 0000 0001  // This is the Address 01 = 0xFF01
1111 1100 1000 0001 // This is data 8A = 0xFC8A
1111 1100 0000 0000 // This is data 00 = 0xFC00
1111 1100 0010 0011 // This is data 23 = 0xFC23

Notice the first 6 bits left to right are always 1, as per the docs "b15 to b10—Reserved. The write value should be 1," so we set them to 1. I also believe this is done for you and does not need to be set by us, but it seems cleaner. Then, for the address, we not only set the 9th bit but also the Multi-Processor Transfer bit just to make it clear that this is the address bit.

In my complete code, I use bitwise OR to add 0xFF00 (address bits on) or 0xFC00 (address bits off) to my data as needed.

So this is a bit wordy and might not be detailed enough to honestly explain what's going on, but the general gist is that 9-bit native works great—at least from the transmitting side. Receiving works as normal. I have not dove into figuring out if the 9th bit is set or not with the received byte yet, but that's for another time. Normal reception works as normal.

I have two open pull requests to allow the no longer editing of the core files.
API pull request #238
ArduinoCore-renesas pull request 379

Thank you for reading, and I hope this helps at least a few people along the way.

3 Likes

Please explain why using a 9 bit UART is necessary and under which circumstances it would be useful

On older hardware like embedded machines or devices the manufacturer setup 9bit as the communication protocol of choice. Its hardwired to allow multiple machines to talk over RS232 at the same time. The Arduino UNO has been used for years for connecting to these devices by manually flopping bits in the register to trick the system by using the parity pit as the 9th bit. But now this new chip has native support for 9bit and allows for us to not have to trick the system or play parity flipping games to achieve what we need to support.

My machine has very rigid protocol requirements and these new RA4M1 chips fill the bill much better then the older UNO hack we are use to. I have more then 15 machines I need to talk to at the moment and many more to come in the future, so 9bit is very important to me and those talking to older machines and embedded systems.

9bit support is the only reason I have Arduino boards sitting around as I can do most anything else on a Raspberry. Having access to all the chips features brings huge value to Arduino and its usefulness among people like me that need things like 9bit serial support.

The 328P processor has native support for 9 databits; from the datasheet

We have used the UCSR0B register for years on the older UNO devices. I never dove into the cores to see if it was possible until it was no longer available on the new R4 boards.

This update allows 9bit to be used directly through the Arduino core API. Giving access to turning on support that is already in the code base.

I understand your point. My point was that there should not be a need to use a hack like parity bits on an 328P based board.

And be careful. An update of the board package will wipe out your modifications.

I just did a simple search in this forum on 9-bit, and found well over 100+ posts talking about it. This is code that is already there, we just need access to the CASE and DEFINEs to make it available without hacking.

That is why I am looking to get the code added to the core itself, so I can push 9bit enabled code from the cloud Iot and so patches don't wipe out my DEFINEs and CASE updates. :wink:

For what it is worth, most Teensy boards support 9 and some 10 bit.
However, you need to edit the source files and turn on a define:

// Uncomment to enable 9 bit formats.  These are default disabled to save memory.
//#define SERIAL_9BIT_SUPPORT

I have done work to support it, but have not used it myself. But I believe
we added a new output function: size_t write9bit(uint32_t c);

Why it was conditional on the Teensy boards?

Did you add in 9 bit support for RX as well? I did not notice it.

On the Teensy, the 9-bit support is on both RX and TX, and we have setup software buffers for both RX and TX. To do this, when 9 bit support is enabled, the RX and TX buffers are defined as 16 bits per element instead of 8, as such more memory is used...

The UNO R4s have RX and TX buffers defined as well:

    arduino::SafeRingBufferN<SERIAL_BUFFER_SIZE> rxBuffer;
    arduino::SafeRingBufferN<SERIAL_BUFFER_SIZE> txBuffer;

However, the TX buffer is not actually used, other in a few places as errors. Like the flush method on the buffer that is not used.

And I believe that the implementation of the SafeRingBufferN only supports 8 bits

template <int N>
class SafeRingBufferN : public RingBufferN<N>
{
  public:
    int read_char();
    void store_char( uint8_t c ) ;
    int available() ;
};

typedef SafeRingBufferN<SERIAL_BUFFER_SIZE> SafeRingBuffer;

So wondering what Arduino has done on other boards which natively support 9 bits.
For example, the Arduino GIGA? My guess is so far they probably don't support it.
At least through the Arduino interfaces. And my quick 2 minute look, is that it is not supported in MBED either. Their format parameter looks like it supports 5-8 bits.

So will be interesting to see what the Arduino developers will pull in. Especially since things like the API github project is used for most if not all Arduino boards.

Good luck

Thank you

I believe the buffer here is not effected as we are casting to 8bit, so the buffer is just storing twice the 8bit data. So other then taking up more memory everything in the normal Arduino write/transmit process is unaffected and unaware of the 16bit data being sent.

This is one of the reasons I wanted to go this way, I wanted to use what was already there. Buffers, irqs, etc would all have to be rebuilt if I wrote my own Serial library. So I dug and dug, and realized that all the code was there and ready to be used for 9bit, it just needed to be enabled. :slight_smile:

I am digging around now to see if there is a way to see the 9th bit on the received side using the Arduino code already in place, but its not a real need for my needs so its secondary to getting transmit to work.

My thinking is that I will just need to cast the received bytes back into uint16_t and from there I will see all the 9bit data. But again that is just the high level first thoughts. Will take some time to dig into the all the abstraction layers, FSP, Arduino, etc. and see how the data transverses the received buffers and ends up at the user.

Not really needed for my protocol but could have some uses if I can get it to work.

Hi vashadow - Thanks for your work on this. I tried to implement your solution for use with my IGT TITO project (IGT SAS) and I keep running into problems. I was also previously using UNO3 boards and the UCSR0B = 0b10011101 register hack to set the 9th bit to 1 when sending the generalPoll and the SASid for SendTypeR and SendTypeS SAS calls.

When I tried your example; I set the Serial1 connection to 19200 9N1; I am able to send the general Poll: Serial1.write((uint8_t *)gPoll, sizeof(gPoll)); where gPoll= {0xFF80, 0xFF81}; - the game seems to like that - but when I try to send other commands - like polling for the meter data - the board seems to send giberish and then locks up completely:

Serial1.write((uint8_t *)SASID, sizeof(SASID));
for (int i = 1; i < len; i++) Serial1.write(temp[i]);

The data in the forloop needs to be sent as 8-bit data. If I try to switch the serial port on-the-fly from 9N1 to 8N1 - bad things seem to happen.

As your solution seems to be for a similar device - can you give me any additional insights?

Github Project: ArduinoTITO-PlayerTracking/AruinoTITO/src/ArduinoTITODeluxeV3 at main · marcrdavis/ArduinoTITO-PlayerTracking · GitHub

It looks like you are trying to loop over the bytes and send them one at a time to the write command; that will not work, as 9bit requires an array to be sent directly to the write command.

SAS 6.02 works perfectly with this; you have to approach it differently than you did on the older devices. I am working on a SAS project as well, and this 9-bit has lit it off. You must build a 16-bit array out of your 8-bit command structure and set the right bits for the wake.

Try this Type S function on for size. Let me know what you think. :slight_smile:

//-----------------------------------------------------------------------------------------------------
// Type S Long Poll - 2.2.2.2
//-----------------------------------------------------------------------------------------------------
void SendTypeS (uint8_t address, uint8_t data[], int len)
{
  data[0] = address; // Add the SAS machine address
  CalculateCRC(data, len - 2); // Generate CRC bytes from SASAdr and Data bytes
  data [len - 2] = CRCH; // Add the CRC high byte
  data [len - 1] = CRCL; // Add the CRC low byte

  uint16_t d[len];
  for (int i = 0; i < len; i++) {
      d[i] = data[i]; // Move data to a 16 bit array
      if(i == 0) {
        d[i] = d[i] | 0xFF00; // For the address byte turn on the wake bit
      } else {
        d[i] = d[i] | 0xFC00; // For all the data turn off the wake bit
      }
  }
  Serial1.write((uint8_t * )d, sizeof(d));
}
1 Like

here is a sample command that calls this type s command:

//-----------------------------------------------------------------------------------------------------
// - #8A - Legacy Bonus Awards - 13.3
//-----------------------------------------------------------------------------------------------------
void LegacyBonus(int cr, int tax) {
  uint8_t d[9]; // Int the full length of the command, including the SASAdr and two CRC bytes
  d[1] = 0x8A; // Command Byte
  // Convert the int credits into 4 bytes in BCD format
  d[2] = dec2bcd((cr / 1000000) % 100);  // Millions and hundred thousands
  d[3] = dec2bcd((cr / 10000) % 100);    // Ten thousands and thousands
  d[4] = dec2bcd((cr / 100) % 100);      // Hundreds and tens
  d[5] = dec2bcd(cr % 100);              // Units and tens
  d[6] = dec2bcd(tax);                   // Tax Type: 0-Deductible, 1-Non-Deductible, 2-Wager Match

  SendTypeS(SASAdr, d, sizeof(d));
}
1 Like

Thank you so much! I suspected you were doing a SAS implementation.

How are you doing the general poll? are you sending 0x80 and 0x81 together or separately with a delay?

Marc

General Poll is only one byte 80 ORed with the machine address, so I send it by itself and listen for replies, if I get 00 or 1F back I ACK with an 80. 80 is a global broadcast and is used to ACK messages.

So my code flow is more like:
Send GenPoll: (0xFF80 | MachineAddress) (This sends 81 with the wake bit set)
Wait for event message from machine. (00, 1F, or any of the many others)
Run a case statement and if 00 or 1F the send ACK 0xFF80.
Other wise run the command the is needed for that event.

If that makes sense. It follow the protocol almost to the letter. There are some timing things to consider and such and I have not even gotten into buffering the events for event handling as of yet, but I am getting there.

1 Like

Thank you so much! This has been gnawing at me for months; I had the 9-bit changes in the files from another post but 16-bit array that hung me up.

If your project is open-source feel free to take a look at the code in my project to see if it can be of help to you.

Thanks again!

Marc

I consider this as a hardware implementation detail and this code suggests that it already has been dealted with:

Does it still work when the upper bits are 0?

I just ran some tests in my environment, and yes, it does seem to work fine if you OR 0x100 to the address byte and leave the other bytes alone.

uint16_t d[4];

d[0] = (0x01 | 0x100); // 0000 0001 0000 0001 - 9th bit enabled (Address byte with wake bit set)
d[1] = 0x8A;           // 0000 0000 1000 1010 - 9th bit disabled
d[2] = 0x02;           // 0000 0000 0000 0010 - 9th bit disabled
d[3] = 0x54;           // 0000 0000 0101 0100 - 9th bit disabled

Serial1.write((uint8_t * )d, sizeof(d));

So, yes, it seems you can send an 8-bit data stream through this process and only OR 0x100 to your address bit; at least, my machine is responding great to it.

I have not found where yet, but somewhere in the serial read path we are Lossing the 9th bit. But I have found a way to see it from the register. So, if you need to see if the 9th bit is set while reading you can try this. I will continue to dig through the layers to find where the 9th bit gets lost. My bet is it's in the buffer code.

Try this to read the 9bit:

uint16_t c = Serial1.read();
bool c9 = (R_SCI2->RDRHL; & 0x100) != 0;