Skip to content

Week 04 - Embedded programming

Weekly goals

  • group assignment: try and compare several MCU / boards
  • browse a MCU datasheet
  • program something on a MCU, use simple inputs and outputs
  • interact with a MCU with the serial monitor

Week's explorations and achievements

  • I had previous experience programming a microcontroller embedded on a commercial board, mainly Arduino Uno boards and Adafruit Circuit Playground boards.
  • This week I discovered a new family of microcontrollers that I'll use often during Fabacademy, the ATtiny family. I started with the ATtiny1614, thanks to a homemade development board designed and milled by our instructor, and soldered by myself during the student's bootcamp.
  • I learned a little more on how the memory is structured in these types of architecture, on how to browse a MCU datasheet and even on how to write directly in the registers.
  • I used the Arduino IDE to program various commercial boards connected in USB to my computer and a simple ATtiny development board, after installing all the required packages.
  • I installed the library megaTinyCore to easily use the common Arduino functions to program the ATtiny1614.
  • I learned how to use an UPDI programmer to upload code an ATtiny.
  • I used serial communication with an ATtiny thanks to the FTDI protocol.
  • I discovered how to use the function analogWrite() to control a simple LED's brightness, thanks to Pulse width modulation.
  • I checked several commercial boards pinouts and compared how similar sketches were managed by their CPU's memory. I also included our homemade development board in this comparison (see group assignment).
  • I used the serial monitor to parse values that I typed, thanks to the parseInt() function. The retrieved value controlled a neopixel RGB LED colors and the brightness of simple LEDS (1 red, 1 green and 1 blue).
  • I used a RC522 RFID reader with the SPI protocol to demonstrate an idea for my final project: using some RFID passive tags to change neopixel LEDs red, green and blue value.
  • Reflect on what I've learned this week: This week I discovered a lot of things: fundamental principles of memory and registers and how to directly read and write in them, programming ATtiny chips thanks to the UPDI protocol and accessing Serial informations thanks to FTDI, comparing the memory of various commercial boards, using the PWM to control leds brightness, parse values in the serial monitor... I had already used the RFID RC522 reader in the past but it was good doing these new tests for my final project. It was also nice doing a very first basic proof of concept of my final project.

General memory considerations

Memory

I found this very useful article Arduino Memory Guide that helped me having a better overview of how memory works in microcontrollers. Here are some fundamental principles that I'm citing from this article:

A microcontroller unit (also known as an MCU) is an integrated circuit (IC), typically used to perform specific applications or tasks. Usually, this type of IC gathers information or data from its surroundings, processes it, and generates specific outputs according to the gathered data. [...] One essential part of a microcontroller is its memory; memory stores information temporarily or permanently in microcontrollers, and can be used for several purposes. -

Memory blocks are semiconductor devices that store and retrieve information or data.

Memory blocks in microcontrollers are usually described as arrays. Memory arrays are divided into cells that can store data and be accessed using a unique identifier representing its address or position relative to the memory array. Information in memory cells is stored using binary digits (bits), usually organized in bytes (8-bits); it can also be retrieved later by the MCU or other components of a microcontroller-based system.

Memory in computing systems can be volatile or non-volatile. Volatile memory is a temporary memory, this means that data is stored while the system is running, but it is lost forever when the system is turned off. Non-volatile memory is permanent memory; data is not lost even if the system is turned off.

Speaking of memory architecture, two big types can be encountered: von Neumann architecture and Harvard architecture. Hybrid architecture can also be found. Moreover, microcontrollers can be separated in two families, AVR® and ARM®. AVR® family microcontrollers are based on the Harvard architecture model. ARM® family microcontrollers can be based on either von Neuman or Harvard architectures models.

Memory types

All the different memory units inside a microcontroller can be divided into two main types: RAM and ROM. RAM (from Random-Access Memory) in microcontroller-based systems is a volatile memory used to store temporary data such as the system's firmware variables. ROM (from Read-Only Memory) in microcontroller-based systems is non-volatile memory used to store permanent data such as the system's firmware.

RAM and ROM in microcontroller-based systems are organized into three main categories:

  • Flash
  • RAM
  • EEPROM

Flash

Flash memory in microcontroller-based systems is part of its ROM. The flash memory is where the system's firmware is stored to be executed. For example, think of the famous Blink.ino sketch: when we compile this sketch, we create a binary file that is later stored in the flash memory of an Arduino board. The sketch is then executed when the board is powered on.

RAM

RAM in microcontroller-based systems is where the system's temporary data or run-time data is stored; for example, the variables created by functions of a program. RAM in microcontrollers usually is SRAM; this is a type of RAM that uses a flip-flop to store one bit of data. There is also another type of RAM that can be found in microcontrollers: DRAM.

EEPROM

In microcontroller-based systems, Erasable Programmable Read-Only Memory, or EEPROM, is also part of its ROM; actually, Flash memory is a type of EEPROM. The main difference between Flash memory and EEPROM is how they are managed; EEPROM can be managed at the byte level (write or erased) while Flash can be managed at the block level.

Getting started with ATtiny1614

We'll now focus on the microcontroller I'll use this week, the ATtiny1614.

ATtiny (also known as TinyAVR) is a subfamily of the popular 8-bit AVR microcontrollers, which typically has fewer features, fewer I/O pins, and less memory than other AVR series chips. The first members of this family were released in 1999 by Atmel (later acquired by Microchip Technology in 2016).- Wikipedia ATtiny microcontroller comparison chart

Browsing the datasheet

Datasheet of the ATtiny1614

Memory

Here's the memory map of the ATtiny1614 that can be found in the datasheet:

6.2 Memory Map (p20)

datasheet

This table gives us the size of the different types of memory of the MCU that I mentionned earlier, and the corresponding adresses range. We can also see some memory storage labelled as registers, which I'll talk about in the next subsection.

Thus a simple sketch like attiny-led-serial.ino (see more below) returns the following message in the console:

Sketch uses 2277 bytes (13%) of program storage space. Maximum is 16384 bytes.
Global variables use 155 bytes (7%) of dynamic memory, leaving 1893 bytes for local variables. Maximum is 2048 bytes.

Registers

The registers are storage space organized in arrays that are easily accessible to read and/or write data. Thanks to our instructor, we learned how to basically manipulate those registers to program simple instructions. You can read more about it in the secion writing in the registers.

The datasheet provides the structure of the MCU's registers and their adresses.

7.1 Peripheral address map (p44)

datasheet

The peripherals adresses are divided in three sections: PORT A, PORT B and PORT C.

16.4 Register Summary : (p136) (PORT I/O PIN configuration)

datasheet

The register summary lists all the registers adresses. We'll use the register DIR, OUT and IN.

16.5.1 Data direction (p137)

datasheet

The Data direction register DIR will allow us to declare which input or output we want to declare in order to be able to access them while the program will be running. We can either declare them as INPUT or OUTPUT, depending on the value we assign to the byte. As we can read in the datasheet, 0 is the value to set the port to input only pin and 1 is the value for output pin. This will be useful for our example in the secion writing in the registers.

16.5.5 Output (p141)

datasheet

The Output register allows us to set an output value of a specific pin. As we can read in the datasheet, 0 corresponds to low level voltage and 1 corresponds to high level voltage.

We're gonna write directly in the Data Direction register and in the Output register (see the secion writing in the registers

Test circuit

Our instructor had prepare beforehand a minimal circuit to test the ATtiny1614 with simple inputs and outputs. It includes a built-in LED, 3 leds (we could pick the colors so I had chose a red, a green and a blue one), a pushbutton, and the possibility to use UPDI and FTDI protocols through a pin header (x6).

Here is the circuit design and the connections in Kicad (designed and produced by my instructor):

circuit circuit

We soldered the components during the bootcamp.

UPDI programmer

Also, he provided us a UPDI programmer. Indeed, code cannot be uploaded directly to an ATtiny1614. Uploading code is the number one thing you want to do when you learn how to programm a board, because it's how you pass all the instructions that you want your MCU to execute.

Some MCU have an integrated programmer or rely on a bootloader. If you are used to commercial boards with ESP32, ESP8266, or Arduino boards, you may have never used an external programmer board. For the ATtiny though, we need to use a UPDI programmer. UPDI is a dedicated protocol, which presents the advantage of requiring only one pin.

Here the programmer circuit board also allows us to establish serial communication through the FTDI protocol. Indeed, the ATtiny doesn't have the serial communication built-in.

Here is the design of the UPDI programmer / FTDI adapter, and the circuit in Kicad (designed, produced and soldered by my instructor):

circuit circuit

To upload code through the UPDI programmer, we can use this 6 wires strip. Be carefull to align correctly the UPDI and the Attiny circuit one to another in order to plug the cable in the correct way. When facing the two circuits, the dots on the MCU must be in the same orientation (for example top-left-corner).

boards

Then you plug the USB UPDI programmer in your computer's USB port. (I use an adapter because my computer only have USB C ports).

And now you're almost ready to write some simple commands in C/C++ in a development environment (IDE), compile them and send them through USB! I'll use the Arduino IDE, which is really dedicated to programm electronic boards. I'll probably also give a try to PlatformIO's Visual Studio Code extension. If I decide to programm a board using micropython, I may also try other interfaces.

Choosing the correct MCU / board in the IDE

After downloading the Arduino IDE (my version is Arduino 2.0.3), you need to select the board / MCU you're using to send them a program. You have the choice in some usual commercial boards (such as Arduino UNO, MEGA, etc.) but for a lot of projects you'll don't find your board / MCU in the default list. You'll then have to download extra content through the board manager. Here's how to do it in Arduino IDE:

  1. Find the URL of the package you need to add for your specific MCU / board family. Copy it.
  2. Once you found it, open Arduino and go in the very top menu : Arduino IDE > Preferences, then look at the bottom of the dialog for Additional Board Manager URL. Paste your URL.
  3. You can now close the dialog and open the Boards Manager: in the top menu, go in Tools > Boards > Boards Manager. This should open a new section or dialog, depending on your Arduino version. In mine it's develops a menu in the left of the window. You can have direct access by clicking on the Boards manager icon.
  4. Here you can use the search bar to look for your board, and click on Install once you found it.
  5. Once it's done you should be able to select your board / MCU in the menu Tools > Boards. Be careful to select the very same model as yours'!
  6. Don't forget to select the chip you're using in Tools > Chip

Uploading code with a UPDI programmer

To upload code with the UPDI programmer, I have first selected which programmer to use: go in the top menu, Tools > Programmer > SerialUPDI - SLOW (57600 bauds)

Then you have to go in Sketch > Upload using programmer.

I wrote this simple sketch to turn on and off my 3 LEDs and uploaded it with no issue.

#define LED_R 10 //PA3
#define LED_G 9 //PA2
#define LED_B 8 //PA1
#define BTN 3 //PA7

int del = 200; //delay in ms

void setup() {
  pinMode(LED_R, OUTPUT);
  pinMode(LED_G, OUTPUT);
  pinMode(LED_B, OUTPUT);
  pinMode(BTN, INPUT_PULLUP); //the button is connected to the ground
}

void loop() {

digitalWrite(LED_R, HIGH);
delay(del);
digitalWrite(LED_R, LOW);
delay(del);

digitalWrite(LED_G, HIGH);
delay(del);
digitalWrite(LED_G, LOW);
delay(del);

digitalWrite(LED_B, HIGH);
delay(del);
digitalWrite(LED_B, LOW);
delay(del);

}

Writing in the registers

Under the guidance of our instructor and thanks to the datasheet, we were able to modify directly the values of the registers in the MCU memory to program similar instructions.

We first used the Data direction register to declare the pin as an output: we need to pass the corresponding pin byte value to 1. But we don't want to change any other pin. That's why we use a logic operator OR | and a mask corresponding to the wanted byte.

  • In our case we want to declare the red led's pin as an output.
  • We look for it in the pinout diagram: it's the PA3 pin
  • We look in the datasheet for the Data Direction register corresponding for Port A pins, and for this specific pin.
  • The register is located at PORTA.DIR and the the PA3 pin is simply the third byte.
  • A mask for the third byte is: 00000100. We'll use a macro shortcut to avoid writing it binary each time: PIN3_bm
  • Finally, setting the third byte to 1 would be assigning PORTA.DIR to the new value PORTA.DIR | PIN3_bm. In C you can simply write PORTA.DIR |= PIN3_bm instead of PORTA.DIR = PORTA.DIR | PIN3_bm
  • Don't forget the ;at the end of any instruction (C language).

We then used the Output register to control the ouput value (LOW or HIGH) on PA3. In the datasheet we can read that we can refer to this register with PORTA.OUT and that PA3 is again the third byte. We'll use the same principle of a logic mask. * We first need to change the value of our third byte to 1 as an equivalent to digitalWrite(PA3, HIGH);. That means setting only the value of our third byte whatever the other bytes values are. So we will use exactly the same logic operator as for the PORTA.dir, which is the OR operator, with the third byte mask. * We then need to set the same byte of the same register to 0 (equivalent of digitalWrite(PA3,LOW);). This time you need to use the logic operators AND + NO: PORTA.OUT = PORTA.OUT & ~PIN3_bm.

Finally this gives us the following instructions, turning on and off our red led on PA3:

void setup() {

PORTA.DIR |= PIN3_bm; // PIN3_bm is a macro reffering to 00000100
}

void loop() {

PORTA.OUT |= PIN3_bm; // PORTA.OUT = PORTA.OUT | PIN3_bm
delay(300);
PORTA.OUT &= ~PIN3_bm; // PORTA.OUT = PORTA.OUT & ~PIN3_bm
delay(300);

}

That was very interesting to dig in the registers! But for the rest of the week I will simply use high level functions, it will definitely be more efficient.

Serial communication

To use serial communication with the ATtiny board, I need to select the FTDI mode on the UPDI/FTDI circuit. My instructor indeed put a small switch to go from one functionnality to another.

Serial communication with the IDE's serial monitor will allow us to collect real-time information as the program is executed. To ask for such information, you need first to initiate the serial communication with Serial.begin(9600) (the value 9600 corresponds of the speed of the communication, given in bauds). Then you have to write instructions in your program, such as Serial.print(...), specifying the messages or the values you want to print in the serial monitor.

Once it's done, you only have to open the serial monitor to see the information printed in real-time. In Arduiono IDE 2 you have an icon with a magnifier lens in the window's top right corner.

Here's a programm to send commands directly from the serial monitor. I used the Serial.parseInt() function to parse integer values written in the monitor. I thus defined 4 parsed values that will condition instructions in my code:

  • if the value entered in the serial monitor is 1, it turns on the red led on and print a message in the monitor
  • if the value entered in the serial monitor is 2, it turns on the green led on and print a message in the monitor
  • if the value entered in the serial monitor is 3, it turns on the blue led on and print a message in the monitor
  • if the value entered in the serial monitor is 4, it turns off all the leds and print a message in the monitor

Remark Avoid using 0 as a value conditionning some instructions, because 0 is often sent in the buffer for some reason.

You can try it yourself with the attached ino file. The corresponding code is:

#define LED_R 10 //PA3
#define LED_G 9 //PA2
#define LED_B 8 //PA1

int del = 100; //delay in ms

void setup() {

  pinMode(LED_R, OUTPUT);
  pinMode(LED_G, OUTPUT);
  pinMode(LED_B, OUTPUT);

  digitalWrite(LED_R, LOW);
  digitalWrite(LED_G, LOW);
  digitalWrite(LED_B, LOW);

  Serial.begin(9600); 
}

void loop() {
  if (Serial.available()) {
      int state = Serial.parseInt();
      if (state == 1) {
        digitalWrite(LED_R, HIGH);
        Serial.println("Command completed LED R turned ON");
      }
      if (state == 2) {
        digitalWrite(LED_G, HIGH);
        Serial.println("Command completed LED G turned ON");
      }

      if (state == 3) {
        digitalWrite(LED_B, HIGH);
        Serial.println("Command completed LED B turned ON");
      }
      if (state == 4) {
        digitalWrite(LED_R, LOW);
        digitalWrite(LED_G, LOW);
        digitalWrite(LED_B, LOW);

        Serial.println("Command completed LEDs turned off");
      }
  }
}

Here's a short video to demonstrate the principle:

Controlling the brightness thanks to PWM

In such digital devices, analog built-in outputs are rare. Nevertheless, it is very common that your board / MCU provides one or several pulse width modulation outputs (PWM), that basically emulates the continuous variations of an analog output. The principle of PWM is to alternate between the high voltage value and the low one with a specified period (for example 0V and 5V). The duration of the period high + low itself is constant, but the ratio of high value duration and low value duration can be changed. The mean amplitude can then vary between the low and the high value, mapping continuous voltage values.

Even if LEDs are controlled with the function digitalWrite(), allowing us to command a binary state (LOW or HIGH), we can use the function analogWrite() to monitor its brightness. I thus decided to vary the brightness of my LEDs thanks to PWM. But for that I need to check something important: that they are connected to PINs configured for PWM!

For this, I have to refer to the Pinout diagram of my MCU.

pinout attiny

I'm in luck, one of my LED is indeed on a pin compatible with PWM - the red one, connected to PA3. I was then able to write a program to modify the brightness of my red LED through the serial monitor. I was first entering values between 1 and 255, then changed for values entered in percentage by introducing a float variable and a line of calculus. Why not 0? Always the buffer issue I didn't solve yet.

#define LED_R 10  //PA3 PWM

int del = 1000;  //delay

void setup() {

  pinMode(LED_R, OUTPUT);
     digitalWrite(LED_R, LOW);
    Serial.begin(9600);
}

void loop() {
  if (Serial.available()) {
    float brightness = Serial.parseInt();
    int state = int(brightness/100.0*255.0);
    if ((state < 256) & (state > 0)) {
      analogWrite(LED_R, state);
      Serial.println("LED R turned at " + String(brightness) + " % brightness");
      delay(del);
    }
  }
}

Here is a picture of it. I didn't include any video because you'll see more serial brightness control in the rest of this week's documentation.

pinout attiny

Trying out the pushbutton

The connections of the pushbutton indicates it is a pullup button: it is by default at level 1 (when not pressed), and when you press it it goes down to level 0.

A pushbutton looks like an easy component to use, but as our instructor explained, it is never perfect, meaning that it cannot switch instantly from one voltage to another: there is a short delay with inconsistant voltage, as a rebound. That's why the good way to programm them is to use Debounce libraries.

I found and try a first one, with no success.

wkoch debounce library

Then my instructor gave the link of its own library.

buttonstates debounce library.

I wasn't seeing any progress, when I realized that I had a hardware problem, and not a code problem! That really looked like a short circuit on my pushbutton, because when I pressed it even the 'built-in led' turned off. So I carried on with the rest of what I wanted to test.

Group assignment

See the group assignment page

I insert the group assignment here as it is chronologically what I did after. The goal was rather broad as I understood it: we had to compare various MCU / boards, not only in theory but also by programming them.

I focused on the following aspects:

  • The programming workflow
  • The pinout, specifically the PWM outputs
  • The flash and sarm memory

Since I was out of office the days I worked on it, I only used the commercial boards I had at my home, plus my ATtiny. I was then able to program and compare :

  • my ATtiny1614 custom circuit (designed by my Instructor)
  • a D1 mini dev board from A-Z circuit with an ESP8266EX
  • a Node MCU 1.0 Amica board, also with an ESP8266EX
  • a Feather Huzzah 32 board from Adafruit, with an ESP-WROOM-32
  • an Arduino UNO with an ATMega328p (*seems it's "outdated" but that's what I had home!)

I kept only the NodeMCU board and not the D1 mini dev board in my comparision summary as they work similarly. Also I had issues with the D1 mini dev board at the end of my eletronic work hours when I decided to get back to my board comparision, so I just carried on with the NodeMCU.

boards

My methodology:

  • boards manager Finding the correct boards familiy package, installing it, picking the correct board / mcu. The workflow was the same for every board as they are all commercial boards with everythhing necessary build in. They could all directly be plugged to the computer's USB port with a USB cable.

Here are all the URL and boards I picked:

Board / MCU Installing packages url Board package name Board name
ATtiny1614 http://drazzy.com/package_drazzy.com_index.json megaTinyCore ATtiny3224/1624/1614/1604/824/814/804/424/414/404/214/204
Adafruit Feather Huzzah32 https://raw.githubusercontent.com/espressif/arduino-esp32/gh-pages/package_esp32_index.json esp32 Adafruit ESP32 Feather
D1 mini dev board ESP8266 http://arduino.esp8266.com/stable/package_esp8266com_index.json esp8266 LOLIN (WEMOS) D1 R2 & mini
Node MCU Amica ESP8266 http://arduino.esp8266.com/stable/package_esp8266com_index.json esp8266 Node MCU 1.0 (ESP-12E Module)
Arduino UNO none Arduino Arduino UNO
  • pinout diagrams

I had a couple of LEDs and few resistors at home, I hopefully found a 390 ohm resistor to protect my red led. I was then able to make a simple test circuit.

I wanted to uploaded identical sketches to the various boards and wanted to carry on with the led brigthness pwm controlling, so whichever the board I needed to connect my LED to a pin working as a PWM output.

I thus looked for the various pinout diagrams. Here is an example with the Feather Huzzah32 pinout (the other are here and there on this page!)

pinout diagram

  • uploading sketches and comparing the use of the memory

I then uploaded the sketches to the various boards, changing only the LED pin, and wrote down informations about the memory. You can find them all here:

See the group assignment page

Going further with the serial communication

Controlling a neopixel through the serial monitor

Circuit and components

For this part I used the ESP8266EX MCU with the D1 dev mini board - because it was compact and worked just fine - but I could have use any other commercial board with an AVR MCU. I thus switched later on for an Arduino UNO (RFID section).

I experimented the serial communication by adding more LEDs to my circuit: one red, one green, one blue, and a neopixel RGB LED (all in my electronic components mess at home). For each of them I needed to place a resistor between the board pins and the LED +, to protect them. You can find typical values with a quick search on internet. It differs from one color to another. For the Neopixel, Adafruit's Neopixel Uberguide advise to use a resistor between 300 and 500 ohms. I used the resistors that I found at home, which were of 390 and 220 ohms.

Also as I will use again the PWM to vary the voltage and play on the brightness, red green and blue LEDs need to be connected to PWM output pins. The neopixel will only be controlled with binary information, so it's ok to use an ordinary digital outuput.

Here is the pinout of the board, indicating the pwm outputs with a wavy line:

pinout diagram

  • RED LED on pin D5. Place a 390 ohms resistor between D5 and the LED +. - connected to the ground
  • GREEN LED on pin D8. Place a 220 ohms resistor between D5 and the LED +. - connected to the ground
  • BLUE LED on pin D7. Place a 390 ohms resistor between D5 and the LED +. - connected to the ground
  • Neopixel on pin D1. Place a 390 ohms resistor between D1 and the data IN Neopixel PIN. - connected to the ground. + connected to 5V PIN of the board.

Here is a nice link to calculate the resistor value.

Libraries

Furthermore I need to install a library to controll the neopixel. The most used library is the Adafruit Neopixel Library. I thus installed it in Arduino IDE. Don't forget to include the library in your sketch with #include <Adafruit_NeoPixel.h>!

Sketch

The goal of my skecth illustrates one functionnality I want to have in my Final project. It corresponds to the color pallette. Indeed, the color pallette is like a paint pod where you would mix primary colors: red, green and blue, and also black to diminish the brightness. I thought about mixing white if I use RGBW LEDs in the future, but for this week I will refer to 'turn off the LEDs and reset the values' when you'll see the word 'White'.

So to sum up I'll ask the user to enter successively a r value between 0 and 255 that will both change the brightness of the red LED and set the 'R' color value of the RGB neopixel. Same for green and blue. Apart from being a demonstration of serial communication, it is also a sort of pedagogical device to understand how RGB color mode works.

//D1 mini

#include <Adafruit_NeoPixel.h>

#define LED_R D5  
#define LED_G D8
#define LED_B D7
#define PIX D1 // Pin where I connect my neopixel / neopixel strip
#define NUMPIXELS 1 // number of neopixels. Only one here!

Adafruit_NeoPixel pixels(NUMPIXELS, PIX, NEO_GRB + NEO_KHZ800); //declaring an object thanks to the library

int del = 100;  // delay in ms
int r = 0; 
int g = 0 ; 
int b = 0;
// r, g and b are integer variables where we will store values parsed from the serial monitor (entered manually by the user). They will be used to monitor both the brightness of the 3 simple LEDs (respectively the red, the green and the blue one) and the (R,G,B) color value of the Neopixel.

void setup() {

  pinMode(LED_R, OUTPUT);
  pinMode(LED_G, OUTPUT);
  pinMode(LED_B, OUTPUT);
  pinMode(PIX, OUTPUT);

  pixels.begin();
  pixels.clear();

  digitalWrite(LED_R, LOW);
  digitalWrite(LED_G, LOW);
  digitalWrite(LED_B, LOW);

  Serial.begin(9600); // initiate serial communication

}

void loop() {

  while(Serial.available() == 0){
  } // while nothing is sent through serial communication, do nothing

  r = Serial.parseInt();
  // store the parsed values acquired in the monitor in a variable
  //(integer delcared at the beginning of the skecth)
  while (r == 0){
    delay(del);
    r = Serial.parseInt();
  }
     Serial.println("Value of red color is " + String(r));
  analogWrite(LED_R, r); 
  pixels.setPixelColor(0, pixels.Color(r,g,b)); //starts at 0!
  pixels.setBrightness(155);
  pixels.show();

  while(Serial.available() == 0){
  }

  g = Serial.parseInt();   
  while (g == 0){
    delay(del);
    g = Serial.parseInt();
    }
  Serial.println("Value of green color is " + String(g));
  analogWrite(LED_G, g); 
  pixels.setPixelColor(0, pixels.Color(r,g,b)); //starts at 0!
  pixels.setBrightness(155);
  pixels.show();

  while(Serial.available() == 0){
  }

  b = Serial.parseInt();   
  while (b == 0){
    delay(del);
    b = Serial.parseInt();
    }
  Serial.println("Value of blue color is " + String(b));
  analogWrite(LED_B, b); 
  pixels.setPixelColor(0, pixels.Color(r,g,b)); //starts at 0!
  pixels.setBrightness(155);
  pixels.show();

}

The difficult part of writing this sketch was managing the serial buffer as I recieved several unwanted "0". First I managed artificially to avoid these zeros and parse values for r, g and b with delays but what worked in the end was :

  • avoiding moments where nothing happened with while(Serial.available() == 0){} instead of using the if (Serial.available()) {} condition
  • asking to reevaluate my r,g,b values while they are equal to zero:
while (g == 0){
    delay(del);
    g = Serial.parseInt();
    }

Here is a video of both the monitor and the circuit:

RFID tags

Another part of the exercise is also to read values in the serial monitor. For now I don't have any inputs in my circuit apart from the instructions sent through the serial monitor. I first thought of using some sensors like a PIR motion sensor, but what made more sense for me was mimicking my final project color palette.

One of the solution I thought about was using a RFID reader and RFID passive tags. I had used some in the past, with not always success, but at least I had the components at home!

What is RFID techology?

RFID technology is a low cost and low power distant communication technology. I'll cite the article of the website lastminuteengineers.com that gives a simple introduction to RFID technology and to a component I'll use this week and which is quite popular in Arduino projects, the RC522 RFID reader.

An RFID or radio frequency identification system consists of two main components, a tag attached to the object to be identified, and a reader that reads the tag.

A reader consists of a radio frequency module and an antenna that generates a high frequency electromagnetic field. It consists of a microchip that stores and processes information, and an antenna for receiving and transmitting a signal.

The tag is usually a passive device (it does not have a battery). When the tag is brought close to the reader, the reader generates an electromagnetic field. This causes electrons to move through the tag’s antenna and subsequently powers the chip.

The chip then responds by sending its stored information back to the reader in the form of another radio signal. This is called a backscatter. The reader detects and interprets this backscatter and sends the data to a computer or microcontroller.

RC522 RFID reader and pinout

Here's what lastminuteengineers.com explains about RC522 RFID readers:

The RC522 RFID reader module is designed to create a 13.56MHz electromagnetic field and communicate with RFID tags (ISO 14443A standard tags).

The reader can communicate with a microcontroller over a 4-pin SPI with a maximum data rate of 10 Mbps. It also supports communication over I2C and UART protocols.

The RC522 RFID module can be programmed to generate an interrupt, allowing the module to alert us when a tag approaches it, instead of constantly asking the module “Is there a card nearby?”.

The module’s operating voltage ranges from 2.5 to 3.3V, but the good news is that the logic pins are 5-volt tolerant, so we can easily connect it to an Arduino or any 5V logic microcontroller without using a logic level converter.

Technical Specifications

Specification Value
Host Interface SPI / I2C / UART
SPI / I2C / UART 2.5 V to 3.3 V
Max. Operating Current 13-26mA
Min. Current(Power down) 10µA
Logic Inputs 5V Tolerant
Read Range 5 cm

I chose SPI protocol as I already used it on another project, but maybe I should also try other protocols during the communication week. These readers work with 3.3 V so you should connect them to the 3.3V pin. It's probably better to use a level shifter if you're designing your own board and it's powered with 5V, but it seems that its logic pins are 5-volt tolerant.

They need a lot of PINs to work, due to the use of the SPI protocol.

pinout diagram

Here's also the Arduino UNO Pinout Diagram:

pinout diagram

I also had RFID passive tags: one blue badge and several transparent stickers.

Libraries

I used the MFRC522 library of miguelbalboa, which is in freeze mode, that is very low maintenance and no further developement. Why this one? Because I used it in the past and it was okay, but I may switch to another library if I carry on with these RFID readers, and depeding on the MCU family I use. I also have to admit I could'nt make it work with my D1 mini so I used a regular Arduino UNO. It relies on SPI protocol so you also have to include the SPI.h library. For that one you don't need to install anything new, it usually comes with the board manager packages.

Connections depends from the board you’re using.

ESP8266 UNO
Wemos D1 mini
Signal Pin Pin
RST/Reset D3 9
SPI SS D8 10
SPI MOSI D7 11
SPI MISO D6 12
SPI SCK D5 13

Read the UID of passive tags

I used the DumpInfo.ino example which comes up with the rfid library to read the UID of my various passive tags in the serial monitor. The UID is a unique identifier, a sort of 'address' of the tags. I could also read other info of my tags, but UID is the one that interests me the most since I use conditions to trigger various actions depending on which tag is presented to the reader.

I had nothing to change to the example to read the UID, except the pins at the beginning of the sketch that vary depending of your board. Don't forget to adapt them too:

#define RST_PIN         9          // Configurable, see typical pin layout above
#define SS_PIN          10         // Configurable, see typical pin layout above

I had two types of tags: one blue badge and several stickers

This is when I read the blue badge:

Firmware Version: 0x92 = v2.0
Scan PICC to see UID, SAK, type, Firmware Version: 0x92 = v2.0
Scan PICC to see UID, SAK, type, and data blocks...
**Card UID: 9A 2B 98 15**
Card SAK: 08
PICC type: MIFARE 1KB
Sector Block   0  1  2  3   4  5  6  7   8  9 10 11  12 13 14 15  AccessBits
  15     63   00 00 00 00  00 00 FF 07  80 69 FF FF  FF FF FF FF  [ 0 0 1 ] 
         62   00 00 00 00  00 00 00 00  00 00 00 00  00 00 00 00  [ 0 0 0 ] 
         61   00 00 00 00  00 00 00 00  00 00 00 00  00 00 00 00  [ 0 0 0 ] 
         60   00 00 00 00  00 00 00 00  00 00 00 00  00 00 00 00  [ 0 0 0 ] 
  14     59   00 00 00 00  00 00 FF 07  80 69 FF FF  FF FF FF FF  [ 0 0 1 ] 
         58   00 00 00 00  00 00 00 00  00 00 00 00  00 00 00 00  [ 0 0 0 ] 
         57   00 00 00 00  00 00 00 00  00 00 00 00  00 00 00 00  [ 0 0 0 ] 
         56   00 00 00 00  00 00 00 00  00 00 00 00  00 00 00 00  [ 0 0 0 ] 
  13     55   00 00 00 00  00 00 FF 07  80 69 FF FF  FF FF FF FF  [ 0 0 1 ] 
         54   00 00 00 00  00 00 00 00  00 00 00 00  00 00 00 00  [ 0 0 0 ] 
         53   00 00 00 00  00 00 00 00  00 00 00 00  00 00 00 00  [ 0 0 0 ] 
         52   00 00 00 00  00 00 00 00  00 00 00 00  00 00 00 00  [ 0 0 0 ] 
  12     51   00 00 00 00  00 00 FF 07  80 69 FF FF  FF FF FF FF  [ 0 0 1 ] 
         50   00 00 00 00  00 00 00 00  00 00 00 00  00 00 00 00  [ 0 0 0 ] 
         49   00 00 00 00  00 00 00 00  00 00 00 00  00 00 00 00  [ 0 0 0 ] 
         48   00 00 00 00  00 00 00 00  00 00 00 00  00 00 00 00  [ 0 0 0 ] 
  11     47   00 00 00 00  00 00 FF 07  80 69 FF FF  FF FF FF FF  [ 0 0 1 ] 
         46   00 00 00 00  00 00 00 00  00 00 00 00  00 00 00 00  [ 0 0 0 ] 
         45   00 00 00 00  00 00 00 00  00 00 00 00  00 00 00 00  [ 0 0 0 ] 
         44   00 00 00 00  00 00 00 00  00 00 00 00  00 00 00 00  [ 0 0 0 ] 
  10     43   00 00 00 00  00 00 FF 07  80 69 FF FF  FF FF FF FF  [ 0 0 1 ] 
         42   00 00 00 00  00 00 00 00  00 00 00 00  00 00 00 00  [ 0 0 0 ] 
         41   00 00 00 00  00 00 00 00  00 00 00 00  00 00 00 00  [ 0 0 0 ] 
         40   00 00 00 00  00 00 00 00  00 00 00 00  00 00 00 00  [ 0 0 0 ] 
   9     39   00 00 00 00  00 00 FF 07  80 69 FF FF  FF FF FF FF  [ 0 0 1 ] 
         38   00 00 00 00  00 00 00 00  00 00 00 00  00 00 00 00  [ 0 0 0 ] 
         37   00 00 00 00  00 00 00 00  00 00 00 00  00 00 00 00  [ 0 0 0 ]

The stickers return less values, here are 3 examples :

Card UID: 04 FB 32 22 DA 64 80
Card SAK: 00
PICC type: MIFARE Ultralight or Ultralight C
Page  0  1  2  3
  0   04 FB 32 45
  1   22 DA 64 80
  2   1C 48 00 00
  3   E1 10 3E 00
  4   03 00 FE 00
  5   00 00 00 00
  6   00 00 00 00
  7   00 00 00 00
Card UID: 04 F3 30 22 DA 64 80
Card SAK: 00
PICC type: MIFARE Ultralight or Ultralight C
Page  0  1  2  3
  0   04 F3 30 4F
  1   22 DA 64 80
  2   1C 48 00 00
  3   E1 10 3E 00
Card UID: 04 02 34 22 DA 64 81
Card SAK: 00
PICC type: MIFARE Ultralight or Ultralight C
Page  0  1  2  3
  0   04 02 34 BA
  1   22 DA 64 81
  2   1D 48 00 00
  3   E1 10 3E 00
  4   03 00 FE 00
  5   00 00 00 0
  6   00 00 00 00
  7   00 00 00 00

In all the case it is the first row of the matrix that interests me. For now I will start with 5 sticker tags. Here are their adress and how i named them:

Name Card UID
Red 04 FB 32 22 DA 64 80
Green 04 F3 30 22 DA 64 80
Blue 04 87 31 22 DA 64 80
White 04 96 31 22 DA 64 80
Black 04 02 34 22 DA 64 81

Trigger actions depending on the tag presented

The provided example allow us to use several RC522 readers, while it could cause some trouble as we're using SPI protocol. I set values to use only one reader.

I then adapted the example to trigger actions. As in my serial examples, I wanted to change the brightness of the three LEDs and the (R,G,B) color of the Neopixel.

The logic is a bit different and closer to what I want to do in my final project, which is not entering a fixed value but increment it with a given tag. So here I will increment r, g and b variables from 10 to 10 (max is 255, min is 0).

Also I added a black tag which decreases the neopixel's brightness by 20 (max is 255, min is 0), and a white tag which turns all LEDs off and reset all values.

/**

 * --------------------------------------------------------------------------------------------------------------------
 * Example sketch/program showing how to read data from more than one PICC to serial.
 * --------------------------------------------------------------------------------------------------------------------
 * This is a MFRC522 library example; for further details and other examples see: https://github.com/miguelbalboa/rfid
 *
 * Example sketch/program showing how to read data from more than one PICC (that is: a RFID Tag or Card) using a
 * MFRC522 based RFID Reader on the Arduino SPI interface.
 *
 * Warning: This may not work! Multiple devices at one SPI are difficult and cause many trouble!! Engineering skill
 *          and knowledge are required!
 *
 * @license Released into the public domain.
 *
 * Typical pin layout used:
 * -----------------------------------------------------------------------------------------
 *             MFRC522      Arduino       Arduino   Arduino    Arduino          Arduino
 *             Reader/PCD   Uno/101       Mega      Nano v3    Leonardo/Micro   Pro Micro
 * Signal      Pin          Pin           Pin       Pin        Pin              Pin
 * -----------------------------------------------------------------------------------------
 * RST/Reset   RST          9             5         D9         RESET/ICSP-5     RST
 * SPI SS 1    SDA(SS)      ** custom, take a unused pin, only HIGH/LOW required **
 * SPI SS 2    SDA(SS)      ** custom, take a unused pin, only HIGH/LOW required **
 * SPI MOSI    MOSI         11 / ICSP-4   51        D11        ICSP-4           16
 * SPI MISO    MISO         12 / ICSP-1   50        D12        ICSP-1           14
 * SPI SCK     SCK          13 / ICSP-3   52        D13        ICSP-3           15
 *
 */

#include <SPI.h>
#include <MFRC522.h>
#include <Adafruit_NeoPixel.h>

#define LED_R 3  
#define LED_G 5
#define LED_B 6
#define PIX 4
#define NUMPIXELS 1 //number of neopixels


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

int del = 50;  //delay
int r = 0;
int g = 0; 
int b = 0;
int w = 0;
int k = 200;

#define RST_PIN 9          // Configurable, see typical pin layout above
#define SS_1_PIN 10        // Configurable, take a unused pin, only HIGH/LOW required, must be diffrent to SS 2
//#define SS_2_PIN 49          // Configurable, take a unused pin, only HIGH/LOW required, must be diffrent to SS 1

#define NR_OF_READERS 1

byte ssPins[] = {SS_1_PIN};

MFRC522 mfrc522[NR_OF_READERS];   // Create MFRC522 instance.



/**
 * Initialize.
 */
void setup() {
  pinMode(LED_R, OUTPUT);
  pinMode(LED_G, OUTPUT);
  pinMode(LED_B, OUTPUT);
  pinMode(PIX, OUTPUT);

// pixels.begin();
// pixels.clear();

  digitalWrite(LED_R, LOW);
  digitalWrite(LED_G, LOW);
  digitalWrite(LED_B, LOW);

  Serial.begin(9600); // Initialize serial communications with the PC
  while (!Serial);    // Do nothing if no serial port is opened (added for Arduinos based on ATMEGA32U4)

  SPI.begin();        // Init SPI bus

  for (uint8_t reader = 0; reader < NR_OF_READERS; reader++) {
    mfrc522[reader].PCD_Init(ssPins[reader], RST_PIN); // Init each MFRC522 card

  }
}

/**
 * Main loop.
 */
void loop() {

  for (uint8_t reader = 0; reader < NR_OF_READERS; reader++) {
    // Look for new cards
    // String content[reader];

    if (mfrc522[reader].PICC_IsNewCardPresent() && mfrc522[reader].PICC_ReadCardSerial()) {

      Serial.print(F("Reader "));
      Serial.print(reader);
      // Show some details of the PICC (that is: the tag/card)
      Serial.print(F(": Card UID:"));
      Serial.println();

      readTags(mfrc522[reader].uid.uidByte, mfrc522[reader].uid.size);

    } 

      // Halt PICC
      mfrc522[reader].PICC_HaltA();
      // Stop encryption on PCD
      mfrc522[reader].PCD_StopCrypto1();

  } //fin de la boucle avec les reader
}

/**
 * Helper routine to dump a byte array as hex values to Serial.
 */
void readTags(byte *buffer, byte bufferSize) {

  String adress="";

  for (byte i = 0; i < bufferSize; i++) {
    Serial.print(buffer[i] < 0x10 ? " 0" : " ");
    Serial.print(buffer[i], HEX);
    adress.concat(String(buffer[i] < 0x10 ? " 0" : " "));
    adress.concat(String(buffer[i], HEX));

  }

  adress.toUpperCase();

  if (adress.substring(1) == "04 FB 32 22 DA 64 80")
  {
    r += 10;
    Serial.print(F(" red tag identified")); 
    // String wrapper for printing strings in the serial monitor and using Flash memory instead of SRAM
    analogWrite(LED_R, r); 
    pixels.setPixelColor(0, pixels.Color(r,g,b)); //starts at 0!
    pixels.setBrightness(k);
    pixels.show();
    Serial.println();
    Serial.println("Value of red color is " + String(r));
    delay(del);
  }


else if (adress.substring(1) == "04 F3 30 22 DA 64 80")
  {
    g += 10;
    Serial.print(F(" green tag identified"));
    delay(del);
    Serial.println();
    analogWrite(LED_G, g); 
    pixels.setPixelColor(0, pixels.Color(r,g,b)); //starts at 0!
    pixels.setBrightness(k);
    pixels.show();
    Serial.println();
    Serial.println("Value of green color is " + String(g));
    delay(del);

  }


 else if (adress.substring(1) == "04 87 31 22 DA 64 80")
  {
    b += 10;
    Serial.print(F(" blue tag identified"));
    delay(del);
    Serial.println();
    analogWrite(LED_B, b); 
    pixels.setPixelColor(0, pixels.Color(r,g,b)); //starts at 0!
    pixels.setBrightness(k);
    pixels.show();
    Serial.println();
    Serial.println("Value of blue color is " + String(b));

  }

 else if (adress.substring(1) == "04 96 31 22 DA 64 80")
  {
    Serial.print(F(" white tag identified"));
    delay(del);
    Serial.println();
    digitalWrite(LED_R, LOW); 
    digitalWrite(LED_G, LOW); 
    digitalWrite(LED_B, LOW); 
    r=0;
    g=0;
    b=0;
    k = 200;
    pixels.setPixelColor(0, pixels.Color(r,g,b)); //starts at 0!
    pixels.setBrightness(k);
    pixels.show();

  }

   else if (adress.substring(1) == "04 02 34 22 DA 64 81")
  {
    Serial.print(F(" black tag identified"));
    k -= 20;
    Serial.println();
    Serial.println("Value of brightness is " + String(k));
    delay(del);
    Serial.println();
    pixels.setBrightness(k);
    pixels.setPixelColor(0, pixels.Color(r,g,b)); //starts at 0!
    pixels.show();
  }

else
  {
    Serial.print("unknown tag");
    delay(500);
  }

}

More hero shots and videos 8-) It's unfortunately a bit hard to really see on the video the color changing of the neopixel. Come to the lab to see them in real life.

hero shot hero shot

Files

  • Controling 3 LEDs from the serial monitor: attiny-led-serial.ino

  • Monitoring the led's brightness from the serial monitor : attiny-led-pwm.ino

  • Monitoring both the leds brightness and the (R,G,B) color value of a neopixel, from the serial monitor: esp8266-ledRGB-neopixel_serial.ino

    • Needs Adafruit_Neopixel.h library to work
  • Trigger change in leds brightness and (R,G,B) color of a neopixel with passive RFID tags and a MCFR522 reader: rfid-detect-led_uno.ino

    • Needs Adafruit_Neopixel.h, SPI.hand MFRC522.h libraries to work