Posted on Leave a comment

Getting getchar() to Get Characters

22 February 2025

Yesterday I was poking around inside the GPIO ports of the CH32V003 chip, and “printing” out the results to the “console”. The default applications created by the MounRiver Studio 2 “new project wizard” sets up a nice mechanism whereby us old-school programmers can use the printf() function from the standard I/O library, stdio. I tried, unsuccessfully to use the corresponding getchar() function to read a single character back from the console, but it flat didn’t work at all.

I totally guessed yesterday, albeit correctly, that this was due to a lack of a lower-level function to redirect console input from the USART. Today, I did some research and discovered that it takes that lower-level function and an additional incantation to get the thing to work as I wish it to work.

That lower-level function is called _read(), with a leading underscore, and it is expected to have the following prototype:

int _read(int file, char *result, size_t len);

Since I’m not using a bunch of files and other niceties, I can just ignore the first parameter. If, in the future, I wanted to support “getting characters” from other sources, I could go back and match up the file number specified with the various sources. Today I skip it. If you like to compile with lots of error checking, you will most likely get an “unused parameter” error, so you might need to flag it as used. The default setup provided by the MRS2 project is quite forgiving in this regard.

The “result” parameter is a pointer to an array where we will store the incoming number of bytes requested. The final parameter indicates how many characters are wanted by the caller.

The little “helper function” needs to send back a return code that either represents the number of characers actually read or -1 in the case of an error.

Here’s what mine ended up looking like:

int _read(int file, char *result, size_t len) { // support stdio.h getchar(), etc.
    
    int return_code = 0; // code we will return
    size_t bytes_to_return = len; // capture number of requested bytes to return

    if(len == 0) return 0; // that was easy

    while(bytes_to_return) {

        if((USART1->STATR & USART_STATR_RXNE) == USART_STATR_RXNE) {

            // there is a character ready to be read from the USART

            *result = USART1->DATAR; // read character from USART data register and store it in the requested buffer
            result++; // advance buffer address
            bytes_to_return--; // decrement requested number of bytes requested
            return_code++; // count how many bytes have been returned so far

        } else {
            // probably should add some sort of time-out mechanism here
        }
    }

    return return_code; // number of bytes returned;  no error states to report as yet
}

You’ll note a special case at the beginning where if the caller asks for exactly zero bytes, we throw up our hands in joy and say, “Your wish is granted!” and just return. After all, we did everything that was asked.

Next the code goes into a loop to retrieve the requested number of characters, one at a time. Within the loop, we just look at the status bit RXNE (receive register not empty) in the USART STATR status register. If it is set, we read the character from the USART data register DATAR and stuff it into the receive buffer. If it is not set, we effectively wait forever until something comes in.

The number of bytes to return is tracked as is the return_code, which represents the number of bytes actually returned and is kept in the variable named return_code. The loop eventually exits and the return_code (number of bytes returned, in this case) is returned to the caller.

This code by itself is not enough to make the getchar() function get any characters; well, at least not in the way you would expect it to. Since all these standard I/O functions are inherited from Linux/UNIX and were originally written for much larger systems, the default behavior of the function is to gather up a bunch of characters and buffer them before actually returning to the caller. This makes sense when your program is being run alongside many other programs as well as an operating system. On the scale of our little CH32V chip, maybe not so much.

So here is the incantation I promised you:

setvbuf(stdin, NULL, _IONBF, 0); // disable buffering on stdin

As you see explained in my little code comment, this otherwise cryptic and mysterious function disables buffering on ‘stdin’, the default input device for the imaginary console we are using.

Now we can use the very handy getchar() function to wait for and get a character from the USART, which is in turn connected via a system of tubes and hoses to our host development system, somehow.

Future improvements to the very basic _read() function demonstrated here would include both a time-out mechanism and the ability to differentiate between the various possible sources for input to our little chip. Perhaps you can think of some more improvements, as well. Please do share them in the comments.

Leave a Reply