8 years, 3 months ago.

BufferedSerial overlapped IO not working

Hello,

I need to find a way to send some UART characters while allowing my main() loop to continue to run. I am using an STM32F446. The async methods in Serial are not implemented for this processor so I am looking for alternatives. I am currently trying BufferedSerial. Unfortunately, it seems each call to BufferedSerial::write() blocks until the entire string has been transmitted. What I had hoped for is the call would just prime a transmit interrupt chain and return almost immediately. What I'm looking for is a way for my program to run between transmit interrupts.

Here is my main program:

#include "mbed.h"
#include "BufferedSerial.h"

BufferedSerial pc(USBTX, USBRX);
DigitalOut GPIO_A(PB_8);

volatile uint32_t dont_optimize_me_out;
char my_string[] = "hello world\n";

int main()
{
    pc.baud(1843200);

    while (true) {

        // do some CPU work
        for (uint32_t i = 0; i < 1000; i++) {
            dont_optimize_me_out = i;
            GPIO_A = !GPIO_A;
        }

        // send some UART string
        pc.write (my_string, sizeof(my_string)-1);
    }
}

In BufferedSerial.cpp I added another DigitalOut to PB_9 called GPIO_B. At the end of BufferedSerial::txIrq(void) I added "GPIO_B = !GPIO_B". On the 'scope, I can see all 12 interrupts happening on GPIO_B, one every 8 us or so as expected for this baud rate. Meanwhile during this period there is no change on GPIO_A. It's as if the interrupt is never being cleared until the last char is transmitted. After the last char is sent, then GPIO_A toggles 1000 times and it all repeats.

What I was hoping to see is some main() loop activity in between the serial interrupts.

I would appreciate comments on any of the following:

1) how I can make BufferedSerial work as I require 2) pointers to other solutions 3) future roadmap for Serial::write() async implemented for STM32F processors

Thank you for your time.

Elwood Downey Steward Observatory University of Arizona

1 Answer

8 years, 3 months ago.

I have not specifically tried to use BufferedSerial to do what you're trying to do. But I do something similar with RawSerial. Can you transfer your output data to an output buffer (use sprintf...). Then with an interrupt handler using the .attach method you can service the interrupt. Perhaps even fold in a CircularBuffer to handle the data stream...

Also, you can set the optimization level down to 0 in your online compiler under "Compile Macros".. The volatile works as well of course.

Hi Bill,

Thanks for taking the time to answer. With your idea I have tried the following test program:

#include "mbed.h"

Serial pc(USBTX, USBRX);
DigitalOut GPIO_A(PB_8);
DigitalOut GPIO_B(PB_9);

volatile uint32_t dont_optimize_me_out;
char my_string[] = "hello world\n";
const int n_my_string = sizeof(my_string)-1;
int ms_i;

void txIRQ()
{
        pc.putc(my_string[ms_i]);
        if (++ms_i == n_my_string)
            ms_i = 0;
        GPIO_B = !GPIO_B;
}

int main()
{
    pc.baud(1843200);

    pc.attach(txIRQ, RawSerial::TxIrq);
    pc.putc (my_string[ms_i++]);

    while (true) {

        // do some CPU work
        for (uint32_t i = 0; i < 1000; i++) {
            dont_optimize_me_out = i;
            GPIO_A = !GPIO_A;
        }
    }
}

Now the main loop never runs at all because GPIO_A never toggles. I see the message on the serial port and GPIO_B toggles every 8 us as expected. Evidently the processor never returns from the interrupts.

So again: how can I do some processing while each character is being transmitted?

Thanks again for any help.

posted by Elwood Downey 17 Aug 2016

Oh so close :) I did a slight adaptation of your code on my F446 Nucleo and it's working great. I bet you can get this cleaned up and groomed for your application. See if you can make it work with a circular buffer and timers for the blinker instead of a variable countdown.

#include "mbed.h"

DigitalOut myled(LED1);
#include "mbed.h"
 
RawSerial pc(USBTX, USBRX);
 
volatile uint32_t dont_optimize_me_out;
char my_string[] = "hello world\n";
const int n_my_string = sizeof(my_string)-1;
int ms_i=0;
 
void txIRQ()
{
        while(pc.writeable() && ms_i < n_my_string)pc.putc(my_string[ms_i++]);
}
 
int main()
{
    pc.baud(9600);
    myled = 1;
    pc.printf("ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789:%d:",n_my_string);
    wait(5);
    pc.printf("GO BABY GO!");
    pc.attach(txIRQ, RawSerial::TxIrq);
 
    while (true) {
 
        // do some CPU work
        myled = !myled;
        dont_optimize_me_out = 10000000;
        while(dont_optimize_me_out)dont_optimize_me_out--;
        if (ms_i == n_my_string){
            ms_i = 0;
            pc.putc('*');
        }

    }
}
posted by Bill Bellis 17 Aug 2016

Hi Bill,

Your example does result in interleaved "hello world" and "*" messages, suggesting things are overlapping. But if I put in some GPIOs and look at things on a 'scope, the main loop is still not running while characters are being transmitted. Main() is only running during the very brief time between loading the characters into the UART.

It looks to me like putc() blocks until a character has been fully transmitted. What I was hoping is it would only block until the character has been accepted by the UART. This wastes almost all the time that could be available for other work. I need to do work in main() during the time the UART is doing the transmitting.

Looks like mbed is too simplistic here, I need to get closer to the metal.

Thanks much for your time.

Elwood

posted by Elwood Downey 22 Aug 2016