Recently I've been getting into bare-metal programming with the Arduino-Nano, and wanted to play around with the UART protocool. I made a simple "shell" using rust on my host machine that when sending the command "ledON" or "ledOFF" It would turn on or off the built in LED on the nano.
The C code on the Nano is as follows
Header file (avr-uart.h):
#include <stdint.h>
// Register Defines
#define UBRR0L (*(volatile uint8_t *)0xC4)
#define UBRR0H (*(volatile uint8_t *)0xC5)
#define UCSR0A (*(volatile uint8_t *)0xC0)
#define UCSR0B (*(volatile uint8_t *)0xC1)
#define UCSR0C (*(volatile uint8_t *)0xC2)
#define UDR0 (*(volatile uint8_t *)0xC6)
// Register Bit Defines
#define RXEN0 5 // USART Receiver
#define TXEN0 4 // USART Transmitter
#define USBS0 3 // Sets number of stop bits
#define UCSZ00 1 // Sets Character Size
#define UDRE0 5
#define RXC0 7
// Constant Defines
#define CLOCK_SPEED 16000000 // MCU Clock Speed
#define BAUD 115200 // BAUD rate
#define MYUBRR CLOCK_SPEED / 16 / BAUD - 1 // UART Baud Rate
/*
* Function Desc: Function to handle Data buffer transfer
* @param data => Buffer holding the data to be transmitted
*/
void USART_Transmit(unsigned char data) {
// Wait for empty transmit buffer
while ( !( UCSR0A & (1 << UDRE0)));
// Puts data in the buffer and sends data
UDR0 = data;
}
/*
* Function Desc: Function to handle Data Reception
*/
volatile uint8_t USART_Recieve(void) {
while ( !( UCSR0A & (1 << RXC0)));
return UDR0;
}
/*
* Function Desc: Function to handling Printing strings to UART
* @param *data => Pointer to data buffer
*/
void USART_Print(const char* data) {
while (*data) {
USART_Transmit(*data++);
}
}
/*
* Function Desc: Function to handle USART setup for Data Transmission and Reception
* @param ubrr => BAUD Rate
*/
void USART_Init(unsigned int ubrr) {
// This sets BAUD Rate
UBRR0H = (unsigned char) (ubrr >> 8); // Zeroes out higher bit register (according to datasheet)
UBRR0L = (unsigned char) ubrr; // Sets lower bit register baud rate
// Enable Receiver and Transmitter
UCSR0B = (1 << RXEN0) | (1 << TXEN0);
// Sets frame format
UCSR0C = (1 << USBS0) | (3 << UCSZ00);
}
Main File:
#include "avr-uart.h"
#include <stdint.h>
#include <string.h>
#define BUFFER_SIZE 50
#define DDRB (*(volatile uint8_t *)0x24) // Address of DDRB Register
#define PORTB (*(volatile uint8_t *)0x25) // Address of PORTB Register
#define PB5 5 // PB5 (Port B, bit 5)
int main(void) {
// Sets the DDRB Register to Output
DDRB |= (1 << PB5);
// Initializes UART on the Micro-controller
USART_Init(MYUBRR);
char commandBuffer[BUFFER_SIZE];
uint8_t buffer = 0;
while (1) {
// Save the received character in a single char variable
char received = USART_Recieve();
// Echo the character
USART_Transmit(received);
if (received == '\n' || received == '\r') {
commandBuffer[buffer] = '\0';
if (strcmp(commandBuffer, "ledON") == 0) {
PORTB |= (1 << PB5); // Turns on the LED
USART_Print("\r\n[NANO] INTERACTED WITH LED\r\n");
}
if (strcmp(commandBuffer, "ledOFF") == 0) {
PORTB &= ~(1 << PB5); // Turns off the LED
USART_Print("\r\n[NANO] INTERACTED WITH LED\r\n");
}
else {
USART_Print("\r\n[NANO] INVALID COMMAND\r\n");
}
buffer = 0;
}
else {
if (buffer < BUFFER_SIZE - 1) {
commandBuffer[buffer++] = received;
}
}
}
}
And finally the simple shell I wrote in rust:
use std::io::{self, Write, Read};
use std::time::Duration;
use serialport::SerialPort;
fn main() {
// Variables for Serial Port name and BAUD rate
let port_name = "/dev/ttyUSB1";
let baud = 115200;
println!("[!] Initializing Arduino-Nano c0smic Shell! :)\n");
// Opens a serial port connection to the Arduino
let mut port = serialport::new(port_name, baud)
.timeout(Duration::from_millis(1000))
.open().expect("Failed to open port");
println!("[!] Successfully connected to serial port /dev/ttyUSB1!\n");
println!("Shell is initialized, you may start sending commands :)\n");
let mut inputBuffer = String::new();
loop {
print!(">> ");
io::stdout().flush().unwrap();
inputBuffer.clear();
// Creates input buffer for commands
// Clears reads line
io::stdin().read_line(&mut inputBuffer).expect("Failed to read line!");
// Trims command buffer
let inputBuffer: String = inputBuffer.trim().parse().expect("Failed to clean buffer");
if inputBuffer == "ledON" || inputBuffer == "ledOFF" {
// Sends data as bytes
port.write_all(format!("{}\n", inputBuffer).as_bytes()).unwrap();
// Makes the CPU wait (sleep) for 500 ms
std::thread::sleep(Duration::from_millis(500));
// Creates buffer for receiving data from Nano
let mut buffer = [0u8; 128];
match port.read(&mut buffer) {
Ok(n) if n > 0 => {
let response = String::from_utf8_lossy(&buffer[..n]);
println!("{}", response);
}
// Checks if a response is received or if there is an error
Ok(_) => println!("No response receieved"),
Err(e) => println!("Failed to read: {}", e),
}
} else {
println!("[!] Error ::: Invalid Command!\n");
}
}
}
The Makefile for compilation:
default: build
build:
avr-gcc -Os -DF_CPU=16000000UL -mmcu=atmega328p main.c -o main
burn: build
avr-objcopy -O ihex -R .eeprom main main.hex
avrdude -F -V -c arduino -pm328p -P /dev/ttyUSB1 -b 115200 -U flash:w:main.hex
I was able to confirm that the rust shell is able to connect to the Arduino and actually send data (as the RX LED flickers everytime I send the command) but everytime the shell waits for a response, I get the error "Timed Out". From what I gather, the Arduino Nano isn't able to read the data sent properly. Any help is appreciated. Tyia!
Consider trying this already proven solution: https://github.com/johncobb/avr_328p_usart
Also, please consider using the toolchain header files like avr/io.h
.
I was able to use the posted code to see output after these changes:
// Register Bit Defines
#define RXEN0 4 // USART Receiver
#define TXEN0 3 // USART Transmitter
#define UCSZ00 1 // Sets Character Size
#define UCSZ01 2
And Updating the function USART_Init
like this:
UCSR0C |= (1 << UCSZ01) | (1 << UCSZ00);
The value USBS0
might be correct, but I don't know the specific bits enough to give good advice so I am copying the value used by the reference solution.
Here is the file avr-uart.h
that is working for me:
#include <stdint.h>
// Register Defines
#define UBRR0L (*(volatile uint8_t *)0xC4)
#define UBRR0H (*(volatile uint8_t *)0xC5)
#define UCSR0A (*(volatile uint8_t *)0xC0)
#define UCSR0B (*(volatile uint8_t *)0xC1)
#define UCSR0C (*(volatile uint8_t *)0xC2)
#define UDR0 (*(volatile uint8_t *)0xC6)
// Register Bit Defines
#define RXEN0 4 // USART Receiver
#define TXEN0 3 // USART Transmitter
#define USBS0 3 // Sets number of stop bits
#define UCSZ00 1 // Sets Character Size
#define UCSZ01 2
#define UDRE0 5
#define RXC0 7
// Constant Defines
#define CLOCK_SPEED 16000000 // MCU Clock Speed
#define BAUD 115200 // BAUD rate
#define MYUBRR CLOCK_SPEED / 16 / BAUD - 1 // UART Baud Rate
/*
* Function Desc: Function to handle Data buffer transfer
* @param data => Buffer holding the data to be transmitted
*/
void USART_Transmit(unsigned char data) {
// Wait for empty transmit buffer
while ( !( UCSR0A & (1 << UDRE0)));
// Puts data in the buffer and sends data
UDR0 = data;
}
/*
* Function Desc: Function to handle Data Reception
*/
volatile uint8_t USART_Recieve(void) {
while ( !( UCSR0A & (1 << RXC0)));
return UDR0;
}
/*
* Function Desc: Function to handling Printing strings to UART
* @param *data => Pointer to data buffer
*/
void USART_Print(const char* data) {
while (*data) {
USART_Transmit(*data++);
}
}
/*
* Function Desc: Function to handle USART setup for Data Transmission and Reception
* @param ubrr => BAUD Rate
*/
void USART_Init(unsigned int ubrr) {
// This sets BAUD Rate
UBRR0H = (unsigned char) (ubrr >> 8); // Zeroes out higher bit register (according to datasheet)
UBRR0L = (unsigned char) ubrr; // Sets lower bit register baud rate
// Enable Receiver and Transmitter
UCSR0B = (1 << RXEN0) | (1 << TXEN0);
// Sets frame format
UCSR0C |= (1 << UCSZ01) | (1 << UCSZ00);
}