4
\$\begingroup\$

I am sending a small 16-Bit RGB BMP image file from my Linux PC via USART (with a loosely wired USB to serial converter) to an AVR ATmega MCU sitting on a breadboard, running at 8 MHz with the internal oscillator. It is working, but only very slow.

In the controller in the main loop, I am checking if data was received:

if (bit_is_set(UCSR0A, RXC0)) {
    char data = UDR0;
    handle(data);
}

For now in handle(data), I'm just counting the bytes received to be as inexpensive as possible but still being able to know if at least the correct number of bytes was received. The main loop does nothing else.

USART is set to BAUD = 38400 and initialized like this:

void initUSART(void) {
    UBRR0H = UBRRH_VALUE;
    UBRR0L = UBRRL_VALUE;

    UCSR0B = (1 << TXEN0) | (1 << RXEN0);
    UCSR0C = (1 << UCSZ01) | (1 << UCSZ00);
}

So I set GTKTerm to 38400-8-N-1 and send the .bmp as raw file. This fails however, only about 50 of the total 650 bytes are sent and then it just stops.

The exact same happens if I just cat small.bmp > /dev/ttyUSB0

So I have come up with this super clumsy way of slowly sending byte by byte from the console:

count=0; while([ $count -lt 650 ]); do byte=$(xxd -p -l1 -s $count small.bmp); echo -en "\x$byte" > /dev/ttyUSB0; sleep 0.05; ((count=$count+1)); done

This works, but is obviously extremely slow, and if I reduce the sleep to below 0.05 seconds, transfer is unreliable.

Some info about the PC's USB:

Bus 002 Device 107: ID 10c4:ea60 Silicon Labs CP210x UART Bridge
Bus 002 Device 095: ID 03eb:2104 Atmel Corp. AVR ISP mkII
Bus 002 Device 002: ID 8087:0024 Intel Corp. Integrated Rate Matching Hub
Bus 002 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hub
Bus 001 Device 060: ID 0424:2514 Microchip Technology, Inc. (formerly SMSC) USB 2.0 Hub
Bus 001 Device 002: ID 8087:0024 Intel Corp. Integrated Rate Matching Hub
Bus 001 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hub

What am I missing?

Update: It seems that printing some debugging info back to the PC while sending data to the controller can also break communication.

\$\endgroup\$
14
  • \$\begingroup\$ The problem is likely not in the code you present, but in the one you hide (like that handle function). Sounds like you have an issue with timing. Maybe your handling is too slow for example. Or you have some frequent interrupt choking the main loop. Or something else \$\endgroup\$
    – Eugene Sh.
    Commented Nov 23, 2023 at 19:20
  • 1
    \$\begingroup\$ You are missing info about your PC serial port. It clearly is an USB serial port so already a red flag. What does dmesg or lsusb say about it? Do you know the chipset? Also, if you use inaccurate clocks on AVR it can't receive correctly. Are you having internal RC oscillator or external crystal, and what is the clock speed? \$\endgroup\$
    – Justme
    Commented Nov 23, 2023 at 19:21
  • 2
    \$\begingroup\$ only about 50 of the total 650 bytes are sent and then it just stops - have you checked if these 50 bytes are the actual 50 bytes of the source data? I would imagine that you are missing every few bytes instead based on what you are describing.. \$\endgroup\$
    – Eugene Sh.
    Commented Nov 23, 2023 at 19:31
  • 1
    \$\begingroup\$ I'm somewhat confused as to how sending the bytes individually with a relatively long delay in between could fix the issue with the timing of the internal oscillator being off... I suppose in theory, with bad enough timing, the receiver could still be sampling the end of the previous byte when the sender started with the next one, so that an extra stop bit could help. But you'd still likely have issues with getting the last data bit right anyway, and 0.05 seconds is 1920 bit times at 38400 b/s, far larger scale than a single byte. \$\endgroup\$
    – ilkkachu
    Commented Nov 25, 2023 at 18:22
  • 1
    \$\begingroup\$ @ilkkachu The clock error isn't large enough to slip or gain a bit in a single character. So leaving a gap between characters allows the timing to reset between each character. Sending characters continuously with no gap between a stop bit and the following start bit allows the timing error to accumulate until a bit is lost or gained, corrupting the transmission. \$\endgroup\$
    – Graham Nye
    Commented Nov 26, 2023 at 1:37

2 Answers 2

10
\$\begingroup\$

an AVR ATmega MCU sitting on a breadboard, running at 8 MHz with the internal oscillator.

This is likely to be your problem. This Atmel application note (source) suggests that an uncalibrated internal clock can have a tolerance of as much as ±10% (depending on the model) which is too loose for reliable serial communications.

You can calibrate the internal oscillator but temperature variations can still make it unsuitable for serial comms (see fig 1-2 of the app note).

You can verify if this is the problem by looking for framing errors from your ATmega USART.

You would be better off using an external crystal. This earlier question might help.

\$\endgroup\$
6
  • 1
    \$\begingroup\$ It is pure magic. Just added a 16 MHz crystal and two caps, and now it races. Before, with all the throttling, writing a 17418 byte image took 990 seconds. Now it takes less than 5 @38.4k Baud. Slight improvement. \$\endgroup\$ Commented Nov 23, 2023 at 23:54
  • \$\begingroup\$ The clever question (thanks to Claude Shannon!) is whether lowering the baud rate would eventually allow finishing the task faster ;-) \$\endgroup\$
    – U. Windl
    Commented Nov 24, 2023 at 9:13
  • 1
    \$\begingroup\$ @U.Windl This is not a problem of data rate vs S/N and bandwidth as discussed by Shannon. It's a framing error because the clock rate is wrong. Instead of each bit being sampled precisely once an occasional bit is either sampled twice or missed out giving communication errors. Lowering the baud rate won't prevent this. \$\endgroup\$
    – Graham Nye
    Commented Nov 24, 2023 at 11:31
  • 2
    \$\begingroup\$ @U.Windl Yes. All baud rates will have the same error. What's likely to happen is that the framing error shows up after the same number of bits so slowing the baud rate increases the time between framing errors. \$\endgroup\$
    – Graham Nye
    Commented Nov 24, 2023 at 11:44
  • 1
    \$\begingroup\$ @U.Windl If you calculate baud rates such as 1200 or 2400 based on 8 MHz clock, then if the 8 MHz clock is 10% too slow, also the 1200 and 2400 baud rates are 10% slow. The point about high baud rates is different, because with 8 MHz clock, it is impossible to get 115200 baud anyway. \$\endgroup\$
    – Justme
    Commented Nov 24, 2023 at 12:59
6
\$\begingroup\$

So you are using the internal oscillator.

This is most likely the issue.

You don't say which AVR you are using, but generally, internal oscillators on AVRs are not accurate enough for UART.

At least not without fine-tuning the oscillator frequency manually.

The oscillator frequency needs to be within 2% tolerance to work and preferably much less.

For a generic AVR like Mega328P, the factory calibration is done at 3V and 25°C and only to accuracy of within 10% tolerance.

So the expectation is that internal oscillator should not be used for UART, or at least not without user calibration. It simply might not work at all with factory calibration.

One way to figure out a suitable calibration value is to write a for loop going through all calibration register values and printing out what the value is. Smallest and largest values will not work correctly and don't print properly. For the values that do work properly, pick a value from the middle of the range that works.

\$\endgroup\$
1
  • \$\begingroup\$ I'd of course accept your answer too, if it would be possible to accept more than one answer. \$\endgroup\$ Commented Nov 23, 2023 at 23:58

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.