Week 8 - Embedded Programming

Assignment


ATTiny44A - The Datasheet

The product page of the ATTiny44A collects all the essential information about this microcontroller (formely made by Atmel, now part of Microchip Inc.): datasheet, application notes, quickstart guides, pricing and sample info etc.

The datasheet is available in two formats:

The Summary is ok for getting an idea of the features of this microcontroller, but the complete one is full of interesting data.

The hardware info is extremely detailed and includes information on Pins and Packaging, integrated Analog to Digital converter, Interrupts, Clocks, Power etc.

From a programming point of view, it is interesting to note that the ATtiny44A is an 8 bit microcontroller based on a RISC Architecture with 120 instructions, and it uses an Harvard architecture, with separate memories and buses for program and data. Moreover, the micro has 32 GP registers, 4K of program memory, 256 bytes of in-system EEPROM and 256 bytes of RAM.

Programming the ATTiny44A using different languages

In Week 6 I showed how to:

This week I tried to blink my on-board LED using 4 different languages: C, Assembly, Basic and Wiring/Arduino.

C

I started from the blink code written in C and used in week 6 to test the hello board.

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

#define F_CPU 20000000UL		/* 20MHz crystal oscillator */

#define LED_PORT PORTB			/* PORTx - register for LED output */
#define LED_BIT PB2			/* bit for button input/output */
#define LED_DDR DDRB			/* LED data direction register */

#define BLINK_TIME 100			/* time to wait after a button press */

int main(void)
{
  LED_DDR |= (1 << LED_BIT);         // set LED pin as output

  while(1) {
      LED_PORT |= (1 << LED_BIT);    // turn LED on
      _delay_ms(BLINK_TIME);         // wait BLINK_TIME milliseconds

      LED_PORT &= ~(1 << LED_BIT);   // turn LED off
      _delay_ms(BLINK_TIME);         // wait BLINK_TIME milliseconds
  }
}

The code is simple - the most obscure part is the (1 << LED_BIT); bitwise operation. The (1 << LED_BIT) operation set the PORTB data register to PB2 (=2) - with 1 << Xthe bit number X is converted into a byte value that can be assigned directly to the register. In C we can set bits using bitmasking:

PORT |= 0x02     set the bit 1 only
PORT &= ~0x02    clear bit 1 only 
PORT ^= 0x01     toggle bit 1 only
PORT &= 0x02     set also bit 1

So with LED_PORT |= (1 << LED_BIT) we set the PB2 bit, and with LED_PORT |= (1 << LED_BIT) we clear the PB2 bit.

A more readable way of setting

In order to access a particular bit number as a byte value, you use the _BV() macro ("BV" stands for "bit value"). _BV(argument) converts a bit number into a byte value - since it's the compiler which perform the bitshift and then inserts the result into the code, there is no run-time overhead when using _BV().

However, using the _BV macro often makes the program more readable, as you can see below.

int main(void)
{
        LED_DDR = _BV(LED_BIT); 
        LED_PORT |= _BV(LED_BIT);

        while (1) {
       		_delay_ms(BLINK_TIME);
       		LED_PORT ^= _BV(LED_BIT);
        }
}

In both cases, I compiled the code using the avr-gcc toolchain.

Assembly

I found a basic Blink assembly code on a post on the Arduino.cc forum. I used the version of the code with the GNU Assembler syntax, so I was able to use again the GNU toolchain.

The code is quite straightforward, but I had to fix because it was targeted to the ATmega168/328p.

The ATtiny44A does not include a JMP opcode in its instruction set, probably because the smaller RJMP is able to reach the whole 4K address space (i.e. 2048 locations - from 0x0000 to 0x07FF). The same applies to the CALL instruction. If you want to know more about the program memory of the ATtiny refers to section 5.1 of the datasheet; registers are described in section 22, while a summary of the instruction set is presented in section 23.

It is worth noting how the code implements the delay function: most RISC instructions in the micro are single clock cycle execution, so they can be used to design a delay subroutine.

; Assembly Code for blinking led

;;; Allow us to use names like "PORTB"
#define __SFR_OFFSET 0

#include   "avr/io.h"

start:
	SBI     DDRB,2

blink:
	LDI     r20,250
	RCALL   delay_n_ms
	SBI     PORTB,2
	LDI     r20,250
	RCALL   delay_n_ms
	CBI     PORTB,2
	RJMP    blink

;;; Delay about r20 * 1ms.
;;; One millisecond is about 20000 cycles at 20MHz.
;;; The basic loop takes about 5 cycles, so we need about 4000 loops.
;;; Uses registries r20, r30, and r31.

delay_n_ms:
	LDI 31, 5000>>8		; high(5000)
	LDI 30, 5000&255	; low(5000)
	
delaylp:
	SBIW r30, 1
	BRNE delaylp
	SUBI r20, 1
	BRNE delay_n_ms
	RET

The code can be compiled with the GNU avr-gcc front end compiler.

Basic

I have found Great Cow BASIC, a very nice project for creating is a BASIC compiler for PIC and AVR microcontrollers released under the GNU GPL2.

The syntax of Great Cow BASIC is based on that of QBASIC/FreeBASIC, but with some alterations to suit the vastly different system that it compiles for. Great Cow BASIC will allow you to program most 8 bit PIC microcontrollers (10F, 12C, 12F, 16C, 16F, 18C and 18F chips), and also includes near-complete support for most AVR microcontrollers (Classic AVR, Tiny AVR and Mega AVR).

Given the huge amount of hours spent on my old Commodore 64, I decided I needed to give it a try.

I added the getdeb_games repo to my sources list, then I installed freebasic and some other packages with
apt install gcc-multilib libc-dev freebasic freebasic-dev lib32ncurses5-dev libncurses5-dev

Then I built GreatCowBasic from sources and installed it with

cd GreatCowBasic/Sources
chmod +x install.sh 
./install.sh build
./install.sh install

and finally I added GCBasic binaries and lib (PATH=$PATH:/opt/GCBASIC) to system path.

The code is quite readable, and can be compiled with gcbasic and the hex is generated with the makehex.sh script.

#chip tiny44a, 20
#define LED PORTB.2      'Led on PIN 14 via 0.5K resistor
DIR LED OUT
wait 1 sec

;======== MAIN PROGRAM LOOP ================
Do
   Flash_LED ( 3,250 )        '3 Flashes 250 ms equal on/off time
   Wait 2 Sec
   Flash_LED ( 5,250,500 )    '5 flashes On 250 ms / off 500 ms
   Wait 2 Sec
   Flash_LED ( 10,100 )       '10 rapid flashes
   Wait 2 Sec
Loop
;==========================================

Sub Flash_LED (in numtimes, in OnTime as WORD, optional OffTime as word = OnTime)
	repeat numtimes
		set LED on
		wait OnTime ms
		set LED OFF
		wait OffTime ms
	end repeat
End Sub

Wiring / Arduino

The language of the Arduino IDE is derived from Wiring, a (somewhat dumbed-down) standard C/C++ language which provides similar functionality for a more tightly restricted board design, plus a number of simple functions and classes for embedded systems.

Arduino/Wiring programs only need two functions to make a runnable program: setup() and loop(). So, you can think to an Arduino program as a regular C program having the following main

int main()
{
	init();
	setup();
	for(;;)
		loop();
	return 0;
}

The classic Blink.ino file looks like that

void setup() {
  pinMode(PIN_B2, OUTPUT);
}

void loop() {
  digitalWrite(PIN_B2, HIGH);       // turn the LED on (HIGH is the voltage level)
  delay(1000);                      // wait for a second
  digitalWrite(PIN_B2, LOW);        // turn the LED off by making the voltage LOW
  delay(1000);                      // wait for a second
}

The Arduino IDE can be extended to support third-party boards and additional microcontrollers. The ATTinyCore project has created an extension for essentially every ATtiny processor that makes sense to use with Arduino (including the ATtiny44).

I also wanted to use Arduino headlessly, that is compile my code without passing through the UI. Following the instructions posted here, I was able to setup a fully working workflow.

First, I installed Arduino IDE ver. 1.6.13 (the scripts for using Arduino from command line have been tested on 1.6 only until now - March 2017).

Then, I open and I installed the package SpenceKonde ATTinyCore simply cloning the git repo within the arduino-1.6.13/hardware/ATTinyCore/avr directory (first create the ATTinyCore/avr directory tree, then clone or copy the files within it).

You can also install the package from the IDE following the instructions posted on their repo, but remember that real programmers use butterflies (or, at least, command line)

Finally, I installed Sudar Arduino-Makefile for Arduino sketches - this suite defines the workflow for compiling code, flashing it to Arduino and even communicating through Serial. I had some troubles using the packaged version for Ubuntu, so I pulled the latest version from github.

The Sudar makefile suite consists of two main parts – a file named “Arduino.mk” that resides in a folder named Arduino-Makefile and a Makefile that you create for your project and locate in your project's folder.

The most complex part of this process is creating your project Makefile. I created the following Makefile, which

PROJECT_DIR       = /home/emanuele/Desktop/ardu
AVR_GCC_VERSION  := $(shell expr `avr-gcc -dumpversion | cut -f1` \>= 4.9)
ARDMK_DIR         = /home/emanuele/Arduino-Makefile
ARDUINO_DIR       = /home/emanuele/arduino-1.6.13

ARDUINO_SKETCHBOOK = /home/emanuele/arduino-1.6.13
USER_LIB_PATH     :=  $(realpath $(PROJECT_DIR)/$(ARDUINO_DIR)/hardware/tools/avr/avr/include)

BOARD_TAG         = attinyx4
BOARD_SUB         = 44
VARIANT           = tinyX4
ALTERNATE_CORE    = ATTinyCore
F_CPU             = 20000000L

AVRDUDE           = /usr/bin/avrdude
AVR_TOOLS_DIR     = $(ARDUINO_DIR)/hardware/tools/avr

CFLAGS_STD        = -std=gnu11
CXXFLAGS_STD      = -std=gnu++11
CXXFLAGS         += -pedantic -Wall -Wextra

CURRENT_DIR       = $(shell basename $(CURDIR))

OBJDIR            = $(PROJECT_DIR)/$(CURRENT_DIR)/$(BOARD_TAG)/bin

include $(ARDMK_DIR)/Arduino.mk

I run the Makefile with make and the script compiled the code and saved all the output file (.elf, .eep, .o and .hex) in the set OBJDIR (attinyx4/bin/). Finally, I uploaded the code on the board as usual with:

avrdude -p t44 -P usb -c usbtiny -U flash:w:Blink.hex

Sizes

It's worth comparing the size of the binaries produced by different compilers - Assembly wins, while Wiring seems the most greedy language.

Program (.text + .data + .bootloader) Data (.data + .bss + .noinit)
C 90 bytes (2.2%) 0 bytes (0.0 %)
Assembly 30 bytes (0.7%) 0 bytes (0.0 %)
Basic 137 bytes (6.69%) 6 bytes (2.35 %)
Wiring 892 bytes (21.8%) 9 bytes (3.5%)

Download zone - List of files