UNO R3 SMD(atmega328p) - serial write (c bare metal)

Hi, sorry for my C in advance. I'm trying to write a simple echo serial program for my Arduino UNO R3 SMD (ATmega328p) using C and only AVR libraries.

#include <avr/io.h>
#include <util/delay.h>
#include <avr/interrupt.h>

// buffer and corresponding read/write pointers
#define SIZE 64
uint8_t buffer[SIZE];
uint8_t* buffer_w = buffer;
uint8_t* buffer_r = buffer;

uint8_t is_buffer_empty() {
    return buffer_r == buffer_w;
}

uint8_t read() {
    uint8_t c = *buffer_r;
    buffer_r++;
    if (buffer_r >= ((uint8_t*) buffer) + SIZE) {
        buffer_r = buffer;
    }
    return c;
}

void write(uint8_t c) {
    *buffer_w = c;
    buffer_w++;
    if (buffer_w >= ((uint8_t*) buffer) + SIZE) {
        buffer_w = buffer;
    }
}

void usartInit(unsigned int ubrr) {
    // Set baud rate
    UBRR0H = (unsigned char)(ubrr >> 8);
    UBRR0L = (unsigned char)ubrr;

    // Enable transmitter (TX) and receiver (RX)
    UCSR0B = (1 << TXEN0) | (1 << RXEN0);

    // Set frame format: 8 data bits, 1 stop bit (8N1)
    UCSR0C = (1 << UCSZ01) | (1 << UCSZ00);
}

// enable USART RX Complete Interrupt
void enableRXCInterapt(uint8_t enabled) {
    uint8_t mask = (1 << RXCIE0);
    UCSR0B = (UCSR0B & ~mask) | (enabled ? mask : 0);
}

// enable USART Data Register Empty Interrupt
void enableEmptyInterapt(uint8_t enabled) {
    uint8_t mask = (1 << UDRIE0);
    UCSR0B = (UCSR0B & ~mask) | (enabled ? mask : 0);
}

// USART Data Register Empty Interrupt Handler
ISR(USART_UDRE_vect) {
    if (is_buffer_empty()) {
        enableEmptyInterapt(0);
    }
    if (!is_buffer_empty()) {
        UDR0 = read();
    }
}

// USART RX Complete Interrupt Handler
ISR(USART_RX_vect) {
    write(UDR0);
}

void setup() {
    usartInit(103); // 103 corresponds to ~9600 baudrate
    sei(); // enable avr interrupts
    enableRXCInterapt(1);
}

void loop() {
    if (!is_buffer_empty()) {
        enableEmptyInterapt(1);
    }
}

int main(void) {
    setup();
    while(1) {
        loop();
    }
}

When I test it using the Arduino CLI monitor, everything works fine:

$ ino monitor -p /dev/ttyACM0
Using default monitor configuration for board: arduino:avr:uno
Monitor port settings:
  baudrate=9600
  bits=8
  dtr=on
  parity=none
  rts=on
  stop_bits=1

Connecting to /dev/ttyACM0. Press CTRL-C to exit.
A #input
A
TEST LONG SENTENCE ........ #input
TEST LONG SENTENCE ........

But when I try to create some Python tests, it gets stuck on reading.

import serial

def get_serial():
    ser = serial.Serial()
    ser.port = '/dev/ttyACM0'
    ser.baudrate = 9600
    ser.bytesize =  serial.EIGHTBITS
    ser.parity = serial.PARITY_NONE
    ser.stopbits = serial.STOPBITS_ONE
    ser.open()
    return ser

if __name__ == "__main__":
    ser = get_serial()
    print("write")
    ser.write(b'a')
    print("read")
    print(ser.read())
    ser.close()

When using arudino-cli:

write into terminal => TX and RX diode light up and I got same bytes back.

python:

running script => diod RX light up and builtin led, script stack in reading :confused:

I also tried

cat -v < /dev/ttyACM0

to see what would happen and I got simmular behaviour, bultin led blinked few times.

I have 2 questions:

  1. Why does the Python script not work, but using the Arduino CLI does?
  2. Do you think it is a good idea to learn 'bare metal C' using Arduino, or should I buy a different product?

source code

uint8_t is_buffer_empty() {
    return buffer_r == buffer_w;
}

I know this will also return true if I overflow the buffer, but I only send 1 byte from Python. And I think, it should be fast enough to move the read pointer before the buffer overflows.

You should compare your code to the HardwareSerial.cpp file and see how they initialize the hardware and read/write a byte.

1 Like

Does the python code need to set "raw" mode or something, to prevent it from reading a line at a time?

I was unable to reproduce that, whether using CLI, IDE, teraterm or Python. However, when I remove your main(), it works ok in CLI and IDE.

I don't know why your main does not work.

Modified sketch:

#include <avr/io.h>
#include <avr/interrupt.h>
#include <util/delay.h>

// buffer and corresponding read/write pointers
#define SIZE 64

uint8_t buffer [SIZE];
uint8_t* buffer_w = buffer;
uint8_t* buffer_r = buffer;
/*
          read       write
          v          v 
  buffer [                     ]
*/
uint8_t is_buffer_empty()
{
  return buffer_r == buffer_w;
}

// get a byte from the buffer
uint8_t read()
{
  uint8_t c = *buffer_r;
  buffer_r++;
  if (buffer_r >= buffer + SIZE)
  {
    buffer_r = buffer;
  }
  return c;
}

// put a byte into the buffer
void write (uint8_t c)
{
  *buffer_w = c;
  buffer_w++;
  if (buffer_w >= buffer + SIZE)
  {
    buffer_w = buffer;
  }
}

void usartInit(unsigned int ubrr)
{
  // Set baud rate
  UBRR0H = (unsigned char)(ubrr >> 8);
  UBRR0L = (unsigned char)ubrr;

  // Enable transmitter (TX) and receiver (RX)
  UCSR0B = (1 << TXEN0) | (1 << RXEN0);

  // Set frame format: 8 data bits, 1 stop bit (8N1)
  UCSR0C = (1 << UCSZ01) | (1 << UCSZ00);
}

// enable USART RX Complete Interrupt
void enableReceiveInterapt(uint8_t enabled)
{
  uint8_t mask = (1 << RXCIE0);
  UCSR0B = (UCSR0B & ~mask) | (enabled ? mask : 0);
}

// enable USART Data Register Empty Interrupt
void enableTransmitInterapt(uint8_t enabled)
{
  uint8_t mask = (1 << UDRIE0);
  UCSR0B = (UCSR0B & ~mask) | (enabled ? mask : 0);
}

#if 1
// USART Data Register Empty Interrupt Handler
ISR(USART_UDRE_vect)
{
  if (is_buffer_empty())
  {
    enableTransmitInterapt(0);
  }
  if (!is_buffer_empty())
  {
    UDR0 = read();
  }
}

// USART RX Complete Interrupt Handler
ISR(USART_RX_vect)
{
  digitalWrite (13, !digitalRead (13));
  write(UDR0);
}
#endif

void setup()
{
  pinMode (13, OUTPUT);
  usartInit(103);  // 103 corresponds to ~9600 baudrate
  enableReceiveInterapt(1);
  sei();           // enable avr interrupts
}

void loop()
{
  if (!is_buffer_empty())
  {
    enableTransmitInterapt(1);
  }
}

#if 0
int main(void)
{
  init();
  setup();

  while (1)
  {
    loop();
  }
}
#endif

The Python script seems to have a start up/timing issue. If I add a loop,it starts receiving data.

I'm not an expert on Python, so did not look further. I ran on Windows so that might make a difference.

Modified Python:

import serial
import time

def get_serial():
    ser = serial.Serial()
    # ser.port = '/dev/ttyACM0'
    ser.port = 'COM4'
    ser.baudrate = 9600
    ser.bytesize =  serial.EIGHTBITS
    ser.parity = serial.PARITY_NONE
    ser.stopbits = serial.STOPBITS_ONE
    ser.timeout = 1.0
    ser.open()
    return ser

if __name__ == "__main__":
    ser = get_serial()
    
    while 1:
        print("write")
        ser.write(b'A')
        
        print("read")
        rx = ser.read(16);
        if (len(rx) == 0):
            print ("timeout")
        else:    
            print(f"received {rx}")
        
        time.sleep (0.5);
    
    ser.close()

There are many types of Arduino, I'll assume you mean Uno R3 (AVR). The AVR architecture is a fine platform to learn bare metal coding on. The register set is very simple compared to say any ARM CPU.

A downside with Uno is you don't have a debugger (at least not out of the box). It can be very useful to step through code, view the assembler, step through assemble, examine memory, examine registers etc, which you can get with ARM and a JTAG probe and suitable IDE.

On Uno, you have to be bit more methodical and maybe use some creative debugging techniques, although simply flashing an LED can be useful. A cheap $10 logic analyzer can capture data in real-time, so you could output lots of info on GPIO pins.

First of all thank you all for quick answers.
@blh64 It was a good tip. I rewrote it using the Arduino core, and its behavior is the same.

#define SIZE 64
uint8_t buffer[SIZE];
uint8_t* buffer_w = buffer;
uint8_t* buffer_r = buffer;

uint8_t is_buffer_empty() {
    return buffer_r == buffer_w;
}

uint8_t read() {
    uint8_t c = *buffer_r;
    buffer_r++;
    if (buffer_r >=  buffer + SIZE) {
        buffer_r = buffer;
    }
    return c;
}

void write(uint8_t c) {
    *buffer_w = c;
    buffer_w++;
    if (buffer_w >= buffer + SIZE) {
        buffer_w = buffer;
    }
}
void setup() {
    Serial.begin(9600);
}


void loop() {
    if (Serial.available() > 0) {
        write(Serial.read());
    }
    if (!is_buffer_empty()) {
        Serial.write(read());
    }
}

So it is not related to my setup code. But now I know that I need to look somewhere else.

@bobcousins I think you compiled your code using aruduino idea and you included arduino core lib because you use pinMode/digitalWrite from arduino core. But I'm trying to avoid that. But thank you, you are right python have some problem on start up.

I added few steps in my python script:

    state = {}
    ser = get_serial()
    while True:
        b = randbytes(1)
        ser.write(b)
        c = ser.read()
        if c == b:
            break
    ser.write(b'a')
    print(ser.read())
    ser.close()

And it works like a charm now.

If someone have any idea how to avoid this setup steps in my python scrip let me know.

For reference, how did you compile your code?

using avr-gcc, avr-objcopy and avrdude for flashing

This topic was automatically closed 180 days after the last reply. New replies are no longer allowed.