Controlling OLED part 2

Sam LeeSam Lee
7 min read

This time, let's create a status display.


Egg Incubator

Current Status: Incubating

Incubation Period: 15 days 3 hours

Time to Hatch: 5 days 21 hours

Hatching Progress: 71%

Expected Hatch Day: May 10, 2024

Temperature: 38.5° C

Heater: on/off


Our goal is to display the information as shown above.

The function to display the status looks like this:

void ssd1306_show_egg_incubator_state(char* status, int day, int hour, int progress, int date, float temperature, int heater_state);

It supports essential statuses such as status, day, hour, progress, date, temperature, and heater_state.

But can we display all this information at once?

I tried writing a few lines as a test:

.

It doesn't work. Therefore, we need to split the interface into multiple pages and display them sequentially.

To show all the information, I can consider two methods: displaying everything and scrolling down, or dividing it into pages and showing them sequentially.

I chose the method of displaying information sequentially.

Scrolling down requires a cumbersome process.

지금 ssd1306에 뭔가를 표시하는 방법을 보면

Currently, to display something on the SSD1306, we use:

ssd1306_FILL(colour); - This clears the previous screen information and writes the new information on top of it. Then, the buffer with the written information is updated to the OLED using the update screen function.

If we scroll down, we need to clear the screen and write the information again, updating the buffer to the OLED each time. We are displaying about 9 lines, but we can only show about 6 lines per page. This means we need to scroll at least 3-4 times.

To make scrolling smoother, we need to increase the frame rate significantly, requiring more scrolling operations.

On the other hand, if we divide the information into pages, we only need to change the page every 2-3 seconds.

ssd1306_FILL(); 
ssd1306_draw(); 
ssd1306_updatescreen();

This means we only need to perform this process every 2-3 seconds.

If we choose the scrolling method, it requires many times more work.

Which method looks better?

I personally think displaying information in pages is better for the user.

Why do I think so?

Because we can categorize the information. Let's categorize it by importance.

What are the most important and necessary pieces of information? If we rank the most important information:

Egg Incubator

Current Status: Incubating = 1

Incubation Period: 15 days 3 hours = 2

Time to Hatch: 5 days 21 hours = 2

Hatching Progress: 71% = 3

Expected Hatch Day: May 10, 2024 = 2

Temperature: 38.5° C = 1

Heater: on/off = 1

The current status, temperature, and heater on/off information are the most important, so we will show these three together.

We can show the time-related information together.

The progress can be shown with the time or separately with an effect.

Page 1

Egg Incubator

Current Status: Incubating

Temperature: 38.5° C

Heater: on/off

Page 2

Hatching Progress: 71%

Incubation Period: 15 days 3 hours

Time to Hatch: 5 days 21 hours

Expected Hatch Day: May 10, 2024

This way, we can categorize and display the information neatly. I believe the page method is better for the user and more efficient in terms of operation.

void ssd1306_show_egg_incubator_state_test_1(){
ssd1306_Fill(Black);

ssd1306_SetCursor(30, 4);

ssd1306_WriteString("Egg Incubator", Font_6x8, White);

ssd1306_SetCursor(4, 14);

ssd1306_WriteString(" ---------------------", Font_6x8, White);

ssd1306_SetCursor(4, 24);

ssd1306_WriteString("Current Status: ", Font_6x8, White);

ssd1306_SetCursor(4, 34);

ssd1306_WriteString("Incubating", Font_6x8, White);

ssd1306_SetCursor(4, 44);

ssd1306_WriteString("Temperature: 38.5C", Font_6x8, White);

ssd1306_SetCursor(4, 54);

ssd1306_WriteString("Heater: ON ", Font_6x8, White);

ssd1306_UpdateScreen(); }

Page 1 display:

Page 2 display code:

void ssd1306_show_egg_incubator_state_test_2(){

ssd1306_Fill(Black);

ssd1306_SetCursor(4, 4);

ssd1306_WriteString("Progress: 71% ", Font_6x8, White);

ssd1306_SetCursor(4, 14);

ssd1306_WriteString(" ---------------------", Font_6x8, White);

ssd1306_SetCursor(4, 24);

ssd1306_WriteString("Incubation Period: ", Font_6x8, White);

ssd1306_SetCursor(4, 34);

ssd1306_WriteString(" 15 days 3 hours", Font_6x8, White);

ssd1306_SetCursor(4, 44);

ssd1306_WriteString("Time to Hatch:", Font_6x8, White);

ssd1306_SetCursor(4, 54);

ssd1306_WriteString(" 5 days 21 hours ", Font_6x8, White);

//ssd1306_SetCursor(4, 54);

//ssd1306_WriteString("Expected Hatch Day: May 10, 2024 ", Font_6x8, White);

ssd1306_UpdateScreen();

}

Page 2 display:

Since we can only display up to 6 lines, we couldn't show everything, but it seems fine.

 //ssd1306_WriteString("Expected Hatch Day: May 10, 2024 ", Font_6x8, White);

Now that we can display the desired information on the screen, let's pass the actual information to the function.

ssd1306_show_egg_incubator_state_first_page(int status, float temperature, int heater_state)

For Page 1, we want to display the incubator status, temperature, and heater on/off state.

So, we need to retrieve these three pieces of information.

char ssd1306_WriteString(char* str, SSD1306_Font_t Font, SSD1306_COLOR color)

The writeString code takes a parameter char* str, so it cannot display temperature and heater_state directly.

For example:

ssd1306_WriteString(38.5C, Font_6x8, White); 
ssd1306_WriteString(38.5, Font_6x8, White);

If we do it like this, it will definitely cause an error. We need to convert the number 38.5 or any arbitrary number from the temperature sensor to char[].

char buffer[32]; sprintf(buffer, "Temperature: %.1f C", temperature); 
ssd1306_WriteString(buffer, Font_6x8, White);

We will use the sprintf() function from stdio.h to convert the desired data to char[]. Then, we will store it in a buffer and pass the buffer to ssd1306_WriteString.

So, we write the code like this:

void ssd1306_show_egg_incubator_state_first_page(char* status, float temperature, int heater_state){
char buffer[32]; // Buffer with a enough size

ssd1306_Fill(Black);

ssd1306_SetCursor(30, 4);

ssd1306_WriteString("Egg Incubator", Font_6x8, White);

ssd1306_SetCursor(4, 14);

ssd1306_WriteString(" ---------------------", Font_6x8, White);

ssd1306_SetCursor(4, 24);

ssd1306_WriteString("Current Status: ", Font_6x8, White);

ssd1306_SetCursor(4, 34);

sprintf(buffer, "%s", status);

ssd1306_WriteString(buffer, Font_6x8, White);

ssd1306_SetCursor(4, 44);

sprintf(buffer, "Temperature: %.1f C", temperature);

ssd1306_WriteString(buffer, Font_6x8, White);

ssd1306_SetCursor(4, 54);

if(heater_state){

sprintf(buffer, "Heater: %s", "On");

ssd1306_WriteString(buffer, Font_6x8, White);

}else{

sprintf(buffer, "Heater: %s", "Off");

ssd1306_WriteString(buffer, Font_6x8, White);

}

ssd1306_UpdateScreen();

HAL_Delay(2000);

}

And in the main function, we test it with ssd1306_show_egg_incubator_state_first_page("Test1", 55.0, 1);

It displays well:

.

void ssd1306_show_egg_incubator_state_second_page(int day, int hour, int progress)

The function to display the second page is similar to the first page function. We use sprintf() to convert the desired data to char[] and pass the buffer to the writeString() function.

void ssd1306_show_egg_incubator_state_second_page(int day, int hour, int progress){
char buffer[32];

ssd1306_Fill(Black);

ssd1306_SetCursor(4, 4);

sprintf(buffer, "Progress: %d %", progress);

ssd1306_WriteString(buffer, Font_6x8, White);

ssd1306_SetCursor(4, 14);

ssd1306_WriteString(" ---------------------", Font_6x8, White);

ssd1306_SetCursor(4, 24);

ssd1306_WriteString("Incubation Period: ", Font_6x8, White);

ssd1306_SetCursor(4, 34);

sprintf(buffer, " %d days %d hours", day, hour);

ssd1306_WriteString(buffer, Font_6x8, White);

ssd1306_SetCursor(4, 44);

ssd1306_WriteString("Time to Hatch:", Font_6x8, White);

ssd1306_SetCursor(4, 54);

sprintf(buffer, " %d days %d hours", day, hour);

ssd1306_WriteString(buffer, Font_6x8, White);

ssd1306_UpdateScreen();

HAL_Delay(4000);

}

In the main function, we call ssd1306_show_egg_incubator_state_second_page(15, 15, 15); to test it.

It displays well:

Now, we just need to modify the code to calculate the progress and remaining time.

Since we have implemented all the functions, let's combine them.

ssd1306_show_egg_incubator_state_second_page(15, 15, 15);

The first function takes the status, temperature, and heater state as parameters, while the second function needs to calculate the time.

We need to calculate the time the device has been on and the current time to determine how much time has passed and how much time is left.

int main(void)
{

/* USER CODE BEGIN 1 */

/* USER CODE END 1 */

/* MCU Configuration--------------------------------------------------------*/

/* Reset of all peripherals, Initializes the Flash interface and the Systick. */

HAL_Init();

/* USER CODE BEGIN Init */

uint32_t startTime = HAL_GetTick()/1000;

//call startTime in main() and pass it onto calculate_time()

//make a struct to store time related data

typedef struct time_egg_incubator {

uint32_t elapsed_days;

uint32_t elapsed_hours;

uint32_t remaining_days;

uint32_t remaining_hours;

uint32_t progress;

} time_info_egg_incubator;

We calculate the time and store it in a structure.

void calculate_time(uint32_t *startTime) {
// Reset if the program startup was not saved

if (*startTime == 0) {

*startTime = HAL_GetTick()/1000;

}

uint32_t current_time = HAL_GetTick()/1000;

// Calculate elapsed time (in seconds) from the start of the program

uint32_t elapsed_seconds = current_time - (*startTime);

// Convert seconds to days and hours

uint32_t days_elapsed = elapsed_seconds / (60  60  24);

uint32_t hours_elapsed = (int)((elapsed_seconds / (60  60)) - (days_elapsed  24));

incubator_info.elapsed_days = days_elapsed;

incubator_info.elapsed_hours = hours_elapsed;

// calculate remaining seconds

uint32_t total_seconds = TOTAL_DAYS  24  60 * 60;

uint32_t remaining_seconds = total_seconds - elapsed_seconds;

// Convert seconds to days and hours

uint32_t days_left = remaining_seconds / (60  60  24);

uint32_t hours_left = (uint32_t)((remaining_seconds / (60  60)) - (days_left  24));

incubator_info.remaining_days = days_left;

incubator_info.remaining_hours = hours_left;

// Progress Calculation

uint32_t progress_percent = (elapsed_seconds * 100)/ total_seconds;

incubator_info.progress = progress_percent; }

Then, we extract the time from the structure and display it on the OLED.

void ssd1306_show_egg_incubator_state_second_page(){
 //calculate_time();

char buffer[32];

ssd1306_Fill(Black);

ssd1306_SetCursor(4, 4);

sprintf(buffer, "Progress: %lu %%", incubator_info.progress);

ssd1306_WriteString(buffer, Font_6x8, White);

ssd1306_SetCursor(4, 14);

ssd1306_WriteString(" ---------------------", Font_6x8, White);

ssd1306_SetCursor(4, 24);

ssd1306_WriteString("Incubation Period: ", Font_6x8, White);

ssd1306_SetCursor(4, 34);

sprintf(buffer, " %lu days %lu hours", incubator_info.elapsed_days, incubator_info.elapsed_hours);

ssd1306_WriteString(buffer, Font_6x8, White);

ssd1306_SetCursor(4, 44);

ssd1306_WriteString("Time to Hatch:", Font_6x8, White);

ssd1306_SetCursor(4, 54);

sprintf(buffer, " %lu days %lu hours", incubator_info.remaining_days, incubator_info.remaining_hours);

ssd1306_WriteString(buffer, Font_6x8, White);

ssd1306_UpdateScreen();

HAL_Delay(4000); }

By combining everything, we have modularized it for easy use in the main function or interrupts.

The time update doesn't need to be too frequent. Once every minute should be sufficient.

void display_ssd1306(uint32_t startTime, char status, float temperature, int heater_state){ if(updateDue==60){

calculate_time(startTime);

updateDue =0;

}

ssd1306_show_egg_incubator_state_first_page(status, temperature, heater_state);

ssd1306_show_egg_incubator_state_second_page();

Hal_delay(1000);
updateDue++;

}
0
Subscribe to my newsletter

Read articles from Sam Lee directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Sam Lee
Sam Lee