Noob How To: Use a hardware serial port

Status
Not open for further replies.

TelephoneBill

Well-known member
Recently I came across a need to talk to another (non-Teensy) electronic module using RS232 protocol via a LOW LEVEL TTL inteface (logic levels 3.3 volts and 0 volts). One method is buy a USB/TTL serial converter and send commands to the module from a PC using "Terminal" type software. But Teensy boards have hardware serial ports, so I thought I might investigate how a Noobie (like myself) would go about using these instead. I'm using the new Teensy 3.6 board, but much of what I found out will apply to other boards.

I've used digital I/O pins on Teensy boards before (and I know enough about RS232 interfaces), but being a newcomer to this area for Teensy, it was not immediately obvious how to go about coding for this function. Would I have to "waggle" the output pin between logic levels using my own code? Or is there another simpler way? A quick google for Teensy serial port took me to the PJRC UART web page (https://www.pjrc.com/teensy/td_uart.html) and this gives a small bit of Example Code.

Great I thought. This should be very simple indeed... just use the built-in Serial1 port on T36 pin 0 (RX1=receive) and pin 1 (TX1=transmit) and copy the code in the example...

Hmm... Things didn't quite turn out to be so simple.

When I wrote an almost identical program (as per the example) and popped a scope lead on pin 1 (TX1), I started typing simple one byte characters into the ARDUINO IDE SERIAL MONITOR textbox (and hit RETURN) and I expected these to be shown on the scope trace as equivalent RS232 encoded bytes. But no, something was not quite correct. There seemed to be too many encoded bytes in the scope display. To investigate, I put the scope into "single trigger mode" to freeze the display for analysis, and just typed in the character "a" then hit the RETURN key.

In analysing the scope result, it became immediately apparent that the action of the RETURN key was to add two further ASCII characters (CR = Carriage Return, and LF = Line Feed). I expected these. This should make 3 ASCII encoded characters in total. But even this did not explain the actual scope trace result. There were still far too many "encoded bytes" in the output signal.

In an attempt to understand what was happening, I added a delay (in code) after sending each individual byte to the hardware serial port. This separated out the character "a" from the CR and LF characters on the scope trace. The sequence should have shown binary encoding of "0x61" plus "0x0D" plus "0x0A" with a delay between them. What I actually got was two bytes for each character. Could this be some form of Unicode encoding? Hmm. To unravel this I would have to get down to "bit level" analysis of the scope display. That was when "the penny dropped". What I was looking at was not Unicode, but simply "0x39" quickly followed with "0x37", then my delay, then "0x31" with "0x33", then another delay, then "0x31" with "0x30". So there were 6 bytes in total encoded in binary and not 3 bytes as expected.

The problem seemed to be in the "Example Code" on the PJRC web page. The code line which sent the byte to the hardware UART port was "HWSERIAL.println(incomingByte, DEC);" and the declaration for the "incomingByte" is of type "int" (integer) given at the start of the Main Loop as "int incomingByte;". This does not actually send correct ASCII byte representations of the "incomingByte" but translates the single byte "a" = "0x61" = decimal 97 into the actual numbers of the decimal equivalent of "9" and "7" and then sends these numbers as separate bytes themselves as "0x39" (= ASCII 9) and then "0x37" (= ASCII 7) - that is, the ASCII representations of the decimal numbers. The same is true for CR (decimal 13) and LF (decimal 10) - that is, two bytes for each of these symbols.

The next experiment was to change the declaration for "incomingByte". I tried type "byte" first, but this gave exactly the same display as type "int" - still too many bytes in the scope display. So then I tried type "char".... Lo and behold !! This made a big difference. Now when I typed the character "a" and hit the RETURN key, I only got the 3 bytes as I originally wanted.

The picture shown below shows the scope display after typing the three characters "123" followed by the RETURN Key, and I have annotated each individual bit so that other users can better understand how the data should actually physically appear on the output pin 1 (TX1) when using RS232 serial encoded data. I also include a picture of what appeared on the ARDUINO IDE SERIAL MONITOR after typing.

ScopePicture.jpg

SerialMonitor.jpg

You can see in the second picture that the display of "123" is repeated twice. This is because I have wired the two pins 0 and 1 (RX1 and TX1) together and my code is also showing the incoming bytes as well as the outgoing bytes. Looping the RX1 and TX1 pins together is a good simple way to test the output. If the incoming bytes are not identical to the outgoing ones, then something is not correct.

The code for this simple experiment is as follows...

Code:
//Serial36001 - Example use of RS232 Serial port on Teensy 3.6
//------------------------------------------------------------
//Date:   07 OCT 2016
//Notes:  Hardware serial port is physically on Teensy 3.6 pins: pin0 = RX1 (Incoming) and pin1 = TX1 (Outgoing)
//        This port is called "Serial1" (hence the use of "1" suffix for RX1 and TX1).
//        To output ASCII characters on this hardware port, the "print" function is used, and the data type
//        must be declared as type "char" in order to physically frame the data as one single data byte for either
//        an input or output character. The framed electrical signal has 1 start bit and 1 stop bit (no parity).
//        When a character (or string of characters) is typed into the SERIAL MONITOR then two extra data bytes
//        are automatically added after the RETURN Key has been pressed - CR (0x0D) and LF (0x0A). These are
//        appended to the character (or string of characters).

#define LED_ON     GPIOC_PSOR=(1<<5)
#define LED_OFF    GPIOC_PCOR=(1<<5)
#define HWSERIAL Serial1

//declare variables
char IncomingChar, OutgoingChar;

//-------------------------------------------------------------------  
//Setup routine
//-------------------------------------------------------------------
void setup() {
  //initialise variables

  //initialise hardware
  PORTC_PCR5 = PORT_PCR_MUX(0x1);   //LED PC5 pin 13, config GPIO alt = 1
  GPIOC_PDDR = (1<<5);              //make this an output pin
  LED_OFF;                          //start with LED off
  Serial.begin(115200);             //setup Serial Monitor speed
  HWSERIAL.begin(9600);             //setup hardware serial port speed

  //blink 4 times
  Blink();
  Blink();
  Blink();
  Blink();
}

//-------------------------------------------------------------------  
//Subroutines
//-------------------------------------------------------------------
//Blink Routine
void Blink() {
  //blink the LED
  LED_ON;
  delay(10);
  LED_OFF;
  delay(400);
} //end of Blink routine

//-------------------------------------------------------------------  
//Main Loop
//-------------------------------------------------------------------
void loop() {
  //transfer outgoing serial bytes from Serial Monitor (PC) to hardware
  if (Serial.available() > 0) {
    OutgoingChar = Serial.read();
    Serial.print(OutgoingChar);       //print the outgoing byte to Serial Monitor (PC)
    HWSERIAL.print(OutgoingChar);     //print the outgoing byte to hardware serial port
  }
  //transfer incoming serial bytes from hardware to Serial Monitor (PC)
  if (HWSERIAL.available() > 0) {
    IncomingChar = HWSERIAL.read();
    Serial.print(IncomingChar);       //print the incoming byte to Serial Monitor (PC)
  }
}

Note in the scope display that there are additional "bits" added before and after the data byte for each one transmitted. The first one is a START bit of "logic 0" (equals 0 volts) and the latter one is a STOP bit of "logic 1" (equals 3.3 volts). That makes 10 bits to be transmitted for each ACSII byte sent. This example does not use "parity checking". The baud rate chosen was 9600 and you can observe in the scope display what this means for the time period for each single bit.

When data is not being transmitted, the "quiescent state" of the TX1 pin is at 3.3 volts. The START bit is then able to show that a new byte is starting by dipping the voltage down to zero volts, and the STOP bit has to return the voltage to 3.3 volts at the end of each byte ready for the next byte to be sent.

Note also that all the bits are sent in what may seem to be the reverse order of least significant bits first (in time = to the left side of the scope trace). I refer to this order of transmission as sending the bytes "feet first" rather than "head first" - the more conventional term is "little endian".

(PS. The blink routine in the Setup routine is a very useful way to give confidence that your code is actually executing properly. Each time you download it you should see the four blinks when the bootloader has finished).
 
Last edited:
Hi,

i know your post is "under construction", but i guess this helps a bit:

Code:
[COLOR=#7E7E7E]// set this to the hardware serial port you wish to use[/COLOR]
#define HWSERIAL [COLOR=#CC6600][B]Serial1[/B][/COLOR]

[COLOR=#CC6600]void[/COLOR] [COLOR=#CC6600][B]setup[/B][/COLOR]() {
        HWSERIAL.[COLOR=#CC6600]begin[/COLOR](9600);
        delay(1000);
}

[COLOR=#CC6600]void[/COLOR] [COLOR=#CC6600][B]loop[/B][/COLOR]() {        
    [COLOR=#CC6600]if[/COLOR] ([COLOR=#CC6600][B]Serial[/B][/COLOR].[COLOR=#CC6600]available[/COLOR]() > 0) {    
        HWSERIAL.write(Serial.read());

    }
    [COLOR=#CC6600]if[/COLOR] (HWSERIAL.[COLOR=#CC6600]available[/COLOR]() > 0) {
        Serial.write(HWSERIAL.read());
    }
}

It prints to the Hardware-Serial exactly what it gets from the USB-Serial and vice versa.
 
Last edited:
Thanks Frank. Your example is an elegant way to do the job.

What I have tried to do in my posting is throw some light on what happens down at the lowest level. That way, any Noob who has access to a scope can do a bit of their own fault finding on their differing projects (and why they may not be getting the results they were expecting - just as it happened to me !!).
 
serialEvent code is nice too - especially for something like this - happens after each loop() or on yield() or delay(). I used this to test KurtE's serial 1,2,3,4,5,6 with those added for the T_3.6/3.5. Used for the Teensy ESP proxy too - need to check if support was added to ESP_Arduino yet where I had to simulate it before.

Code:
void loop() {        
}

void serialEvent1() {
    while (HWSERIAL.available() > 0) {
        Serial.write(HWSERIAL.read());
    }
}

void serialEvent() {
    while (Serial.available() > 0) {    
        HWSERIAL.write(Serial.read());
    }
}

And code like this gives a nice notice until Serial comes online:
Code:
while (!Serial && millis() < 5000) Blink();
 
Status
Not open for further replies.
Back
Top