07_SSD1306_FeatherWing_OLED

The goal of this project is to get started with Adafruit's FeatherWing OLED module.




Schematic Source: Adafruit, License: Attribution-ShareAlike Creative Commons

FeatherM0 - FeatherWing OLED connections

solder in the headers correctly.

Unfortunately there is a problem with that.

The designers of the FeatherWing OLED have decided to use line D9 (= FeatherM0 pin JP3-8 on the headers, PA07 on the ATSAMD21G18 MCU) to connect the button A of the FeatherWing OLED to the MCU.

Readers of my 05 ADC tutorial might remember that this is the same pin that is used to measure the charging state of the LiPo battery.

So, if you stack the FeatherWing OLED on top of the FeatherM0 you either have the choice of:

For this tutorial though, we will stack the FeatherWing OLED on top of the FetherM0 (not using the ADC).

The connection between the MCU and the SSD1306 display driver IC are shown in the schematics above:

JP2/3 ATSAMD21G18 SSD1306
Pos. 08 PA07 !Button A active low, no external pull-up resistor
Pos. 09 PA20 !Button B active low, 100k external pull-up resistor
Pos. 10 PA15 !Button C active low, no external pull-up resistor
Pos. 11 PA23 I2C SCL SERCOM3+5.1, I2C serial clock
Pos. 12 PA22 I2C SDA SERCOM3+5.0, I2C serial data

While the SSD1306 datasheet shows that the chip supports a couple of other interfaces beside I2C, including SPI, only the I2C interface is connected on the FeatherWing OLED.

This unfortunately means, that we can not use the SSD1306 OLED component found in the Atmel Software Foundation (ASF), because this one seems to be hard-wired to SPI. So we have to write our own driver for the SSD1306 based FeatherWing OLED.

Software

The program will:

Than the program will wait for the user to press one of the buttons on the FeatherWing OLED:

RESET (SW1) displays the initial welcome screen, depending on the parameter for the on_reset() function:
FONT_08PX
FONT_06PX
A (SW2) toggles the display between ON and OFF state
B (SW4) starts the test for the draw_fill() function
released
pushed
C (SW3) starts the test for the draw_line() function
drawing
done

Adding a new Atmel Software Framework (ASF) Skeleton Project

Add a new ASF Skeleton Project for the 07_I2C_SSD1306_FeatherWing_OLED tutorial to the existing solution.

See my Step-By-Step tutorial if you are not sure how to do this.

Choosing a startup project

To make sure AtmelStudio always compiles and runs the 07_I2C_SSD1306_FeatherWing_OLED project we make it the StartUp Project.

Adding the project specific Atmel Software Framework (ASF) code

The next step is to import the ASF modules needed for the specific project.

or use the ASF Wizard entry in 07_I2C_SSD1306_FeatherWing_OLED's context menu.

You should see two lists:

In Selected Modules we already see:

This is the standard framework needed for any ASF application.

In Available Modules find:

And Add>> the module to the project.

This is the driver for the SAM external interrupts. It provides a unified interface for the configuration and management of external interrupts generated by the physical device pins.

This driver enables us to use interrupts and callbacks functions as Interrupt Service Routines for when a button was pressed.

Next in Available Modules find:

And Add>> the module to the project.

This is the driver for the SAM PORT module. It provides a unified interface for the configuration and management of the physical device GPIO pins, including input/output state control.

We use it to configure the MCU pins the buttons of the FeatherWIng OLED are connected to.

Also in Available Modules find:

And Add>> the module to the project.

This is the driver for the SAM SERCOM I2C module in Master Mode. It provides a unified interface for the configuration and management of the SERCOM module in I2C mode.

This is our communication interface to the SSD1306 on the FeatherWing OLED.

Finally in Available Modules find:

And Add>> this module to the project.

This is the common standard serial I/O management driver that implements a stdio serial interface on SAM devices.

While this driver is strictly speaking not necessary for the project, it provides the printf() function for printing to the serial terminal, making printing messages more convenient.

This driver also includes the

So there is no need to import it as stand alone module.

Now press Apply. This will add all the necessary ASF code to our project.

Using the DIT Adafruit Feather Library

For this project we need to:

Also we need to add the path to the following *.h files to the project search path and links to the following *.c files to the project:

Com_Driver (.h) Generic Communication Driver interface
Com_Driver_i2c_master (.h,.c) I2C bus master communication driver
Draw (.h, .c) A library of graphic drawing functions
Draw_Fill_Test_Pattern (.h,.c) Provides a test-pattern to test the draw_fill() function
Draw_Line_Test_Pattern (.h,.c) A function to draw a test-pattern using the draw_line() function
Font (.h) Provides global declarations for the font system
Font_06px (.h,.c) Tiny 6px proportional bitmap font
Font_08px (.h,.c) Readable 8px proportional bitmap font
Framebuffer (.h) Generic framebuffer interface
Framebuffer_SSD1306 (.h,.c) A 1bit per pixel framebuffer implementation for the SSD1306 display controller IC
SSD1306 (.h,.c) SSD1306 display controller driver
Stack (.h,.c) A simple stack implementation

See my Step-By-Step tutorial if you are unsure how to do this.

Writing the application code

In the Solution Explorer find the generated main.c.

As usual, after some file level comments, we start by referencing the include files used.

All we need to import to get the whole ASF magic working is the asf.h, that was generated by the ASF wizard. The entry should already be there. The wizard took care of that for us (line 35).

The rest of the lines 36..45 include the files as described in the previous section.

main()

To better understand how the application works, we will start the explanation of the code with the main() function in lines 229..294.

However, there are a couple of things on top of main(), that are worth noting and that we will come back to in detail later:

The main() function itself starts by defining a couple of variables:

usart_instamce an instance of a SERCOM UART module, that is used to write debug messages to a serial terminal (Line 231)
i2c_master_instance an instance of an I2C master module, that is used to communicate with the FeatherWing_OLED (Line 232)
status a variable for the return values of different calls (Line 232)

Up to now, the code is pretty much known from the previous tutorials.

Now we start setting up the I2C subsystem:

After a successful initialization of the I2C subsystem we bring up the FeatherWing OLED:

Note on Framebuffers

If you are not yet familiar with the concept of framebuffers, please read the Framebuffer article on Wikipedia.
And since we have a slow I2C connection between the MCU and the DRAM on the FeatherWing OLED, I actually use two framebuffers, in a setup known as double buffering:
  • First all graphic functions write there updates to the local (MCU internal) framebuffer
  • Once the new frame is complete, the changes get transferred to the remote (SSD1306 internal) framebuffer of the OLED display
This is more efficient than sending every pixel changed by one of the graphic functions directly to the OLED display. It also helps to decouple the graphic function implementations from the peculiars of the display hardware access.

With the setup done, we can now start using the FeatherWing OLED in our application's logic.

The first step is to clear the screen of the FeatherWing OLED:

Next we use the on_reset() function, implemented in lines 147..167, to draw an initial content to the framebuffer (line 288).

Line 283 transfers the this content from the internal framebuffer to the FeatherWing OLED display.

The rest of the application logic is driven by the events the systems generates in reaction to the user pushing the different buttons on the FeatherWing OLED. So there is nothing left to do for the main application loop in lines 291..293.

i2c_master_configure()

This function configures SERCOM3 as an I2C master and the pins, the FeatherWing OLED SSD1306 is connected to as the I2C bus to use.

It doesn't seem to make much of a difference if you user SERCOM3 or SERCOM5, so I used the first on the list.

The function:

featherwing_oled_init()

This function

on_reset()

This function is called by main() in line 288, right after the initialization is done and before the control is handed over to the event handlers.

Since I'm not using a mono-spaced (all characters have the same width), but a proportional-spaced font (every character has an individual width), the ultimate width of a string on the screen not only depends on the number of characters in the string, but also on which characters appear how often in the string.

To be able to position any following string on the screen correctly, without going through some extra calculations, the *_draw_string() functions of the font libraries I use (Font_06px, Font_08px), return the accumulated width of the string drawn in every call.

Even so not used in this tutorial, we still need to pass a variable that receives this value to the functions calls.
This variable is defined in line 149.

Line 150 defines a variable for the x position to draw the test-strings to.
The way the code looks now, we could have used a constant, but if you want to experiment and write two or more strings in a line, you need a variable x position.

on_reset() is capable of drawing two different test screens, depending on the rsc parameter.
The valid values for this parameter are defined in the resetscreen_content enumeration in line 79..82.

FONT_08PX
  • Prints the string "Hello World", in the 8px height font, at x=x_pos and y=1
  • Prints a test-string (simulating WLAN connection information), in the 6px height font, at x=x_pos and y=25
FONT_06PX
  • Prints the string "Hello World", in the 6px height font, at x=x_pos and y=1
  • Prints four more rows of test-strings (ASCII chars a..z, A..Z), in the 6px height font, at x=x_pos and y=7, 13, 19, 15

The 6px height font was as small as I could get it. If you can get it smaller and still readable, mail me!

oled_button_a_on_pressed()

This is the function we configured in line 189 to be notified when the A button on the FeatherWing OLED was pressed.

It:

oled_button_a_on_released()

This is the function we configured in line 190 to be notified when the A button on the FeatherWing OLED was released.

In this tutorial there is nothing to do for this event, so we just:

oled_button_b_on_pressed()

This is the function we configured in line 191 to be notified when the B button on the FeatherWing OLED was pressed.

It:

While the first time the button is pressed the result depends on the screen content before (see on_reset()), every consecutive time button B is pressed the result should look like this:

oled_button_b_on_released()

This is the function we configured in line 192 to be notified when the B button on the FeatherWing OLED was released.

It:

oled_button_c_on_pressed()

This is the function we configured in line 193 to be notified when the C button on the FeatherWing OLED was pressed.

It:

Drawing the lines in the line draw test takes a while, because, in difference to all other draw_*() functions, that are intended for production use, the draw_line_testpattern() function updates the OLED after each line drawn.

One can easily see why we don't do that in production code, but instead draw the complete frame to the framebuffer first and then send the changes over to the OLED.

If you replace &i2c_master_instance with NULL you can see the difference it makes.

Also: This function can not be interrupted by other buttons than the RESET button, while drawing.

This is actually true for all the test functions, since they are implemented in the button event interrupt service routines (ISR), which YOU SHOULD NEVER EVER DO! in production code, exactly for the reason that your ISR blocks other interrupts from taking place.

The correct way to implement something like this would be:

This way new interrupts are free to interrupt your task functions.

In this tutorial I have decided to do it the WRONG WAY, only for the reason to not add another level of complexity to the demonstration.

drawing the line test pattern

done

oled_button_c_on_released()

This is the function we configured in line 194 to be notified when the C button on the FeatherWing OLED was released.

In this tutorial there is nothing to do for this event, so we just: