Pages

Saturday, May 12, 2018

Programming on Arduino without Arduino IDE


Hello World!

It's been a long time since I wanted to write this post but due to human nature I'm writing it just now. Arduino is definitely a very simple and an awesome development platform with a great and simple IDE! But after some time playing with it, I got curious about the IDE.
How does it work? And can I write a program on a different editor and still be able to compile and upload it to my board?

In this post I'm going to show the outcome of what I have gone through to develop a simple application on Arduino UNO.
It's a very simple app that reads the input from the computer, and outputs it back.

The first thing we have to do is setup so checkout my previous post on how to Set up the AVR development environment tools and libraries.

While setting up the environment you might have already got the idea that Arduino IDE is a cool tool or a wrapper around all the AVR DE. We will be doing almost the same but manually.
Why? Because it's a playground!

First let's make a simple C program that will scan a string and output it...

#include <stdio.h>
#define MAX_CHAR 100

int main(void) {
    char input[MAX_CHAR];

    while(1) {
        printf("Hello world!");
        scanf("%s", input);
        printf("You wrote %s\n", input);
    }

    return 0;
}

Great! We can compile and run this program now.

gcc main.cpp -o run
./run

UART.h
In this sample program we will be using UART to communicate with our computer from the Arduino Board.
Go ahead and create uart.h and uart.c files.
In our header file we want to declare three functions. Which are getchar, putchar, and an initialization function.
At this point we have to make sure that we are using libraries from our AVR directory (/avr/include). It has its own "stdio.h" and all the other header files that we will need for this sample program.

We should now include "uart.h" in our "main.c" file under #include <stdio.h>  and in our "uart.h" file we will write:

void initialize_uart(void);
int uart_putchar(char c, FILE *stream);
int uart_getchar(FILE *stream);

FILE uart_out = FDEV_SETUP_STREAM(uart_putchar, NULL, _FDEV_SETUP_WRITE);
FILE uart_in = FDEV_SETUP_STREAM(NULL, uart_getchar, _FDEV_SETUP_READ);

Now let's edit the "uart.c" file. We need to specify the MCU that we are using by defining "__AVR_ATmega328P__".
#ifndef __AVR_ATmega328P__
#define __AVR_ATmega328P__
#endif
After we define the board, and include the `avr/io.h`, the part that is hiding behind the
#elif defined (__AVR_ATmega328P__) || defined (__AVR_ATmega328__)
in the "avr/io.h" will be enabled.
Next we need to define the F_CPU and the BAUD rate.
And include "util/setbaud.h". After that we can enable and use the RX and TX on the board for input and output.
#ifndef __AVR_ATmega328P__
#define __AVR_ATmega328P__
#endif

#include 
#include 

#define F_CPU 16000000UL
#define BAUD  9600// 115200

#include 

void initialize_uart(void) {
    UBRR0H = UBRRH_VALUE;
    UBRR0L = UBRRL_VALUE;
#if USE_2X
    UCSR0A |= _BV(U2X0);
#else
    UCSR0A &= ~(_BV(U2X0));
#endif

    UCSR0C = _BV(UCSZ01) | _BV(UCSZ00); // 8-bit data
    UCSR0B = _BV(RXEN0) | _BV(TXEN0);   // Enabling RX and TX
}

int uart_putchar(char c, FILE *stream) {
    if (c == '\n') {
        uart_putchar('\r', stream);
    }
    loop_until_bit_is_set(UCSR0A, UDRE0);
    UDR0 = c;
    return 0;
}

int uart_getchar(FILE *stream) {
    loop_until_bit_is_set(UCSR0A, RXC0); // Wait until data is set.
    return UDR0;
}
We are done with the program. Next thing we need to do is to build and upload it to our Arduino UNO.

Let's write a Makefile with 'build' and 'upload' functionalities. Here is a great tutorial on how to make a simple Makefile.

# -- High Level Config -- #

## Executable file name.
EXECUTABLE = main
## Microcontroller Model
MCU_NAME = atmega328p
## AVR Dude programmer
PROGRAMMER_NAME = arduino
## Port
PORT_PATH = /dev/tty.usb*

# ---- ----------------- --- #

# ---- ---- Config ---- ---- #

## Binaries directory.
BIN = bin
## Build path.
BUILD = build

## Dependancies to track.
DEPS = uart.h
## C sources and Assembly sources
CSOURCES = main.c uart.c
ASOURCES =

## C compiler.
CC = avr-gcc
## C++ compiler.
CXX = avr-g++

# Programs and commands.
OBJCOPY = avr-objcopy
OBJDUMP = avr-objdump
SIZE = avr-size
NM = avr-nm
AVRDUDE = avrdude

#  AVR variables.
## MCU name.
MCU = ${MCU_NAME}

## Processor frequency.
F_CPU = 16000000

# Debugging format.
#     Native formats for AVR-GCC's -g are dwarf-2 [default] or stabs.
#     AVR Studio 4.10 requires dwarf-2.
#     AVR [Extended] COFF format requires stabs, plus an avr-objcopy run.
DBG = -gdwarf-2

# Optimization level, can be [0, 1, 2, 3, s].
#     0 = turn off optimization. s = optimize for size.
#     (Note: 3 is not always the best optimization level. See avr-libc FAQ.)
OPT = -Os

## Output format. (can be srec, ihex, binary).
FORMAT = ihex

#  Minimalistic printf version
PRINTF_LIB_MIN = -Wl,-u,vfprintf -lprintf_min
## > Floating point printf version (requires MATH_LIB = -lm below)
PRINTF_LIB_FLOAT = -Wl,-u,vfprintf -lprintf_flt
### = If this is left blank, then it will use the Standard printf version.
PRINTF_LIB =
# PRINTF_LIB = $(PRINTF_LIB_MIN)
# PRINTF_LIB = $(PRINTF_LIB_FLOAT)

## > Minimalistic scanf version
SCANF_LIB_MIN = -Wl,-u,vfscanf -lscanf_min

## > Floating point + %[ scanf version (requires MATH_LIB = -lm below)
SCANF_LIB_FLOAT = -Wl,-u,vfscanf -lscanf_flt

### = If this is left blank, then it will use the Standard scanf version.
SCANF_LIB =
#SCANF_LIB = $(SCANF_LIB_MIN)
#SCANF_LIB = $(SCANF_LIB_FLOAT)

MATH_LIB = -lm

# ---- ---------------- --- #

# ---- External Memory ---- #

# 64 KB of external RAM, starting after internal RAM (ATmega128!),
# used for variables (.data/.bss) and heap (malloc()).
#EXTMEMOPTS = -Wl,-Tdata=0x801100,--defsym=__heap_end=0x80ffff

# 64 KB of external RAM, starting after internal RAM (ATmega128!),
# only used for heap (malloc()).
#EXTMEMOPTS = -Wl,--defsym=__heap_start=0x801100,--defsym=__heap_end=0x80ffff

EXTMEMOPTS =

# ---- ---------------- ---- #

# ---- ---- Linker ---- ---- #

#  -Wl,...:     tell GCC to pass this to linker.
#    -Map:      create map file
#    --cref:    add cross reference to  map file
LDFLAGS = -Wl,-Map=$(BUILD)/$(EXECUTABLE).map,--cref
LDFLAGS += $(EXTMEMOPTS)
LDFLAGS += $(PRINTF_LIB) $(SCANF_LIB) $(MATH_LIB)

# ---- ---------------- ---- #

# ---- Utils variables  ---- #

# Display size of file.
ELFFILE = ${BIN}/${EXECUTABLE}.elf
HEXFILE = ${BIN}/${EXECUTABLE}.hex

HEXSIZE = $(SIZE) --target=$(FORMAT) $(HEXFILE)
ELFSIZE = $(SIZE) -A $(ELFFILE)
AVRMEM = avr-mem.sh $(ELFFILE) $(MCU)

# ---- --------------------------- ---- #

# --- Programming Options (avrdude) --- #

# Programming hardware: alf avr910 avrisp bascom bsd
# dt006 pavr picoweb pony-stk200 sp12 stk200 stk500
#
# Type: avrdude -c ?
# to get a full listing.
#
AVRDUDE_PROGRAMMER = ${PROGRAMMER_NAME}

# com1 = serial port. Use lpt1 to connect to parallel port.
# programmer connected to serial device
AVRDUDE_PORT = ${PORT_PATH}

AVRDUDE_WRITE_FLASH = -U flash:w:${BIN}/${EXECUTABLE}.hex
#AVRDUDE_WRITE_EEPROM = -U eeprom:w:${BIN}/${EXECUTABLE}.eep


# Uncomment the following if you want avrdude's erase cycle counter.
# Note that this counter needs to be initialized first using -Yn,
# see avrdude manual.
#AVRDUDE_ERASE_COUNTER = -y

# Uncomment the following if you do /not/ wish a verification to be
# performed after programming the device.
#AVRDUDE_NO_VERIFY = -V

# Increase verbosity level.  Please use this when submitting bug
# reports about avrdude. See 
# to submit bug reports.
#AVRDUDE_VERBOSE = -v -v

AVRDUDE_FLAGS = -p $(MCU) \
    -P $(AVRDUDE_PORT) \
    -c $(AVRDUDE_PROGRAMMER) \
    -b 115200 #57600 \
    $(AVRDUDE_NO_VERIFY) $(AVRDUDE_VERBOSE) $(AVRDUDE_ERASE_COUNTER)

# ---- --------------------------- ---- #

info:
 @echo $(OBJECTS)
 @echo $(LISTS)

all: init \
 prebuild \
 build \
 postbuild

init: \
 gccversion
 @echo ""
 @echo " ## Initializing..."
 @if [ ! -d "bin" ]; then \
  echo " > mkdir bin"; mkdir bin; \
 else echo " > bin folder exists."; \
 fi
 @if [ ! -d "build" ]; then \
  echo " > mkdir build"; mkdir build; \
 else echo " > build folder exists."; \
 fi
 @sleep 1
 @echo ""

gccversion:
 @echo ""
 @$(CC) --version

prebuild: \
 sizebefore
 @echo " ## Building Objects..."

build: \
 build-elf \
 build-hex \
 build-eep \
 build-lss \
 build-sym

postbuild: \
 sizeafter
 @echo " ## Done building objects..."

#$(OBJECTS)
# $(CC) -o ${BIN}/${EXECUTABLE} $(OBJECTS) $(CFLAGS)

build-elf: ${BIN}/${EXECUTABLE}.elf
build-hex: ${BIN}/${EXECUTABLE}.hex
build-eep: ${BIN}/${EXECUTABLE}.eep
build-lss: ${BIN}/${EXECUTABLE}.lss
build-sym: ${BIN}/${EXECUTABLE}.sym

# ---- --------------------------- ---- #

# ---- ------- Fun utils: -------- ---- #

sizebefore:
 @echo $(ELFFILE);
 @if test -f $(ELFFILE); then \
  echo; echo "Size before: "; $(ELFSIZE); \
  $(AVRMEM) 2>/dev/null; echo; \
 fi

sizeafter:
 @if test -f $(ELFFILE); then \
  echo; echo "Size after: "; $(ELFSIZE); \
  $(AVRMEM) 2>/dev/null; echo; \
 fi

# ---- --------------------------- ---- #

# ---- --------- Upload ---------- ---- #

HEXFILE = ${BIN}/${EXECUTABLE}.hex
EEPFILE = ${BIN}/${EXECUTABLE}.eep

upload: $(HEXFILE) $(EEPFILE)
 $(AVRDUDE) $(AVRDUDE_FLAGS) $(AVRDUDE_WRITE_FLASH) $(AVRDUDE_WRITE_EEPROM)

# ---- --------------------------- ---- #

# ---- --------- CleanUp --------- ---- #

.PHONY: clean

clean:
 rm -rf ${BIN}
 rm -rf ${BUILD}

# ---- --------------------------- ---- #

# ---- ----- Transformations ----- ---- #

## C
.SECONDARY : ${BUILD}/${EXECUTABLE}.elf
.PRECIOUS : $(OBJECTS)
$(BUILD)/%.o: %.c $(DEPS)
 $(CC) -c $(ALL_CFLAGS) $< -o $@
# $(CC) -c -o $@ $< $(CFLAGS)

$(BIN)/%.elf: $(OBJECTS)
 @echo
 @echo " ## Linking .o files into " $@
 $(CC) $(ALL_CFLAGS) $^ --output $@ $(LDFLAGS)

$(BIN)/%.hex: $(BIN)/%.elf
 @echo
 @echo "Creating .hex from ELF file" $@
 $(OBJCOPY) -O $(FORMAT) -R .eeprom $< $@

$(BIN)/%.eep: $(BIN)/%.elf
 @echo
 @echo "Creating .eep from ELF file" $@
 -$(OBJCOPY) -j .eeprom --set-section-flags=.eeprom="alloc,load" \
 --change-section-lma .eeprom=0 -O $(FORMAT) $< $@

$(BIN)/%.lss: $(BIN)/%.elf
 @echo
 @echo "Create extended listing file (.lss) from ELF output file." $@
 $(OBJDUMP) -h -S $< > $@

$(BIN)/%.sym: $(BIN)/%.elf
 @echo
 @echo "Create a symbol (.sym) table from ELF file" $@
 $(NM) -n $< > $@

## Assembler
# Compile: create assembler files from C source files.
%.s : %.c
 $(CC) -S $(ALL_CFLAGS) $< -o $@

# Assemble: create object files from assembler source files.
%.o : %.S
 @echo
 @echo $(MSG_ASSEMBLING) $<
 $(CC) -c $(ALL_ASFLAGS) $< -o $@

# Create preprocessed source for use in sending a bug report.
%.i : %.c
 $(CC) -E -mmcu=$(MCU) -I. $(CFLAGS) $< -o $@


## $< is the first item in the dependencies list
## special macros '$@' and '$^' are the left and right sides of the ':'

Setting up AVR Development Environment


Hello World!

I'm using Mac OSX and I'm not fan of  `brew`ing everything so in this part I'm going to show how to install the tools necessary to develop for AVR microcontrollers from the sources. So let's start!
Here is an alternative link to show how to do that:
https://www.nongnu.org/avr-libc/user-manual/install_tools.html
I was following the steps but it didn't quite help me. So I came up with my own steps.

AVR

We need to define couple variables in the beginning. In case anything goes wrong, or you want to reinstall your AVR you'll be able to just remove the avr directory. Let's say it's /usr/local/ directory.

PREFIX - folder with all avr related stuff.
INITDIR - folder where you're going to download and configure the sources.

# Initialize useful variables & create directories.
export PREFIX="/usr/local/avr"
export INITDIR="/anywhere/on/your/computer/"
export PATH:"$PREFIX/bin/:$PATH"  # including binaries in the `PATH`

mkdir $PREFIX
mkdir $EXTRAS
You might want to use sudo.

Installing BINUTILS:
You can choose the binutils version over here.
cd $INITDIR
# replace `*` with appropriate version. 
# Download
curl -OL https://mirror.freedif.org/GNU/binutils/binutils-*.tar.gz .
# Extract
tar xzf binutils-*.tar.xz -
# Configure
cd binutils-*
mkdir build-avr
cd build-avr
../configure --prefix=$PREFIX --target=avr --disable-nls
# Build & Install
make
make install  # use `sudo` or `chmod/chown` on the /usr/local/avr/


Installing GCC for AVR:
Choose the GCC version over here or straight here.
cd $INITDIR
# replace `*` with appropriate version. 
# Download
curl -OL http://mirrors.concertpass.com/gcc/releases/gcc-*/gcc-*.tar.gz .
# Extract
tar xf gcc-*.tar.xz -
cd gcc-*
# Download dependencies
./contrib/download_prerequisites
# Configure
mkdir build-avr
cd build-avr
../configure --prefix=$PREFIX --target=avr --enable-languages=c,c++ --disable-nls --disable-libssp --with-dwarf2
# Build & Install
make
make install


The rest of the setup requires some additional tools like autoconf and automake.
Installing Autoconf and Automake:
Choose the Autoconf version over  here.
Choose the Automake version over  here.
# -- Autoconf --
cd $INITDIR
curl -OL https://ftp.gnu.org/gnu/autoconf/autoconf-*.tar.gz .
tar xzf autoconf-*.tar.gz
cd autoconf-*
./configure --prefix=$PREFIX
make
make install

# -- Automake --
cd $INITDIR
curl -OL https://ftp.gnu.org/gnu/automake/automake-*.tar.gz .
tar xf automake-*.tar.xz
cd automake-*
mkdir build
cd build
../configure --prefix=$PREFIX
make
make install

Next, we want to install AVR libs and a tools to be able to upload the program to the board.

Installing AVR-LibC:
AVR-LibC Documentation
Choose the AVR-LibC version over here.
cd $INITDIR
# replace `*` with appropriate version. 
# Download
curl -OL http://download.savannah.gnu.org/releases/avr-libc/avr-libc-*.tar.bz2 .
# Extract
tar xf avr-libc-*.tar.bz2 -
cd avr-libc-*
# Configure
mkdir build
cd build
../configure --prefix=$PREFIX --build=`./config.guess` --host=avr
# Build & Install
make
make install


Installing AVRDUDE:
http://savannah.nongnu.org/projects/avrdude
https://www.nongnu.org/avrdude/
AVRDownloaderUploaDEr is a utility to download, upload, and manipulate the ROM and EEPROM contents of AVR micro controllers using the ISP (in-system programming technique)

Choose the AVRDUDE version over here.
cd $INITDIR
# replace `*` with appropriate version. 
# Download
curl -OL http://download.savannah.gnu.org/releases/avrdude/avrdude-6.3.tar.gz .
# Extract
tar xzf avrdude-*
cd avrdude-*
# Configure
mkdir build
cd build
../configure --prefix=$PREFIX
# Build & Install
make
make install

Here we are. We must be all setup by now to start developing on AVR MCUs.
In the next post I will try to show how to build and upload a sample program for the Arduino UNO (with ATmega328p on it).