0

I try to read Strings transmitted over USB to the Raspberry Pi Pico using stdio. The strings are supposed to be read within a callback function as soon as characters are available. I don't want to poll the interface for new characters or use a repeating timer.

Because UART0 and UART1 are needed for other purposes and all PIO-State machines are toll used for even more UART Interfaces USB is the only option.

The PICO_SDK provides the function void stdio_set_chars_available_callback(void(*)(void *) fn, void *param) to get notified whenever characters are available to be read.

I have following code for testing purposes. Right now I don't attempt to read Strings just a single character (as an integer) and than echo the integer back to the user. When I can't read a char I can't read a string either.

Edit June 19th 2023

As I state in a Comment, this is not the actual project. The real project is over 3000 lines long and build entirely on the concepts of interrupts and callbacks. Besides UART0 and UART1 I also implemented UART2, UART3, UART4 and UART5 using the PIO-Statemachines. All which are needed for the project. using getchar_timeout_us(0); is sadly not an option it would break the achitecture and lead to problems down the line. Making parsing of the protocol, checking they byte spacing (i.e the time between Bytes) harder.

End Edit

#include <stdio.h>
#include "pico/stdlib.h"

void callback(void *ptr){

    int *i = (int*) ptr;  // cast void pointer back to int pointer
    // read the character which caused to callback (and in the future read the whole string)
    *i = getchar_timeout_us(100); // length of timeout does not affect results
}

int main(){
    stdio_usb_init(); // init usb only. uart0 and uart1 are needed elsewhere
    while(!stdio_usb_connected()); // wait until USB connection
    int i = 0;
    stdio_set_chars_available_callback(callback, (void*)  &i); //register callback

    // main loop
    while(1){
        if(i!=0){
            printf("%i\n",i); //print the integer value of the character we typed
            i = 0; //reset the value
        }
        sleep_ms(1000);
    }
    return 0;
}

The callback itself works I initially tested it by enabling the LED inside the callback. When I typed anything in the terminal the LED turned on. The passing of the void pointer works too. Because the value is changed by the callback.

I presume the issue lies *i = getchar_timeout_us(100);.

The function always returns -1 not the ascii (integer) value of the character I typed. get_char_timeout_us() returns the PICO_ERROR_TIMEOUT macro when a timeout occurs.

The macro PICO_ERROR_TIMEOUT has the value -1. So in other words we timeout.

The timeout is curios, we timeout reading a char within the callback method notifying us that characters are available to read.

The 100 μs in getchar_timeout_us(100); is chosen because it is not much greater than the minimum time of between bytes at a baudrate of 115200. The minimum time would be 86,805 μs. Maybe to fast for a human to type but I'll wirte a python script sending the strings to the Raspberry Pi Pico.

I would expect that reading a character within the callback method notifying me that characters are available to would return the send character and not PICO_ERROR_TIMEOUT.

My initial assumption was that the timeout of 100 μs in getchar_timeout_us(100); was just to short for a human being to type a character. Although there should be at least a single character in buffer because else the callback shouldn't have triggered in the first place. But anyway I tried to increase the timeout at the way to 1,000,000 μs, so 1 second. Still PICO_ERROR_TIMEOUT.

I also tried multiple terminals, usually I use the PySerial module serial.tools.miniterm on Windows. I also tried minicom and screen on Fedora Linux 38 (different computer), no different result.

Getting rid of the timeout all together by using getchar(); but this just leads to that the callback get blocked indefinitely.

It appears to be that the characters I typed just disappear into the void when I enter the callback.

But my assumption is that getchar_timeout_us(100); and getchar(); are just the wrong functions.

What are the correct function to call? Or how do I use the existing function correctly to read the characters in the buffer?

Too exclude any issues I made during compile time, here is the CMakeList.txt:

# Generated Cmake Pico project file

cmake_minimum_required(VERSION 3.13)

set(CMAKE_C_STANDARD 11)
set(CMAKE_CXX_STANDARD 17)

# Initialise pico_sdk from installed location
# (note this can come from environment, CMake cache etc)
set(PICO_SDK_PATH "../../pico-sdk")

set(PICO_BOARD pico CACHE STRING "Board type")

# Pull in Raspberry Pi Pico SDK (must be before project)
include(pico_sdk_import.cmake)

if (PICO_SDK_VERSION_STRING VERSION_LESS "1.4.0")
  message(FATAL_ERROR "Raspberry Pi Pico SDK version 1.4.0 (or later) required. Your version is ${PICO_SDK_VERSION_STRING}")
endif()

project(foo C CXX ASM)

# Initialise the Raspberry Pi Pico SDK
pico_sdk_init()

# Add executable. Default name is the project name, version 0.1

add_executable(foo src/foo.cpp )

target_include_directories(foo
  PUBLIC
    include/
)

pico_set_program_name(foo "foo")
pico_set_program_version(foo "0.1")

pico_enable_stdio_uart(foo 0) # disable stdio over UART we need it
pico_enable_stdio_usb(foo 1)  # enable stdio over USB

# Add the standard library to the build
target_link_libraries(foo
        pico_stdlib)

# Add the standard include files to the build
target_include_directories(foo PRIVATE
  ${CMAKE_CURRENT_LIST_DIR}
  ${CMAKE_CURRENT_LIST_DIR}/.. # for our common lwipopts or any other standard includes, if required
)

pico_add_extra_outputs(foo)

and my terminal output is following.

py -m  serial.tools.miniterm COM14 115200
--- Miniterm on com14  115200,8,N,1 ---
--- Quit: Ctrl+] | Menu: Ctrl+T | Help: Ctrl+T followed by Ctrl+H ---
-1
-1
-1
-1
-1

--- exit ---

I typed Hello (72, 101, 108, 108, 111) but I got (-1, -1, -1, -1, -1)

EDIT June 19th 2023

A barebone Echo program such as this:

#include <stdio.h>
#include "pico/stdlib.h"

int main()
{
    //stdio_init_all();
    stdio_usb_init(); // init usb only. uart0 and uart1 are needed elsewhere
    while(!stdio_usb_connected()); // wait until USB connection
    while(1){
        int i = getchar_timeout_us(100);
        if(i >= 0){
            printf("%i", i);
        }
    }
    return 0;
}

works perfectly fine When I type Hello ich get I (72, 101, 108, 108, 111) as expected.

Making the variable i volatile sadly does not change the output. Also because the vooid poniter I pass is not supposed to be volatile it throws a warning (which is expected). Replacing i with a global volatile int g = 0 (no example) variable either.

#include <stdio.h>
#include "pico/stdlib.h"

void callback(volatile void *ptr){

    volatile int *i = (volatile int*) ptr;  // cast void pointer back to int pointer
    // read the character which caused to callback (and in the future read the whole string)
    *i = getchar_timeout_us(100); // length of timeout does not affect results
}

int main(){
    stdio_usb_init(); // init usb only. uart0 and uart1 are needed elsewhere
    while(!stdio_usb_connected()); // wait until USB connection
    volatile int i = 0;
    stdio_set_chars_available_callback(callback, (volatile void*)  &i); //register callback

    // main loop
    while(1){
        if(i!=0){
            printf("%i\n",i); //print the integer value of the character we typed
            i = 0; //reset the value
        }
        sleep_ms(1000);
    }
    return 0;
}

What I noticed putting gpio_put(PICO_DEFAULT_LED_PIN, 1) after *i = getchar_timeout_us(100); inside the callback will turn on the LED (this is expected).

But putting gpio_put(PICO_DEFAULT_LED_PIN, 0) after sleep_ms(1000); inside the main loop will not turn of the LED, as it would be expected.

End Edit

  • In your example code, there isn't much point to using the callback. How about you just call `getchar_timeout_us(0)` in the main loop? I've successfully read data from USB before by doing that. – David Grayson Jun 16 '23 at 19:24
  • @DavidGrayson. I agree, that there is no point for a callback in the example. The real project has over 3000 Lines of Code using up to 6-UART ports 2 Native and 4 via PIO and is entirely based on callbacks/interrupts for every UART port. I send a protocol and with in-frame defined length. Having `getchar_timeout_us(0)` would make it more difficult to check if all bytes arrived in a timely manner, it wouldn't be a great solution because it could cause framing issues. – theholypumpkin Jun 18 '23 at 06:22
  • Fine, but simplifying the code would help reveal what is working and what isn't. For example, maybe the callback is called in an interrupt and you're not properly dealing with the complexities of interrupts (I don't see a `volatile` or memory barrier). So if we just try not using a callback, we eliminate that as something that can cause problems. – David Grayson Jun 18 '23 at 15:26
  • @DavidGrayson first and foremost thanks for the help. Unfortunately `volatile` did not change the output. I also attempted to replace the passed `volatile int i` which is passed to the callback with a global variable `volatile int g` to removed the uncertainty that this could be the cause of the issue. I too wrote a very simple echo program **without** the callback (see edit of the original post) which works as you expect. It echo the typed characters (or the ascii integer). – theholypumpkin Jun 19 '23 at 08:32

0 Answers0