Trouble getting RX to work on USART's

I’ve been successfully using USART1 for RX/TX and USART2 for TX for a while now. I’m not able to get USART2 RX to work, however. Here is the setup code (with the working USART1 lines commented out). What do I need to change?? Thanks.

    UartHandler         uart;
    UartHandler::Config config;
    config.baudrate = 9600 ;
    //config.periph   = UartHandler::Config::Peripheral::USART_1;
    config.periph   = UartHandler::Config::Peripheral::USART_2;
    config.stopbits      = UartHandler::Config::StopBits::BITS_1;
    config.parity        = UartHandler::Config::Parity::NONE;
    config.mode          = UartHandler::Config::Mode::TX_RX;
    config.wordlength    = UartHandler::Config::WordLength::BITS_8;
    //config.pin_config.rx = {DSY_GPIOB, 7};  // (USART_1 RX) Daisy pin 15
    //config.pin_config.tx = {DSY_GPIOB, 6};  // (USART_1 TX) Daisy pin 14
    config.pin_config.rx = {DSY_GPIOA, 3};  // (USART_2 RX) Daisy pin 23
    config.pin_config.tx = {DSY_GPIOA, 2};  // (USART_2 TX) Daisy pin 35
    
    uart.Init(config);
    uart.StartRx();

Hmmm I don’t see anything wrong at first glance. Could you post more complete code maybe?

It might also be helpful to know the context in terms of hardware. Which devices are communicating, connected pins, circuits, etc.

Btw have you tried in RX mode? Just curious.

1 Like

Thanks, Ben. I’ll post the code at the bottom here. Note that I’ve tried USART1 successfully, but 2, 3, 4 produce no output.

The setup is a WeMos D1 mini (ESP8266) as sender, with the RX/TX pins wired directly to the corresponding pins on the Daisy (RX to TX and vice-versa, of course!). GND on the WeMos is connected to DGND on the Daisy. That’s it! I’ve enclosed a photo to show the setup.

It does work in RX mode. In fact, I have code that sends on USART1, USART2, USART3 simultaneously, and I can read from all 3 ports. If I add UART4, however, as soon as I do an Init() on it, 1, 2, and 3 are no longer “live” :frowning:

Here’s the code:

#include "daisy_seed.h"

using namespace daisy;

DaisySeed seed;

int main(void)
{
    seed.Configure();
    seed.Init();

    seed.StartLog(false);
    System::Delay(10000);
    seed.PrintLine("Starting Read-test");

    UartHandler         uart;
    UartHandler::Config config;
    config.baudrate = 9600 ;
    //config.periph   = UartHandler::Config::Peripheral::USART_1;
    config.periph   = UartHandler::Config::Peripheral::USART_2;
    //config.periph        = UartHandler::Config::Peripheral::USART_3;
    //config.periph        = UartHandler::Config::Peripheral::UART_4;
    config.stopbits      = UartHandler::Config::StopBits::BITS_1;
    config.parity        = UartHandler::Config::Parity::NONE;
    config.mode          = UartHandler::Config::Mode::TX_RX;
    config.wordlength    = UartHandler::Config::WordLength::BITS_8;
    //config.pin_config.rx = {DSY_GPIOB, 7};  // (USART_1 RX) Daisy pin 15
    //config.pin_config.tx = {DSY_GPIOB, 6};  // (USART_1 TX) Daisy pin 14
    config.pin_config.rx = {DSY_GPIOA, 3};    // (USART_2 RX) Daisy pin 23
    config.pin_config.tx = {DSY_GPIOA, 2};    // (USART_2 TX) Daisy pin 35
    //config.pin_config.rx = {DSY_GPIOC, 11}; // (USART_3 RX) Daisy pin 1
    //config.pin_config.tx = {DSY_GPIOC, 10}; // (USART_3 TX) Daisy pin 2
    //config.pin_config.rx = {DSY_GPIOA, 1};  // (UART_4 RX)  Daisy pin 12  (or 31?   or 2? )
    //config.pin_config.tx = {DSY_GPIOA, 0};  // (UART_4 TX)  Daisy pin 13  (or 32?   or 3? )
    
    uart.Init(config);
    uart.StartRx();

    // Loop forever
    for(;;)
    {
        while(uart.Readable())
        {
            uint8_t singleByte = uart.PopRx();
            seed.Print("%c", singleByte);
        }

        // In case of UART Error, (particularly
        //  overrun error), UART disables itself.
        // Flush the buff, and restart.
        if(!uart.RxActive())
        {
            seed.PrintLine("UART reset");
            uart.FlushRx();
            uart.StartRx();
        }
    }
}

The output when USART1 lines are activated is:

Daisy is online
===============
Starting Read-test
test
test
test

When USART2, USART3, or UART4 lines are activated, the output is the same, except that no “test” lines appear.

BTW, The ambiguous comments following the UART4 pins in the code are due to my confusion in interpreting the Daisy seed pinout (below). I’d love some guidance as to how to read this data sheet! Thanks.

For completeness, here is the sender code I’m running on the WeMos:


void setup() {
  Serial.begin(9600);
}

unsigned long cachedTime = millis();

void loop() {
  unsigned long currentTime = millis();
  if (currentTime - cachedTime > 2000) {
    Serial.println("test");
    Serial.flush();
    cachedTime = currentTime;
  }
}

The pin connections for USART1 (working) are:

WeMos RX  ->  Daisy pin 14
WeMos TX  ->  Daisy pin 15

Pin connections for USART2 (not working) are:

WeMos RX  ->  Daisy pin 35
WeMos TX  ->  Daisy pin 23

BTW, I just double-checked connections in the photo I sent; it was wired backwards, reversing the connection to be correct made no difference.

Pin connections for USART3 (not working) are:

WeMos RX  ->  Daisy pin 2
WeMos TX  ->  Daisy pin 1

This is the last hurdle I need to overcome to get my sequencer/synth working and into a gallery show on August 26! @ben_serge , could you or someone else at Electro-Smith get me back on track? Thanks!

The csv file can be a bit confusing. I can walk you through how to use it correctly (it’s important to note the “Daisy Pin” on there is the inner number on the DaisyPinout diagram). I don’t think that’s your issue though.

I can recommend this code snippet rather than the receive code you have now.

uint8_t singleByte;
uart.PollReceive(&singleByte, 1, 10);
seed.Print(%c, singleByte);

If I recall correctly USART1 is the only periph setup to use the other method (which I believe relies on DMA). That may be incorrect, but the code snippet I posted is what I used to originally test all of the UART periphs. There’s a uart branch on DaisyExamples with some throwaway testing code you can look at. uart test.
Let me know if that works!

1 Like

@Elby Did this fix work for you? If there’s any more issues I’d like to know so we can work together to sort them out.

Thanks very much for asking, Ben! The answer is yes and no :laughing: I was able to get USART1 - 3 working with the change you suggested (not 4, but I don’t need it). However, when I tried to integrate with my full system, I ran into snags. A couple of days of tail-chasing led me to discover that PollReceive doesn’t play well with i2c. Below is a test program used with the setup I described in an earlier post (with wiring change to pins 14/15 on the Daisy, but using the same sending code). The program stops receiving/printing bytes after 1 or 2 are received. Note that I needed to call i2c.ReceiveBlocking 4 or more times for failure, and that the same problem occurs if I call i2c.TransmitBlocking instead. BTW, I tried this at 9600 baud; no difference. Also, no i2c device required in the test setup, although my full system, where I first encountered this snag, does have a set of i2c devices which my full code successfully accesses.

If there’s an easy fix for this, please let me know as soon as possible. If not, since time is tight, I’ll start reorganizing my code to change the ratio of UART to i2c calls :frowning: Thanks!

#include "daisy_seed.h"
#include <per/i2c.h>

using namespace daisy;
DaisySeed seed;

int main(void)
{
    seed.Configure();
    seed.Init();
    seed.StartLog(false);
    System::Delay(10000);  // allow time to open serial monitor in vscode

    seed.PrintLine("Starting Read-UART-i2c-test");

    UartHandler         uart;
    UartHandler::Config uart_config;
    uart_config.baudrate      = 115200;
    uart_config.periph        = UartHandler::Config::Peripheral::USART_1;
    uart_config.stopbits      = UartHandler::Config::StopBits::BITS_2;
    uart_config.parity        = UartHandler::Config::Parity::NONE;
    uart_config.mode          = UartHandler::Config::Mode::TX_RX;
    uart_config.wordlength    = UartHandler::Config::WordLength::BITS_8;
    uart_config.pin_config.rx = {DSY_GPIOB, 7}; // (USART_1 RX) Daisy pin 15
    uart_config.pin_config.tx = {DSY_GPIOB, 6}; // (USART_1 TX) Daisy pin 14

    uart.Init(uart_config);

    daisy::I2CHandle  i2c;
    I2CHandle::Config i2c_config = {I2CHandle::Config::Peripheral::I2C_1,
                                    {{DSY_GPIOB, 8},  // SCL
                                     {DSY_GPIOB, 9}}, // SDA
                                    I2CHandle::Config::Speed::I2C_1MHZ};
    i2c.Init(i2c_config);

    for(;;)
    {
        uint8_t singleByte;
        int     ret = uart.PollReceive(&singleByte, 1, 0);
        if(ret == 0)
            seed.PrintLine("%d, %c", singleByte, singleByte);

        // NOTE: Fails when loop index >= 4, is fine for <4
        for(uint8_t numI2c = 0; numI2c < 4; ++numI2c)
        {
            uint8_t  addr   = 0x27;
            uint8_t  value   = 0;
            uint32_t timeout = 1;
            i2c.ReceiveBlocking(addr, &value, 1, timeout);
        }
    }
}

I think the i2c is a red herring! I replaced the loop containing the i2c call with the single statement:

  System::Delay(1);

This produces the same problem.

One other thing I thought I’d mention, Ben. In the example you gave me, there was a timeout of 10ms on PollReceive. When I tried that, the audio became severely distorted. At 1ms the distortion was less, but still noticeable. The only way I found to get clean audio was to make the routine non-blocking by setting the timeout to 0. I wonder if this is the problem??

The i2c must be taking too long to finish. Maybe try putting the uart in the callback.
That may help, depending on what the rest of your code looks like.

I’ve tried that, and that’s not working either. What do you mean “taking too long to finish”? I understand how that affects the audio callback, but why would a delay cause uart RX to fail?

Here’s my current test code, Ben. If I comment out the delay it works. With the delay, it prints out a single ‘t’ and that’s it. Is this expected behavior? I’m still hoping I’m doing something obviously wrong. Please let me know; my attempts to reorganize my code have crashed and burned - everything I try runs into the same problem - and I’m stuck.

#include "daisy_seed.h"

using namespace daisy;
DaisySeed seed;

int main(void)
{
    seed.Configure();
    seed.Init();
    seed.StartLog(false);
    System::Delay(10000);  // allow time to open serial monitor in vscode

    seed.PrintLine("Starting Read-UART-delay-test");

    UartHandler         uart;
    UartHandler::Config uart_config;
    uart_config.baudrate      = 115200;
    uart_config.periph        = UartHandler::Config::Peripheral::USART_1;
    uart_config.stopbits      = UartHandler::Config::StopBits::BITS_2;
    uart_config.parity        = UartHandler::Config::Parity::NONE;
    uart_config.mode          = UartHandler::Config::Mode::TX_RX;
    uart_config.wordlength    = UartHandler::Config::WordLength::BITS_8;
    uart_config.pin_config.rx = {DSY_GPIOB, 7}; // (USART_1 RX) Daisy pin 15
    uart_config.pin_config.tx = {DSY_GPIOB, 6}; // (USART_1 TX) Daisy pin 14

    uart.Init(uart_config);

    for(;;)
    {
        uint8_t singleByte;
        int     ret = uart.PollReceive(&singleByte, 1, 0);
        if(ret == 0) {
            seed.PrintLine("%c", singleByte);
        }

        System::Delay(1);
    }
}

Sorry about that, you’re correct the callback won’t really help anything. I was thinking about TX.

As I understand it Polling RX simply blocks for up to the time you allow it and reads whatever packets come in while doing so. When you don’t have the delay, the amount of time you wait for messages each function call is short, but you’re doing it over and over again really fast, so you don’t miss much.

If you add the delay however, you’re now only reading for a tiny amount of time, and then idling between. I’m not sure why a longer delay causes audio crackle. Are you sending audio over I2C? If so it would benefit from being in the callback. Otherwise I’d have to see your code to make a better guess.

What you’re describing, Ben, sounds like synchronous communication. I’ve been assuming that this was an asynchronous receive (with buffering). In any case, the behavior I’m seeing is not consistent with either synchronous or asynchronous.

I have a new test case running on the sender (WeMos), below, which rapid-fires bytes. I also changed the timeout in the Daisy routine to 10ms. If, as you explain, PollReceive is returning bytes that are received between when it’s called and when it times out, I should see a whole sequence of bytes printed. I’m still seeing only a single byte. When I remove the System::Delay(1), I’m getting a whole sequence of “test” printed, just as expected. This is looking like a bug to me. I wonder if you could try the code I’ve posted with whatever rig you use for testing serial ports and see if you can replicate this? Thanks!

The crackle I was talking about comes from the timeout in PollReceive. I looked into the code and see a comment from Stephen Hensley that says the routine is blocking. If it is, I would expect that any non-zero value for timeout would starve the audio callback, which is what I seem to have observed. There must be something that I’m not understanding here.

void setup() {
  Serial.begin(115200);
}

void loop() {
    Serial.println("test");
    Serial.flush();
}

In my last post, the last paragraph was horribly confusing; my apologies. Here’s what I actually meant. My audio callback routine simply transfers bytes out of a buffer that I manage. This is based on a suggestion Stephen Hensley gave me a number of months back, and has been reinforced by some others on the forum. The buffer is filled by a routine I call play(). One of the challenges in structuring my code has been to ensure that play() gets called often enough to prevent buffer underflows. That is the problem I’m talking about in my mis-languaged phrase “starve the audio callback”. If I call an I/O routine which blocks for 1ms (let alone 10!) I’m already in trouble. I need to either have non-blocking I/O, or a different approach to “feeding the audio”. I’m open to suggestions here.

OK I’ve been testing a bit this morning.

I can confirm two things.

  1. PollReceive is in fact blocking. So a long timeout will potentially starve your audio callback.
  2. PollReceive stops working when a 1 ms delay is introduced to the loop.

The blocking receive will end if either you hit the timeout OR you fill your buffer. If you are receiving bits at 9600 bits/s and you are only filling one byte that means you’ll only be blocking for about 1ms or so per call plus whatever overhead the function call includes.

If I interpret your code correctly, it configures UART at 115200 baud, but then its receive rate becomes 1000 baud. You’re adding 1 ms delay after every byte, so there’s no way to transfer more than 1000 bytes / seconds. I would guess that you’re getting UART overrun error after that. LibDaisy deactivates UART in this case

Thanks for looking into this and the feedback, guys! Your explanation makes sense, @antisvin , BUT, please note that my initial test case (posted in this thread a while back), sends a half-dozen characters every two seconds. And that fails the same way. So I think there must be something else going on.

I’m sorry, Ben, I’m not following the final paragraph of your last post. Is there a problem that you’re investigating, or is there “user error” that I need to understand? Thanks.