I'm using the below code for UART transmission, limited but super small which is great on ATtiny processors where you only need to transmit some values.
The code works almost perfectly, except for one weird behaviour: it does not transmit zeros properly. I am testing by receiving the values on a Nano with SoftwareSerial, then printing the values to the serial monitor.
When I send a series of random values (say 20, 30, 40, 50
) they come through correct every time.
When I send a series of four zeros (0, 0, 0, 0
), they come through as four zeros less than half the time, the rest of the time I get to see 0, 0, 4
or 0, 0, 8
or 0, 0, 2
. It seems that somewhere the timing is thrown off, and the start bit of the last byte is seen as a bti in the previous byte. The values I see are always numbers with a single bit set (so 1, 2, 4, 8
). Not even always the last bit (or the value would always be 1), commonly I see 4, 8, and even 16.
So either a bunch of bits are skipped when sending, or the receiving side waits too long.
However, if a non-zero value follows the series of zeros, that value always comes through fine as well. E.g. I transmit the series 0, 0, 0, 0, 42
the 42 always comes through correctly, even if one of the zeros gets mangled. Received would be e.g. 0, 0, 4, 42
.
Also this behaviour only ever seems to affect the last two zeros of the sequence, and not every time. Sometimes it's all coming through just fine.
Example: this code transmits the values 10, 0, 0, 0, 0, 42
.
void setup() {
UART_init();
}
void loop() {
uint8_t data[6] = {10, 0, 0, 0, 0, 42};
for (uint8_t i = 0; i < 6; i++) {
UART_tx(data[i]);
}
UART_flush();
delay(1000);
}
/* Software ("bit-bang") UART Transmitter (8 data bits, 1 stop bit, no parity)
https://marcelmg.github.io/software_uart/
https://github.com/MarcelMG/AVR8_BitBang_UART_TX
*/
// The millis timer is implemented in https://github.com/SpenceKonde/ATTinyCore/blob/master/avr/cores/tiny/wiring.c
// It uses the overflow vector of timer0, prescaler: f/64
//
// This code uses OCRA0 and the COMPA interrupt of timer0, and disables PWM on pin 7 (PA7) and pin 8 (PB2).
//
// Adapted for use with Arduino / ATTinyCore.
//
// This implementation is Tx only, uses Timer0 COMPA interrupt, fixed 9600 baud rate.
// The internal buffer is 1 byte, if more than 1 byte is sent the code blocks until all but one byte
// has been transmitted completely.
//
// Set compare value to 104.1667µs to achieve a 9600 baud rate. TIMER0 is set to clk/64 by the millis() timer.
//
// Supported frequencies:
// 8 MHz : TIMER = 8M * 104.1667µ / 64 = 13 (13.02, as good as exact)
// 11.0592 MHz : TIMER = 11.0592M * 104.1667µ / 64 = 18 (exact)
// 12 MHz : TIMER = 12M * 104.1667µ / 64 = 20 (19.53, -2.35% - may not work reliably)
// 16 MHz : TIMER = 16M * 104.1667µ / 64 = 26 (26.04, as good as exact)
// 18.432 MHz : TIMER = 18.432M * 104.1667µ / 64 = 30 (exact)
// 20 MHz : TIMER = 20M * 104.1667µ / 64 = 33 (32.55, -1.36% - works well)
#if F_CPU == 8000000
#define TIMER 13
#elif F_CPU == 11059200
#define TIMER 18
#elif F_CPU == 12000000
#define TIMER 20
#elif F_CPU == 16000000
#define TIMER 26
#elif F_CPU == 18432000
#define TIMER 30
#elif F_CPU == 20000000
#define TIMER 33
#else
#error Unsupported CPU frequency.
#endif
// change these to use another pin
#define TX_PORT PORTA
#define TX_PIN PA7
#define TX_DDR DDRA
#define TX_DDR_PIN DDA7
volatile uint16_t tx_shift_reg = 0; // holds the 8 bits of the byte to transmit plus 1 start bit and 1 stop bit.
void UART_tx(char character) {
// If sending the previous character is not yet finished, wait for this to be completed.
UART_flush();
// Fill the TX shift register with the character to be sent and the start & stop bits (start bit (1<<0) is already 0)
uint16_t local_tx_shift_reg = (character << 1) | (1 << 9); // Adds the stop bit (1<<9)
tx_shift_reg = local_tx_shift_reg;
OCR0A = TCNT0 + TIMER; // Set the timer compare point to the correct value.
bitSet(TIFR0, OCF0A); // Clear the interrupt flag.
bitSet(TIMSK0, OCIE0A); // Enable output compare 0 A interrupt.
}
void UART_tx_str(char* string) { // pointer to null terminated string.
while (*string) {
UART_tx(*string++); // Transmit the characters one by one.
}
}
void UART_flush() { // Flush the buffer.
while (tx_shift_reg) {} // tx_shift_reg becomes 0 when the stop bit is transmitted.
}
void UART_init() {
//set TX pin as output, high.
bitSet(TX_DDR, TX_DDR_PIN);
bitSet(TX_PORT, TX_PIN);
// ATTinyCore sets timer0 to Fast PWM mode, we have to disable this or the updates to OCR0A don't
// work properly.
bitClear(TCCR0A, WGM01);
bitClear(TCCR0A, WGM00);
}
// timer0 compare A match interrupt
ISR(TIM0_COMPA_vect ) {
uint16_t local_tx_shift_reg = tx_shift_reg;
// Output LSB of the TX shift register at the TX pin
if (local_tx_shift_reg & 0x01) {
bitSet(TX_PORT, TX_PIN); // Logic 1: high level signal.
}
else {
bitClear(TX_PORT, TX_PIN); // Logic 0: low level signal.
}
// Shift the TX shift register one bit to the right.
local_tx_shift_reg >>= 1;
tx_shift_reg = local_tx_shift_reg;
// Set the timer compare point for the next bit.
OCR0A += TIMER;
// When the stop bit has been sent, the shift register will be 0
// and the transmission is completed, so we can stop this interrupt.
if (!local_tx_shift_reg) {
bitClear(TIMSK0, OCIE0A);
}
}
The Nano receives these values on a SoftwareSerial instance and prints the result to Serial monitor. This is the result:
10, 0, 0, 0, 168,
10, 0, 0, 0, 168,
10, 0, 0, 168,
10, 0, 0, 0, 42,
10, 0, 0, 0, 168,
10, 0, 0, 168,
10, 0, 0, 0, 168,
10, 0, 0, 0, 168,
10, 0, 160, 249,
10, 0, 0, 0, 168,
10, 0, 0, 0, 0, 42,
10, 0, 0, 0, 0, 42,
10, 0, 0, 160, 254,
10, 0, 0, 42,
10, 0, 0, 168,
10, 0, 0, 168,
10, 0, 160, 249,
10, 0, 0, 42,
10, 0, 0, 168,
10, 0, 0, 0, 168,
10, 0, 0, 168,
10, 0, 0, 0, 168,
10, 0, 0, 0, 0, 42,
10, 0, 0, 0, 0, 42,
10, 0, 0, 0, 42,
10, 0, 0, 0, 0, 42,
10, 0, 0, 0, 42,
10, 0, 0, 168,
10, 0, 0, 168,
10, 0, 0, 160, 249,
10, 0, 0, 0, 0, 42,
10, 0, 0, 42,
10, 0, 160, 249,
10, 0, 0, 0, 0, 42,
10, 0, 0, 0, 168,
10, 0, 160, 249,
10, 0, 0, 0, 168,
10, 0, 0, 160, 249,
10, 0, 0, 0, 168,
10, 0, 0, 0, 168,
10, 0, 0, 42,
10, 0, 0, 0, 42,
10, 0, 0, 0, 168,
10, 0, 0, 0, 42,
10, 0, 168,
10, 0, 0, 0, 168,
10, 0, 0, 0, 168,
10, 0, 0, 0, 42,
10, 0, 0, 168,
10, 0, 168,
10, 0, 0, 0, 42,
10, 0, 0, 0, 0, 42,
10, 0, 0, 0, 42,
10, 0, 0, 0, 42,
10, 0, 0, 0, 42,
10, 0, 0, 42,
10, 0, 0, 160, 254,
10, 0, 160, 249,
10, 0, 0, 42,
10, 0, 0, 160, 249,
10, 0, 0, 42,
10, 0, 0, 0, 0, 42,
10, 0, 160, 249,
10, 0, 0, 0, 0, 42,
10, 0, 0, 168,
10, 0, 0, 42,
10, 0, 0, 42,
10, 0, 0, 168,
10, 0, 168,
10, 0, 160, 249,
10, 0, 0, 0, 42,
10, 0, 0, 168,
10, 0, 0, 42,
10, 0, 0, 168,
10, 0, 0, 0, 42,
10, 0, 0, 0, 42,
10, 0, 168,
10, 0, 0, 0, 42,
10, 0, 0, 168,
10, 0, 0, 160, 249,
10, 0, 0, 0, 168,
10, 0, 0, 0, 42,
10, 0, 0, 160, 249,
10, 0, 0, 42,
10, 0, 0, 168,
10, 0, 0, 168,
10, 0, 0, 168,
10, 0, 0, 0, 168,
10, 0, 168,
10, 0, 0, 168,
10, 0, 0, 0, 168,
10, 0, 0, 168,
10, 0, 0, 168,
10, 0, 0, 0, 0, 42,
10, 0, 160, 249,
10, 0, 0, 42,
10, 0, 0, 168,
10, 0, 0, 0, 42,
10, 0, 0, 0, 42,
10, 0, 160, 249,
10, 0, 0, 0, 168,
10, 0, 0, 160, 249,
10, 0, 0, 42,
10, 0, 0, 42,
10, 0, 0, 42,
10, 0, 0, 42,
The first two values (10, 0) come through all the time. In rare cases all four zeros come through as they should. In some cases they're simply missed, in other cases later values get mangled.
42 = 0b00101010
160 = 0b10100000
168 = 0b10101000
249 = 0b11111001
Especially the last one is confusing as I don't know how to assemble that from the various bits that are being transmitted...
(another example in the next message...)