This project uses the ATmega4809 Curiosity Nano with the Curiosity Nano Explorer and its OLED display to demonstrate how a real display peripheral can be integrated into an AVR firmware project.
Rather than treating the OLED as an isolated example, it provides a useful introduction to the relationship between the ATmega4809, the Explorer hardware, the display interface and the firmware responsible for transferring display data.
The finished program can be used as a starting point for projects that need to present sensor readings, operating states, menus, diagnostic information or other data directly on the embedded hardware.
The firmware for this project was developed in Visual Studio Code, demonstrating that ATmega4809 development does not have to be tied to a traditional integrated development environment. With the compiler, device support and build environment configured correctly, VS Code provides a practical workflow for editing, building and maintaining AVR C projects.
The existing OLED code also provides a useful base for experimenting with the ATmega4809’s communication peripherals and GPIO resources while working with actual Curiosity Nano Explorer hardware rather than a simulated example.
On the Curiosity Nano Explorer, the hardware I2C pins map to PA2 (SDA) and PA3 (SCL) when the ATmega4809 is mounted. We’ll use the megaAVR hardware TWI0 I2C peripheral in Master mode at 100 kHz.
Update .vscode/c_cpp_properties.json
Ensure your IntelliSense configuration points to version 3.6.299 so TWI register definitions (TWI0.MCTRLA, TWI0.MBAUD, etc.) resolve properly:
{
"configurations": [
{
"name": "ATmega4809",
"includePath": [
"${workspaceFolder}/**",
"C:/Users/user/.mchp_packs/Microchip/ATmega_DFP/3.6.299/include/**"
],
"defines": [
"__AVR_ATmega4809__",
"F_CPU=20000000UL"
],
"forcedInclude": [
"C:/Users/user/.mchp_packs/Microchip/ATmega_DFP/3.6.299/include/avr/io.h"
],
"compilerPath": "C:/Program Files/Microchip/xc8/v3.10/avr/bin/avr-gcc.exe",
"cStandard": "c11",
"intelliSenseMode": "windows-gcc-x86"
}
],
"version": 4
}
ATmega4809 SSD1306 Driver Code
This code initializes TWI0 on PA2/PA3, sets up the display using your verified 0x3D \text{I}^2\text{C} address, and draws "4809" on page 3.
Replace your main.c with the following:
#define F_CPU 20000000UL // 20 MHz Clock
#include <avr/io.h>
#include <util/delay.h>
// SSD1306 7-bit Address
#define SSD1306_ADDR 0x3D
// Minimal 5x7 Font for digits
const uint8_t FONT_5x7[][5] = {
{0x3E, 0x51, 0x49, 0x45, 0x3E}, // 0
{0x00, 0x42, 0x7F, 0x40, 0x00}, // 1
{0x42, 0x61, 0x51, 0x49, 0x46}, // 2
{0x21, 0x41, 0x45, 0x4B, 0x31}, // 3
{0x18, 0x14, 0x12, 0x7F, 0x10}, // 4
{0x27, 0x45, 0x45, 0x45, 0x39}, // 5
{0x3C, 0x4A, 0x49, 0x49, 0x30}, // 6
{0x01, 0x71, 0x09, 0x05, 0x03}, // 7
{0x36, 0x49, 0x49, 0x49, 0x36}, // 8
{0x06, 0x49, 0x49, 0x29, 0x1E}, // 9
{0x00, 0x00, 0x00, 0x00, 0x00} // Space
};
// TWI0 (I2C Master) Hardware Driver
void TWI0_Init(void)
{
// Enable internal pull-ups on PA2 (SDA) and PA3 (SCL)
PORTA.PIN2CTRL = PORT_PULLUPEN_bm;
PORTA.PIN3CTRL = PORT_PULLUPEN_bm;
// Set TWI baud rate for ~100 kHz at 20 MHz F_CPU
// MBAUD = (F_CPU / (2 * F_SCL)) - 10
TWI0.MBAUD = 90;
// Enable TWI Master
TWI0.MCTRLA = TWI_ENABLE_bm;
// Force TWI Bus State to IDLE
TWI0.MSTATUS = TWI_BUSSTATE_IDLE_gc;
}
void TWI0_Start(uint8_t addr_rw)
{
TWI0.MADDR = addr_rw;
while (!(TWI0.MSTATUS & (TWI_WIF_bm | TWI_RIF_bm))); // Wait for Write/Read Interrupt Flag
}
void TWI0_Write(uint8_t data)
{
TWI0.MDATA = data;
while (!(TWI0.MSTATUS & TWI_WIF_bm)); // Wait for transmission complete
}
void TWI0_Stop(void)
{
TWI0.MCTRLB = TWI_MCMD_STOP_gc;
}
// SSD1306 Command & Data Helpers
void SSD1306_Command(uint8_t cmd)
{
TWI0_Start((SSD1306_ADDR << 1) | 0); // Write Mode (0x7A)
TWI0_Write(0x00); // Control byte: Command
TWI0_Write(cmd);
TWI0_Stop();
}
void SSD1306_Data(uint8_t data)
{
TWI0_Start((SSD1306_ADDR << 1) | 0); // Write Mode (0x7A)
TWI0_Write(0x40); // Control byte: Data
TWI0_Write(data);
TWI0_Stop();
}
void SSD1306_Init(void)
{
_delay_ms(100);
SSD1306_Command(0xAE); // Display OFF
SSD1306_Command(0xD5); SSD1306_Command(0x80); // Set Display Clock Divide Ratio
SSD1306_Command(0xA8); SSD1306_Command(0x3F); // Multiplex Ratio (128x64)
SSD1306_Command(0xD3); SSD1306_Command(0x00); // Display Offset
SSD1306_Command(0x40); // Set Start Line
SSD1306_Command(0x8D); SSD1306_Command(0x14); // Enable Charge Pump
SSD1306_Command(0x20); SSD1306_Command(0x00); // Horizontal Addressing Mode
SSD1306_Command(0xA1); // Segment Re-map
SSD1306_Command(0xC8); // COM Scan Direction
SSD1306_Command(0xDA); SSD1306_Command(0x12); // COM Pins Config
SSD1306_Command(0x81); SSD1306_Command(0xCF); // Set Contrast
SSD1306_Command(0xD9); SSD1306_Command(0xF1); // Pre-charge Period
SSD1306_Command(0xDB); SSD1306_Command(0x40); // VCOMH Deselect
SSD1306_Command(0xA4); // Output Follows RAM
SSD1306_Command(0xA6); // Normal Display
SSD1306_Command(0xAF); // Display ON
}
void SSD1306_Clear(void)
{
for (uint8_t page = 0; page < 8; page++)
{
SSD1306_Command(0xB0 + page);
SSD1306_Command(0x00);
SSD1306_Command(0x10);
for (uint8_t col = 0; col < 128; col++)
{
SSD1306_Data(0x00);
}
}
}
void SSD1306_DrawDigit(uint8_t digit, uint8_t page, uint8_t col)
{
if (digit > 10) digit = 10;
SSD1306_Command(0xB0 + page);
SSD1306_Command(0x00 + (col & 0x0F));
SSD1306_Command(0x10 + ((col >> 4) & 0x0F));
for (uint8_t i = 0; i < 5; i++)
{
SSD1306_Data(FONT_5x7[digit][i]);
}
SSD1306_Data(0x00); // 1-px gap
}
int main(void)
{
// 1. Set CPU to 20 MHz
CPU_CCP = CCP_IOREG_gc;
CLKCTRL.MCLKCTRLB = 0x00;
// 2. Configure Onboard LED0 (PF5) as Heartbeat Output
PORTF.DIRSET = PIN5_bm;
// 3. Initialize Peripherals
TWI0_Init();
SSD1306_Init();
SSD1306_Clear();
// 4. Render "4809" on OLED
SSD1306_DrawDigit(4, 3, 38);
SSD1306_DrawDigit(8, 3, 46);
SSD1306_DrawDigit(0, 3, 54);
SSD1306_DrawDigit(9, 3, 62);
while (1)
{
// Toggle Heartbeat LED (PF5)
PORTF.OUTTGL = PIN5_bm;
_delay_ms(500);
}
return 0;
}
Flash and Verify
- Run
MPLAB: Program Device. - PF5 LED0 on the ATmega4809 board will blink every 0.5 seconds as a heartbeat.
- The OLED screen will turn on and render
4809centered on the display.
