Terminal I/O (Serial Communication)
Intro

Intro

To work interactively with a Forth system, you need some kind of console. A text terminal with RS-232 port has been used for this purpose in the past. Today's PCs have software to emulate a terminal, but usually no longer have serial ports (RS-232 interfaces). Instead, there are usually USB interfaces.

The USB-CDC appears as a traditional RS-232 port in your operating system (Linux /dev/ttyACMx, Windoze COMx). No need to bother with baud rates, handshaking, parity and other awkward things.

There is also a wireless console for PCs and mobile devices. The proprietary STM BLE Cable Replacement Service is used as communication protocol.

Terminal-IO

emit and key can block the calling thread, if the buffer is full (emit) or empty (key). The other threads are not affected (no busy-wait). The incoming characters (Rx) are buffered in a RTOS message queue, outgoing characters are also buffered.

Default console is USB-CDC, but if you press button SW2 on reset, the console is redirected to the UART RS-232 serial device (for details see mecrisp.s). If you press button SW1 on reset, the console is redirected to the Bluetooth LE Cable Replacement Service.

emit         ( char -- )        Emits a character
key          ( -- char )        Waits for and fetches the pressed key
emit?        ( -- flag )        Ready to send a character?
key?         ( -- flag )        Checks if a key is waiting

hook-emit?   ( -- addr )        Hooks for redirecting terminal IO on the fly
hook-key?    ( -- addr ) 
hook-key     ( -- addr ) 
hook-emit    ( -- addr ) 

uart         ( -- )             redirect console to serial interface (UART)
>uart        ( -- addr1 addr2 ) redirect console out (addr1 original =emit=, addr2 original =emit?=) to the serial interface (UART)
<uart        ( -- addr1 addr2 ) redirect console in (addr1 original =key=, addr2 original =key?=) to the serial interface (UART)
cdc          ( -- )             redirect console to USB-CDC
>cdc         ( -- addr1 addr2 )
<cdc         ( -- addr1 addr2 )
crs          ( -- )             redirect console to BLE CRS
>crs         ( -- addr1 addr2 )
<crs         ( -- addr1 addr2 )

>term        ( addr1 addr2 -- ) terminate (addr1 to restore =emit=, addr2 to restore =emit?=) redirection for console out
<term        ( addr1 addr2 -- ) terminate (addr1 to restore =key=, addr2 to restore =key?=) redirection for console in

: ascii ( -- ) 
  127 32 do 
    i emit 
  loop 
;

: crs-ascii ( -- ) 
  127 32 do 
    i crs-emit 
  loop 
  10 crs-emit / LF
;

USB-CDC Serial Communication (API)

Buffered USB-CDC serial communication. The USB-CDC appears as a traditional RS-232 port in your operating system (Linux /dev/ttyACMx, Windoze COMx). No need to bother with baud rates, handshaking, parity and other awkward things. Under Linux I recommend to disable the ModemManager, otherwise the ModemManager tries to configure the ACM with some escape sequences.

After the first connect to the USB host, the green LED is switched on. Reset (and power cycle) enumerate the USB, this leads to a disconnect of the serial line.

USB_USER CN1 Micro USB connector on the MB1355 Nucleo Board.
USB_USER CN3 USB A plug on the MB1293 dongle.

cdc-emit   ( char -- )   Emits one character to the USB-CDC interface. Blocking if the Buffer is full.
cdc-key    ( -- char )   Waits and gets one character from the USB-CDC interface. Blocking if the buffer is empty.
cdc-emit?  ( -- flag )   Ready to send a character. Buffer is not full.
cdc-key?   ( -- flag )   Checks if a character is in the buffer.

UART Serial Communication (API)

The Rx buffer is 5 k, the Tx buffer 1 k. Copy and paste source code into the terminal is possible without buffer overrun.

  • USB-CDC on the ST-LINK Micro USB connector on the MB1355 Nucleo Board. Serial port UART1 on PB6/PB7 (remove jumper SB2, close jumper SB6) on the MB1293 dongle.
  • USB to TTL Serial Cable for Raspberry Pi from Adafruit (RX green, TX white, GND black).
  • TTL-232R-RPi cable from FTDI Chip (RX orange, TX yellow, GND black).

Do not forget to set the baud rate. The default baud rate for the MecrispCube is 115200 baud, e.g. microcom -D /dev/ttyACM1 -s 115200 .

serial-emit  ( char -- )  Emits one character to the UART interface. Blocking if the Buffer is full.
serial-key   ( -- char )  Waits and gets one character from the UART interface. Blocking if the buffer is empty.
serial-emit? ( -- flag )  Ready to send a character. Buffer is not full.
serial-key?  ( -- flag )  Checks if a character is in the buffer.

baudrate!    ( u -- )     sets baud rate (e.g. 300, 600, 1200, 2400, 4800, 9600, 19200, 38400, 57600, 115200)
paritybit!   ( u -- )     sets parity bit 0 none, 1 odd, 2 even
wordlength!  ( u -- )     sets word length 7, 8, 9 (including parity)
stopbits!    ( u -- )     sets stop bits 0 1 bit, 1 1.5 bit, 2 2 bit

BLE Cable Replacement Serial Communication (API)

Classic Bluetooth has a Serial Port Profile (SPP). It emulates a serial cable to provide a simple substitute for existing RS-232, including the familiar control signals. But for BLE there is no official/standard service for serial communication. Many chip manufactures have their own proprietary service.

My own CRS Terminal is derived from Danila Loginov's Progressive Web App Web-Bluetooth-Terminal and is based on Web Bluetooth.

https://spyr.ch/crs-terminal/

This Progressive Web App works in the Chrome browser on Linux, OSX, and Windows operating systems. It also works on Android devices, you can download the App and use it offline.

The Serial Bluetooth Terminal App for Android works great. You have to configure the the STM CRS to Custom, choose the default UUIDs (Service 0000fe60-cc7a-482a-984a-7f2ed5b3e58f, Read Characteristic 0000fe62-8e22-4541-9d4c-21edae82ed19, Write Characteristic 0000fe61-8e22-4541-9d4c-21edae82ed19).

crs-emit   ( char -- )  Emits one character to the BLE Cable Replacement Service. Blocking if the Buffer is full.
crs-key    ( -- char )  Waits and gets one character from the BLE Cable Replacement Service. Blocking if the buffer is empty.
crs-emit?  ( -- flag )  Ready to send a character. Buffer is not full.
crs-key?   ( -- flag )  Checks if a character is in the buffer.

The blue LED indicates a connection to a central device.

Real Web-Terminal with ANSI Escape Sequences

My CRS-Terminal and the Serial Bluetooth Terminal have only a command line and no real terminal emulation e.g. ANSI escape sequences and therefore can't be used for the vi.

The JavaScript library "termlib.js" provides a `Terminal' object, which facillitates a simple and object oriented approach to generate and control a terminal-like interface for web services.

"termlib.js" features direct keyboard input and powerful output methods for multiple and simultanious instances of the `Terminal' object.

https://www.masswerk.at/termlib/index.html

Terminal Emulators

Terminal emulator applications for PCs, e.g.:

  • PuTTY - Windows and Linux
  • Tera Term - Windows
  • Realterm - Windows
  • minicom, miniterm, microcom, CuteCom, screen - Linux

Terminal emulator for Android devices:

Settings

  • Terminal: Display mode Terminal
  • Receive: Newline CR+LF
  • Send: Newline CR, Local echo off

openSUSE GNU/Linux: add your account (user name) to the dialout group (for Raspi it's the tty group) otherwise you do not have permission to access the serial line. Disable the modem manager too (openSUSE yast Service Manager, Debiab/Ubuntu sudo apt remove modemmanager).

Some Sort of SIGINT

https://en.wikipedia.org/wiki/Signal_(IPC)#SIGINT

The SIGINT signal is sent to a process by its controlling terminal when a user wishes to interrupt the process. This is typically initiated by pressing Ctrl+C, but on some systems, the "delete" character or "break" key can be used.

You do not have always a reset button to restart a hanging console, power cycle is not convenient in most cases. There is now an assert for ^C, UART errors (FIFO, interruption) throw an assert too.

If you want something similar to an SIGINT, replace the assert ASSERT_nonfatal() with osThreadFlagsSet() or osEventFlagsSet() to inform your application thread. uart.c

/**
  * @brief
  * 	Function implementing the UART Rx thread.
  * @param
  * 	argument: Not used
  * @retval
  * 	None
  */
static void UART_RxThread(void *argument) {
	osStatus_t status;

	osMutexAcquire(UART_MutexID, osWaitForever);
	// wait for the first Rx character
	if (HAL_UART_Receive_IT(&huart1, &UART_RxBuffer, 1) != HAL_OK) {
		// something went wrong
		Error_Handler();
	}
	osMutexRelease(UART_MutexID);

	// Infinite loop
	for(;;) {
		// blocked till a character is received
		status = osThreadFlagsWait(UART_CHAR_RECEIVED, osFlagsWaitAny, osWaitForever);
		ASSERT_nonfatal(UART_RxBuffer != 0x03, ASSERT_UART_SIGINT, 0) // ^C character abort
		// put the received character into the queue
		status = osMessageQueuePut(UART_RxQueueId, &UART_RxBuffer, 0, 100);
		if (status != osOK) {
			// can't put char into queue
			Error_Handler();
		}
		// receive the next character
		osMutexAcquire(UART_MutexID, osWaitForever);
		status = HAL_UART_Receive_IT(&huart1, &UART_RxBuffer, 1);
		osMutexRelease(UART_MutexID);
		if (status != osOK) {
			// can't receive char
			Error_Handler();
		}
	}
}

-- Peter Schmid - 2020-04-20

Creative Commons License
This work by Peter Schmid is licensed under a Creative Commons Attribution-ShareAlike 4.0 International License.

Edit | Attach | Watch | Print version | History: r28 < r27 < r26 < r25 < r24 | Backlinks | Raw View | Raw edit | More topic actions
Topic revision: r28 - 2023-12-13 - PeterSchmid
 
This site is powered by the TWiki collaboration platform Powered by PerlCopyright © 2008-2024 by the contributing authors. All material on this collaboration platform is the property of the contributing authors.
Ideas, requests, problems regarding TWiki? Send feedback