Skip to content

Embedded Programming

  • Read the datasheet for your microcontroller
  • Use your programmer to program your board to do something
  • Try other programming languages and development environments
  • Compare the performance and development workflows for other architectures

This week is about understanding how to embed code in our projects. Luckily I have some experience programming in Python (but especially for data analysis) and I made a few Arduino projects in C++. So this not entirely new to me: I know the basics and I like to code clean. But I would really like to understand better our little microchips this week, and talk to them properly, without the help of Arduino libraries.

Retrospective of the previous weeks

On the Electronic Production week I programmed sucessfully the Hello Board T412 with my UPDI programmer using the Arduino IDE, as I was already familiar with this kind of programmation.

UPDI programming ATtiny 412

I made my own little Arduino during the Electronic Design week with a SAMD11D of 20 pins. I also made a nice pinout for this chip as I found none online, feel free to use it for your projects!

After programming it with the SWD programmer, I made it blink sucessfully using the Arduino IDE. I also tested the integrated button and made it work.

Code

To blink an LED, you only need to send a signal to the pin linked to your LED. HIGH or 1 for lighting up, LOW or 0 to lighting off. Within the Arduino IDE, you can use the digitalWrite() function to send this information. Add a delay() (in milliseconds) and your LED is blinking!

#define LED_PIN 7

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

void loop() {
  digitalWrite(LED_PIN, HIGH);
  delay(100);
  digitalWrite(LED_PIN, LOW);
  delay(100);
}

Upload

To upload your sketch into a microcontroller, you need to define in Tools the board you want to upload the code to. With the new microchips we use in Fab Academy we use the Fab SAM core for Arduino maintained and enhanced by Quentin Bolsee.

Arduino IDE parameters
Board Generic D11D14AS
Clock Source INTERNAL_USB_CALIBRATED_OSCILLATOR
USB Config CDC_ONLY
Serial Config TWO_UART_ONE_WIRE_NO_SPI
Bootloader size 4KB_BOOTLOADER
Timer PWM Frequency 732.4Hz (16-bit)
Floating Point Print & String use auto-promoted doubles only
Build Options config.h enabled (mostly code size reduction)

params

Once everything is ready, you can now compile your code by clicking on Verify or hitting Ctrl + R. Once it’s done without mistakes (check your semi-colons!), you can then upload your code to your board by clicking on Upload or hitting Ctrl + U. You should be greated by a blinking LED and a message like this: Verify successful / done in 0.044 seconds / CPU reset.

Memory comparison

When uplaoding my code to my custom Arduino, I noticed that almost all the space available was taken. It was weird for a code as simple as a blink, so I ran a few tests with other configurations and other boards to try to understand what was happening. Spoiler: I don’t understand yet what the hell is happening with these chips 😢

SAMD11D14AS

  • With config.h disabled
    Sketch uses 9856 bytes (80%) of program storage space. Maximum is 12288 bytes.
    
  • With config.h enabled (mostly code size reductions)
    Sketch uses 9192 bytes (74%) of program storage space. Maximum is 12288 bytes.
    
  • Using another fork from MattairTech SAMD Core by wagiminator and config.h enabled.
    Sketch uses 8780 bytes (71%) of program storage space. Maximum is 12288 bytes.
    

SAMD11C14A

Sketch uses 9784 bytes (79%) of program storage space. Maximum is 12288 bytes.

UNO & Nano

After testing with the SAM microchips I tested some classic Arduino boards and the space taken was ten times smaller!

  • Arduino UNO

    Sketch uses 922 bytes (2%) of program storage space. Maximum is 32256 bytes.
    

  • Arduino Nano

    Sketch uses 922 bytes (3%) of program storage space. Maximum is 30720 bytes.
    

ATtiny 412

I then tested with an ATtiny and the megaTinyCore and obtained a result even more tiny

Sketch uses 540 bytes (13%) of program storage space. Maximum is 4096 bytes.

When doing these tests I was highly frustrated with the Arduino IDE: every time I had to check my parameters in Tools and it was not possible to have an history of what I was doing. I decided to test Platform IO because my instructor told me it was a nice way to keep track of your experiments in an organized manner.

Platform IO

I first read the Quick Start guide but preferred to use the instructions given on the Fab programmers repository. I ran my first test with a blink example using my UPDI as a programmer and my Hello Board T412 made during Electronic Production week.

platform.ini
[env:ATtiny412]
platform = atmelmegaavr
board = ATtiny412
framework = arduino
upload_protocol = custom
upload_speed = 115200
upload_port = /dev/ttyACM0
upload_flags =
  -a
  write
  -b
  $UPLOAD_SPEED
  -d
  $BOARD_MCU
  -u
  $UPLOAD_PORT
upload_command = python3 ~/.platformio/packages/framework-arduino-megaavr-megatinycore/tools/prog.py $UPLOAD_FLAGS -f $SOURCE -v

In the code we need to import the Arduino library to be able to use functions like digitalWrite().

src/main.cpp
#include <Arduino.h>

#define LED 2

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

void loop() {
  digitalWrite(LED, HIGH);
  delay(100);
  digitalWrite(LED, LOW);
  delay(100);
}

When compiling I obtained no errors, so I uploaded the code to the board.

Verify successful. Data in flash matches data in specified hex-file
Action took 0.06s
pymcuprog.serialupdi.application - INFO - Leaving NVM programming mode
pymcuprog.serialupdi.application - INFO - Apply reset
pymcuprog.serialupdi.physical - INFO - send 3 bytes
pymcuprog.serialupdi.application - INFO - Release reset
pymcuprog.serialupdi.physical - INFO - send 3 bytes
pymcuprog.serialupdi.physical - INFO - send 3 bytes
pymcuprog.serialupdi.physical - INFO - Closing port '/dev/ttyACM0'
======================================================== [SUCCESS] Took 2.65 seconds ========================================================

I checked the memory usage and observed that it was slightly lighter than with the Arduino IDE. I’m beginning to understand that different ways of uploading may cause problems with memory.

RAM:   [          ]   3.9% (used 10 bytes from 256 bytes)
Flash: [=         ]  12.5% (used 512 bytes from 4096 bytes)
As a reminder, the same code compiled within the Arduino IDE took 540 bytes of program storage space.

I liked very much my experience with Platform IO. Sadly, I can’t use it with the SAMD yet, but my instructor Stephane is working on it to make it available, fingers crossed!

Failing is learning

I was pretty happy to make my board blink without issues on the previous weeks, and very happy to see my integrated button working too (see the work done in Electronic Design week).

I was now ready to make my integrated adressables RGB leds work as well. I already used them a lot during one of my favorite project and documented how I used the FastLED library to program them asynchronously (in French).

I also want to use them in my final project, so it’s kind of a rehearsal for that too.

Dioramanino zoom on rgb leds

I would like to try other capabilities of my custom board to see if it works, as the I2C connection for example.

Adressable RGB Leds

The leds I soldered on my board are WS2812B. I already saw a problem when plugging my board after soldering: I noticed that according to the position of the USB connector –note for myself, I really need to use a mini-USB variant next time– the leds were lightning up without reasons. It’s just a blast of light and nothing more. If I move my board I can obtain it again but I can’t see a reason why. I checked the board a million times with my instructor and we couldn’t find a reason for this unconsistant behavior.

I decided to test the code anyway and save this problem for later (of course it was a bad idea, but I wanted them so much to work!).

FastLED library

To start I used the popular FastLED library: I used it before and I loved its versatility. I started with a really simple blinking code where I indicated the number of leds on my board (3) and the pin (23).

#include <FastLED.h>

#define NUM_LEDS 3
#define DATA_PIN 23
#define LED_TYPE WS2812B

CRGB leds[NUM_LEDS];

void setup() { 
  FastLED.addLeds<LED_TYPE, DATA_PIN, GRB>(leds, NUM_LEDS);
}

void loop() { 
  leds[0] = CRGB::Red;
  FastLED.show();
  delay(500);

  leds[0] = CRGB::Black;
  FastLED.show();
  delay(500);
}

Unfortunately the library seemed to not know the SAMD11D, according to this error message.

Arduino: 1.8.19 (Linux), Board: "Generic D11D14AS, Print & String use auto-promoted doubles only, config.h disabled, INTERNAL_USB_CALIBRATED_OSCILLATOR, 732.4Hz (16-bit), 4KB_BOOTLOADER, TWO_UART_ONE_WIRE_NO_SPI, CDC_ONLY"

In file included from /home/ejoz/Arduino/libraries/FastLED/src/FastLED.h:43:0,
                 from /home/ejoz/Arduino/Blink_RGB/Blink_RGB.ino:1:
/home/ejoz/Arduino/libraries/FastLED/src/led_sysdefs.h:55:2: error: #error "This platform isn't recognized by FastLED... yet.  See comments in FastLED/led_sysdefs.h for options."
 #error "This platform isn't recognized by FastLED... yet.  See comments in FastLED/led_sysdefs.h for options."
  ^
exit status 1
Error compiling for board Generic D11D14AS.

Info

I found out that then copying an error message from the Arduino IDE, it was formatted to help explaining the error to others. You can see it on the first line, it indicates which board and parameters you used: a very useful feature from Arduino IDE.

Error messages are like a cluedo sometimes, you need to try to understand the mistery: who is guilty, in which room (file) ? So I followed the white rabbit to the source files and looked into Arduino/libraries/FastLED/src/led_sysdefs.h.

src/led_sysdefs.h
//
// We got here because we don't recognize the platform that you're
// trying to compile for: it's not AVR, or an ESP or ARM that we recognize.
//
// If you're reading this because you got the error below,
// and if this new platform is just a minor variant of an
// existing supported ARM platform, you may be able to add
// a new 'defined(XXX)' selector in the apporpriate code above.
//
// If this platform is a new microcontroller, see "PORTING.md".
//

As far as I knew, my SAMD wasn’t that special: it was ARM and possibly very similar to an SAMD21. So I added it to the file to see what was going to happen.

src/led_sysdefs.h
#include "platforms/arm/stm32/led_sysdefs_arm_stm32.h"
#elif defined(__SAMD11D14AS__) || defined(__SAMD21G18A__) || defined(__SAMD21J18A__) || defined(__SAMD21E17A__) || defined(__SAMD21E18A__)

It kind of worked. At least the error changed, so I think I’m on the right path to solve this. Unfortunately another error showed up and I spend a lot of time trying to read on the internet what caused this without finding out.

Strange scope declaration error
Arduino: 1.8.19 (Linux), Board: "Generic D11D14AS, Print & String use auto-promoted doubles only, config.h disabled, INTERNAL_USB_CALIBRATED_OSCILLATOR, 732.4Hz (16-bit), 4KB_BOOTLOADER, ONE_UART_ONE_WIRE_ONE_SPI, CDC_ONLY"

In file included from /home/ejoz/Arduino/libraries/FastLED/src/FastLED.h:67:0,
                from /home/ejoz/Arduino/Blink_RGB/Blink_RGB.ino:1:
/home/ejoz/Arduino/libraries/FastLED/src/fastspi.h:145:23: note: #pragma message: No hardware SPI pins defined.  All SPI access will default to bitbanged output
#      pragma message "No hardware SPI pins defined.  All SPI access will default to bitbanged output"
                    ^
/home/ejoz/Arduino/Blink_RGB/Blink_RGB.ino: In function 'void setup()':
Blink_RGB:6:18: error: 'WS2812B' was not declared in this scope
#define LED_TYPE WS2812B
                ^
/home/ejoz/Arduino/Blink_RGB/Blink_RGB.ino:12:19: note: in expansion of macro 'LED_TYPE'
FastLED.addLeds<LED_TYPE, DATA_PIN, BGR>(leds, NUM_LEDS);  // GRB ordering is typical
                ^
Blink_RGB:12:58: error: no matching function for call to 'CFastLED::addLeds(CRGB [3], int)'
FastLED.addLeds<LED_TYPE, DATA_PIN, BGR>(leds, NUM_LEDS);  // GRB ordering is typical
                                                        ^
/home/ejoz/Arduino/Blink_RGB/Blink_RGB.ino:12:58: note: candidates are:
In file included from /home/ejoz/Arduino/Blink_RGB/Blink_RGB.ino:1:0:
/home/ejoz/Arduino/libraries/FastLED/src/FastLED.h:225:130: note: template<ESPIChipsets CHIPSET, unsigned char DATA_PIN, unsigned char CLOCK_PIN, EOrder RGB_ORDER, long unsigned int SPI_DATA_RATE> CLEDController& CFastLED::addLeds(CRGB*, int, int)
template<ESPIChipsets CHIPSET,  uint8_t DATA_PIN, uint8_t CLOCK_PIN, EOrder RGB_ORDER, uint32_t SPI_DATA_RATE > CLEDController &addLeds(struct CRGB *data, int nLedsOrOffset, int nLedsIfOffset = 0) {
                                                                                                                                ^
/home/ejoz/Arduino/libraries/FastLED/src/FastLED.h:225:130: note:   template argument deduction/substitution failed:
Blink_RGB:12:58: error: template argument 1 is invalid
FastLED.addLeds<LED_TYPE, DATA_PIN, BGR>(leds, NUM_LEDS);  // GRB ordering is typical
                                                        ^
In file included from /home/ejoz/Arduino/Blink_RGB/Blink_RGB.ino:1:0:
/home/ejoz/Arduino/libraries/FastLED/src/FastLED.h:239:95: note: template<ESPIChipsets CHIPSET, unsigned char DATA_PIN, unsigned char CLOCK_PIN> static CLEDController& CFastLED::addLeds(CRGB*, int, int)
template<ESPIChipsets CHIPSET,  uint8_t DATA_PIN, uint8_t CLOCK_PIN > static CLEDController &addLeds(struct CRGB *data, int nLedsOrOffset, int nLedsIfOffset = 0) {
                                                                                            ^
/home/ejoz/Arduino/libraries/FastLED/src/FastLED.h:239:95: note:   template argument deduction/substitution failed:
Blink_RGB:12:58: error: template argument 1 is invalid
FastLED.addLeds<LED_TYPE, DATA_PIN, BGR>(leds, NUM_LEDS);  // GRB ordering is typical
                                                        ^
In file included from /home/ejoz/Arduino/Blink_RGB/Blink_RGB.ino:1:0:
/home/ejoz/Arduino/libraries/FastLED/src/FastLED.h:253:113: note: template<ESPIChipsets CHIPSET, unsigned char DATA_PIN, unsigned char CLOCK_PIN, EOrder RGB_ORDER> static CLEDController& CFastLED::addLeds(CRGB*, int, int)
template<ESPIChipsets CHIPSET,  uint8_t DATA_PIN, uint8_t CLOCK_PIN, EOrder RGB_ORDER > static CLEDController &addLeds(struct CRGB *data, int nLedsOrOffset, int nLedsIfOffset = 0) {
                                                                                                                ^
/home/ejoz/Arduino/libraries/FastLED/src/FastLED.h:253:113: note:   template argument deduction/substitution failed:
Blink_RGB:12:58: error: template argument 1 is invalid
FastLED.addLeds<LED_TYPE, DATA_PIN, BGR>(leds, NUM_LEDS);  // GRB ordering is typical
                                                        ^
In file included from /home/ejoz/Arduino/Blink_RGB/Blink_RGB.ino:1:0:
/home/ejoz/Arduino/libraries/FastLED/src/FastLED.h:359:25: note: template<template<EOrder RGB_ORDER> class CHIPSET, EOrder RGB_ORDER> static CLEDController& CFastLED::addLeds(CRGB*, int, int)
static CLEDController &addLeds(struct CRGB *data, int nLedsOrOffset, int nLedsIfOffset = 0) {
                        ^
/home/ejoz/Arduino/libraries/FastLED/src/FastLED.h:359:25: note:   template argument deduction/substitution failed:
Blink_RGB:12:58: error: wrong number of template arguments (3, should be 2)
FastLED.addLeds<LED_TYPE, DATA_PIN, BGR>(leds, NUM_LEDS);  // GRB ordering is typical
                                                        ^
In file included from /home/ejoz/Arduino/Blink_RGB/Blink_RGB.ino:1:0:
/home/ejoz/Arduino/libraries/FastLED/src/FastLED.h:365:25: note: template<template<EOrder RGB_ORDER> class CHIPSET> static CLEDController& CFastLED::addLeds(CRGB*, int, int)
static CLEDController &addLeds(struct CRGB *data, int nLedsOrOffset, int nLedsIfOffset = 0) {
                        ^
/home/ejoz/Arduino/libraries/FastLED/src/FastLED.h:365:25: note:   template argument deduction/substitution failed:
Blink_RGB:12:58: error: wrong number of template arguments (3, should be 1)
FastLED.addLeds<LED_TYPE, DATA_PIN, BGR>(leds, NUM_LEDS);  // GRB ordering is typical
                                                        ^
exit status 1
'WS2812B' was not declared in this scope

I think I have to make a custom porting for the SAMD11D14AS… I’m not feeling at all confident to do this right now, so I think I’m gonna dive into low level programming: it will give me enough understanding of the registers and port definition to modify the FastLED library (or at least I hope so). If not, I will switch to another board for my final project.

Adafruit NeoPixel library

Before diving in C code, I wanted to give a try to the Adafruit library. I downloaded it and tested it with a simple code.

#include <Adafruit_NeoPixel.h>

#define PIN 23
#define NUMPIXELS 3

Adafruit_NeoPixel pixels(NUMPIXELS, PIN, NEO_GRB + NEO_KHZ800);

void setup() {
  pixels.begin();
}

void loop() {
  pixels.clear();

  for(int i=0; i<NUMPIXELS; i++) {
    pixels.setPixelColor(i, pixels.Color(0, 150, 0));
    pixels.show();

    delay(500);
  }
}

I obtained no errors during compilation.

Sketch uses 10496 bytes (85%) of program storage space. Maximum is 12288 bytes.

And no errors during upload. However… nothing is happening on the board.

Verify successful
done in 0.055 seconds
CPU reset.

With the Logic Analyzer we own at the lab, I tried to understand what was happening. I made a simple test with one of my free available pin first, to check I I remembered correctly how to use it.

logic

I connected the free pin 4 and the ground to the logic analyzer, and send a simple blink code to the board. In PulseView, I configured a PWM protocol decoder and observed my LED blinking on the screen.

pwm

After that I searched for a protocol dedicated to RGB leds and configured it. I uploaded the code above (but change the PIN to 4) but observed nothing on PulseView. Another mistery…

rgb

OLED

I also gathered some ressources to try my mini-OLED screen I wanted to connect in I2C. I tried it without success due to memory usage errors: I will circle back on that later on (during the Output Devices week).

Introduction to C language

It was time to dive in. I’m as excited as I was when I started learning python for real. I used C++ before but only followed tutorials or examples from Arduino and tweaking them a bit. I wish I will better understand what I’m doing this time. I found a tremendous course online that is specific to learning C in the context of embedded systems.

Development Tool Data Flow

First I read how worked the C ecosystem thanks to this data flow schematic (by Microchip, in the course mentionned above). You write the source files (.c) and the headers files (.h), then you compile them thanks to the make file. The make file calls the compiler that produces assembly source files (.asm).

Compiler

To compile the files, there is three processes: the preprocessor removes comments, whitespaces and merges header and source files. The parser analyses the code and pass a list of actions to the code generator, which is unique to each microcontroller architecture (that’s why I obtained different results according to the one I was using).

Once it’s compiled, the assembler transforms the .asm file into an object file (.o), which is a list of actions in binary machine code. Then the object files are treated by the librarian (or archiver) that collects them in a .lib file. Finally the linker combines object files and library files into a single executable file: in our case, an hex file (.hex) readable by microcontrollers.

Blink HEX file

Within the Arduino IDE, you can go to Sketch > Export compiled Binary to obtain an HEX file. For some reason, when I choose the Fab SAM core, I obtain an heavier BIN file and not an HEX file. Maybe my memory issue comes from there?

Blink HEX file for Arduino Nano
:100000000C945C000C946E000C946E000C946E00CA
:100010000C946E000C946E000C946E000C946E00A8
:100020000C946E000C946E000C946E000C946E0098
:100030000C946E000C946E000C946E000C946E0088
:100040000C9412010C946E000C946E000C946E00D3
:100050000C946E000C946E000C946E000C946E0068
:100060000C946E000C946E00000000002400270029
:100070002A0000000000250028002B0004040404CE
:100080000404040402020202020203030303030342
:10009000010204081020408001020408102001021F
:1000A00004081020000000080002010000030407FB
:1000B000000000000000000011241FBECFEFD8E0B8
:1000C000DEBFCDBF21E0A0E0B1E001C01D92A930AC
:1000D000B207E1F70E945C010C94CB010C94000084
:1000E000EBEAF0E02491E7E9F0E09491E3E8F0E056
:1000F000E491EE23C9F0222339F0233001F1A8F472
:10010000213019F1223029F1F0E0EE0FFF1FEE58F7
:10011000FF4FA591B4912FB7F894EC91811126C0AF
:1001200090959E239C932FBF08952730A9F02830E7
:10013000C9F0243049F7209180002F7D03C0209121
:1001400080002F7720938000DFCF24B52F7724BD48
:10015000DBCF24B52F7DFBCF2091B0002F772093EC
:10016000B000D2CF2091B0002F7DF9CF9E2BDACFF7
:100170003FB7F8948091050190910601A091070185
:10018000B091080126B5A89B05C02F3F19F0019634
:10019000A11DB11D3FBFBA2FA92F982F8827BC01E1
:1001A000CD01620F711D811D911D42E0660F771F09
:1001B000881F991F4A95D1F708958F929F92AF9209
:1001C000BF92CF92DF92EF92FF920E94B8004B0154
:1001D0005C0184E6C82ED12CE12CF12C0E94B800E1
:1001E000681979098A099B09683E73408105910560
:1001F000A8F321E0C21AD108E108F10888EE880EC0
:1002000083E0981EA11CB11CC114D104E104F104C7
:1002100029F7FF90EF90DF90CF90BF90AF909F9025
:100220008F9008951F920F920FB60F9211242F9363
:100230003F938F939F93AF93BF93809101019091D0
:100240000201A0910301B09104013091000123E06B
:10025000230F2D3758F50196A11DB11D20930001E4
:100260008093010190930201A0930301B0930401D4
:100270008091050190910601A0910701B0910801BC
:100280000196A11DB11D8093050190930601A093D5
:100290000701B0930801BF91AF919F918F913F915A
:1002A0002F910F900FBE0F901F90189526E8230FE7
:1002B0000296A11DB11DD2CF789484B5826084BD11
:1002C00084B5816084BD85B5826085BD85B581605A
:1002D00085BD80916E00816080936E0010928100D8
:1002E0008091810082608093810080918100816093
:1002F0008093810080918000816080938000809154
:10030000B10084608093B1008091B00081608093DF
:10031000B00080917A00846080937A0080917A00A6
:10032000826080937A0080917A00816080937A0065
:1003300080917A00806880937A001092C100E7E98A
:10034000F0E02491E3E8F0E08491882399F090E0D4
:10035000880F991FFC01E859FF4FA591B491FC014A
:10036000EE58FF4F859194918FB7F894EC91E22B62
:10037000EC938FBFC0E0D0E081E00E9470000E944B
:10038000DD0080E00E9470000E94DD002097A1F354
:0A0390000E940000F1CFF894FFCFA7
:00000001FF

I haven’t finish the course yet but I intend to go a little farther after the Fab Academy, here are some notes for myself 📚

Data Types

Type Description Size (bits)
char Single Character 8
int Integer 16
float Single Precision Floating Point Number 32
double Double Precision Floating Point Number 64

Assignment Operators

Operator Operation Example Result
= Assignment x = y Assign x the value of y
+= Compound Assignment x += y x = x + y
-= Compound Assignment x -= y x = x - y
*= Compound Assignment x *= y x = x * y
/= Compound Assignment x /= y x = x / y
%= Compound Assignment x %= y x = x % y
&= Compound Assignment x &= y x = x & y
^= Compound Assignment x ^= y x = x ^ y
|= Compound Assignment x |= y x = x | y
«= Compound Assignment x <<= y x = x << y
»= Compound Assignment x >>= y x = x >> y

Memory Addressing Operators

Operator Operation Example Result
& Address of &x Pointer to x
* Indirection *p The object or function that p points to
[ ] Subscripting x[y] The yth element of array x
. Struct/Union Member x.y The member named y in the structure or union x
-> Struct/Union Member by Reference p->y The member named y in the structure or union that p points to

Just for fun, you can read about The International Obfuscated C Code Contest to remind yourself to put comments in your code.

Speaking (almost) binary

SAMD Datasheet

I already read a good deal of the datasheet when designing my custom Arduino. This week I want to concentrate on the registers. I also found a really great video presenting the SAMD11X microchips by Quentin and Nicolas from last year: it’s a good introduction to these microcontrollers.

SAMD pinout

Accessing registers

Now that I know a bit more about structuring programs in C, I’m starting to understand why it’s important to use Data Pointers in the context of embedding programs. I found another course on the Microchip website about accessing SAM MCU Registers which is a great introduction to that. I initally began with this course but went back to the fundamentals to understand better what I was reading.

The course is about the SAM D21, but the theory can be applied to any MCU. In Arduino IDE, you can access a pin by typing #define LED_PIN 2;: it’s possible because the entire peripheral register set is mapped into a instance header file, which is itself mapped in a simpler way. For the SAMD11 I found the port.h file under .arduino15/packages/arduino/tools/CMSIS-Atmel/1.0.0/CMSIS/Device/ATMEL/samd11/include/instance/.

Extract of registers definition in port.h
#define REG_PORT_DIR0              (0x41004400U) /**< \brief (PORT) Data Direction 0 */
#define REG_PORT_DIRCLR0           (0x41004404U) /**< \brief (PORT) Data Direction Clear 0 */
#define REG_PORT_DIRSET0           (0x41004408U) /**< \brief (PORT) Data Direction Set 0 */
#define REG_PORT_DIRTGL0           (0x4100440CU) /**< \brief (PORT) Data Direction Toggle 0 */
#define REG_PORT_OUT0              (0x41004410U) /**< \brief (PORT) Data Output Value 0 */
#define REG_PORT_OUTCLR0           (0x41004414U) /**< \brief (PORT) Data Output Value Clear 0 */
#define REG_PORT_OUTSET0           (0x41004418U) /**< \brief (PORT) Data Output Value Set 0 */
#define REG_PORT_OUTTGL0           (0x4100441CU) /**< \brief (PORT) Data Output Value Toggle 0 */
#define REG_PORT_IN0               (0x41004420U) /**< \brief (PORT) Data Input Value 0 */
#define REG_PORT_CTRL0             (0x41004424U) /**< \brief (PORT) Control 0 */
#define REG_PORT_WRCONFIG0         (0x41004428U) /**< \brief (PORT) Write Configuration 0 */
#define REG_PORT_PMUX0             (0x41004430U) /**< \brief (PORT) Peripheral Multiplexing 0 */
#define REG_PORT_PINCFG0           (0x41004440U) /**< \brief (PORT) Pin Configuration 0 */

On the datasheet you can find these informations on 22.7. Register Summary (p. 386-7).

The registers can be accessed in an indirect way using Device-Specific Header Files, named samd11d14as.h in our case. This file contains the base addresses for all peripherals and components. Below you can see an excerpt of this file showing the base address definition of the PORT in the SAMD11D14AS.

Extract from /samd11/include/samd11d14as.h
#define PORT              ((Port     *)0x41004400UL) /**< \brief (PORT) APB Base Address */
#define PORT_IOBUS        ((Port     *)0x60000000UL) /**< \brief (PORT) IOBUS Base Address */
#define PORT_INST_NUM     1                          /**< \brief (PORT) Number of instances */
#define PORT_INSTS        { PORT }                   /**< \brief (PORT) Instances List */

You can also find on the PIO folder the shortcuts that we use to access pins directly.

Extract from /samd11/include/pio/samd11d14as.h
#define PIN_PA02                           2  /**< \brief Pin Number for PA02 */
#define PORT_PA02                 (1ul <<  2) /**< \brief PORT Mask  for PA02 */
#define PIN_PA03                           3  /**< \brief Pin Number for PA03 */
#define PORT_PA03                 (1ul <<  3) /**< \brief PORT Mask  for PA03 */
#define PIN_PA04                           4  /**< \brief Pin Number for PA04 */
#define PORT_PA04                 (1ul <<  4) /**< \brief PORT Mask  for PA04 */
#define PIN_PA05                           5  /**< \brief Pin Number for PA05 */
#define PORT_PA05                 (1ul <<  5) /**< \brief PORT Mask  for PA05 */
#define PIN_PA06                           6  /**< \brief Pin Number for PA06 */
#define PORT_PA06                 (1ul <<  6) /**< \brief PORT Mask  for PA06 */
#define PIN_PA07                           7  /**< \brief Pin Number for PA07 */
#define PORT_PA07                 (1ul <<  7) /**< \brief PORT Mask  for PA07 */
...

The files must be connected to informations in the datasheet: the port system is defined in 22. PORT for example (p. 379). It controls the I/O (inputs and outputs) of the MCU and can be configured for general purposes or assigned to an embedded device peripheral.

Principle of Operation (p. 381)

The I/O pins of the device are controlled by reads and writes of the PORT peripheral registers. For each port pin, a corresponding bit in the Data Direction (DIRn) and Data Output Value (OUTn) registers are used to enable that pin as an output and to define the output state.

The direction of each pin in a port group is configured via the DIR register. If a bit in DIR is written to one, the corresponding pin is configured as an output pin. If a bit in DIR is written to zero, the corresponding pin is configured as an input pin.

When the direction is set as output, the corresponding bit in the OUT register is used to set the level of the pin. If bit y of OUT is written to one, pin y is driven high. If bit y of OUT is written to zero, pin y is driven low. Additional pin configuration can be set by writing to the Pin Configuration (PINCFG0) registers.

The principle of operations is more explicitly described in the 22.6.3 Basic Operation), where I understood how to configure a pin (it was time).

Basic Operation (p.382)

Each I/O pin y can be configured and accessed by reading or writing PORT registers. Because PORT registers are grouped into sets of registers for each group of up to 32 pins, the base address of the register set for pin y is at byte address PORT + (y / 32) * 0x80. (y%32) will be used as the index within that register set.

To use pin y as an output, configure it as output by writing the (y%32) bit in the DIR register to one. To avoid disturbing the configuration of other pins in that group, this can also be done by writing the (y%32) bit in the DIRSET register to one. The desired output value can be set by writing the (y%32) bit to that value in register OUT.

Similarly, writing an OUTSET bit to one will set the corresponding bit in the OUT register to one, while writing an OUTCLR bit to one will set it to zero, and writing an OUTTGL bit to one will toggle that bit in OUT.

To use pin y as an input, configure it as input by writing the (y%32) bit in the DIR register to zero. To avoid disturbing the configuration of other pins in that group, this can also be done by writing the (y%32) bit in DIRCLR register to one. The desired input value can be read from the (y%32) bit in register IN as soon as the INEN bit in the Pin Configuration register (PINCFGy) is written to one. Refer to “I/O Multiplexing and Considerations” on page 13 for details on pin configuration.

You can dive into the 22.8 Register Description (p.387) to understand how to use them.

For example, it means that if you want to set the pin 23 HIGH, you can type REG_PORT_OUTSET0 = (1 << 23); or REG_PORT_OUTSET0 = PORT_PA23;, as described in 22.8.7 Data Output Value Set (p. 395).

Okay, I need a break. I don’t know if you bare with me until this moment, but I think it’s time to get out of the datasheet and theory to blink an LED, don’t you think?

End of the week

Unfortunately I don’t have enough time to test everything I learned this week, but I hope it will make the following Electronic weeks easier!

About this week

What a week! As I already programmed my custom Arduino two weeks ago I thought it was gonna be easy. Foolish me. I think I made good progress in understanding how was working a MCU and how to program it without the Arduino layer.

I’m happy to have learned so much new things, but kind of frustrated to not be able to test it yet. In two weeks we’ll be back on with the Output Devices!


Last update: April 27, 2022
Back to top