Tags

, , , ,

When I last did anything with microcontrollers, it was programming, in assembler language, Motorola M68000 development boards. The Arduino Uno, in comparison to the M68K, is much more accessible, cheaper and easier to work with. It’s also extremely well documented. Though there’s not much one could do with an Arduino board without additional I/O hardware (because it’s a controller), it’s still a better resource than the Raspberry Pi for learning how a microprocessor system works at a low level. If we look at the hardware, we can see why:

Don’t feel daunted by the apparent complexity of the schematics and what’s on the board – most the components are for voltage regulation. What we’re interested in here is a microcontroller, 14 GPIO pins, 6 analogue input pins and a few ancilliary features.
The core component on the Arduino board is the large chip to the right of the schematic. This is the ATmega328 microcontroller, which is essentially a microprocessor. The following diagram shows how each element of that chip maps to its pins:

Most the labels are self-explanatory, showing the I/O, system clock, memory for op code and runtime data, and a data bus. In the main schematic, you’ll notice that every pin to the right of the ATmega IC is connected directly to one of three GPIO rails, and the ATmega IC itself isn’t soldered to the board, so it can be transferred to a custom-made PCB after being programmed.
A diagram is also available for the AVR CPU core:

The processor reads a bootloader and application bytecode from the integrated Flash memory, which I believe has a capacity of 32KB – more than sufficient for a moderately complex program. In practice, there’s a lot more space to play with than I initially thought.
And just like any computer system, the ATmega328 has RAM, which is actually Static RAM, or ‘SRAM’. This has a 2KB region allocated to general data, 32 general purpose data registers and 64 I/O registers. Separate to these are the stack pointer program counter and status registers. Unlike the memory typically used in computing devices, the data structure wouldn’t be dynamic, since there’s a single program with static buffers and data types.

Also on the board, by the way, is an ATmega16U2 IC, which is another, smaller, microcontroller. This appears to be the USB controller, though, functioning as the USB-to-Serial converter.

Developer Environment, Language and API

Available from the official site (linked here), the Arduino IDE is a simple editor, compiler and library of header files. Programs are coded in a variant of C++, with includes for libraries relating to various hardware modules that might be connected to the board. For example, if to use the Ethernet hardware module, we’d import Ethernet.h. The include statements are auto-completed by the Arduino IDE when selecting the hardware from the ‘Include Library,‘ list under the ‘Tools‘ tab.

The following is the typical program structure:

This is what the IDE presents us with whenever a new file is opened. Each program starts with a setup() function, which executes whenever the reset button is pressed or there’s a reset signal to the processor. This function appears to poll the serial interface at 9,600 times per second.


void setup()
{
// initialize serial communication at 9600 bits per second:
Serial.begin(9600);
}

The difference between this and a conventional C++ program is we use ‘void loop()‘ instead of ‘void main()‘ for our main code. This is because we’re doing event-driven programming, where we want the microcontroller to continue looping through the same routine until there is a hardware interrupt. If we wanted a program to execute just once, the main section can be placed in setup() instead.

Let’s look at the setup function for the Ethernet programming example:

void setup()
{
Ethernet.begin(mac, ip, myDns, gateway, subnet);
server.begin();
Serial.begin(9600);
while (!Serial) {;}
}

We can read from a GPIO pin and manipulate the input as a value. e.g.

int sensorValue = analogRead(A0);
Serial.println(sensorValue);

So, in the above function, five variables are passed to Ethernet.begin(), which must be another function somewhere in the header file. The server.begin() and Serial.begin() also are imported from header files.

After coding we use the Verify/Compile operation to create a binary. This will be a bytecode image containing the machine code for our program and bootloader. This bytecode image is loaded into the microcontroller’s memory.

A Couple of My Own Lame Examples

Just to try it out, I added a program that adds, subtracts, multiplies and divides two integers. The main code is in the setup() function.

void setup() {
// put your setup code here, to run once:
Serial.begin(9600);

int firstNum = 124;
int secondNum = 1334;
int summed = firstNum + secondNum;
int multiplied = firstNum * secondNum;
int divided = secondNum / firstNum;
int subtracted = secondNum - firstNum;

Serial.println("Summed: ");
Serial.println(summed);
Serial.println("Multiplied: ");
Serial.println(multiplied);
Serial.println("Divided: ");
Serial.println(divided);
Serial.println("Subtracted: ");
Serial.println(subtracted);
}

void loop()
{
}

All I needed to do was click the ‘Verify/Compile‘ option, wait about 20 seconds and get a compiler message giving some info about the created image:
Sketch uses 1934 bytes (5%) of program storage space. Maximum is 32256 bytes.
Global variables use 232 bytes (11%) of dynamic memory, leaving 1816 bytes for local variables. Maximum is 2048 bytes.

The program is then ready to be uploaded – that is, the image is copied to the controller’s Flash memory. Notice there are two LEDs marked ‘TX’ and ‘RX’. The first will flash when data is being written, and the latter flashes when data is being read. The program starts to execute immediately after it’s been uploaded. Under the ‘Tools‘ tab there’s a ‘Serial Monitor‘ option for viewing the output.

Another simple example uses the integrated LED to signal my name in Morse code. An abbreviated version of the code:

void setup() {
// initialize the integrated LED
pinMode(LED_BUILTIN, OUTPUT);
}

void loop() {
// This is a dash
digitalWrite(LED_BUILTIN, HIGH);
delay(200);
digitalWrite(LED_BUILTIN, LOW);
Serial.print("-");

// This is also a dash
digitalWrite(LED_BUILTIN, HIGH);
delay(200);
digitalWrite(LED_BUILTIN, LOW);
delay(50);
Serial.print("-");

// This is a space
delay(100);
Serial.print(" ");
[...]
}

Advertisements