Search code examples
craspberry-piembeddedmicrocontrollerraspberry-pi-pico

Weirdness using the raspberry pi pico uart with the c sdk


I am extremely disconcerted.

I'm making a remote controlled machine using a pi pico to drive the motors and read some sensors, and a raspberry pi 4 to send commands to the pi pico via serial and host the web interface.

The following code seems to work... but... If I remove the if with uart_is_writable coding and its content it doesn't work. Does anyone have any idea why?

#include <stdlib.h>
#include <string.h>

#include "pico/stdlib.h"
#include "hardware/uart.h"
#include "hardware/irq.h"

//DEFINES
#define UART_ID uart0
#define BAUD_RATE 19200
#define DATA_BITS 8
#define STOP_BITS 1
#define PARITY    UART_PARITY_NONE
#define UART_TX_PIN 0
#define UART_RX_PIN 1
#define LED_PIN PICO_DEFAULT_LED_PIN

static int chars_rxed = 0;

volatile char uCommand[32] = {0, 0};

void on_uart_rx(void) {
   char tmp_string[] = {0, 0};
   new_command = true;
   while (uart_is_readable(UART_ID)) {
       uint8_t ch = uart_getc(UART_ID);
       tmp_string[0] = ch;
       strcat(uCommand, tmp_string);
       if(uart_is_writable(UART_ID)){
         uart_putc(UART_ID, '-');
         uart_puts(UART_ID, uCommand);
         uart_putc(UART_ID, '-');
       }
       chars_rxed++;
   }
}

int main(){

 uart_init(UART_ID, BAUD_RATE);

 gpio_set_function(UART_TX_PIN, GPIO_FUNC_UART);
 gpio_set_function(UART_RX_PIN, GPIO_FUNC_UART);

 uart_set_hw_flow(UART_ID, false, false);

 uart_set_format(UART_ID, DATA_BITS, STOP_BITS, PARITY);

 uart_set_fifo_enabled(UART_ID, false);

 int UART_IRQ = UART_ID == uart0 ? UART0_IRQ : UART1_IRQ;

 irq_set_exclusive_handler(UART_IRQ, on_uart_rx);
 irq_set_enabled(UART_IRQ, true);

 uart_set_irq_enables(UART_ID, true, false);

 uart_puts(UART_ID, "\nOK\n");

   while (1){
       tight_loop_contents();
       if(uCommand[0] != 0){
         uart_putc(UART_ID, '/');
         uart_puts(UART_ID, uCommand);
         memset(uCommand, 0, sizeof(uCommand));
       }
     }

}

Solution

  • The example code you linked to is for a simple tty/echo implementation.

    You'll need to tweak it for your use case.

    Because Tx interrupts are disabled, all output to the transmitter has to be polled I/O. Also, the FIFOs in the uart are disabled, so only single char I/O is used.

    Hence, the uart_is_writable to check whether a char can be transmitted.


    The linked code ...

    echos back the received char in the Rx ISR. So, it needs to call the above function. Note that if Tx is not ready (i.e. full), the char is not echoed and is dropped.

    I don't know whether uart_putc and uart_puts check for ready-to-transmit in this manner.

    So, I'll assume that they do not. This means that if you call uart_putc/uart_puts and the Tx is full, the current/pending char in the uart may get trashed/corrupted.

    So, uart_is_writable should be called for any/each char to be sent.

    Or ... uart_putc/uart_puts do check and will block until space is available in the uart Tx fifo. For you use case, such blocking is not desirable.


    What you want ...

    Side note: I have done similar [product/commercial grade] programming on an RPi for motor control via a uart, so some of this is from my own experience.

    For your use case, you do not want to echo the received char. You want to append it to a receive buffer.

    To implement this, you probably want to use ring queues: one for received chars and one for chars to be transmitted.

    I assume you have [or will have] devised some sort of simple packet protocol to send/receive commands. The Rpi sends commands that are (e.g.):

    1. Set motor speed
    2. Get current sensor data

    The other side should respond to these commands and execute the desired action or return the desired data.

    Both processors probably need to have similar service loops and ISRs.

    The Rx ISR just checks for space available in the Rx ring queue. If space is available, it gets a char from the uart and enqueues it. If no Rx char is available in the uart, the ISR may exit.

    The base level code service loop should:

    1. Check if the uart Tx can accept another character (via uart_is_writable) and, if so, it can dequeue a char from the Tx ring queue [if available] and send it (via uart_putc). It can loop on this to keep the uart transmitter busy.

    2. Check to see if enough chars are received to form a packet/message from the other side. If there is such a packet, it can service the "request" contained in it [dequeueing the chars to make more space in the Rx ring queue].

    3. If the base level needs to send a message, it should enqueue it to the Tx ring queue. It will be sent [eventually] in the prior step.


    Some additional thoughts ...

    The linked code only enables Rx interrupts and runs the Tx in polled mode. This may be enough. But, for maximum throughput and lowest latency, you may want to make the Tx interrupt driven as well.

    You may also wish the enable the FIFOs in the uart so you can queue up multiple characters. This can reduce the number of calls to the ISR and provide better throughput/latency because the service loop doesn't have to poll so often.