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
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.