Serial Terminal

PicoCalc Serial Terminal

This is a serial terminal program for the PicoCalc.

  • Use the USB-C serial port or GPIO 4 (TX), 5 (RX)
  • Select baud rates between 1200 to 11520.
  • Emulates a VT100 terminal
  • 64 columns and 32 rows
  • Hardware-accelerated scrolling

Not really much more to say! I hope it does the job.


11 Likes

The initial release has a very basic menu, and the VT100 emulation has a few bugs.

Let me know if this helps you out.

As always, feedback is welcome, same for feature requests.


3 Likes

Hi,

Great job, thanks :slight_smile:

I tested USB mode without problems, but GPIO mode is giving me trouble. I connect GP4, GP5 and GND to my module, but I have no communication. If I use the same configuration (GPIO) with PicoMite, it works ( BUT-BasicUartTerminal/BUT1.0.bas at main · BuoyantToaster/BUT-BasicUartTerminal · GitHub ).

Available for tests or to provide more information.

Hmmm, I tested that.

Is this on a Pico 1 or 2?

I’m with a Pico 2 W

Will

Ok, strange. Well, this project is following into line of my typical releases - V0.1 is usually a mess.

I’ll take a look this evening when I get home.

I was able to reproduce what you experienced. GPIO is definitely not working.

I’m working on a fix, but it is going to take some evenings. I have it somewhat working now, but I think some characters are being dropped and I need to look into that.

Hi,

I work on this version and works well with UART1:

//
//  PicoCalc Terminal
//  Copyright Blair leduc. See LICENSE for details.
//

#include <stdio.h>
#include "pico/stdlib.h"
#include "pico/multicore.h"
#include "pico/stdio/driver.h"
#include "hardware/structs/uart.h"
#include "hardware/uart.h"
#include "version.h"
#include <drivers/picocalc.h>
#include <drivers/audio.h>
#include <drivers/display.h>
#include <drivers/font.h>
#include <drivers/keyboard.h>
#include <drivers/lcd.h>
#include <drivers/southbridge.h>

bool user_interrupt = false;
bool power_off_requested = false;

int uart_port = 0;
int baudrate = 115200;
int databits = 8;
int stopbits = 1;
uart_parity_t parity = UART_PARITY_NONE;

#define UART_BUFFER_SIZE 4096

static volatile uint8_t rx_buffer[UART_BUFFER_SIZE];
static volatile uint16_t rx_head = 0;
static volatile uint16_t rx_tail = 0;

typedef struct
{
    const char *name;
    int value;
} menu_value_t;

typedef struct
{
    const char *name;
    int *value;
    const menu_value_t *values;
    size_t num_values;
    int selected;
} menu_item_t;

static const menu_value_t baudrates[] = {
    {"1200", 1200},
    {"2400", 2400},
    {"4800", 4800},
    {"9600", 9600},
    {"19200", 19200},
    {"38400", 38400},
    {"57600", 57600},
    {"115200", 115200},
};

static const menu_value_t uart_ports[] = {
    {"USB-C", 0},
    {"GPIO", 1},
};

static menu_item_t menu_items[] = {
    {"Port", &uart_port, uart_ports, sizeof(uart_ports) / sizeof(uart_ports[0]), 0},
    {"Baud Rate", &baudrate, baudrates, sizeof(baudrates) / sizeof(baudrates[0]), 7},
};

void process_one_char(void)
{
    if (rx_head != rx_tail)
    {
        char ch = rx_buffer[rx_tail];
        rx_tail = (rx_tail + 1) & (UART_BUFFER_SIZE - 1);

        // Process the character
        display_emit(ch);
    }
}

void serial_putc(char ch)
{
    uart_putc_raw(uart_port ? uart1 : uart0, ch);
}

void serial_puts(const char *str)
{
    while (*str)
    {
        uart_putc_raw(uart_port ? uart1 : uart0, *str++);
    }
}

void menu(void)
{
    int row = 5;
    for (size_t i = 0; i < sizeof(menu_items) / sizeof(menu_items[0]); i++)
    {
        printf("\033[%d;0H%s: \033[%d;20H\033[%dm%6s\033[0m\n", row++, menu_items[i].name, row, i > 0 ? 0 : 7, menu_items[i].values[menu_items[i].selected].name);
    }

    row += 2;
    printf("\033[%d;0HPress up, down to select, left, right to change,\nEnter to connect\n", row++);
    printf("GP4 - TX, GP5 - RX\n", row++);

    char ch = 0;
    int item = 0;
    while (ch != '\n' && ch != '\r')
    {
        ch = getchar();
        if (ch == KEY_LEFT)
        {
            // Decrease value
            if (menu_items[item].selected > 0)
            {
                *menu_items[item].value = menu_items[item].values[--menu_items[item].selected].value;
                printf("\033[%d;20H\033[7m%6s\033[0m", 6 + item, menu_items[item].values[menu_items[item].selected].name);
            }
        }
        else if (ch == KEY_RIGHT)
        {
            // Increase value
            if (menu_items[item].selected < menu_items[item].num_values - 1)
            {
                *menu_items[item].value = menu_items[item].values[++menu_items[item].selected].value;
                printf("\033[%d;20H\033[7m%6s\033[0m", 6 + item, menu_items[item].values[menu_items[item].selected].name);
            }
        }
        else if (ch == KEY_UP)
        {
            // Previous item
            if (item > 0)
            {
                printf("\033[%d;20H%6s", 6 + item, menu_items[item].values[menu_items[item].selected].name);
                item--;
                printf("\033[%d;20H\033[7m%6s\033[0m", 6 + item, menu_items[item].values[menu_items[item].selected].name);
            }
        }
        else if (ch == KEY_DOWN)
        {
            // Next item
            if (item < (int)(sizeof(menu_items) / sizeof(menu_items[0])) - 1)
            {
                printf("\033[%d;20H%6s", 6 + item, menu_items[item].values[menu_items[item].selected].name);
                item++;
                printf("\033[%d;20H\033[7m%6s\033[0m", 6 + item, menu_items[item].values[menu_items[item].selected].name);
            }
        }
    }
}

void report(const char *message)
{
    while (*message)
    {
        serial_putc(*message++);
    }
}

void bell(void)
{
    audio_play_sound_blocking(PITCH_A4, PITCH_A4, NOTE_SIXTEENTH);
}

void init(void)
{
    sb_init();
    audio_init();

    display_init(NULL, NULL);
    lcd_set_font(&font_5x10); // 64 columns
    display_set_bell_callback(bell);
    display_set_report_callback(report);

    keyboard_init(picocalc_chars_available_notify);

    stdio_set_driver_enabled(&picocalc_stdio_driver, true);
    stdio_set_translate_crlf(&picocalc_stdio_driver, true);
}

void main_core1()
{
    char ch;
    uint16_t next_head;

    while (!user_interrupt)
    {
        // Main loop for core 1

        while (!(uart_get_hw(uart_port ? uart1 : uart0)->fr & UART_UARTFR_RXFE_BITS))
        {
            ch = uart_get_hw(uart_port ? uart1 : uart0)->dr & 0x7F;

            next_head = (rx_head + 1) & (UART_BUFFER_SIZE - 1);
            rx_buffer[rx_head] = ch;
            rx_head = next_head;
        }

        tight_loop_contents();
    }
}

int main()
{
    stdio_init_all();
    init();
    
    while (1)
    {
        user_interrupt = false;

        printf("\033[2J\033[H\033[mTerminal %s\n", PICOCALC_TERMINAL_VERSION);
        printf("Copyright Blair Leduc. See LICENSE for details.\n");
        printf("Connect with USB-C port or GPIO pins, 3.3V only!\n");
        printf("\n");

        menu();

        uart_init(uart_port ? uart1 : uart0, baudrate);
        uart_set_format(uart_port ? uart1 : uart0, databits, stopbits, parity);
        
        // Set the TX and RX pins by using the function select on the GPIO
        // Set datasheet for more information on function select
        gpio_set_function(uart_port ? 4 : 0, UART_FUNCSEL_NUM(uart_port ? uart1 : uart0, uart_port ? 4 : 0));
        gpio_set_function(uart_port ? 5 : 1, UART_FUNCSEL_NUM(uart_port ? uart1 : uart0, uart_port ? 5 : 1));
        
        // Set UART flow control CTS/RTS, we don't want these, so turn them off
        uart_set_hw_flow(uart_port ? uart1 : uart0, false, false);

        // Turn off FIFO's
        uart_set_fifo_enabled(uart_port ? uart1 : uart0, false);

        // Disable interrupts
        uart_set_irqs_enabled(uart_port ? uart1 : uart0, false, false);
        
        // Launch core 1 to handle UART RX
        multicore_launch_core1(main_core1);

        printf("\033[2J\033[H\033[mConnected at %s %d %d%s%d\n",
               uart_port ? "GPIO" : "USB-C",
               baudrate,
               databits,
               (parity == UART_PARITY_NONE)
                   ? "N"
               : (parity == UART_PARITY_EVEN)
                   ? "E"
                   : "O",
               stopbits);
        printf("Press [Brk] to change.\n\n");

        while (!user_interrupt)
        {
            process_one_char();

            if (keyboard_key_available())
            {
                char ch = keyboard_get_key();
                switch (ch)
                {
                case KEY_DEL:
                    serial_putc(0177);
                    break;
                case KEY_ESC:
                    serial_putc(033);
                    break;
                case KEY_HOME:
                    serial_puts("\033[H");
                    break;
                case KEY_END:
                    serial_puts("\033[F");
                    break;
                case KEY_UP:
                    serial_puts("\033[A");
                    break;
                case KEY_DOWN:
                    serial_puts("\033[B");
                    break;
                case KEY_RIGHT:
                    serial_puts("\033[C");
                    break;
                case KEY_LEFT:
                    serial_puts("\033[D");
                    break;
                case KEY_F1:
                    serial_puts("\033OP");
                    break;
                case KEY_F2:
                    serial_puts("\033OQ");
                    break;
                case KEY_F3:
                    serial_puts("\033OR");
                    break;
                case KEY_F4:
                    serial_puts("\033OS");
                    break;
                case KEY_F5:
                    serial_puts("\033[15~");
                    break;
                case KEY_F6:
                    serial_puts("\033[17~");
                    break;
                case KEY_F7:
                    serial_puts("\033[18~");
                    break;
                case KEY_F8:
                    serial_puts("\033[19~");
                    break;
                case KEY_F9:
                    serial_puts("\033[20~");
                    break;
                case KEY_F10:
                    serial_puts("\033[21~");
                    break;
                default:
                    serial_putc(ch);
                }
            }

            tight_loop_contents();
        }

        // Clean up and reset
        multicore_reset_core1();
        uart_deinit(uart_port ? uart1 : uart0);
    }
}
1 Like

I’m now chasing down a problem with dropped characters. :frowning:

Do you think it would be possible to go further with the AINSI processing? Because currently, when connected to an interface, we are quite limited: we can’t cancel a command in progress, and editing files (with nano or vi) is impossible (as the screen doesn’t refresh).

Ultimately, if I ever get the time, I’d like to turn Picocalc into a full Linux console, with serial and SSH access.

1 Like

Yes! On my local machine, I have made a good improvement on this end, though it does get messed up sometimes.

vi is mostly working, same with nano, though something is not reseting correctly after the scrolling region is changed. The reset command is my friend right now.

What isn’t helping is the dropping of characters as you can well imagine.

1 Like

I have uploaded v0.2:

  • No more dropped characters (or much much better at not dropping characters)
  • Improved terminal emulation, though not close to perfect. You should no longer need to set the TERM to vt100 from xterm for instance.
  • Added 230400 baud rate
  • Improved start screen

A problem exists with writing to the last column of the last row and a scroll occurring, so setting export LINES=31 helps for this release. Also, changing the scroll region causes problems.

top and htop mostly render correctly.

If there is an app/tool you use and it doesn’t work, please let me know. I am going to be working on full screen editors, such as vi, nano/pico next.


2 Likes

Hi, would a connection via WiFi be possible when using a 2W?

That would be different from a serial terminal and be a Telnet client.

That said I have already started thinking about it.

1 Like

Need some group wisdom here:

Pico 2 & Basic, working ok on the USB C to USB mini cable, Minicom terminal on Ubuntu

The same setup, with additional PS for Pico2, on Pico Calc & Serial Terminal not working.

With above parameters….

Any tip or trick what is wrong ?

The PicoCalc does not work as a computer. It exposes a serial port on the USB cable that you can connect to with minicom, just as the Pico 2 does.

Trying not to get too technically gory, I guess one way to look at it that there is no such thing as a null modem cable for USB.

The only way to connect the two is to use the GPIO pins (on the side of the PicoCalc as shown in the terminal app) and pins 1, 2, 3 on the Pico 2.

2 Likes

Tested as suggested, but…

It seems that this ver. of MMBASIC is not talking to GPIO pins on Pico 2….

1 Like

I do not know how to configure MMBASIC but there are many in this forum that do.

1 Like

Not sure if this is useful for you or anyone else here but I was able to remotely telnet into it using socat:

sudo socat PTY,link=/tmp/webmite,raw,echo=0 TCP:<webmite_ip>:23

sudo minicom -D /tmp/webmite

So that is why i get this:
[ 7275.270056] usb 7-1: new full-speed USB device number 2 using xhci_hcd
[ 7275.429379] usb 7-1: New USB device found, idVendor=1a86, idProduct=7523, bcdDevice= 2.64
[ 7275.429393] usb 7-1: New USB device strings: Mfr=0, Product=2, SerialNumber=0
[ 7275.429399] usb 7-1: Product: USB Serial
[ 7275.504660] usbcore: registered new interface driver usbserial_generic
[ 7275.504686] usbserial: USB Serial support registered for generic
[ 7275.512660] usbcore: registered new interface driver ch341
[ 7275.512699] usbserial: USB Serial support registered for ch341-uart
[ 7275.512719] ch341 7-1:1.0: ch341-uart converter detected
[ 7275.525612] usb 7-1: ch341-uart converter now attached to ttyUSB0
[ 7332.456367] usb 7-1: failed to receive control message: -110
[ 7332.456384] ch341-uart ttyUSB0: failed to read modem status: -110
[ 7341.800654] usb 7-1: failed to receive control message: -110

This is a pico2w, app loaded via uf2loader.