2020-08-08

2020-08-08 Longan Nano GD32VF103 Demo

 


>>>Longan Nano GD32VF103<<<

Longan Nano GD32VF103 Risc-V 108MHz 32b MCU toolchain, libraries and applications

>>>Longan Nano Demo<<<


Display Class: Interfaces with the ST7735 80x160 display using SPI0 and DMA0.
Screen Class: Provides asynchronous methods to print on the display



1Longan nano GD32VF103 Demo

I built Display, Screen and Chrono class drivers for the Longan nano GD32VF103 board.

This Demo application is meant to show off the features of the drivers and jump start the development of the application.

I spent extra effort in testing and documentation to make sure the drivers are as stable as possible since they are going to be the building blocks for all my future applications on this board.


1.1Chrono Class Driver

The Chrono class uses the 64bit 27MHz@108MHz CPU clock timer to provide accurate timings.

Time units are defined in the Longan_nano::Chrono::Unit enum.

The Chrono class has two modes of operation:

  • start/stop elapsed timer: measures DeltaT

  • start/accumulate: integrate a DeltaT on an accumulator

Those two modes of operations can be used to profile uptime, elapsed time, time spent running code and more.


1.2Display Class Driver

The Display class interfaces directly with the LH096T ST7735S 0.96 inches 80x160 oled color screen.

The library uses the .init method to initialize the display has two modes of operations:

  • register_sprite/update for asynchronous non blocking operations.

  • draw_sprite for synchronous blocking operations.

The Display class uses optional DMA acceleration and the SPI0 and a few GPIOs to communicate with the physical screen.

The Display class does not use interrupts. While using interrupts can make the driver transparent to the user by automagically calling the update method, it can interfere with other real time operations. As design philosophy, the driver is meant to be secondary to the application and meant to show information, giving the application control over the amount of resources used by deciding the call speed of the update method. Calling .update() every 100us will result in about 1250 sprites updated per second. If the user makes more print calls than what the display can handle, the display will simply ignore older calls displaying the most recent sprite. At top load a refresh rate of about 1250/100=12.5 frames per seconds can be expected. The refresh rate becomes 1.25 fps at full load if .update() is executed every 1000us instead of 100us.


1.3Screen Class Driver

The scope of the screen class is to support ascii print of fixed size on fixed grid, and is meant for the common use case of showing debug and runtime number of the application.

The Screen class add a sprite based abstraction layer with print to reduce the size of the frame buffer and CPU use. It also provides a large number of overloaded print methods. The Screen class inherit the Display class, allowing to decouple the physical screen from the print implementation and simplify a move to a bigger screen if needed.

The Screen class supports two ascii fonts. 8x10 Courier Now and 8x16 NSimSum that can be toggled by setting the define FONT_HEIGHT and recompiling, the smaller font shows 8x20=160 sprites on screen, while the bigger font shows 5x20=100 sprites on screen and is easier to read.


2DEMOS

There are ten demo, showcasing the use of the print and timing functions to display an HMI.

  1. Clear the screen to a random color every 500ms

  2. Print a random character in a random position of the screen every 1ms

  3. Same as above but with random foreground and background colors

  4. Print a random string in a random position of the screen every 25ms

  5. Same as above but with random foreground and background colors

  6. Print numbers with right and left adjust

  7. CPU execution and uptime with engineering format 4 significant digit and SI suffix

  8. Same as above but with screen builtin pending and error methods

  9. Same as above but with random foreground and background colors

  10. Constant workload demo. Print 10 sprites every 25ms and show CPU use

Illustration 1 - Demo


 
Video 1 - Demo

3Documentation and Style

I made a point to learn more C++ features, with this project I elected to use .hpp files with header and implementation together since inline needs to be declared alongside the header anyway. I also experimented with scoping of typedef and enum to allow the same enum name to be in multiple libraries without conflicts.

I also integrated the Doxygen documentation style comments and generated the automatic documentation, as well as integrating the documentation alongside the code repository in GitHub.

The same style is going to be used for my next classes and drivers.


4Conclusions

I begun this project to learn a Risc-V MCU. The Longan Nano Demo provides an example application for the scheduler, Chrono, Screen and PA8 button, and can be used as a base to develop applications with the Longan Nano GD32VF103.

I am satisfied with the performance, and how the drivers have turned out. The board provides great value, and in my opinion is held back by the poor examples. Hopefully more people will adopt the Longan Nano and help in building libraries and example code to make it easier to develop applications on Risc-V MCUs to come.

The first application of this MCU will be as new motor controller for OrangeBot.


5Source Code

>>>GitHub Repository<<<


5.1Doxygen Documentation

>>>DoxyGen<<<




5.2Source Code

>>>Screen and Display classes<<<



/****************************************************************************
** OrangeBot Project
*****************************************************************************
** /
** /
** /
** ______ \
** \
** \
*****************************************************************************
** Longan Nano Demo
*****************************************************************************
** Development of the display class for the ST7735S LCD controller
** Demo to show all the uses of the classes. Demo is selected using PA8 boot button
** Codesize exploded. -fno-exceptions as compiler option brought it back under control
****************************************************************************/
/****************************************************************************
** INCLUDES
****************************************************************************/
//C++ std random number generators
#include <random>
//Longan Nano HAL
#include <gd32vf103.h>
//LED class
#include "longan_nano_led.hpp"
//Time class
#include "longan_nano_chrono.hpp"
//Higher level abstraction layer to base Display Class. Provides character sprites and print methods with color
#include "longan_nano_screen.hpp"
/****************************************************************************
** NAMESPACES
****************************************************************************/
/****************************************************************************
** DEFINES
****************************************************************************/
//forever
#define EVER (;;)
/****************************************************************************
** MACROS
****************************************************************************/
/****************************************************************************
** ENUM
****************************************************************************/
//Configurations
typedef enum _Config
{
//Microseconds between calls of the screen update method. User decides how much CPU to allocate to the screen by tuning this time.
//Longer time will mean fewer sprites rendered per second, but no matter what, the screen class will not crash as only the most recent sprite is drawn
SCREEN_US = 100,
//Microseconds between led toggles
LED_BLINK_US = 250000,
//Microseconds between calls of the demos. Various demos can be executed at differing rates depending on how many sprites they update
SLOW_DEMO_US = 500000,
MEDIUM_DEMO_US = 25000,
FAST_DEMO_US = 1000,
} Config;
//List of DEMOS
typedef enum _Demo
{
//Periodically clear the full screen with a new color
TEST_CLEAR_BLINK,
//Periodically write a random ascii character in a random position in the screen
TEST_CHAR_CONSOLE,
//Periodically write a random ascii character in a random position with random colors on the screen
TEST_CHAR_CONSOLE_COLOR,
//Periodically write a random string with random length in a random position
TEST_STRING_CONSOLE,
//Periodically write a random string with random length in a random position with random colors on the screen
TEST_STRING_CONSOLE_COLOR,
//Show numbers updating on the screen
TEST_NUMBERS,
//Engineering format strings
TEST_ENG_NUMBERS,
//Profiling +Pending for update
TEST_PENDING,
//Test the change_color
TEST_CHANGE_COLORS,
//Profile execution time with constant workload
TEST_WORKLOAD,
//Total number of demos installed
NUM_DEMOS,
//Maximum length of a demo string
MAX_STR_LEN = 25,
} Demo;
/****************************************************************************
** STRUCT
****************************************************************************/
typedef struct _Scheduler
{
bool f_screen : 1;
bool f_demo : 1;
bool f_overrun : 1;
} Scheduler;
/****************************************************************************
** PROTOTYPES
****************************************************************************/
extern void init_pa8_button_interrupt( void );
/****************************************************************************
** GLOBAL VARIABILES
****************************************************************************/
volatile bool g_rtc_flag = false;
//C++ Standard Random Number Generator
std::default_random_engine g_rng_engine;
//C++ standard random number distributions
std::uniform_int_distribution<uint8_t> g_rng_char( ' ', '~' );
std::uniform_int_distribution<int> g_rng_height( 0, Longan_nano::Screen::Config::FRAME_BUFFER_HEIGHT -1 );
std::uniform_int_distribution<int> g_rng_width( 0, Longan_nano::Screen::Config::FRAME_BUFFER_WIDTH -1 );
std::uniform_int_distribution<uint8_t> g_rng_color( 0, Longan_nano::Screen::Config::PALETTE_SIZE -1 );
//String length reroll
std::uniform_int_distribution<uint8_t> g_rng_length( 0, MAX_STR_LEN );
//Scheduler for the tasks
Scheduler g_scheduler = { 0 };
//True when the PA8 button is released
volatile bool g_f_pa8_button_up = false;
/****************************************************************************
** FUNCTIONS
****************************************************************************/
/****************************************************************************
** @brief main
** main | void
****************************************************************************/
//! @return int |
//! @details Entry point of program
/***************************************************************************/
int main( void )
{
//----------------------------------------------------------------
// VARS
//----------------------------------------------------------------
//Systick timer used to schedule activities
Longan_nano::Chrono my_timer;
//Systick timers to profile resource use
Longan_nano::Chrono timer_uptime;
Longan_nano::Chrono timer_screen;
Longan_nano::Chrono timer_demo;
//Display Driver
Longan_nano::Screen g_screen;
//elapsed time
int elapsed_us;
//Demo scheduler prescaler
uint16_t scheduler_cnt = 0;
uint16_t demo_pre = 100;
//Default Demo to be executed
Demo demo_index = Demo::TEST_WORKLOAD;
//Demo is not initialized
bool f_demo_init = false;
//----------------------------------------------------------------
// INIT
//----------------------------------------------------------------
//Initialize LEDs
Longan_nano::Leds::init();
Longan_nano::Leds::set_color( Longan_nano::Leds::Color::BLACK );
//Initialize the PA8 Boot button with interrupt. Used to switch between demos
init_pa8_button_interrupt();
//Initialize the Display
g_screen.init();
//Snap start
my_timer.start();
timer_uptime.start();
timer_screen.start();
//----------------------------------------------------------------
// BODY
//----------------------------------------------------------------
for EVER
{
//----------------------------------------------------------------
// Scheduler
//----------------------------------------------------------------
// Hardwired scheduler to release tasks
// Thanks to the Longan Nano SysTick timer there is no need to use peripherals for timekeeping
//Snap stop and get time since last start in microseconds
elapsed_us = my_timer.stop( Longan_nano::Chrono::Unit::microseconds );
//If: for some reason, the timing is invalid. Algorithmic error
if (elapsed_us < 0)
{
Longan_nano::Leds::set( Longan_nano::Leds::Color::BLUE );
}
//If: enough time has passed between screen executions
else if (elapsed_us >= Config::SCREEN_US)
{
//----------------------------------------------------------------
// Screen
//----------------------------------------------------------------
// The screen is the fastest task
//If: the previous task was not cleared
if (g_scheduler.f_screen == true)
{
//There was an overrun. Not enough CPU to keep up
g_scheduler.f_overrun = true;
}
else
{
//Issue a screen update
g_scheduler.f_screen = true;
}
//Snap start. Restart the timer
my_timer.start();
//----------------------------------------------------------------
// Prescaler
//----------------------------------------------------------------
// A prescaler is used to schedule the execution of slow tasks without the need of additional timers
//Prescaler counter
scheduler_cnt++;
//----------------------------------------------------------------
// Demo
//----------------------------------------------------------------
//If: enough ticks of the prescaler counter have elapsed
if (scheduler_cnt%demo_pre == 0)
{
//Issue the execution of the demo
g_scheduler.f_demo = true;
}
//----------------------------------------------------------------
// LED Blink
//----------------------------------------------------------------
//If: enough ticks of the prescaler counter have elapsed
if (scheduler_cnt%(Config::LED_BLINK_US/Config::SCREEN_US) == 0)
{
Longan_nano::Leds::toggle( Longan_nano::Leds::Color::RED );
//----------------------------------------------------------------
// Demo Switch
//----------------------------------------------------------------
// PA8 boot button is used to switch between demos. I read it slowly to avoid bouncing
//If: button released
if (g_f_pa8_button_up == true)
{
//Clear flag
g_f_pa8_button_up = false;
//Next demo
demo_index = (Demo)( ((uint8_t)demo_index < (uint8_t)Demo::NUM_DEMOS-1)?((uint8_t)demo_index +1):(0));
//Initialize the demo
f_demo_init = false;
}
}
} //If: enough time has passed between screen executions
//Default
else
{
//Nothing to do
}
//----------------------------------------------------------------
// OVERRUN
//----------------------------------------------------------------
// Triggered when a task is not completed before the next issue
//If: overrun
if (g_scheduler.f_overrun == true)
{
//Clear error
g_scheduler.f_overrun = false;
//Signal overrun condition
Longan_nano::Leds::toggle( Longan_nano::Leds::Color::BLUE );
}
//----------------------------------------------------------------
// Screen Update
//----------------------------------------------------------------
// Ask the driver to sync with the display
// The driver will do nothing if its not time to update
// Use all the spare CPU time to do so
//If: screen is authorized to update. User controls the CPU dedicated to this task
if (g_scheduler.f_screen == true)
{
//Reset flag
g_scheduler.f_screen = false;
//Snap start
timer_screen.start();
//Execute a step in the screen update
g_screen.update();
//Accumulate DeltaT into timer accumulator
timer_screen.accumulate();
}
//----------------------------------------------------------------
// DEMO
//----------------------------------------------------------------
//If: demo is authorized to execute
if (g_scheduler.f_demo == true)
{
//Profile time spent running the DEMO
timer_demo.start();
//Clear execution flag
g_scheduler.f_demo = false;
//Switch: for each demo
switch (demo_index)
{
//----------------------------------------------------------------
// TEST_CLEAR_BLINK
//----------------------------------------------------------------
// Periodically clear the full screen with a new color
case Demo::TEST_CLEAR_BLINK:
{
//If: demo is yet to be initialized
if (f_demo_init == false)
{
g_screen.reset_colors();
//Clear the screen
g_screen.clear( Longan_nano::Screen::Color::WHITE );
//Configure prescaler to achieve the correct execution time
demo_pre = Config::SLOW_DEMO_US/Config::SCREEN_US;
//Demo is now initialized
f_demo_init = true;
}
//If: demo is initialized and can be run
else
{
//Clear the screen to a random color
g_screen.clear( (Longan_nano::Screen::Color)g_rng_color( g_rng_engine ) );
}
break;
}
//----------------------------------------------------------------
// TEST_CHAR_CONSOLE
//----------------------------------------------------------------
// Periodically write a random ascii character in a random position in the screen
case Demo::TEST_CHAR_CONSOLE:
{
//If: demo is yet to be initialized
if (f_demo_init == false)
{
g_screen.reset_colors();
//Clear the screen
g_screen.clear( Longan_nano::Screen::Color::BLACK );
//Configure prescaler to achieve the correct execution time
demo_pre = Config::FAST_DEMO_US/Config::SCREEN_US;
//Demo is now initialized
f_demo_init = true;
}
//If: demo is initialized and can be run
else
{
//Randomly generate character
char char_tmp = g_rng_char( g_rng_engine );
int height_tmp = g_rng_height( g_rng_engine );
int width_tmp = g_rng_width( g_rng_engine );
//Ask the screen driver to print the character
g_screen.print( height_tmp, width_tmp, char_tmp );
}
break;
}
//----------------------------------------------------------------
// TEST_CHAR_CONSOLE_COLOR
//----------------------------------------------------------------
// Periodically write a random ascii character in a random position with random colors on the screen
case Demo::TEST_CHAR_CONSOLE_COLOR:
{
//If: demo is yet to be initialized
if (f_demo_init == false)
{
g_screen.reset_colors();
//Clear the screen
g_screen.clear( Longan_nano::Screen::Color::BLACK );
//Configure prescaler to achieve the correct execution time
demo_pre = Config::FAST_DEMO_US/Config::SCREEN_US;
//Demo is now initialized
f_demo_init = true;
}
//If: demo is initialized and can be run
else
{
//Randomly generate character
uint8_t char_tmp = g_rng_char( g_rng_engine );
int height_tmp = g_rng_height( g_rng_engine );
int width_tmp = g_rng_width( g_rng_engine );
Longan_nano::Screen::Color background_tmp = (Longan_nano::Screen::Color)g_rng_color( g_rng_engine );
Longan_nano::Screen::Color foreground_tmp = (Longan_nano::Screen::Color)g_rng_color( g_rng_engine );
//Ask the screen driver to print the character
g_screen.print( height_tmp, width_tmp, (char)char_tmp,background_tmp, foreground_tmp );
}
break;
}
//----------------------------------------------------------------
// TEST_STRING_CONSOLE
//----------------------------------------------------------------
// Periodically write a random string with random length in a random position
case Demo::TEST_STRING_CONSOLE:
{
//If: demo is yet to be initialized
if (f_demo_init == false)
{
g_screen.reset_colors();
//Clear the screen
g_screen.clear( Longan_nano::Screen::Color::BLACK );
//Configure prescaler to achieve the correct execution time
demo_pre = Config::MEDIUM_DEMO_US/Config::SCREEN_US;
//Demo is now initialized
f_demo_init = true;
}
//If: demo is initialized and can be run
else
{
//Randomly generate character
uint8_t len_tmp = g_rng_length( g_rng_engine );
//Temp string
char str[MAX_STR_LEN +1];
//Construct string
for (uint8_t t = 0; t < len_tmp;t++)
{
str[t] = g_rng_char( g_rng_engine );
}
//append terminator
str[len_tmp] = '\0';
//Random position
int height_tmp = g_rng_height( g_rng_engine );
int width_tmp = g_rng_width( g_rng_engine );
//Ask the screen driver to print the character
g_screen.print( height_tmp, width_tmp, str );
}
break;
}
//----------------------------------------------------------------
// TEST_STRING_CONSOLE_COLOR
//----------------------------------------------------------------
// Periodically write a random string with random length in a random position with random colors
case Demo::TEST_STRING_CONSOLE_COLOR:
{
//If: demo is yet to be initialized
if (f_demo_init == false)
{
g_screen.reset_colors();
//Clear the screen
g_screen.clear( Longan_nano::Screen::Color::BLACK );
//Configure prescaler to achieve the correct execution time
demo_pre = Config::MEDIUM_DEMO_US/Config::SCREEN_US;
//Demo is now initialized
f_demo_init = true;
}
//If: demo is initialized and can be run
else
{
//Randomly generate character
uint8_t len_tmp = g_rng_length( g_rng_engine );
//Temp string
char str[MAX_STR_LEN +1];
//Construct string
for (uint8_t t = 0; t < len_tmp;t++)
{
str[t] = g_rng_char( g_rng_engine );
}
//append terminator
str[len_tmp] = '\0';
int height_tmp = g_rng_height( g_rng_engine );
int width_tmp = g_rng_width( g_rng_engine );
Longan_nano::Screen::Color background_tmp = (Longan_nano::Screen::Color)g_rng_color( g_rng_engine );
Longan_nano::Screen::Color foreground_tmp = (Longan_nano::Screen::Color)g_rng_color( g_rng_engine );
//Ask the screen driver to print the character
g_screen.print( height_tmp, width_tmp, str, background_tmp, foreground_tmp );
}
break;
}
//----------------------------------------------------------------
// TEST_NUMBERS
//----------------------------------------------------------------
// Test writing numbers on screen using the number to string screen print
// Profile execution times as well using the Chrono class
case Demo::TEST_NUMBERS:
{
//If: demo is yet to be initialized
if (f_demo_init == false)
{
g_screen.reset_colors();
//Clear the screen
g_screen.clear( Longan_nano::Screen::Color::BLACK );
//Configure prescaler to achieve the correct execution time
demo_pre = Config::MEDIUM_DEMO_US/Config::SCREEN_US;
//Demo is now initialized
f_demo_init = true;
}
//If: demo is initialized and can be run
else
{
//Header
g_screen.print( 0, 0, "DEMO: Numeric String" );
//Demo counter
static int demo_cnt = 0;
demo_cnt++;
//Show a counter left aligned
g_screen.print( 1, 1, "Counter: " );
g_screen.set_format( 8, Longan_nano::Screen::Format_align::ADJ_LEFT, Longan_nano::Screen::Format_format::NUM );
g_screen.print( 1, 11, demo_cnt );
//Show a counter right aligned
g_screen.print( 2, 1, "Counter: " );
g_screen.set_format( 8, Longan_nano::Screen::Format_align::ADJ_RIGHT, Longan_nano::Screen::Format_format::NUM );
g_screen.print( 2, 19, demo_cnt );
//Temp
int tmp;
//Show uptime in microseconds
g_screen.print( 3, 1, "Uptime:" );
g_screen.print( 3, 18, "mS" );
g_screen.set_format( 10, Longan_nano::Screen::Format_align::ADJ_RIGHT, Longan_nano::Screen::Format_format::NUM );
tmp = timer_uptime.stop( Longan_nano::Chrono::Unit::milliseconds );
g_screen.print( 3, 17, tmp );
//Show cpu time spent updating the screen
g_screen.print( 4, 1, "Screen:" );
g_screen.print( 4, 18, "mS" );
tmp = timer_screen.get_accumulator( Longan_nano::Chrono::Unit::milliseconds );
g_screen.print( 4, 17, tmp );
}
break;
}
//----------------------------------------------------------------
// TEST_ENG_NUMBERS
//----------------------------------------------------------------
// Test writing numbers on screen using the number to string screen print
// Profile execution times as well using the Chrono class
case Demo::TEST_ENG_NUMBERS:
{
//If: demo is yet to be initialized
if (f_demo_init == false)
{
g_screen.reset_colors();
//Clear the screen
g_screen.clear( Longan_nano::Screen::Color::BLACK );
//Configure prescaler to achieve the correct execution time
demo_pre = Config::MEDIUM_DEMO_US/Config::SCREEN_US;
//Demo is now initialized
f_demo_init = true;
}
//If: demo is initialized and can be run
else
{
int16_t num_sprites_changed = 0;
//Header
num_sprites_changed += g_screen.print( 0, 0, "DEMO: Profile eng" );
num_sprites_changed += g_screen.print( 1, 4, "|Time[s]|CPU [%]" );
//Show uptime in microseconds
int tmp_uptime;
num_sprites_changed += g_screen.print( 2, 0, "Time|" );
num_sprites_changed += g_screen.print( 2, 12, '|' );
g_screen.set_format( User::String::STRING_SIZE_SENG -1, Longan_nano::Screen::Format_align::ADJ_RIGHT, Longan_nano::Screen::Format_format::ENG, -3 );
tmp_uptime = timer_uptime.stop( Longan_nano::Chrono::Unit::milliseconds );
num_sprites_changed += g_screen.print( 2, 11, tmp_uptime );
//Show cpu time spent updating the screen
int tmp_screen;
num_sprites_changed += g_screen.print( 3, 0, "LCD |" );
num_sprites_changed += g_screen.print( 3, 12, '|' );
tmp_screen = timer_screen.get_accumulator( Longan_nano::Chrono::Unit::milliseconds );
num_sprites_changed += g_screen.print( 3, 11, tmp_screen );
//Compute CPU usage for the screen
int64_t cpu_tmp = (int64_t)1 *tmp_screen *100000 /tmp_uptime;
g_screen.set_format( User::String::STRING_SIZE_SENG -1, Longan_nano::Screen::Format_align::ADJ_RIGHT, Longan_nano::Screen::Format_format::ENG, -3 );
num_sprites_changed += g_screen.print( 3, 19, (int)cpu_tmp );
//Show CPU time spent running the DEMO
num_sprites_changed += g_screen.print( 4, 0, "DEMO|" );
num_sprites_changed += g_screen.print( 4, 12, '|' );
tmp_screen = timer_demo.get_accumulator( Longan_nano::Chrono::Unit::milliseconds );
g_screen.print( 4, 11, tmp_screen );
//Compute CPU usage for the DEMO
cpu_tmp = (int64_t)1 *tmp_screen *100000 /tmp_uptime;
g_screen.set_format( User::String::STRING_SIZE_SENG -1, Longan_nano::Screen::Format_align::ADJ_RIGHT, Longan_nano::Screen::Format_format::ENG, -3 );
num_sprites_changed += g_screen.print( 4, 19, (int)cpu_tmp );
//Profile the number of sprites updated by the previous functions
g_screen.set_format( 4, Longan_nano::Screen::Format_align::ADJ_LEFT, Longan_nano::Screen::Format_format::NUM );
g_screen.print( 1, 0, num_sprites_changed);
}
break;
}
//----------------------------------------------------------------
// TEST_PENDING
//----------------------------------------------------------------
// Test writing numbers on screen using the number to string screen print
// Shows the number of sprites pending for update
case Demo::TEST_PENDING:
{
//If: demo is yet to be initialized
if (f_demo_init == false)
{
g_screen.reset_colors();
//Clear the screen
g_screen.clear( Longan_nano::Screen::Color::BLACK );
//Configure prescaler to achieve the correct execution time
demo_pre = Config::MEDIUM_DEMO_US/Config::SCREEN_US;
//Demo is now initialized
f_demo_init = true;
}
//If: demo is initialized and can be run
else
{
//Header
g_screen.print( 0, 0, "DEMO: Profile eng" );
g_screen.print( 1, 4, "|Time[s]|CPU [%]" );
//Show uptime in microseconds
int tmp_uptime;
g_screen.print( 2, 0, "Time|" );
g_screen.print( 2, 12, '|' );
g_screen.set_format( User::String::STRING_SIZE_SENG -1, Longan_nano::Screen::Format_align::ADJ_RIGHT, Longan_nano::Screen::Format_format::ENG, -3 );
tmp_uptime = timer_uptime.stop( Longan_nano::Chrono::Unit::milliseconds );
g_screen.print( 2, 11, tmp_uptime );
//Show cpu time spent updating the screen
int tmp_screen;
g_screen.print( 3, 0, "LCD |" );
g_screen.print( 3, 12, '|' );
tmp_screen = timer_screen.get_accumulator( Longan_nano::Chrono::Unit::milliseconds );
g_screen.print( 3, 11, tmp_screen );
//Compute CPU usage for the screen
int64_t cpu_tmp = (int64_t)1 *tmp_screen *100000 /tmp_uptime;
g_screen.set_format( User::String::STRING_SIZE_SENG -1, Longan_nano::Screen::Format_align::ADJ_RIGHT, Longan_nano::Screen::Format_format::ENG, -3 );
g_screen.print( 3, 19, (int)cpu_tmp );
//Show CPU time spent running the DEMO
g_screen.print( 4, 0, "DEMO|" );
g_screen.print( 4, 12, '|' );
tmp_screen = timer_demo.get_accumulator( Longan_nano::Chrono::Unit::milliseconds );
g_screen.print( 4, 11, tmp_screen );
//Compute CPU usage for the DEMO
cpu_tmp = (int64_t)1 *tmp_screen *100000 /tmp_uptime;
g_screen.set_format( User::String::STRING_SIZE_SENG -1, Longan_nano::Screen::Format_align::ADJ_RIGHT, Longan_nano::Screen::Format_format::ENG, -3 );
g_screen.print( 4, 19, (int)cpu_tmp );
//Profile the number of sprites pending for update
g_screen.set_format( 4, Longan_nano::Screen::Format_align::ADJ_LEFT, Longan_nano::Screen::Format_format::NUM );
int pending_cnt = g_screen.get_pending();
g_screen.print( 1, 0, pending_cnt);
//Show the error of the screen library
g_screen.print_err( 2, 13 );
}
break;
}
//----------------------------------------------------------------
// TEST_CHANGE_COLORS
//----------------------------------------------------------------
// Test the methods that handles the colors of the sprites without changing the content
// "change_color" change a palette color for another
// "set_palette_color"
case Demo::TEST_CHANGE_COLORS:
{
static uint8_t background_change_cnt;
//Randomly change the defaults background and foreground colors
Longan_nano::Screen::Color background_tmp = (Longan_nano::Screen::Color)g_rng_color( g_rng_engine );
Longan_nano::Screen::Color foreground_tmp = (Longan_nano::Screen::Color)g_rng_color( g_rng_engine );
//If: demo is yet to be initialized
if (f_demo_init == false)
{
g_screen.reset_colors();
//Clear the screen
g_screen.clear( Longan_nano::Screen::Color::BLACK );
//Configure prescaler to achieve the correct execution time
demo_pre = Config::MEDIUM_DEMO_US/Config::SCREEN_US;
//Demo is now initialized
f_demo_init = true;
}
//If: demo is initialized and can be run
else
{
int16_t num_sprites_changed = 0;
//Header
num_sprites_changed += g_screen.print( 0, 0, "DEMO: Eng Num Color" );
num_sprites_changed += g_screen.print( 1, 4, "|Time[s]|CPU [%]" );
//Show uptime in microseconds
int tmp_uptime;
num_sprites_changed += g_screen.print( 2, 0, "Time|" );
num_sprites_changed += g_screen.print( 2, 12, '|' );
g_screen.set_format( User::String::STRING_SIZE_SENG -1, Longan_nano::Screen::Format_align::ADJ_RIGHT, Longan_nano::Screen::Format_format::ENG, -3 );
tmp_uptime = timer_uptime.stop( Longan_nano::Chrono::Unit::milliseconds );
num_sprites_changed += g_screen.print( 2, 11, tmp_uptime );
//Show cpu time spent updating the screen
int tmp_screen;
num_sprites_changed += g_screen.print( 3, 0, "LCD |" );
num_sprites_changed += g_screen.print( 3, 12, '|' );
tmp_screen = timer_screen.get_accumulator( Longan_nano::Chrono::Unit::milliseconds );
num_sprites_changed += g_screen.print( 3, 11, tmp_screen );
//Compute CPU usage for the screen
int64_t cpu_tmp = (int64_t)1 *tmp_screen *100000 /tmp_uptime;
g_screen.set_format( User::String::STRING_SIZE_SENG -1, Longan_nano::Screen::Format_align::ADJ_RIGHT, Longan_nano::Screen::Format_format::ENG, -3 );
num_sprites_changed += g_screen.print( 3, 19, (int)cpu_tmp );
//Show CPU time spent running the DEMO
num_sprites_changed += g_screen.print( 4, 0, "DEMO|" );
num_sprites_changed += g_screen.print( 4, 12, '|' );
tmp_screen = timer_demo.get_accumulator( Longan_nano::Chrono::Unit::milliseconds );
g_screen.print( 4, 11, tmp_screen );
//Compute CPU usage for the DEMO
cpu_tmp = (int64_t)1 *tmp_screen *100000 /tmp_uptime;
g_screen.set_format( User::String::STRING_SIZE_SENG -1, Longan_nano::Screen::Format_align::ADJ_RIGHT, Longan_nano::Screen::Format_format::ENG, -3 );
num_sprites_changed += g_screen.print( 4, 19, (int)cpu_tmp );
//Randomly color a sprite
background_tmp = (Longan_nano::Screen::Color)g_rng_color( g_rng_engine );
foreground_tmp = (Longan_nano::Screen::Color)g_rng_color( g_rng_engine );
background_change_cnt++;
if (background_change_cnt > 15)
{
background_change_cnt = 0;
num_sprites_changed += g_screen.set_default_colors( background_tmp, foreground_tmp );
}
//Profile the number of sprites updated by the previous functions
g_screen.set_format( 4, Longan_nano::Screen::Format_align::ADJ_LEFT, Longan_nano::Screen::Format_format::NUM );
g_screen.print( 1, 0, num_sprites_changed);
}
break;
}
//----------------------------------------------------------------
// TEST_WORKLOAD
//----------------------------------------------------------------
// Profile execution time with constant workload
// Allow to test improvements in the Driver Class and Screen Class
case Demo::TEST_WORKLOAD:
{
//If: demo is yet to be initialized
if (f_demo_init == false)
{
g_screen.reset_colors();
//Clear the screen
g_screen.clear( Longan_nano::Screen::Color::BLACK );
//Configure prescaler to achieve the correct execution time
demo_pre = Config::MEDIUM_DEMO_US/Config::SCREEN_US;
//Demo is now initialized
f_demo_init = true;
}
//If: demo is initialized and can be run
else
{
//Print random color squares on the screen
for (uint8_t t = 0;t < 10;t++)
{
int th = g_rng_height( g_rng_engine );
int tw = g_rng_width( g_rng_engine );
Longan_nano::Screen::Color color_tmp = (Longan_nano::Screen::Color)g_rng_color( g_rng_engine );
//Ask the screen driver to print the character
g_screen.paint( th, tw, color_tmp );
}
//Header
g_screen.print( 0, 0, "CPU |" );
g_screen.print( 0, 12, '|' );
//Compute CPU usage for the screen
int tmp_uptime = timer_uptime.stop( Longan_nano::Chrono::Unit::milliseconds );
int tmp_deltat = timer_screen.get_accumulator( Longan_nano::Chrono::Unit::milliseconds );
int cpu_tmp = (int64_t)1 *tmp_deltat *100000 /tmp_uptime;
g_screen.set_format( User::String::STRING_SIZE_SENG -1, Longan_nano::Screen::Format_align::ADJ_RIGHT, Longan_nano::Screen::Format_format::ENG, -3 );
g_screen.print( 0, 11, (int)cpu_tmp );
//Compute CPU usage for the DEMO
tmp_deltat = timer_demo.get_accumulator( Longan_nano::Chrono::Unit::milliseconds );
cpu_tmp = (int64_t)1 *tmp_deltat *100000 /tmp_uptime;
g_screen.set_format( User::String::STRING_SIZE_SENG -1, Longan_nano::Screen::Format_align::ADJ_RIGHT, Longan_nano::Screen::Format_format::ENG, -3 );
g_screen.print( 0, 19, (int)cpu_tmp );
}
break;
}
//Unhandled demo
default:
{
//Do nothing
}
} //End Switch: for each demo
//Profile time spent running the DEMO
timer_demo.accumulate();
} //End If: demo is authorized to execute
} //End forever
//----------------------------------------------------------------
// RETURN
//----------------------------------------------------------------
return 0;
} //end function: main
/****************************************************************************
** @brief init
** init_pa8_button_interrupt | void
****************************************************************************/
//! @details
//! User clicked the PA8 boot button
//! This button is used to switch between demos
/***************************************************************************/
void init_pa8_button_interrupt( void )
{
//Clock the GPIO banks
rcu_periph_clock_enable(RCU_GPIOA);
//Setup the boot button
gpio_init( GPIOA, GPIO_MODE_IN_FLOATING, GPIO_OSPEED_50MHZ, GPIO_PIN_8 );
//Initialize the ECLIC IRQ lines
eclic_priority_group_set(ECLIC_PRIGROUP_LEVEL3_PRIO1);
eclic_irq_enable(EXTI5_9_IRQn, 1, 1);
//Initialize the EXTI. IRQ can be generated from GPIO edge detectors
gpio_exti_source_select(GPIO_PORT_SOURCE_GPIOA, GPIO_PIN_SOURCE_8);
exti_init(EXTI_8, EXTI_INTERRUPT, EXTI_TRIG_BOTH);
//Clear interrupt flag. Ensure no spurious execution at start
exti_interrupt_flag_clear(EXTI_8);
//Enable the interrupts. From now on interrupt handlers can be executed
eclic_global_interrupt_enable();
return;
} //End init: init_pa8_button_interrupt | void
/****************************************************************************
** @brief isr
** EXTI5_9_IRQHandler | void
****************************************************************************/
//! @details
//! User clicked the PA8 boot button
//! This button is used to switch between demos
/***************************************************************************/
extern "C"
void EXTI5_9_IRQHandler( void )
{
//If: interrupt from PA8 boot button
if (exti_interrupt_flag_get(EXTI_8) != RESET)
{
//Clear the interrupt from PA8 boot button
exti_interrupt_flag_clear(EXTI_8);
//If: the button is released after ISR (UP)
if (gpio_input_bit_get( GPIOA, GPIO_PIN_8 ) == RESET)
{
//Signal event
g_f_pa8_button_up = true;
}
}
//Default: interrupt from an unhandled GPIO
else
{
//Do nothing (should clear the interrupt flags)
}
} //End isr: EXTI5_9_IRQHandler | void
view raw main.cpp hosted with ❤ by GitHub






2020-08-03

2020-08-02 Longan Nano GD32vf103 Display and Screen Classes

>>>Longan Nano GD32VF103<<<

Longan Nano GD32VF103 Risc-V 108MHz 32b MCU toolchain, libraries and applications

>>>Longan Nano Screen and Display Classes<<<


Display Class: Interfaces with the ST7735 80x160 display using SPI0 and DMA0.
Screen Class: Provides asynchronous methods to print on the display



1Longan Nano GD32VF103 Display and Screen Classes

The Longan Nano is equipped with an OLED 0.96 inches 80x160 color screen interfaced to the GD32VF103 through the SPI0 peripheral. Additional GPIO pins are used for chip select, reset and data/command mode.

The scope of this document is to explain the design decision, constraints and architectural choices behind the Screen and Display classes that control the display.



2Pin Configuration

The Screen uses a ST7735S controller. The screen is interfaced directly with the microcontroller.

Illustration 1: Schematics

The CS pin unfortunately is not the SPI0 CS, so cannot be generated by hardware and must be controlled via software.

The RS pin as well must be toggled quite often. The SS7735S has a communication protocol in which a command byte is followed by a number of data bytes. In a typical draw operation there are at least four commands to be sent, requiring at least four toggles.


2.1MCU Peripherals

The GD32VF103 is equipped with two majestic DMA controllers for a total of twelve channels.

To minimize CPU use I can use the DMA0 to handle multi byte transfers from memory to the SPI0 peripheral, making the job of transferring pixel data that much cheaper for the CPU.

All of the DMA channels can steal up to 50% of the CPU main bus bandwidth, so some performance degradation can be expected on the CPU.


Illustration 2: GD32VF103 Peripherals


2.2Screen Configuration

The screen uses a 16bit RGB565 color space with 80x160 = 12800 pixels.

SPI0 has been tested up to 6.7MHz of speed.

Others modes are available but would take time to develop and not offer much savings. Current Screen and Display classes are good enough.


3Software Architecture

A lot of effort went into the design of the software, partitioning the workload and in deciding the ABI and HAL structures.


3.1Specifications

First thing to do is to understand my use case and what I want out of the Longan Nano screen.

  • Debug and profiling: Show things like voltages, currents, encoder readings, communication and errors.

  • Mostly ASCII characters: A fancy real time chart would be cool, but a waste of performance. I have the Raspberry Pi if I want to show fancy charts, and have more pixels and cpu to do so.

  • Low CPU and Memory footprint: I need the MCU to perform a fixed function. That's its primary objective. Debug and profile helps out and is secondary.


3.2Bandwidth and Memory Considerations

In order to refresh each pixel I need at minimum a 80x160x2x8=204.800 [Kb] transfer which yield a theoretical maximum of 32.8 [FPS], but would require the CPU to do nothing but line up bytes for the screen considering the prep time.

Any meaningful implementation needs a frame buffer, that for a full screen would be: 80x160x2 = 25.6 [KB] on an available memory of 32 [KB] meaning at minimum I would consume 80% of the working memory just for the screen.


3.3Architecture

I made a sprite based library. This means that the screen only need a frame buffer as big as the biggest sprite.

I partition the driver in two:

  • Display Class: HAL and interface with the physical OLED. Provides sprite draw methods.

  • Screen Class: I abstract one level up and build all my sprite methods in a Screen class. All the print, sprite maps, frame buffer, etc... So if I change display, I can reuse the screen class.

The division means that I have two frame buffers. A frame buffer for the sprites that has the size of the number of sprites that can be drawn on the screen, and a frame buffer for the pixel data of a single sprite that is used for drawing.

Imagining a screen size of 80x160 and a sprite size of 16x8, this mean the frame buffer for the pixel data will be 16x8x2 = 256[B] while the frame buffer for the sprites will be 80/16x160/8 = 100 [B]. A massive saving in memory. Since I need to only show ascii characters anyway, this is not even limiting the flexibility of the draw too much. A drawback is that character are in a fixed grid, instead of being printable in each position.

The SPI needs time to send data, I can make the class asynchronous by returning after initiating the communication. I construct a core Update() method that returns without doing anything if the peripherals are busy.

This architectural decision means that the user will have to periodically call the update method, and it also means that the user decides how much resources allocate to the screen. If the user calls the update method slower than they are calling the print methods, simply some sprites won't be drawn.

Finally, I decide to split the update method in two. The Display::Update() is busy while sending a sprite, and idle otherwise. The higher Screen::Update() scans the frame buffer and register a sprite to send, then loops around the Display::Update() until the draw is complete. Since they are all non blocking calls, this reduces the CPU use to the minimum.

Another optimization is to have an update flag. Sprites are only redrawn if they have changed. This optimization costs an additional update frame buffer, but massively reduces bandwidth use when characters change sparsely.

Illustration 3: Display and Screen Class Architectures


4Conclusions

The Screen Class and Display class provide a ascii based frame buffer and support asynchronous control of the screen on board the Longan Nano board.

Extensive print methods allow to format numbers in a variety of ways for the library primary propose, which is to show fast updating debug information while using little CPU.

No graphics mode is supported as of now.

Future developments include:

  • Reduce idle use by using a pending counter

  • Improve the FSM of the display

  • Improve CPU use


5Source Code

Source code of the Demo application. Repository.

/**********************************************************************************
BSD 3-Clause License
Original Copyright (c) 2019-2020, Samuli Laine
Modified Copyright (c) 2020, Orso Eric
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
3. Neither the name of the copyright holder nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
**********************************************************************************/
/**********************************************************************************
** ENVIROMENT VARIABILE
**********************************************************************************/
#ifndef ST7735S_W160_H80_C16_HPP_
#define ST7735S_W160_H80_C16_HPP_
/**********************************************************************************
** GLOBAL INCLUDES
**********************************************************************************/
//Standard bit size types
#include <stdint.h>
//Longan Nano HAL
#include <gd32vf103.h>
#ifndef LONGAN_NANO_CHRONO_H_
//Longan Nano Chrono functions
#include "longan_nano_chrono.hpp"
#endif
//! @namespace Longan_nano namespace encapsulating all related drivers and HAL
namespace Longan_nano
{
/**********************************************************************************
** TYPEDEFS
**********************************************************************************/
/**********************************************************************************
** PROTOTYPE: STRUCTURES
**********************************************************************************/
/**********************************************************************************
** PROTOTYPE: GLOBAL VARIABILES
**********************************************************************************/
/**********************************************************************************
** PROTOTYPE: CLASS
**********************************************************************************/
/************************************************************************************/
//! @class Display
/************************************************************************************/
//! @author Orso Eric
//! @version 2020-08-08
//! @brief Longan Nano Display Driver
//! @copyright BSD 3-Clause License (c) 2019-2020, Samuli Laine
//! @copyright BSD 3-Clause License (c) 2020, Orso Eric
//! @details
//! \n Driver to use the embedded 160x80 0.96' LCD on the longan nano
//! \n SPI0 interface with physical screen
//! \n DMA0: executes a number of SPI0 transfers in background
//! \n Hardware display has a ST7735S controller
//! \n The driver is meant to accelerate sprite based drawing
//! \n Sprite buffer and draw are going to be handled by an higher level class
//! \n 2020-07-07
//! \n Ported original LCD example to class. Basic operation. DMA doesn't work
//! \n 2020-07-08
//! \n Skeletron for sprites
//! \n 2020-07-10
//! \n Change architecture. S7735S_W160_H80_C16.hpp. Common name drive class: Display
//! \n With another display controller, Screen class and Display class interface remains the same
//! \n S7735S_W160_H80_C16.hpp is the specific lbrary for the physical display
//! \n Clean up, partition in a more intelligent way calls. Test project for driver only without sprites
//! \n 2020-07-15
//! \n Tested draw with HAL builtin
//! \n 2020-07-21
//! \n Found working DMA example from Samuli Laine https://github.com/slmisc/gd32v-lcd
//! \n 2020-07-22
//! \n Transplant working driver function
//! \n Refactor function to use configuration and fit class form
//! \n set_sprite and update_sprite provide a non blocking draw method that hide the wait time
//! \n application can call update_sprite and immediately continue to do work while SPI and DMA are busy
//! \n update_sprite will immediately return without doing work if SPI or DMA are busy or if there is nothing to draw
//! \n 2020-07-23
//! \n Clean up example
//! \n Prune excess methods from display class. Leave only the draw sprite primitives. Application is to be given control on resource utilization of the display
//! \n Refactor all calls to use configuration enum for the hardware peripherals
//! \n 2020-07-24
//! \n Fixed color conversion function
//! \n 2020-08-06
//! \n Massive update of style
//! \n Refactor functions, especially initialization
//! \n Full support for USE_DMA=false. screen updates much slower at the same CPU use
//! \n 2020-08-07
//! \n Bugfix: Solid color draw was bugged
/************************************************************************************/
class Display
{
//Visible to all
public:
/*********************************************************************************************************************************************************
**********************************************************************************************************************************************************
** CONSTRUCTORS
**********************************************************************************************************************************************************
*********************************************************************************************************************************************************/
//Empty Constructor
Display( void );
/*********************************************************************************************************************************************************
**********************************************************************************************************************************************************
** DESTRUCTORS
**********************************************************************************************************************************************************
*********************************************************************************************************************************************************/
//Empty Destructor
~Display( void );
/*********************************************************************************************************************************************************
**********************************************************************************************************************************************************
** PUBLIC INIT
**********************************************************************************************************************************************************
*********************************************************************************************************************************************************/
//Initialize the longan nano display and related peripherals
bool init( void );
/*********************************************************************************************************************************************************
**********************************************************************************************************************************************************
** PUBLIC METHODS
**********************************************************************************************************************************************************
*********************************************************************************************************************************************************/
//convert from 24b 8R8G8B space to 16bit 5R6G5B space
static uint16_t color( uint8_t r, uint8_t g, uint8_t b );
//Register a sprite for the driver to draw. Complex pixel map.
int register_sprite( int origin_h, int origin_w, int size_h, int size_w, uint16_t* sprite_ptr );
//Register a sprite for the driver to draw. Solid color.
int register_sprite( int origin_h, int origin_w, int size_h, int size_w, uint16_t sprite_color );
//Core method. FSM that physically updates the screen. Return: false = IDLE | true = BUSY
bool update_sprite( void );
//Draw a sprite. Complex color map. Blocking Method.
int draw_sprite( int origin_h, int origin_w, int size_h, int size_w, uint16_t* sprite_ptr );
//Draw a sprite. Solid color. Blocking Method.
int draw_sprite( int origin_h, int origin_w, int size_h, int size_w, uint16_t sprite_color );
//Clear the screen to black. Blocking method.
int clear( void );
//Clear the screen to a given color. Blocking method.
int clear( uint16_t color );
protected:
/*********************************************************************************************************************************************************
**********************************************************************************************************************************************************
** PROTECTED ENUM
**********************************************************************************************************************************************************
*********************************************************************************************************************************************************/
//! @brief Configuration parameters of the LCD Display class
typedef enum _Config
{
//Screen physical configuration
WIDTH = 160, //WIdth of the LCD display
HEIGHT = 80, //Height of the LCD display
PIXEL_COUNT = WIDTH *HEIGHT, //Number of pixels
COLOR_DEPTH = 16, //Color depth. Screen allows 12, 16 and 18
ROW_ADDRESS_OFFSET = 1, //Offset to be applied to the row address (physical pixels do not begin in 0,0)
COL_ADDRESS_OFFSET = 26, //Offset to be applied to the col address (physical pixels do not begin in 0,0)
//Screen GPIO Configuration
RS_GPIO = GPIOB, //RS pin of the LCD
RS_PIN = GPIO_PIN_0, //RS pin of the LCD
RST_GPIO = GPIOB, //Reset pin of the LCD
RST_PIN = GPIO_PIN_1, //Reset pin of the LCD
RESET_DELAY = 1, //TIme needed for the reset to take effect in milliseconds
//SPI Configuration
SPI_CH = SPI0, //SPI used for the ST7735S
SPI_CS_GPIO = GPIOB, //SPI Chip Select pin of the LCD
SPI_CS_PIN = GPIO_PIN_2, //SPI Chip Select pin of the LCD
SPI_CLK_GPIO = GPIOA, //SPI Clock pin of the LCD
SPI_CLK_PIN = GPIO_PIN_5, //SPI Clock pin of the LCD
SPI_MISO_GPIO = GPIOA, //SPI MISO In pin of the LCD
SPI_MISO_PIN = GPIO_PIN_6, //SPI MISO In pin of the LCD
SPI_MOSI_GPIO = GPIOA, //SPI MOSI In pin of the LCD
SPI_MOSI_PIN = GPIO_PIN_7, //SPI MOSI In pin of the LCD
//DMA Configuration
USE_DMA = true, //With DMA acceleration disabled, CPU use is the same, but the screen takes a lot longer to refresh as there are 74 update/sprite instead of 8 update/sprite
DMA_SPI_TX = DMA0, //DMA pheriperal used for the SPI transmit
DMA_SPI_TX_CH = (dma_channel_enum)DMA_CH2, //DMA channel used for the SPI transmit
//DMA_SPI_RX = DMA0, //DMA pheriperal used for the SPI receive
//DMA_SPI_RX_CH = (dma_channel_enum)DMA_CH1, //DMA channel used for the SPI receive
} Config;
private:
/*********************************************************************************************************************************************************
**********************************************************************************************************************************************************
** PRIVATE ENUM
**********************************************************************************************************************************************************
*********************************************************************************************************************************************************/
//! @brief ST7735S Commands
typedef enum _Command
{
// SETUP_NORMAL_MODE
//Frame rate=850kHz/((RTNA x 2 + 40) x (LINE + FPA + BPA +2))
//Frame Rate = 850Khz/( (5*2+40)*(80+56+56+2) )= 87.6[Hz]
//RTNA | FPA | BPA
SETUP_NORMAL_MODE = 0xB1,
// SETUP_IDLE_MODE
//Frame rate=850kHz/((RTNA x 2 + 40) x (LINE + FPA + BPA +2))
//Frame Rate = 850Khz/( (5*2+40)*(80+56+56+2) )= 87.6[Hz]
//RTNA | FPA | BPA
SETUP_IDLE_MODE = 0xB2,
SETUP_PARTIAL_MODE = 0xB3,
// DISPLAY_INVERSION_CONTROL
//false Dot Inversion | true = Normal Mode Column Inversion
//Bit 0 | Normal Mode
//Bit 1 | idle Mode
//Bit 2 | partial mode/full colors
DISPLAY_INVERSION_CONTROL = 0xB4,
POWER_GVDD = 0xC0,
POWER_VGH_VGL = 0xC1,
POWER_VCOM1 = 0xC5,
POWER_MODE_NORMAL = 0xC2,
POWER_MODE_IDLE = 0xC3,
POWER_MODE_PARTIAL = 0xC4,
ADJUST_GAMMA_PLUS = 0xE0,
ADJUST_GAMMA_MINUS = 0xE1,
// COLOR_FORMAT
//0x03 | 12b |4R4G|4B...4R|4G4B| 3 bytes transfer 2 color
//0x05 | 16b |5R3G|3G5B| 2 bytes transfer 1 color
//0x06 | 18b |6R..|6G..|6b..| 3 bytes transfer 1 color
COLOR_FORMAT = 0x3A,
MEMORY_DATA_ACCESS_CONTROL = 0x36,
DISPLAY_ON = 0x29,
SLEEP_OUT_BOOSTER_ON = 0x11,
ENABLE_DISPLAY_INVERSION = 0x21,
SEND_ROW_ADDRESS = 0x2A, //Column address (ST7735S datasheet page 128/201)
SEND_COL_ADDRESS = 0x2B, //Row Address (ST7735S datasheet page 131/201)
WRITE_MEM = 0x2C, //Memory Write. After this command, the display expects pixel data (ST7735S datasheet page 132/201)
TERMINATOR = 0xFF, //Special terminator for the command parser FSM
} Command;
/*********************************************************************************************************************************************************
**********************************************************************************************************************************************************
** PRIVATE STRUCT
**********************************************************************************************************************************************************
*********************************************************************************************************************************************************/
//! @brief Sprite informations
typedef struct _Sprite
{
//origin pixel of the sprite on the screen
uint16_t origin_h;
uint16_t origin_w;
//Size of the sprite transfer
uint16_t size_h;
uint16_t size_w;
uint32_t size;
//false = sprite pixel buffer | true = solid color
bool b_solid_color;
//I do not use the sprite map and the solid color at the same time
union
{
//pointer to the sprite pixel buffer
uint16_t *sprite_ptr;
//When in solid color mode, save the color to be applied to the full sprite
uint16_t solid_color;
};
} Sprite;
/*********************************************************************************************************************************************************
**********************************************************************************************************************************************************
** PRIVATE INIT
**********************************************************************************************************************************************************
*********************************************************************************************************************************************************/
//initialize GPIO configuration
bool init_gpio( void );
//Initialize SPI that communicates with the screen
bool init_spi( void );
//Initialize DMA that accelerates the SPI. Skipped if Config::USE_DMA is set to false
bool init_dma( void );
//Send ST7735 init sequence
bool init_st7735( void );
/*********************************************************************************************************************************************************
**********************************************************************************************************************************************************
** PRIVATE HAL
**********************************************************************************************************************************************************
*********************************************************************************************************************************************************/
//return true when the SPI is IDLE
bool is_spi_idle( void );
//Return true when the SPI is done TX
bool is_spi_done_tx( void );
//wait until SPI is idle. Blocking function.
void spi_wait_idle( void );
//wait until SPI is dont TX. Blocking function.
void spi_wait_tbe( void );
//select display
void cs_active( void );
//de-select display
void cs_inactive( void );
//Display in command mode
void rs_mode_cmd( void );
//Display in data mode
void rs_mode_data( void );
//Reset the display
void rst_active( void );
//Activate the display
void rst_inactive( void );
//Configure the SPI to 8b
void spi_set_8bit( void );
//Configure the SPI to 16b
void spi_set_16bit( void );
//Use the DMA to send a 16b memory through the SPI
void dma_send_map16( uint16_t *data_ptr, uint16_t data_size );
//Use the DMA to send a 16b data through the SPI a number of times
void dma_send_solid16( uint16_t *data_ptr, uint16_t data_size );
/*********************************************************************************************************************************************************
**********************************************************************************************************************************************************
** PRIVATE VARS
**********************************************************************************************************************************************************
*********************************************************************************************************************************************************/
//! @brief ST7735S initialization sequence. Stored in flash memory
static constexpr uint8_t g_st7735s_init_sequence[] =
{
Command::ENABLE_DISPLAY_INVERSION, Command::TERMINATOR,
Command::SETUP_NORMAL_MODE, 0x05, 0x3a, 0x3a, Command::TERMINATOR,
Command::SETUP_IDLE_MODE, 0x05, 0x3a, 0x3a, Command::TERMINATOR,
Command::SETUP_PARTIAL_MODE, 0x05, 0x3a, 0x3a, 0x05, 0x3a, 0x3a, Command::TERMINATOR,
Command::DISPLAY_INVERSION_CONTROL, 0x03, Command::TERMINATOR,
Command::POWER_GVDD, 0x62, 0x02, 0x04, Command::TERMINATOR,
Command::POWER_VGH_VGL, 0xc0, Command::TERMINATOR,
Command::POWER_MODE_NORMAL, 0x0d, 0x00, Command::TERMINATOR,
Command::POWER_MODE_IDLE, 0x8d, 0x6a, Command::TERMINATOR,
Command::POWER_MODE_PARTIAL, 0x8d, 0xee, Command::TERMINATOR,
Command::POWER_VCOM1, 0x0e, Command::TERMINATOR,
Command::ADJUST_GAMMA_PLUS, 0x10, 0x0e, 0x02, 0x03, 0x0e, 0x07, 0x02, 0x07, 0x0a, 0x12, 0x27, 0x37, 0x00, 0x0d, 0x0e, 0x10, Command::TERMINATOR,
Command::ADJUST_GAMMA_MINUS, 0x10, 0x0e, 0x03, 0x03, 0x0f, 0x06, 0x02, 0x08, 0x0a, 0x13, 0x26, 0x36, 0x00, 0x0d, 0x0e, 0x10, Command::TERMINATOR,
Command::COLOR_FORMAT, 0x55, Command::TERMINATOR,
Command::MEMORY_DATA_ACCESS_CONTROL, 0x78, Command::TERMINATOR,
Command::DISPLAY_ON, Command::TERMINATOR,
Command::SLEEP_OUT_BOOSTER_ON, Command::TERMINATOR,
Command::TERMINATOR,
};
//! @brief Runtime sprite informations
Sprite g_sprite;
//! @brief Buffer to send address data using DMA
uint16_t g_address_buffer[2];
//! @brief FSM status
uint32_t g_sprite_status;
//--------------------------------------------------------------------------
// End Private
//--------------------------------------------------------------------------
}; //End class: Lcd
/*********************************************************************************************************************************************************
**********************************************************************************************************************************************************
** CONSTRUCTORS
**********************************************************************************************************************************************************
*********************************************************************************************************************************************************/
/***************************************************************************/
//! @brief constructor
//! Display | void |
/***************************************************************************/
//! @details
//! \n initialize display class
//! \n will NOT initialize the peripherals
/***************************************************************************/
Display::Display( void )
{
//----------------------------------------------------------------
// BODY
//----------------------------------------------------------------
//Initialize class vars
//FSM to idle
this -> g_sprite_status = 0;
//----------------------------------------------------------------
// RETURN
//----------------------------------------------------------------
return;
} //End Constructor: Display | void |
/*********************************************************************************************************************************************************
**********************************************************************************************************************************************************
** DESTRUCTORS
**********************************************************************************************************************************************************
*********************************************************************************************************************************************************/
/***************************************************************************/
//! @brief destructor
//! ~Display | void |
/***************************************************************************/
Display::~Display( void )
{
//----------------------------------------------------------------
// RETURN
//----------------------------------------------------------------
return;
} //End Destructor: ~Display | void |
/*********************************************************************************************************************************************************
**********************************************************************************************************************************************************
** PUBLIC INIT
**********************************************************************************************************************************************************
*********************************************************************************************************************************************************/
/***************************************************************************/
//! @brief Public init
//! init | void |
/***************************************************************************/
//! @return bool | false = OK | true = ERR
//! @details
//! \n initialize the longan nano display and related peripherals
/***************************************************************************/
bool Display::init( void )
{
//----------------------------------------------------------------
// VARS
//----------------------------------------------------------------
//Retun flag
bool f_ret = false;
//----------------------------------------------------------------
// BODY
//----------------------------------------------------------------
//Initialize GPIO configuration
f_ret |= this -> init_gpio();
//Reset the ST7735 display
this -> rs_mode_data();
this -> rst_active();
this -> cs_inactive();
Longan_nano::Chrono::delay( Longan_nano::Chrono::Unit::milliseconds, Config::RESET_DELAY );
//Activate the ST7735 display
this -> rst_inactive();
Longan_nano::Chrono::delay( Longan_nano::Chrono::Unit::milliseconds, Config::RESET_DELAY );
//Initialize SPI that communicates with the display
f_ret |= this -> init_spi();
//Initialize the DMA that accelerates the SPI
f_ret |= this -> init_dma();
//Send ST7735 Initialization sequence
f_ret |= this -> init_st7735();
//----------------------------------------------------------------
// RETURN
//----------------------------------------------------------------
return f_ret;
} //End Public init: init | void |
/*********************************************************************************************************************************************************
**********************************************************************************************************************************************************
** PUBLIC METHODS
**********************************************************************************************************************************************************
*********************************************************************************************************************************************************/
/***************************************************************************/
//! @brief Public Method
//! color | uint8_t | uint8_t | uint8_t |
/***************************************************************************/
//! @param r | uint8_t | 8bit red color channel
//! @param g | uint8_t | 8bit green color channel
//! @param b | uint8_t | 8bit blue color channel
//! @return uint16_t | RGB565 color compatible with ST7735 display color space
//! @details
//! \n convert from 24b 8R8G8B space to 16bit 5R6G5B space
//! \n RRRRRRRR
//! \n GGGGGGGG
//! \n BBBBBBBB
//! \n RRRRRGGGGGGBBBBB
/***************************************************************************/
uint16_t Display::color( uint8_t r, uint8_t g, uint8_t b )
{
//----------------------------------------------------------------
// RETURN
//----------------------------------------------------------------
return ( ((uint16_t)0) | ((r & 0xF8) << 8) | ((g & 0xFC) << 3) | ((b & 0xF8) >> 3) );
} //End Public Method: color | uint8_t | uint8_t | uint8_t |
/***************************************************************************/
//! @brief public method
//! register_sprite | int | int | int | int | uint16_t * |
/***************************************************************************/
//! @param origin_h | int | starting top left corner height of the sprite
//! @param origin_w | int | starting top left corner height of the sprite
//! @param size_h | int | height size of the sprite
//! @param size_w | int | width size of the sprite
//! @param sprite_ptr | uint16_t * | pointer to RGB565 pixel color map
//! @return int | number of pixels queued for draw
//! @details
//! \n Ask the driver to draw a sprite
//! \n The draw method handles sprites that fill only part of the screen
//! \n If reworked, the buffer stays the same or is smaller than user buffer
//! \n 1) Sprite fully inside screen area: the sprite is queued for draw
//! \n 2) Sprite is fully outside screen area: no pixels are queued for draw
//! \n 3) sprite is partially outside screen area: the method rework the buffer to include only pixel that can be drawn
/***************************************************************************/
int Display::register_sprite( int origin_h, int origin_w, int size_h, int size_w, uint16_t* sprite_ptr )
{
//----------------------------------------------------------------
// BODY
//----------------------------------------------------------------
//! @todo handle partial sprite draw
this -> g_sprite.origin_w = origin_w;
this -> g_sprite.origin_h = origin_h;
this -> g_sprite.size_w = size_w;
this -> g_sprite.size_h = size_h;
this -> g_sprite.size = size_w *size_h;
this -> g_sprite.b_solid_color = false;
this -> g_sprite.sprite_ptr = sprite_ptr;
//Start the FSM
this -> g_sprite_status = 1;
//----------------------------------------------------------------
// RETURN
//----------------------------------------------------------------
return (size_w *size_h);
} //End Public Method: register_sprite | int | int | int | int | uint16_t * |
/***************************************************************************/
//! @brief public method
//! register_sprite | int | int | int | int | uint16_t |
/***************************************************************************/
//! @param origin_h | int | starting top left corner height of the sprite
//! @param origin_w | int | starting top left corner height of the sprite
//! @param size_h | int | height size of the sprite
//! @param size_w | int | width size of the sprite
//! @param sprite_color | uint16_t | RGB565 pixel solid color for the full sprite
//! @return int | number of pixels queued for draw
//! @details
//! \n Ask the driver to draw a sprite with a solid color
//! \n The draw method handles sprites that fill only part of the screen
//! \n If reworked, the buffer stays the same or is smaller than user buffer
//! \n 1) Sprite fully inside screen area: the sprite is queued for draw
//! \n 2) Sprite is fully outside screen area: no pixels are queued for draw
//! \n 3) sprite is partially outside screen area: the method rework the buffer to include only pixel that can be drawn
/***************************************************************************/
int Display::register_sprite( int origin_h, int origin_w, int size_h, int size_w, uint16_t sprite_color )
{
//----------------------------------------------------------------
// BODY
//----------------------------------------------------------------
//! @todo handle partial sprite draw
//Size of the sprite
this -> g_sprite.origin_w = origin_w;
this -> g_sprite.origin_h = origin_h;
this -> g_sprite.size_w = size_w;
this -> g_sprite.size_h = size_h;
this -> g_sprite.size = size_w *size_h;
//Draw a solid color
this -> g_sprite.b_solid_color = true;
this -> g_sprite.solid_color = sprite_color;
//Start the FSM
this -> g_sprite_status = 1;
//----------------------------------------------------------------
// RETURN
//----------------------------------------------------------------
return (this -> g_sprite.size);
} //End Public Method: register_sprite | int | int | int | int | uint16_t * |
/***************************************************************************/
//! @brief public method
//! update_sprite | void |
/***************************************************************************/
//! @return bool | false = IDLE | true = BUSY
//! @details
//! \n Execute a step of the FSM that interfaces with the physical display
//! \n Handle both solid color and color map
//! \n sprite data must already be valid before execution
/***************************************************************************/
bool Display::update_sprite( void )
{
//----------------------------------------------------------------
// BODY
//----------------------------------------------------------------
//Switch: FSM status
switch (this -> g_sprite_status)
{
//IDLE
case 0:
{
//Do Nothing
break;
}
//Row address
case 1:
{
if (this -> is_spi_idle() == true)
{
//Commands are 8b
this -> spi_set_8bit();
this -> rs_mode_cmd();
spi_i2s_data_transmit(Config::SPI_CH, Command::SEND_ROW_ADDRESS);
//Next state
this -> g_sprite_status++;
}
break;
}
//Row Address start
case 2:
{
//If: user wants to use the DMA
if (Config::USE_DMA == true)
{
if (this -> is_spi_idle() == true)
{
//Configure Data are 16b
this -> rs_mode_data();
this -> spi_set_16bit();
//Load addresses on the address buffer
this -> g_address_buffer[ 0 ] = this -> g_sprite.origin_w +Config::ROW_ADDRESS_OFFSET;
this -> g_address_buffer[ 1 ] = this -> g_sprite.origin_w +Config::ROW_ADDRESS_OFFSET +this -> g_sprite.size_w -1;
//Program the DMA to send the address
this -> dma_send_map16( this -> g_address_buffer, 2 );
//Next state. Skip second SPI transfer
this -> g_sprite_status += 2;
}
}
//End If: user doesn't want to use the DMA
else
{
if (this -> is_spi_idle() == true)
{
this -> spi_set_16bit();
this -> rs_mode_data();
spi_i2s_data_transmit(Config::SPI_CH, this -> g_sprite.origin_w +Config::ROW_ADDRESS_OFFSET);
//Next state
this -> g_sprite_status++;
}
}
break;
}
//Row Address stop
case 3:
{
if (this -> is_spi_done_tx() == true)
{
spi_i2s_data_transmit(Config::SPI_CH, this -> g_sprite.origin_w +Config::ROW_ADDRESS_OFFSET +this -> g_sprite.size_w -1);
//Next state
this -> g_sprite_status++;
}
break;
}
//Col Address
case 4:
{
if (this -> is_spi_idle() == true)
{
//Commands are 8b
this -> spi_set_8bit();
this -> rs_mode_cmd();
spi_i2s_data_transmit(Config::SPI_CH, Command::SEND_COL_ADDRESS);
//Next state
this -> g_sprite_status++;
}
break;
}
//Col Address Start
case 5:
{
//If: user wants to use the DMA
if (Config::USE_DMA == true)
{
if (this -> is_spi_idle() == true)
{
//Configure HW
this -> rs_mode_data();
this -> spi_set_16bit();
//Load addresses on the address buffer
this -> g_address_buffer[ 0 ] = this -> g_sprite.origin_h +Config::COL_ADDRESS_OFFSET;
this -> g_address_buffer[ 1 ] = this -> g_sprite.origin_h +Config::COL_ADDRESS_OFFSET +this -> g_sprite.size_h -1;
//Program the DMA to send the address
this -> dma_send_map16( this -> g_address_buffer, 2 );
//Next state. Skip second SPI transfer
this -> g_sprite_status += 2;
}
}
//End If: user doesn't want to use the DMA
else
{
if (this -> is_spi_idle() == true)
{
this -> spi_set_16bit();
this -> rs_mode_data();
spi_i2s_data_transmit(Config::SPI_CH, this -> g_sprite.origin_h +Config::COL_ADDRESS_OFFSET);
//Next state
this -> g_sprite_status++;
}
}
break;
}
//Col Address Stop
case 6:
{
if (this -> is_spi_done_tx() == true)
{
spi_i2s_data_transmit( Config::SPI_CH, this -> g_sprite.origin_h +Config::COL_ADDRESS_OFFSET +this -> g_sprite.size_h -1 );
//Next state
this -> g_sprite_status++;
}
break;
}
//Write Memory
case 7:
{
if (this -> is_spi_idle() == true)
{
this -> spi_set_8bit();
this -> rs_mode_cmd();
spi_i2s_data_transmit( Config::SPI_CH, Command::WRITE_MEM );
//Next state. DMA uses a single state, SPI use one state per pixel
this -> g_sprite_status = ((Config::USE_DMA == true)?(8):(10));
}
break;
}
//DMA Send
case 8:
{
if (this -> is_spi_idle() == true)
{
this -> rs_mode_data();
this -> spi_set_16bit();
//If: the sprite uses a color map
if (this -> g_sprite.b_solid_color == false)
{
//Program the DMA to send the pixel map
this -> dma_send_map16( this -> g_sprite.sprite_ptr, this -> g_sprite.size );
} //End If: the sprite uses a color map
//If: the sprite is solid color
else //if (this -> g_sprite.b_solid_color == true)
{
//Program the DMA to send the same pixel a number of times
this -> dma_send_solid16( &this -> g_sprite.solid_color, this -> g_sprite.size );
} //End If: the sprite is solid color
//STOP
this -> g_sprite_status = 9;
}
break;
}
//STOP
case 9:
{
if (this -> is_spi_idle() == true)
{
//Return to IDLE
this -> g_sprite_status = 0;
}
break;
}
//SPI SEND FIRST
case 10:
{
if (this -> is_spi_idle() == true)
{
this -> rs_mode_data();
this -> spi_set_16bit();
spi_i2s_data_transmit( Config::SPI_CH, (this -> g_sprite.b_solid_color == false)?(this -> g_sprite.sprite_ptr[this -> g_sprite_status -10]):(this -> g_sprite.solid_color) );
//If: all pixels have been transfered
if ((this -> g_sprite_status -10) >= (this -> g_sprite.size -1))
{
//STOP
this -> g_sprite_status = 9;
}
//If: there are pixels to be transfered
else
{
//Next pixel
this -> g_sprite_status++;
}
}
break;
}
//SPI SEND
default:
{
if (this -> is_spi_done_tx() == true)
{
spi_i2s_data_transmit( Config::SPI_CH, (this -> g_sprite.b_solid_color == false)?(this -> g_sprite.sprite_ptr[this -> g_sprite_status -10]):(this -> g_sprite.solid_color) );
//If: all pixels have been transfered
if ((this -> g_sprite_status -10) >= (this -> g_sprite.size -1))
{
//STOP
this -> g_sprite_status = 9;
}
//If: there are pixels to be transfered
else
{
//Next pixel
this -> g_sprite_status++;
}
}
}
} //End Switch: FSM Status
//----------------------------------------------------------------
// RETURN
//----------------------------------------------------------------
return (this -> g_sprite_status != 0);
} //End public method: update_sprite | void |
/***************************************************************************/
//! @brief public method
//! draw_sprite | int | int | int | int | uint16_t * |
/***************************************************************************/
//! @param origin_h | int | starting top left corner height of the sprite
//! @param origin_w | int | starting top left corner height of the sprite
//! @param size_h | int | height size of the sprite
//! @param size_w | int | width size of the sprite
//! @param sprite_ptr | uint16_t * | pointer to RGB565 pixel color map
//! @return int | number of pixels drawn
//! @details
//! \n Draw a sprite
//! \n Blocking method
//! \n Draw a color map, requires a user buffer of the proper size
/***************************************************************************/
int Display::draw_sprite( int origin_h, int origin_w, int size_h, int size_w, uint16_t* sprite_ptr )
{
//----------------------------------------------------------------
// VARS
//----------------------------------------------------------------
//temp return
bool f_ret;
//Number of pixels drawn by the method
int pixel_count;
//----------------------------------------------------------------
// BODY
//----------------------------------------------------------------
//Register the sprite to be drawn. Authorize the update FSM to draw the sprite through the "update_sprite" non blocking method
pixel_count = this -> register_sprite( origin_h, origin_w, size_h, size_w, sprite_ptr );
//Allow FSM to run if I have at least one pixel to draw
f_ret = (pixel_count > 0);
//While: the driver FSM is busy
while (f_ret == true)
{
//Execute a step of the FSM
f_ret = this -> update_sprite();
}
//----------------------------------------------------------------
// RETURN
//----------------------------------------------------------------
return pixel_count;
} //End Public Method: draw_sprite | int | int | int | int | uint16_t * |
/***************************************************************************/
//! @brief public method
//! draw_sprite | int | int | int | int | uint16_t |
/***************************************************************************/
//! @param origin_h | int | starting top left corner height of the sprite
//! @param origin_w | int | starting top left corner height of the sprite
//! @param size_h | int | height size of the sprite
//! @param size_w | int | width size of the sprite
//! @param sprite_color | uint16_t | RGB565 pixel solid color for the full sprite
//! @return int | number of pixels drawn
//! @details
//! \n Draw a sprite
//! \n Blocking method
//! \n Draw a solid color sprite and doesn't require a color map
/***************************************************************************/
int Display::draw_sprite( int origin_h, int origin_w, int size_h, int size_w, uint16_t sprite_color )
{
//----------------------------------------------------------------
// VARS
//----------------------------------------------------------------
//temp return
bool f_ret;
//Number of pixels drawn by the method
int pixel_count;
//----------------------------------------------------------------
// BODY
//----------------------------------------------------------------
//Register the sprite to be drawn. Authorize the update FSM to draw the sprite through the "update_sprite" non blocking method
pixel_count = this -> register_sprite( origin_h, origin_w, size_h, size_w, sprite_color );
//Allow FSM to run if I have at least one pixel to draw
f_ret = (pixel_count > 0);
//While: the driver FSM is busy
while (f_ret == true)
{
//Execute a step of the FSM
f_ret = this -> update_sprite();
}
//----------------------------------------------------------------
// RETURN
//----------------------------------------------------------------
return pixel_count;
} //End Public Method: draw_sprite | int | int | int | int | uint16_t |
/***************************************************************************/
//! @brief public method
//! clear | void |
/***************************************************************************/
//! @return int | number of pixels drawn
//! @details
//! \n Clear the screen. Blocking method.
//! \n Draw a black sprite the size of the screen
/***************************************************************************/
inline int Display::clear( void )
{
//----------------------------------------------------------------
// BODY
//----------------------------------------------------------------
//blocking draw sprite
int pixel_count = this -> draw_sprite( 0, 0, Config::HEIGHT, Config::WIDTH, Display::color( 0, 0, 0 ) );
//----------------------------------------------------------------
// RETURN
//----------------------------------------------------------------
return pixel_count;
} //End public method: clear | void |
/***************************************************************************/
//! @brief public method
//! clear | void |
/***************************************************************************/
//! @param color | uint16_t | clear the display to a solid color
//! @return int | number of pixels drawn
//! @details
//! \n Clear the screen. Blocking method.
//! \n Draw a black sprite the size of the screen
/***************************************************************************/
inline int Display::clear( uint16_t color )
{
//----------------------------------------------------------------
// BODY
//----------------------------------------------------------------
int pixel_count = this -> draw_sprite( 0, 0, Config::HEIGHT, Config::WIDTH, color );
//----------------------------------------------------------------
// RETURN
//----------------------------------------------------------------
return pixel_count;
} //End public method: clear | void |
/*********************************************************************************************************************************************************
**********************************************************************************************************************************************************
** PRIVATE INIT
**********************************************************************************************************************************************************
*********************************************************************************************************************************************************/
/***************************************************************************/
//! @brief Private init
//! init_gpio | void |
/***************************************************************************/
//! @return bool | false = OK | true = ERR
//! @details
//! \n HAL method
//! \n initialize GPIO configuration
/***************************************************************************/
inline bool Display::init_gpio( void )
{
//----------------------------------------------------------------
// BODY
//----------------------------------------------------------------
//Clock the GPIOs
rcu_periph_clock_enable( RCU_GPIOA );
rcu_periph_clock_enable( RCU_GPIOB );
rcu_periph_clock_enable( RCU_AF );
//LCD RS command pin
gpio_init( Config::RS_GPIO, GPIO_MODE_OUT_PP, GPIO_OSPEED_50MHZ, Config::RS_PIN );
//LCD RST reset pin
gpio_init( Config::RST_GPIO, GPIO_MODE_OUT_PP, GPIO_OSPEED_50MHZ, Config::RST_PIN );
//LCD CS chip select pin
gpio_init( Config::SPI_CS_GPIO, GPIO_MODE_OUT_PP, GPIO_OSPEED_50MHZ, Config::SPI_CS_PIN );
//Set the SPI0 pins to Alternate Functions
gpio_init( Config::SPI_CLK_GPIO, GPIO_MODE_AF_PP, GPIO_OSPEED_50MHZ, Config::SPI_CLK_PIN );
gpio_init( Config::SPI_MISO_GPIO, GPIO_MODE_AF_PP, GPIO_OSPEED_50MHZ, Config::SPI_MISO_PIN );
gpio_init( Config::SPI_MOSI_GPIO, GPIO_MODE_AF_PP, GPIO_OSPEED_50MHZ, Config::SPI_MOSI_PIN );
//----------------------------------------------------------------
// RETURN
//----------------------------------------------------------------
return false; //OK
} //End Private init: init_gpio | void |
/***************************************************************************/
//! @brief Private init
//! init_spi | void |
/***************************************************************************/
//! @return bool | false = OK | true = ERR
//! @details
//! \n HAL method
//! \n Initialize SPI that communicates with the Display
/***************************************************************************/
inline bool Display::init_spi( void )
{
//----------------------------------------------------------------
// BODY
//----------------------------------------------------------------
//Clock the SPI
rcu_periph_clock_enable( RCU_SPI0 );
spi_i2s_deinit( Config::SPI_CH );
SPI_CTL0( Config::SPI_CH ) = (uint32_t)(SPI_MASTER | SPI_TRANSMODE_FULLDUPLEX | SPI_FRAMESIZE_8BIT | SPI_NSS_SOFT | SPI_ENDIAN_MSB | SPI_CK_PL_LOW_PH_1EDGE | SPI_PSC_8);
//If: DMA is accelerating the SPI
if (Config::USE_DMA == true)
{
SPI_CTL1( Config::SPI_CH ) = (uint32_t)(SPI_CTL1_DMATEN);
}
spi_enable( Config::SPI_CH );
//----------------------------------------------------------------
// RETURN
//----------------------------------------------------------------
return false; //OK
} //End Private init: init_spi | void |
/***************************************************************************/
//! @brief Private init
//! init_dma | void |
/***************************************************************************/
//! @return bool | false = OK | true = ERR
//! @details
//! \n Initialize DMA that accelerates the SPI
/***************************************************************************/
inline bool Display::init_dma( void )
{
//----------------------------------------------------------------
// BODY
//----------------------------------------------------------------
//CLock the DMA
rcu_periph_clock_enable( RCU_DMA0 );
dma_deinit( Config::DMA_SPI_TX, (dma_channel_enum)Config::DMA_SPI_TX_CH );
DMA_CHCTL( Config::DMA_SPI_TX, Config::DMA_SPI_TX_CH ) = (uint32_t)(DMA_PRIORITY_ULTRA_HIGH | DMA_CHXCTL_DIR);
DMA_CHPADDR( Config::DMA_SPI_TX, Config::DMA_SPI_TX_CH ) = (uint32_t)&SPI_DATA(Config::SPI_CH);
//----------------------------------------------------------------
// RETURN
//----------------------------------------------------------------
return false; //OK
} //End Private init: init_dma | void |
/***************************************************************************/
//! @brief Private init
//! init_st7735 | void |
/***************************************************************************/
//! @return bool | false = OK | true = ERR
//! @details
//! \n Send initialization sequence to the ST7735 display controller
//! \n First byte in the sequence is command
//! \n Next bytes in the sequence are terminators
//! \n If the command is a terminator, it means the initialization sequence is complete
/***************************************************************************/
bool Display::init_st7735( void )
{
//----------------------------------------------------------------
// BODY
//----------------------------------------------------------------
//Configure SPI and select display
this -> cs_active();
this -> spi_set_8bit();
//For: each command sequence
for (const uint8_t *str_p = this -> g_st7735s_init_sequence; *str_p != Command::TERMINATOR; str_p++)
{
//Wait SPI
this -> spi_wait_idle();
//Send command
this -> rs_mode_cmd();
spi_i2s_data_transmit(Config::SPI_CH, *str_p++);
//Wait SPI
this -> spi_wait_idle();
//Enter data mode
this -> rs_mode_data();
//While: I'm not done sendinga data
while(*str_p != Command::TERMINATOR)
{
//Wait SPI
this -> spi_wait_tbe();
//Send DATA
spi_i2s_data_transmit(Config::SPI_CH, *str_p++);
}
} //End For: each command sequence
//----------------------------------------------------------------
// RETURN
//----------------------------------------------------------------
return false; //OK
} //End Private init: init_st7735 | void |
/*********************************************************************************************************************************************************
**********************************************************************************************************************************************************
** PRIVATE HAL
**********************************************************************************************************************************************************
*********************************************************************************************************************************************************/
/***************************************************************************/
//! @brief Private HAL Method
//! is_spi_idle | void
/***************************************************************************/
//! @return bool | false = BUSY | true = IDLE
//! @details
//! \n return true when the SPI is IDLE
/***************************************************************************/
inline bool Display::is_spi_idle( void )
{
//----------------------------------------------------------------
// RETURN
//----------------------------------------------------------------
return ((SPI_STAT( Config::SPI_CH ) &SPI_STAT_TRANS) == 0);
} //End Private HAL Method: is_spi_idle | void
/***************************************************************************/
//! @brief Private HAL Method
//! spi_wait_idle | void
/***************************************************************************/
//! @details
//! \n Block execution until SPI is IDLE
/***************************************************************************/
inline void Display::spi_wait_idle( void )
{
//----------------------------------------------------------------
// BODY
//----------------------------------------------------------------
//While: SPI is BUSY
while( (SPI_STAT( Config::SPI_CH ) &SPI_STAT_TRANS) != 0 )
{
//Block Execution
}
//----------------------------------------------------------------
// RETURN
//----------------------------------------------------------------
return;
} //End Private HAL Method: is_spi_idle | void
/***************************************************************************/
//! @brief Private HAL Method
//! is_spi_done_tx | void
/***************************************************************************/
//! @return bool | false = BUSY | true = IDLE
//! @details
//! \n return true when the SPI is done TX
/***************************************************************************/
inline bool Display::is_spi_done_tx( void )
{
//----------------------------------------------------------------
// RETURN
//----------------------------------------------------------------
return ((SPI_STAT( Config::SPI_CH ) &SPI_STAT_TBE ) != 0);
} //End Private HAL Method: is_spi_idle | void
/***************************************************************************/
//! @brief Private HAL Method
//! spi_wait_tbe | void |
/***************************************************************************/
//! @details
//! \n Block execution until SPI is done TX
/***************************************************************************/
inline void Display::spi_wait_tbe( void )
{
//----------------------------------------------------------------
// BODY
//----------------------------------------------------------------
while( ( SPI_STAT( Config::SPI_CH ) &SPI_STAT_TBE ) == 0 )
{
//Block Execution
}
//----------------------------------------------------------------
// RETURN
//----------------------------------------------------------------
return;
} //End Private HAL Method: spi_wait_tbe | void |
/***************************************************************************/
//! @brief Private HAL Method
//! cs_active | void |
/***************************************************************************/
//! @details
//! \n Select the ST7735 Display
/***************************************************************************/
inline void Display::cs_active( void )
{
//----------------------------------------------------------------
// BODY
//----------------------------------------------------------------
gpio_bit_reset( Config::SPI_CS_GPIO, Config::SPI_CS_PIN );
//----------------------------------------------------------------
// RETURN
//----------------------------------------------------------------
return;
} //End Private HAL Method: cs_active | void |
/***************************************************************************/
//! @brief Private HAL Method
//! cs_inactive | void |
/***************************************************************************/
//! @details
//! \n Deselect the ST7735 Display
/***************************************************************************/
inline void Display::cs_inactive( void )
{
//----------------------------------------------------------------
// BODY
//----------------------------------------------------------------
gpio_bit_set( Config::SPI_CS_GPIO, Config::SPI_CS_PIN );
//----------------------------------------------------------------
// RETURN
//----------------------------------------------------------------
return;
} //End Private HAL Method: cs_inactive | void |
/***************************************************************************/
//! @brief Private HAL Method
//! rs_mode_cmd | void |
/***************************************************************************/
//! @details
//! \n Send a command to the display
/***************************************************************************/
inline void Display::rs_mode_cmd( void )
{
//----------------------------------------------------------------
// BODY
//----------------------------------------------------------------
gpio_bit_reset( Config::RS_GPIO, Config::RS_PIN );
//----------------------------------------------------------------
// RETURN
//----------------------------------------------------------------
return;
} //End Private HAL Method: rs_mode_cmd | void |
/***************************************************************************/
//! @brief Private HAL Method
//! rs_mode_data | void |
/***************************************************************************/
//! @details
//! \n Send data to the display
/***************************************************************************/
inline void Display::rs_mode_data( void )
{
//----------------------------------------------------------------
// BODY
//----------------------------------------------------------------
gpio_bit_set( Config::RS_GPIO, Config::RS_PIN );
//----------------------------------------------------------------
// RETURN
//----------------------------------------------------------------
return;
} //End Private HAL Method: rs_mode_data | void |
/***************************************************************************/
//! @brief Private HAL Method
//! rst_active | void |
/***************************************************************************/
//! @details
//! \n Assert the reset pin of the physical display
//! \n The display will need time to become ready after an assert
/***************************************************************************/
inline void Display::rst_active( void )
{
//----------------------------------------------------------------
// BODY
//----------------------------------------------------------------
gpio_bit_reset( Config::RST_GPIO, Config::RST_PIN );
//----------------------------------------------------------------
// RETURN
//----------------------------------------------------------------
return;
} //End Private HAL Method: rst_active | void |
/***************************************************************************/
//! @brief Private HAL Method
//! rst_inactive | void |
/***************************************************************************/
//! @details
//! \n Deassert the reset pin of the physical display
/***************************************************************************/
inline void Display::rst_inactive( void )
{
//----------------------------------------------------------------
// BODY
//----------------------------------------------------------------
gpio_bit_set( Config::RST_GPIO, Config::RST_PIN );
//----------------------------------------------------------------
// RETURN
//----------------------------------------------------------------
return;
} //End Private HAL Method: rst_inactive | void |
/***************************************************************************/
//! @brief Private HAL Method
//! spi_set_8bit | void |
/***************************************************************************/
//! @details
//! \n Configure the SPI to 8b
/***************************************************************************/
inline void Display::spi_set_8bit( void )
{
//----------------------------------------------------------------
// BODY
//----------------------------------------------------------------
if (SPI_CTL0(Config::SPI_CH) & (uint32_t)(SPI_CTL0_FF16))
{
SPI_CTL0(Config::SPI_CH) &= ~(uint32_t)(SPI_CTL0_SPIEN);
SPI_CTL0(Config::SPI_CH) &= ~(uint32_t)(SPI_CTL0_FF16);
SPI_CTL0(Config::SPI_CH) |= (uint32_t)(SPI_CTL0_SPIEN);
}
//----------------------------------------------------------------
// RETURN
//----------------------------------------------------------------
return;
} //End Private HAL Method: spi_set_8bit | void |
/***************************************************************************/
//! @brief Private HAL Method
//! spi_set_16bit | void |
/***************************************************************************/
//! @details
//! \n Configure the SPI to 16b
/***************************************************************************/
inline void Display::spi_set_16bit( void )
{
//----------------------------------------------------------------
// BODY
//----------------------------------------------------------------
if (!(SPI_CTL0(Config::SPI_CH) & (uint32_t)(SPI_CTL0_FF16)))
{
SPI_CTL0(Config::SPI_CH) &= ~(uint32_t)(SPI_CTL0_SPIEN);
SPI_CTL0(Config::SPI_CH) |= (uint32_t)(SPI_CTL0_FF16);
SPI_CTL0(Config::SPI_CH) |= (uint32_t)(SPI_CTL0_SPIEN);
}
//----------------------------------------------------------------
// RETURN
//----------------------------------------------------------------
return;
} //End Private HAL Method: spi_set_16bit | void |
/***************************************************************************/
//! @brief Private HAL Method
//! dma_send_map16 | uint16_t * | uint16_t |
/***************************************************************************/
//! @param data_ptr | uint16_t * | pointer to RGB565 pixel color map
//! @param data_size | uint16_t | size of the pixel color map in pixels
//! @return void
//! @details
//! \n Use the DMA to send a 16b memory through the SPI
/***************************************************************************/
inline void Display::dma_send_map16( uint16_t *data_ptr, uint16_t data_size )
{
//----------------------------------------------------------------
// BODY
//----------------------------------------------------------------
dma_channel_disable( Config::DMA_SPI_TX, (dma_channel_enum)Config::DMA_SPI_TX_CH );
dma_memory_width_config( Config::DMA_SPI_TX, (dma_channel_enum)Config::DMA_SPI_TX_CH, DMA_MEMORY_WIDTH_16BIT );
dma_periph_width_config( Config::DMA_SPI_TX, (dma_channel_enum)Config::DMA_SPI_TX_CH, DMA_PERIPHERAL_WIDTH_16BIT );
dma_memory_address_config( Config::DMA_SPI_TX, (dma_channel_enum)Config::DMA_SPI_TX_CH, (uint32_t)(data_ptr) );
dma_memory_increase_enable( Config::DMA_SPI_TX, (dma_channel_enum)Config::DMA_SPI_TX_CH );
dma_transfer_number_config( Config::DMA_SPI_TX, (dma_channel_enum)Config::DMA_SPI_TX_CH, data_size );
//Begin the DMA transfer
dma_channel_enable( Config::DMA_SPI_TX, (dma_channel_enum)Config::DMA_SPI_TX_CH );
//----------------------------------------------------------------
// RETURN
//----------------------------------------------------------------
return;
} //End Private HAL Method: dma_send_map16 | uint16_t * | uint16_t |
/***************************************************************************/
//! @brief Private HAL Method
//! dma_send_map16 | uint16_t * | uint16_t |
/***************************************************************************/
//! @param data_ptr | uint16_t * | pointer to RGB565 solid color for the full sprite
//! @param data_size | uint16_t | size of the pixel color map in pixels
//! @return void
//! @details
//! \n Use the DMA to send a 16b data through the SPI a number of times
/***************************************************************************/
inline void Display::dma_send_solid16( uint16_t* data_ptr, uint16_t data_size )
{
//----------------------------------------------------------------
// BODY
//----------------------------------------------------------------
dma_channel_disable( Config::DMA_SPI_TX, (dma_channel_enum)Config::DMA_SPI_TX_CH );
dma_memory_width_config( Config::DMA_SPI_TX, (dma_channel_enum)Config::DMA_SPI_TX_CH, DMA_MEMORY_WIDTH_16BIT );
dma_periph_width_config( Config::DMA_SPI_TX, (dma_channel_enum)Config::DMA_SPI_TX_CH, DMA_PERIPHERAL_WIDTH_16BIT );
dma_memory_address_config( Config::DMA_SPI_TX, (dma_channel_enum)Config::DMA_SPI_TX_CH, (uint32_t)(data_ptr) );
dma_memory_increase_disable( Config::DMA_SPI_TX, (dma_channel_enum)Config::DMA_SPI_TX_CH );
dma_transfer_number_config( Config::DMA_SPI_TX, (dma_channel_enum)Config::DMA_SPI_TX_CH, data_size );
//Begin the DMA transfer
dma_channel_enable( Config::DMA_SPI_TX, (dma_channel_enum)Config::DMA_SPI_TX_CH );
//----------------------------------------------------------------
// RETURN
//----------------------------------------------------------------
return;
} //End Private HAL Method: dma_send_map16 | uint16_t * | uint16_t |
/**********************************************************************************
** NAMESPACE
**********************************************************************************/
} //End namespace: Longan_nano
#else
#warning "Multiple inclusion of hader file ST7735S_W160_H80_C16_HPP_"
#endif
/**********************************************************************************
BSD 3-Clause License
Copyright (c) 2020, Orso Eric
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
3. Neither the name of the copyright holder nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
**********************************************************************************/
/**********************************************************************************
** ENVIROMENT VARIABILE
**********************************************************************************/
#ifndef SCREEN_HPP_
#define SCREEN_HPP_
/**********************************************************************************
** GLOBAL INCLUDES
**********************************************************************************/
//Display driver. Physical interface and microcontroller peripherals.
#include "ST7735S_W160_H80_C16.hpp"
//Number -> string
#include "embedded_string.hpp"
/**********************************************************************************
** DEBUG
**********************************************************************************/
/*
#ifndef DEBUG_H_
#include <stdio.h>
#define ENABLE_DEBUG
#include "debug.h"
#endif
*/
//If DEBUG is not needed, blank out the implementations
#ifndef DEBUG_H_
#define DEBUG_VARS_PROTOTYPES()
#define DEBUG_VARS()
#define DSHOW( ... )
#define DSTART( ... )
#define DSTOP()
#define DTAB( ... )
#define DPRINT( ... )
#define DPRINT_NOTAB( ... )
#define DENTER( ... )
#define DRETURN( ... )
#define DENTER_ARG( ... )
#define DRETURN_ARG( ... )
#define show_frame_sprite( ... )
#define show_pixels( ... )
#endif
/**********************************************************************************
** DEFINES
**********************************************************************************/
/**********************************************************************************
** MACROS
**********************************************************************************/
/**********************************************************************************
** NAMESPACE
**********************************************************************************/
//! @namespace Longan_nano specialized for the eval board Longan_nano with GD32VF103 RiscV MCU
namespace Longan_nano
{
/**********************************************************************************
** TYPEDEFS
**********************************************************************************/
//Font size 10 has 8x20 sprites on screen
//Font size 16 has 5x20 sprites on screen
#define FONT_HEIGHT 10
/**********************************************************************************
** PROTOTYPE: STRUCTURES
**********************************************************************************/
/**********************************************************************************
** PROTOTYPE: GLOBAL VARIABILES
**********************************************************************************/
/**********************************************************************************
** PROTOTYPE: CLASS
**********************************************************************************/
/**********************************************************************************
** DESCRIPTION
***********************************************************************************
** SPRITE
** Logically devide screen in blocks width*height
**
** NULL COLOR vs. NULL SPRITE
** I need at least one combination that forces a sprite not to be updated (transparent)
**
** GLOBAL BACKGROUND/FOREGROUND
** SCREEN
** WIDTH
** 0,0 ---X--> 159,0
** |
** Y
** |
** v
** 0,79 159,79
**
** Use the following build flags in platformio.ini
** build_flags = -fno-exceptions
**********************************************************************************/
/*********************************************************************************/
//! @class Screen
/*********************************************************************************/
//! @author Orso Eric
//! @version 2020-08-08
//! @date 2020-07-09
//! @brief Longan Nano Screen Class
//! @copyright BSD 3-Clause License Copyright (c) 2020, Orso Eric
//! @details
//! \n Screen class: abstraction layer for low level LCD HAL for the ST7735S Longan Nano Display
//! \n Screen provides a frame buffer and functions to write on it
//! \n LCD class: low level driver for screen hardware. Inherited by screen
//! \n 2020-07-09
//! \n Formulated architeture
//! \n Focus on Display dirver first
//! \n 2020-07-23
//! \n Display driver working
//! \n Core methods: register_sprite and update
//! \n Formulated palette, frame sprite and sprite pixel buffer architecture
//! \n Use 16x08 sprites. They already exists and 8 by 8 are too small
//! \n Structure of the Screen Update FSM Up
//! \n FSM is partitioned into the Display side FSM that handles the physical display and the Screen side FSM that handles the sprite based frame buffer
//! \n Screen class is designed to massively reduce memory usage and data sent to physical display. Most of the time I need fast updating characters and nothing more.
//! \n 2020-07-24
//! \n Added through debug
//! \n BUG: Color decoding macro: FIXED
//! \n Test clear black and white inherited from display: SUCCESS
//! \n clear function tested with background calls from update and periodic calls of clear triggered by the RTC interrupt
//! \n Test clear screen cycling colors from the palette: SUCCESS
//! \n 2020-07-25
//! \n Test random characters in random position: SUCCESS
//! \n "is_same" check if two sprites are the same looking for all the nuances
//! \n "paint" method, meant to draw special graphics sprites
//! \n "Symbol" enum, meant to provide a few graphical sprites. Few in numbers and meant for light graphics, like progress bars.
//! \n Test draw a random sprite with a random color:
//! \n 2020-07-26
//! \n Test print string functions: SUCCESS
//! \n Change architecture: do not use RTC ISR but use chrono class for timings. Save a peripheral and achieve a mini hardwired scheduler. PA8 release button switches between demos: SUCCESS
//! \n 2020-07-27
//! \n print number with format
//! \n User::String class handles number to string conversion up to 32b signed and unsigned
//! \n 2020-07-28
//! \n tested print numbers with format: SUCCESS
//! \n bugfix: check that the number fits than the allowed size. Caused dot artifacts on screen
//! \n 2020-07-29
//! \n "change_color" allow to change the color of all sprites from one to another. E.G. red background to blue background
//! \n "set_default_colors" allow to set the default background and foreground colors to be used. Change all the sprites that use those colors as well
//! \n "set_palette_color" change a color in the palette. Mark all the sprites that use those colors for update
//! \n 2020-07-30
//! \n color methods
//! \n 2020-07-31
//! \n change color demo
//! \n with this release, I have a working screen library that does all I need it to do with 2.804% CPU usage at 100uS slice time
//! \n I will add to this library as i work on the end applications and find bugs and shortcomings
//! \n BUGFIX: print string had a botched same sprite detection 0.18% absolute performance improvement
//! \n Now print number won't refresh sprites that have not changed
//! \n Migrated print functions to return the number of updated sprites. -1 if error
//! \n 2020-08-03
//! \n g_pending_cnt was meant to skip scan if zero sprites need updating in the frame buffer
//! \n it just increases CPU use overall, so was removed
//! \n Paint square method. Removed Symbols.
//! \n 2020-08-04
//! \n Unpack methods from class to make the header interface clear while keeping the implementation in the .hpp
//! \n Complete doxygen documentation for all methods
//! \n 2020-08-06
//! \n Add pending_cnt. When counter is zero, the update method won't waste time scanning and print method will start seeking from the first valid sprite saving CPU
//! \n Adding error codes and print_err
//! \n Add rule. allow conditional formatting of number based on rules
//! \n Generated two fonts. 16 pixel height NSimSun and 10 pixel height Courier Now
//! \n 2020-08-07
//! \n Bugfix: Driver wasn't taking advantage of the Display solid color register method to avoid computing a complex color map
//! \n and was always computing a full color map. Now solid sprites just send one color.
//! \n 2020-08-08
//! \n Bugfix: The draw string with colors and set color methods sometimes didn't update the sprites correctly
//! \n either duplicating the previous char or leaving an artifact on pixel 0,0 of the sprite
//! \n it was caused by the update FSM disagreeing on which sprites were solid colors or color maps
//! \n now a register_sprite method combine the processing of color data and registering of sprites
//! \n this reduces workload and solves the bug. Now Screen::register_sprite and Display::register_sprite nicely handle the hierarchy
//! \n just like Screen::update and Display::update
/*********************************************************************************/
class Screen : Longan_nano::Display
{
//Visible to all
public:
/*********************************************************************************************************************************************************
**********************************************************************************************************************************************************
** PUBLIC ENUM
**********************************************************************************************************************************************************
*********************************************************************************************************************************************************/
//! @brief Configuration parameters for the logical screen
typedef enum _Config
{
PEDANTIC_CHECKS = true, //Pedantic check meant for debug
//Screen Logical Configuration. The screen is divided in sprites, shrinking the frame buffer
SPRITE_WIDTH = 8, //Width of a sprite
SPRITE_HEIGHT = FONT_HEIGHT, //Height of a sprite
SPRITE_PIXEL_COUNT = SPRITE_HEIGHT *SPRITE_WIDTH, //Number of pixels in a sprite
SPRITE_SCAN_LIMIT = 5, //Scan at most # idle sprites before releasing control from update() anyway
//Special sprite codes
NUM_SPECIAL_SPRITES = 5, //Number of special sprites
SPRITE_TRANSPARENT = 0, //Transparent sprite. Never updated. Ignore update flag.
SPRITE_BLACK = 1, //Always mapped to a full black sprite, invariant to palette index and content
SPRITE_WHITE = 2, //Always mapped to a full white sprite, invariant to palette index and content
SPRITE_BACKGROUND = 3, //Sprite with the background color
SPRITE_FOREGROUND = 4, //Sprite with the foreground color
//ASCII Sprite Table Definitions
ASCII_START = ' ', //First ASCII character defined in the sprite table
ASCII_STOP = '~', //Last ASCII character defined in the sprite table
//Colors are discretized in a palette
PALETTE_SIZE = 16, //Size of the palette
PALETTE_SIZE_BIT = 4, //Number of bit required to describe a color in the palette
//Size of the frame buffer. Display phisical size comes from the Physical Display class
FRAME_BUFFER_WIDTH = Longan_nano::Display::Config::WIDTH /SPRITE_WIDTH,
FRAME_BUFFER_HEIGHT = Longan_nano::Display::Config::HEIGHT /SPRITE_HEIGHT,
FRAME_BUFFER_SIZE = FRAME_BUFFER_WIDTH *FRAME_BUFFER_HEIGHT,
SPRITE_SIZE = 128, //Number of sprites in the sprite table
SPRITE_SIZE_BIT = 7 //Size of the sprite table
} Config;
//! @brief Use the default Color palette. Short hand indexes for user. User can change the palette at will
typedef enum _Color
{
BLACK,
BLUE,
GREEN,
CYAN,
RED,
MAGENTA,
BROWN,
LGRAY,
DGRAY,
LBLUE,
LGREEN,
LCYAN,
LRED,
LMAGENTA,
YELLOW,
WHITE,
} Color;
//! @brief Possible number configurations
typedef enum _Format_format
{
NUM, //Extended number
ENG, //Engineering format with four significant digits and SI suffix
} Format_format;
//! @brief Left or Right alignment for a number
typedef enum _Format_align
{
ADJ_LEFT,
ADJ_RIGHT,
} Format_align;
//! @brief Enumerate possible error of the Screen class
typedef enum _Error
{
//Screen class working as intended
OK,
//Pending counter either underflow or overflow. Means an algorithmic error occurred in the screen class
PENDING_OVERFLOW,
PENDING_UNDERFLOW,
REGISTER_SPRITE_FAIL,
//Handle error of error. errorception!
BAD_ERROR_CODE,
NUM_ERROR_CODES,
} Error;
/*
//Possible rules for conditional formatting of numbers
typedef enum _Rule_condition
{
NEVER,
ALWAYS,
EQUAL,
GREATER,
GREATER_EQUAL,
SMALLER,
SMALLER_EQUAL,
} Rule_condition;
*/
/*********************************************************************************************************************************************************
**********************************************************************************************************************************************************
** PUBLIC STRUCT
**********************************************************************************************************************************************************
*********************************************************************************************************************************************************/
/*
//Define a conditional formatting rule
typedef struct _Rule
{
Rule_condition condition;
int number;
Color background;
Color foreground;
typedef struct _Rule next_rule;
} Rule;
*/
/*********************************************************************************************************************************************************
**********************************************************************************************************************************************************
** CONSTRUCTORS
**********************************************************************************************************************************************************
*********************************************************************************************************************************************************/
//Empty constructor
Screen( void );
/*********************************************************************************************************************************************************
**********************************************************************************************************************************************************
** DESTRUCTORS
**********************************************************************************************************************************************************
*********************************************************************************************************************************************************/
//Empty Destructor
~Screen( void );
/*********************************************************************************************************************************************************
**********************************************************************************************************************************************************
** PUBLIC INIT
**********************************************************************************************************************************************************
*********************************************************************************************************************************************************/
//call the initializations for the driver
bool init( void );
//Reset the colors to default
bool reset_colors( void );
/*********************************************************************************************************************************************************
**********************************************************************************************************************************************************
** PUBLIC SETTER
**********************************************************************************************************************************************************
*********************************************************************************************************************************************************/
//Set the background and foreground color of a sprite in a given position without changing the content
int set_color( int origin_h, int origin_w, Color background, Color foreground );
//Set the default background and foreground color. Change all sprites that use those colors to the new ones
int set_default_colors( Color new_background, Color new_foreground );
//Change a color in the palette to another RGB color
int set_palette_color( Color palette_index, uint8_t r, uint8_t g, uint8_t b );
//Set the display format of the print number method
bool set_format( int number_size, Format_align align, Format_format format );
//Set the display format of the print number method. Include default exponent for ENG number
bool set_format( int number_size, Format_align align, Format_format format, int exp );
/*********************************************************************************************************************************************************
**********************************************************************************************************************************************************
** PUBLIC GETTER
**********************************************************************************************************************************************************
*********************************************************************************************************************************************************/
//Get the number of sprites that are pending for update
int get_pending( void );
//Get current error of the screen class
Error get_error( void );
/*********************************************************************************************************************************************************
**********************************************************************************************************************************************************
** PUBLIC METHODS
**********************************************************************************************************************************************************
*********************************************************************************************************************************************************/
//Expose color conversion method
using Display::color;
//Core method. FSM that synchronize the frame buffer with the display using the driver
bool update( void );
//Swap source color for dest color for each sprite
int change_color( Color source, Color dest );
//Clear the screen to solid black, even if there is no black in the palette
int clear( void );
//Clear the screen with given colors from the palette. Return number of sprites updated
int clear( Color color_tmp );
//Print a character on screen with given palette colors. Return number of sprites updated.
int print( int origin_h, int origin_w, char c, Color background, Color foreground );
//Print character with just foreground color. use default colors for background
int print( int origin_h, int origin_w, char c, Color foreground );
//Print character. Use default colors
int print( int origin_h, int origin_w, char c );
//Print a string with given colors from the palette. Return number of sprites updated
int print( int origin_h, int origin_w, const char *str, Color background, Color foreground );
//Print string with just foreground color. use default colors for background
int print( int origin_h, int origin_w, const char *str, Color foreground );
//Print string. Use default colors
int print( int origin_h, int origin_w, const char *str );
//Print number with given colors. Number format configuration is handled by set_format. Return number of sprites updated
int print( int origin_h, int origin_w, int num, Color background, Color foreground );
//Print number with just foreground color. Number format configuration is handled by set_format.
int print( int origin_h, int origin_w, int num, Color foreground );
//Print number. Use default colors. Number format configuration is handled by set_format.
int print( int origin_h, int origin_w, int num );
//Draw a solid color sprite on the screen
int paint( int origin_h, int origin_w, Color color );
//Show the current error code on the screen. green foreground for ok. red foreground for error
int print_err( int origin_h, int origin_w );
//Visible only inside the class
private:
/*********************************************************************************************************************************************************
**********************************************************************************************************************************************************
** PRIVATE ENUM
**********************************************************************************************************************************************************
*********************************************************************************************************************************************************/
//! @brief States of the Screen side FSM. Display driver also has its own FSM states
typedef enum _Fsm_state
{
SCAN_SPRITE, //Search for a sprite
SEND_SPRITE, //Ask the Display driver to send sprites to the physical display
} Fsm_state;
/*********************************************************************************************************************************************************
**********************************************************************************************************************************************************
** PRIVATE STRUCT
**********************************************************************************************************************************************************
*********************************************************************************************************************************************************/
//! @brief Structure that describes a sprite in the sprite frame buffer
typedef struct _Frame_buffer_sprite
{
//true: this sprite was changed since last scan | false: this sprite does not need updating
uint8_t f_update : 1;
//sprite index inside the sprite table. A number are special sprite. Not all sprites are mapped
uint8_t sprite_index : Screen::Config::SPRITE_SIZE_BIT;
//foreground palette color index
uint8_t foreground_color : Screen::Config::PALETTE_SIZE_BIT;
//background palette color index
uint8_t background_color : Screen::Config::PALETTE_SIZE_BIT;
} Frame_buffer_sprite;
//! @brief Status of the update FSM
typedef struct _Fsm_status
{
//width and height scan indexes
uint16_t scan_w, scan_h;
//Index withing the sprite sending sequence. Number of steps is defined inside the driver
uint8_t cnt;
//Status of the scan
Fsm_state phase;
} Fsm_status;
//! @brief number format to be printed by the print number method
typedef struct _Format_number
{
//Number of characters allowed for the number. 0 means no limit
int size;
//Number alignment
Format_align align;
//Number format
Format_format format;
//Base Exponent for the engineering number notation
int8_t eng_exp;
} Format_number;
/*********************************************************************************************************************************************************
**********************************************************************************************************************************************************
** PRIVATE INIT
**********************************************************************************************************************************************************
*********************************************************************************************************************************************************/
//Initialize class vars
bool init_class_vars( void );
//Initialize the sprite frame buffer
bool init_frame_buffer( void );
//Initialize the default colors
bool init_default_colors( void );
//Initialize the default color palette
bool init_palette( void );
//Initialize Screen FSM
bool init_fsm( void );
/*********************************************************************************************************************************************************
**********************************************************************************************************************************************************
** PRIVATE TESTERS
**********************************************************************************************************************************************************
*********************************************************************************************************************************************************/
//Return true if the character is in the ascii sprite table
bool is_valid_char( char c );
//return true if the sprite make use of the background palette color
bool is_using_background( uint8_t sprite );
//return true if the sprite make use of the foreground palette color
bool is_using_foreground( uint8_t sprite );
//true = sprite_a functionally the same as sprite_b
bool is_same_sprite( Frame_buffer_sprite sprite_a, Frame_buffer_sprite sprite_b );
/*********************************************************************************************************************************************************
**********************************************************************************************************************************************************
** PRIVATE METHODS
**********************************************************************************************************************************************************
*********************************************************************************************************************************************************/
//Register a sprite for draw in the display driver if possible. Sprite can be xomplex color map or solid color
int8_t register_sprite( uint16_t index_h, uint16_t index_w );
//Update a sprite in the frame buffer and mark it for update if required. Increase workload counter if required.
int8_t update_sprite( uint16_t index_h, uint16_t index_w, Frame_buffer_sprite new_sprite );
//Report an error in the Screen class
void report_error( Error error_code );
/*********************************************************************************************************************************************************
**********************************************************************************************************************************************************
** PRIVATE DEBUGGERS
**********************************************************************************************************************************************************
*********************************************************************************************************************************************************/
#ifdef ENABLE_DEBUG
void show_frame_sprite( Frame_buffer_sprite sprite_tmp )
{
DPRINT("update: %d | index: %5d | background: %5d | foreground: %5d |\n", sprite_tmp.f_update, sprite_tmp.sprite_index, sprite_tmp.background_color, sprite_tmp.foreground_color );
DPRINT("decoded background color: %6x | decoded foreground color: %6x |\n", g_palette[sprite_tmp.background_color], sprite_tmp.foreground_color );
return;
}
void show_pixels( void )
{
DENTER_ARG("oh: %5d, ow: %5d, sh: %5d, sw: %5d\n", g_sprite.origin_h, g_sprite.origin_w, g_sprite.size_h, g_sprite.size_w);
for (int th = 0;th < g_sprite.size_h;th++)
{
DPRINT("");
for (int tw = 0;tw < g_sprite.size_w;tw++)
{
DPRINT_NOTAB("%6x | ", g_pixel_data[th *g_sprite.size_w +tw]);
}
DPRINT_NOTAB("\n");
}
DRETURN();
return;
}
void show_palette( void )
{
DENTER();
for (int t = 0;t < Config::PALETTE_SIZE;t++)
{
DPRINT("%d -> %6x\n", t, g_palette[t] );
}
DRETURN();
return;
}
#endif
/*********************************************************************************************************************************************************
**********************************************************************************************************************************************************
** PRIVATE VARS
**********************************************************************************************************************************************************
*********************************************************************************************************************************************************/
//! @brief Error code of the library. Default is Error::OK
Error g_error_code;
//! @brief Default background and foreground colors to be used
Color g_default_background_color;
Color g_default_foreground_color;
//! @brief Color Palette. One special code for transparent. Two special indexes store global background and foreground
uint16_t g_palette[ Config::PALETTE_SIZE ];
//! @brief Frame Buffer
Frame_buffer_sprite g_frame_buffer[ Config::FRAME_BUFFER_HEIGHT ][ Config::FRAME_BUFFER_WIDTH ];
//! @brief Track the number of sprites that require update. At zero the update method quit without scanning and print methods will set the scan to the correct index
uint16_t g_pending_cnt;
//! @brief Sprite buffer that stores raw pixel data for a single sprite
uint16_t g_pixel_data[ Config::SPRITE_PIXEL_COUNT ];
//! @brief Status of the update FSM
Fsm_status g_status;
//! @brief Display format for print numeric values
Format_number g_format_number;
//Support for font with height of 10 pixels
#if FONT_HEIGHT == 10
//! @brief ASCII Sprites. Stored in the flash memory. 95 sprites from space ' ' code 32 to tilda '~' code 126 + special code 127. FONT_HEIGHT is used to change font
//Font: Courier New 8 with two rows removed from bot and four rows removed from top
static constexpr uint8_t g_ascii_sprites[96*10] =
{
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, //char: 32 ' '
0x10, 0x10, 0x10, 0x10, 0x10, 0x00, 0x00, 0x10, 0x00, 0x00, //char: 33 '!'
0x6C, 0x6C, 0x24, 0x24, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, //char: 34 '"'
0x48, 0x24, 0x7E, 0x24, 0x24, 0x7E, 0x24, 0x12, 0x12, 0x00, //char: 35 '#'
0x38, 0x24, 0x04, 0x18, 0x20, 0x24, 0x1C, 0x10, 0x10, 0x00, //char: 36 '$'
0x04, 0x0A, 0x04, 0x30, 0x0E, 0x10, 0x28, 0x10, 0x00, 0x00, //char: 37 '%'
0x00, 0x70, 0x08, 0x08, 0x18, 0x54, 0x24, 0x78, 0x00, 0x00, //char: 38 '&'
0x08, 0x08, 0x08, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, //char: 39 '''
0x20, 0x20, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x20, 0x20, //char: 40 '('
0x04, 0x04, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x04, 0x04, //char: 41 ')'
0x10, 0x7C, 0x10, 0x28, 0x28, 0x00, 0x00, 0x00, 0x00, 0x00, //char: 42 '*'
0x00, 0x10, 0x10, 0x10, 0xFE, 0x10, 0x10, 0x10, 0x00, 0x00, //char: 43 '+'
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x08, 0x0C, 0x04, //char: 44 ','
0x00, 0x00, 0x00, 0x00, 0x7E, 0x00, 0x00, 0x00, 0x00, 0x00, //char: 45 '-'
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x18, 0x00, 0x00, //char: 46 '.'
0x20, 0x10, 0x10, 0x08, 0x08, 0x04, 0x04, 0x02, 0x02, 0x00, //char: 47 '/'
0x3C, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x3C, 0x00, 0x00, //char: 48 '0'
0x10, 0x1C, 0x10, 0x10, 0x10, 0x10, 0x10, 0x7C, 0x00, 0x00, //char: 49 '1'
0x1C, 0x22, 0x20, 0x10, 0x08, 0x04, 0x22, 0x3E, 0x00, 0x00, //char: 50 '2'
0x1C, 0x22, 0x20, 0x18, 0x20, 0x20, 0x22, 0x1C, 0x00, 0x00, //char: 51 '3'
0x30, 0x28, 0x24, 0x24, 0x7E, 0x20, 0x20, 0x70, 0x00, 0x00, //char: 52 '4'
0x7C, 0x04, 0x04, 0x3C, 0x40, 0x40, 0x42, 0x3C, 0x00, 0x00, //char: 53 '5'
0x70, 0x08, 0x04, 0x3C, 0x44, 0x44, 0x44, 0x38, 0x00, 0x00, //char: 54 '6'
0x7E, 0x42, 0x40, 0x20, 0x20, 0x10, 0x10, 0x10, 0x00, 0x00, //char: 55 '7'
0x3C, 0x42, 0x42, 0x3C, 0x42, 0x42, 0x42, 0x3C, 0x00, 0x00, //char: 56 '8'
0x3C, 0x42, 0x42, 0x42, 0x7C, 0x40, 0x20, 0x1E, 0x00, 0x00, //char: 57 '9'
0x00, 0x00, 0x18, 0x18, 0x00, 0x00, 0x18, 0x18, 0x00, 0x00, //char: 58 ':'
0x00, 0x00, 0x18, 0x18, 0x00, 0x00, 0x18, 0x0C, 0x04, 0x00, //char: 59 ';'
0x00, 0x40, 0x30, 0x08, 0x06, 0x08, 0x30, 0x40, 0x00, 0x00, //char: 60 '<'
0x00, 0x00, 0x7E, 0x00, 0x7E, 0x00, 0x00, 0x00, 0x00, 0x00, //char: 61 '='
0x00, 0x02, 0x0C, 0x10, 0x60, 0x10, 0x0C, 0x02, 0x00, 0x00, //char: 62 '>'
0x38, 0x44, 0x40, 0x40, 0x20, 0x10, 0x00, 0x18, 0x00, 0x00, //char: 63 '?'
0x22, 0x22, 0x32, 0x2A, 0x2A, 0x32, 0x02, 0x22, 0x1C, 0x00, //char: 64 '@'
0x18, 0x10, 0x28, 0x28, 0x28, 0x38, 0x44, 0xEE, 0x00, 0x00, //char: 65 'A'
0x3E, 0x44, 0x44, 0x3C, 0x44, 0x44, 0x44, 0x3E, 0x00, 0x00, //char: 66 'B'
0x78, 0x44, 0x02, 0x02, 0x02, 0x02, 0x44, 0x38, 0x00, 0x00, //char: 67 'C'
0x1E, 0x24, 0x44, 0x44, 0x44, 0x44, 0x24, 0x1E, 0x00, 0x00, //char: 68 'D'
0x7E, 0x44, 0x14, 0x1C, 0x14, 0x04, 0x44, 0x7E, 0x00, 0x00, //char: 69 'E'
0x7E, 0x44, 0x14, 0x1C, 0x14, 0x04, 0x04, 0x0E, 0x00, 0x00, //char: 70 'F'
0x78, 0x44, 0x02, 0x02, 0xE2, 0x42, 0x44, 0x38, 0x00, 0x00, //char: 71 'G'
0xEE, 0x44, 0x44, 0x7C, 0x44, 0x44, 0x44, 0xEE, 0x00, 0x00, //char: 72 'H'
0x7C, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x7C, 0x00, 0x00, //char: 73 'I'
0x78, 0x20, 0x20, 0x20, 0x22, 0x22, 0x22, 0x1C, 0x00, 0x00, //char: 74 'J'
0xEE, 0x44, 0x24, 0x14, 0x1C, 0x24, 0x44, 0xCE, 0x00, 0x00, //char: 75 'K'
0x0E, 0x04, 0x04, 0x04, 0x04, 0x44, 0x44, 0x7E, 0x00, 0x00, //char: 76 'L'
0xEE, 0x6C, 0x6C, 0x54, 0x54, 0x44, 0x44, 0xEE, 0x00, 0x00, //char: 77 'M'
0xE7, 0x46, 0x4A, 0x4A, 0x52, 0x52, 0x62, 0x67, 0x00, 0x00, //char: 78 'N'
0x38, 0x44, 0x82, 0x82, 0x82, 0x82, 0x44, 0x38, 0x00, 0x00, //char: 79 'O'
0x3E, 0x44, 0x44, 0x44, 0x3C, 0x04, 0x04, 0x0E, 0x00, 0x00, //char: 80 'P'
0x38, 0x44, 0x82, 0x82, 0x82, 0x82, 0x44, 0x38, 0xF8, 0x00, //char: 81 'Q'
0x3E, 0x44, 0x44, 0x44, 0x3C, 0x24, 0x44, 0x8E, 0x00, 0x00, //char: 82 'R'
0x5C, 0x62, 0x02, 0x3C, 0x40, 0x40, 0x46, 0x3A, 0x00, 0x00, //char: 83 'S'
0xFE, 0x92, 0x10, 0x10, 0x10, 0x10, 0x10, 0x38, 0x00, 0x00, //char: 84 'T'
0xEE, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x38, 0x00, 0x00, //char: 85 'U'
0xE7, 0x42, 0x42, 0x24, 0x24, 0x24, 0x18, 0x18, 0x00, 0x00, //char: 86 'V'
0xEE, 0x44, 0x44, 0x54, 0x54, 0x54, 0x54, 0x28, 0x00, 0x00, //char: 87 'W'
0xEE, 0x44, 0x28, 0x10, 0x10, 0x28, 0x44, 0xEE, 0x00, 0x00, //char: 88 'X'
0xEE, 0x44, 0x28, 0x28, 0x10, 0x10, 0x10, 0x38, 0x00, 0x00, //char: 89 'Y'
0x7C, 0x44, 0x20, 0x10, 0x10, 0x08, 0x44, 0x7C, 0x00, 0x00, //char: 90 'Z'
0x38, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x38, //char: 91 '['
0x02, 0x04, 0x04, 0x08, 0x08, 0x08, 0x10, 0x10, 0x10, 0x00, //char: 92 '\'
0x0E, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x0E, //char: 93 ']'
0x08, 0x14, 0x22, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, //char: 94 '^'
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, //char: 95 '_'
0x08, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, //char: 96 '`'
0x00, 0x00, 0x3C, 0x42, 0x7C, 0x42, 0x62, 0xDC, 0x00, 0x00, //char: 97 'a'
0x03, 0x02, 0x3A, 0x46, 0x42, 0x42, 0x46, 0x3B, 0x00, 0x00, //char: 98 'b'
0x00, 0x00, 0x5C, 0x62, 0x02, 0x02, 0x42, 0x3C, 0x00, 0x00, //char: 99 'c'
0x60, 0x40, 0x5C, 0x62, 0x42, 0x42, 0x62, 0xDC, 0x00, 0x00, //char: 100 'd'
0x00, 0x00, 0x3C, 0x42, 0x7E, 0x02, 0x02, 0x7C, 0x00, 0x00, //char: 101 'e'
0x70, 0x08, 0x7E, 0x08, 0x08, 0x08, 0x08, 0x7E, 0x00, 0x00, //char: 102 'f'
0x00, 0x00, 0xDC, 0x62, 0x42, 0x42, 0x62, 0x5C, 0x40, 0x3C, //char: 103 'g'
0x06, 0x04, 0x34, 0x4C, 0x44, 0x44, 0x44, 0xEE, 0x00, 0x00, //char: 104 'h'
0x10, 0x00, 0x1C, 0x10, 0x10, 0x10, 0x10, 0x7C, 0x00, 0x00, //char: 105 'i'
0x10, 0x00, 0x3C, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x1E, //char: 106 'j'
0x06, 0x04, 0xF4, 0x24, 0x1C, 0x14, 0x24, 0xE6, 0x00, 0x00, //char: 107 'k'
0x18, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x7C, 0x00, 0x00, //char: 108 'l'
0x00, 0x00, 0x4B, 0xB6, 0x92, 0x92, 0x92, 0xB7, 0x00, 0x00, //char: 109 'm'
0x00, 0x00, 0x36, 0x4C, 0x44, 0x44, 0x44, 0xEE, 0x00, 0x00, //char: 110 'n'
0x00, 0x00, 0x3C, 0x42, 0x42, 0x42, 0x42, 0x3C, 0x00, 0x00, //char: 111 'o'
0x00, 0x00, 0x36, 0x4C, 0x44, 0x44, 0x44, 0x3C, 0x04, 0x0E, //char: 112 'p'
0x00, 0x00, 0xDC, 0x62, 0x42, 0x42, 0x62, 0x5C, 0x40, 0xE0, //char: 113 'q'
0x00, 0x00, 0x76, 0x0C, 0x04, 0x04, 0x04, 0x3E, 0x00, 0x00, //char: 114 'r'
0x00, 0x00, 0x7C, 0x42, 0x3C, 0x40, 0x42, 0x3E, 0x00, 0x00, //char: 115 's'
0x00, 0x04, 0x3E, 0x04, 0x04, 0x04, 0x44, 0x38, 0x00, 0x00, //char: 116 't'
0x00, 0x00, 0x66, 0x44, 0x44, 0x44, 0x64, 0xD8, 0x00, 0x00, //char: 117 'u'
0x00, 0x00, 0xE7, 0x42, 0x24, 0x24, 0x18, 0x18, 0x00, 0x00, //char: 118 'v'
0x00, 0x00, 0xEE, 0x44, 0x54, 0x54, 0x54, 0x28, 0x00, 0x00, //char: 119 'w'
0x00, 0x00, 0x66, 0x24, 0x18, 0x18, 0x24, 0x66, 0x00, 0x00, //char: 120 'x'
0x00, 0x00, 0xEE, 0x44, 0x44, 0x28, 0x28, 0x10, 0x10, 0x1C, //char: 121 'y'
0x00, 0x00, 0x7C, 0x24, 0x10, 0x08, 0x44, 0x7C, 0x00, 0x00, //char: 122 'z'
0x10, 0x08, 0x08, 0x08, 0x04, 0x08, 0x08, 0x08, 0x10, 0x00, //char: 123 '{'
0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, //char: 124 '|'
0x04, 0x08, 0x08, 0x08, 0x10, 0x08, 0x08, 0x08, 0x04, 0x00, //char: 125 '}'
0x00, 0x00, 0x00, 0x4C, 0x32, 0x00, 0x00, 0x00, 0x00, 0x00, //char: 126 '~'
0xC6, 0xBA, 0xBE, 0xBE, 0xDE, 0xEE, 0xFE, 0xEE, 0xEE, 0x7C, //char: 127 ' '
};
//Support for font with height of 16 pixels
#elif FONT_HEIGHT == 16
//! @brief ASCII Sprites. Stored in the flash memory. 95 sprites from space ' ' code 32 to tilda '~' code 126 + special code 127. FONT_HEIGHT is used to change font
//Font: NSimSun 11 with one row added on top
static constexpr uint8_t g_ascii_sprites[96*16] =
{
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, //char: 32 ' '
0x00, 0x00, 0x00, 0x00, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x00, 0x08, 0x08, 0x00, 0x00, //char: 33 '!'
0x00, 0x00, 0x28, 0x14, 0x14, 0x0A, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, //char: 34 '"'
0x00, 0x00, 0x00, 0x00, 0x48, 0x48, 0x7E, 0x24, 0x24, 0x24, 0x7E, 0x24, 0x24, 0x24, 0x00, 0x00, //char: 35 '#'
0x00, 0x00, 0x00, 0x10, 0x3C, 0x52, 0x52, 0x14, 0x18, 0x30, 0x50, 0x52, 0x52, 0x3C, 0x10, 0x10, //char: 36 '$'
0x00, 0x00, 0x00, 0x00, 0x22, 0x25, 0x15, 0x15, 0x2D, 0x5A, 0x54, 0x54, 0x52, 0x22, 0x00, 0x00, //char: 37 '%'
0x00, 0x00, 0x00, 0x00, 0x0C, 0x12, 0x12, 0x0A, 0x66, 0x25, 0x25, 0x29, 0x91, 0x6E, 0x00, 0x00, //char: 38 '&'
0x00, 0x00, 0x06, 0x04, 0x04, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, //char: 39 '''
0x00, 0x00, 0x40, 0x20, 0x10, 0x10, 0x08, 0x08, 0x08, 0x08, 0x08, 0x10, 0x10, 0x20, 0x40, 0x00, //char: 40 '('
0x00, 0x00, 0x01, 0x02, 0x04, 0x04, 0x08, 0x08, 0x08, 0x08, 0x08, 0x04, 0x04, 0x02, 0x01, 0x00, //char: 41 ')'
0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x08, 0x6B, 0x1C, 0x1C, 0x6B, 0x08, 0x08, 0x00, 0x00, 0x00, //char: 42 '*'
0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x10, 0x10, 0xFE, 0x10, 0x10, 0x10, 0x00, 0x00, 0x00, 0x00, //char: 43 '+'
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x04, 0x04, 0x02, //char: 44 ','
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, //char: 45 '-'
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x06, 0x00, 0x00, //char: 46 '.'
0x00, 0x00, 0x40, 0x20, 0x20, 0x20, 0x10, 0x10, 0x08, 0x08, 0x08, 0x04, 0x04, 0x02, 0x02, 0x00, //char: 47 '/'
0x00, 0x00, 0x00, 0x00, 0x18, 0x24, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x24, 0x18, 0x00, 0x00, //char: 48 '0'
0x00, 0x00, 0x00, 0x00, 0x08, 0x0E, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x3E, 0x00, 0x00, //char: 49 '1'
0x00, 0x00, 0x00, 0x00, 0x3C, 0x42, 0x42, 0x40, 0x20, 0x10, 0x08, 0x04, 0x42, 0x7E, 0x00, 0x00, //char: 50 '2'
0x00, 0x00, 0x00, 0x00, 0x3C, 0x42, 0x42, 0x20, 0x18, 0x20, 0x40, 0x42, 0x42, 0x3C, 0x00, 0x00, //char: 51 '3'
0x00, 0x00, 0x00, 0x00, 0x20, 0x30, 0x30, 0x28, 0x24, 0x22, 0xFE, 0x20, 0x20, 0x78, 0x00, 0x00, //char: 52 '4'
0x00, 0x00, 0x00, 0x00, 0x7E, 0x02, 0x02, 0x3E, 0x42, 0x40, 0x40, 0x42, 0x42, 0x3C, 0x00, 0x00, //char: 53 '5'
0x00, 0x00, 0x00, 0x00, 0x18, 0x24, 0x02, 0x3A, 0x46, 0x42, 0x42, 0x42, 0x44, 0x38, 0x00, 0x00, //char: 54 '6'
0x00, 0x00, 0x00, 0x00, 0x7E, 0x42, 0x20, 0x10, 0x10, 0x10, 0x08, 0x08, 0x08, 0x08, 0x00, 0x00, //char: 55 '7'
0x00, 0x00, 0x00, 0x00, 0x3C, 0x42, 0x42, 0x42, 0x3C, 0x24, 0x42, 0x42, 0x42, 0x3C, 0x00, 0x00, //char: 56 '8'
0x00, 0x00, 0x00, 0x00, 0x1C, 0x22, 0x42, 0x42, 0x62, 0x5C, 0x40, 0x40, 0x24, 0x18, 0x00, 0x00, //char: 57 '9'
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x18, 0x00, 0x00, 0x00, 0x18, 0x18, 0x00, 0x00, //char: 58 ':'
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x08, 0x08, //char: 59 ';'
0x00, 0x00, 0x00, 0x00, 0x20, 0x10, 0x08, 0x04, 0x02, 0x02, 0x04, 0x08, 0x10, 0x20, 0x00, 0x00, //char: 60 '<'
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7F, 0x00, 0x00, 0x7F, 0x00, 0x00, 0x00, 0x00, 0x00, //char: 61 '='
0x00, 0x00, 0x00, 0x00, 0x04, 0x08, 0x10, 0x20, 0x40, 0x40, 0x20, 0x10, 0x08, 0x04, 0x00, 0x00, //char: 62 '>'
0x00, 0x00, 0x00, 0x00, 0x3C, 0x42, 0x42, 0x42, 0x20, 0x10, 0x10, 0x00, 0x18, 0x18, 0x00, 0x00, //char: 63 '?'
0x00, 0x00, 0x00, 0x00, 0x1C, 0x22, 0x59, 0x55, 0x55, 0x55, 0x55, 0x39, 0x42, 0x3C, 0x00, 0x00, //char: 64 '@'
0x00, 0x00, 0x00, 0x00, 0x08, 0x08, 0x14, 0x14, 0x14, 0x14, 0x3E, 0x22, 0x22, 0x77, 0x00, 0x00, //char: 65 'A'
0x00, 0x00, 0x00, 0x00, 0x3F, 0x42, 0x42, 0x22, 0x1E, 0x22, 0x42, 0x42, 0x42, 0x3F, 0x00, 0x00, //char: 66 'B'
0x00, 0x00, 0x00, 0x00, 0x7C, 0x42, 0x41, 0x01, 0x01, 0x01, 0x01, 0x41, 0x22, 0x1C, 0x00, 0x00, //char: 67 'C'
0x00, 0x00, 0x00, 0x00, 0x1F, 0x22, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x22, 0x1F, 0x00, 0x00, //char: 68 'D'
0x00, 0x00, 0x00, 0x00, 0x3F, 0x42, 0x12, 0x12, 0x1E, 0x12, 0x12, 0x02, 0x42, 0x3F, 0x00, 0x00, //char: 69 'E'
0x00, 0x00, 0x00, 0x00, 0x3F, 0x42, 0x12, 0x12, 0x1E, 0x12, 0x12, 0x02, 0x02, 0x07, 0x00, 0x00, //char: 70 'F'
0x00, 0x00, 0x00, 0x00, 0x3C, 0x22, 0x21, 0x01, 0x01, 0x01, 0x71, 0x21, 0x22, 0x1C, 0x00, 0x00, //char: 71 'G'
0x00, 0x00, 0x00, 0x00, 0x77, 0x22, 0x22, 0x22, 0x3E, 0x22, 0x22, 0x22, 0x22, 0x77, 0x00, 0x00, //char: 72 'H'
0x00, 0x00, 0x00, 0x00, 0x3E, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x3E, 0x00, 0x00, //char: 73 'I'
0x00, 0x00, 0x00, 0x00, 0x7C, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x11, 0x0F, //char: 74 'J'
0x00, 0x00, 0x00, 0x00, 0x77, 0x22, 0x12, 0x0A, 0x0E, 0x0A, 0x12, 0x12, 0x22, 0x77, 0x00, 0x00, //char: 75 'K'
0x00, 0x00, 0x00, 0x00, 0x07, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x42, 0x7F, 0x00, 0x00, //char: 76 'L'
0x00, 0x00, 0x00, 0x00, 0x77, 0x36, 0x36, 0x36, 0x36, 0x2A, 0x2A, 0x2A, 0x2A, 0x6B, 0x00, 0x00, //char: 77 'M'
0x00, 0x00, 0x00, 0x00, 0x77, 0x26, 0x26, 0x2A, 0x2A, 0x2A, 0x32, 0x32, 0x32, 0x27, 0x00, 0x00, //char: 78 'N'
0x00, 0x00, 0x00, 0x00, 0x1C, 0x22, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x22, 0x1C, 0x00, 0x00, //char: 79 'O'
0x00, 0x00, 0x00, 0x00, 0x3F, 0x42, 0x42, 0x42, 0x3E, 0x02, 0x02, 0x02, 0x02, 0x07, 0x00, 0x00, //char: 80 'P'
0x00, 0x00, 0x00, 0x00, 0x1C, 0x22, 0x41, 0x41, 0x41, 0x41, 0x41, 0x4D, 0x32, 0x1C, 0x60, 0x00, //char: 81 'Q'
0x00, 0x00, 0x00, 0x00, 0x1F, 0x22, 0x22, 0x22, 0x1E, 0x0A, 0x12, 0x12, 0x22, 0x67, 0x00, 0x00, //char: 82 'R'
0x00, 0x00, 0x00, 0x00, 0x7C, 0x42, 0x42, 0x02, 0x0C, 0x30, 0x40, 0x42, 0x42, 0x3E, 0x00, 0x00, //char: 83 'S'
0x00, 0x00, 0x00, 0x00, 0x7F, 0x49, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x1C, 0x00, 0x00, //char: 84 'T'
0x00, 0x00, 0x00, 0x00, 0x77, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x1C, 0x00, 0x00, //char: 85 'U'
0x00, 0x00, 0x00, 0x00, 0x77, 0x22, 0x22, 0x22, 0x14, 0x14, 0x14, 0x14, 0x08, 0x08, 0x00, 0x00, //char: 86 'V'
0x00, 0x00, 0x00, 0x00, 0x6B, 0x2A, 0x2A, 0x2A, 0x2A, 0x36, 0x14, 0x14, 0x14, 0x14, 0x00, 0x00, //char: 87 'W'
0x00, 0x00, 0x00, 0x00, 0x77, 0x22, 0x14, 0x14, 0x08, 0x08, 0x14, 0x14, 0x22, 0x77, 0x00, 0x00, //char: 88 'X'
0x00, 0x00, 0x00, 0x00, 0x77, 0x22, 0x22, 0x14, 0x14, 0x08, 0x08, 0x08, 0x08, 0x1C, 0x00, 0x00, //char: 89 'Y'
0x00, 0x00, 0x00, 0x00, 0x7C, 0x22, 0x20, 0x10, 0x10, 0x08, 0x08, 0x04, 0x44, 0x7E, 0x00, 0x00, //char: 90 'Z'
0x00, 0x00, 0x38, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x38, 0x00, //char: 91 '['
0x00, 0x00, 0x00, 0x02, 0x02, 0x04, 0x04, 0x08, 0x08, 0x08, 0x10, 0x10, 0x20, 0x20, 0x20, 0x40, //char: 92 '\'
0x00, 0x00, 0x1E, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x1E, 0x00, //char: 93 ']'
0x00, 0x00, 0x18, 0x24, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, //char: 94 '^'
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7F, //char: 95 '_'
0x00, 0x06, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, //char: 96 '`'
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1C, 0x22, 0x30, 0x2C, 0x22, 0x32, 0x6C, 0x00, 0x00, //char: 97 'a'
0x00, 0x00, 0x00, 0x03, 0x02, 0x02, 0x02, 0x3A, 0x46, 0x42, 0x42, 0x42, 0x42, 0x3E, 0x00, 0x00, //char: 98 'b'
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x38, 0x44, 0x02, 0x02, 0x02, 0x44, 0x38, 0x00, 0x00, //char: 99 'c'
0x00, 0x00, 0x00, 0x60, 0x40, 0x40, 0x40, 0x7C, 0x42, 0x42, 0x42, 0x42, 0x62, 0xDC, 0x00, 0x00, //char: 100 'd'
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3C, 0x42, 0x42, 0x7E, 0x02, 0x42, 0x3C, 0x00, 0x00, //char: 101 'e'
0x00, 0x00, 0x00, 0x30, 0x48, 0x08, 0x08, 0x3E, 0x08, 0x08, 0x08, 0x08, 0x08, 0x3E, 0x00, 0x00, //char: 102 'f'
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7C, 0x22, 0x22, 0x1C, 0x02, 0x3C, 0x42, 0x42, 0x3C, //char: 103 'g'
0x00, 0x00, 0x00, 0x03, 0x02, 0x02, 0x02, 0x3A, 0x46, 0x42, 0x42, 0x42, 0x42, 0xE7, 0x00, 0x00, //char: 104 'h'
0x00, 0x00, 0x00, 0x0C, 0x0C, 0x00, 0x00, 0x0E, 0x08, 0x08, 0x08, 0x08, 0x08, 0x3E, 0x00, 0x00, //char: 105 'i'
0x00, 0x00, 0x00, 0x30, 0x30, 0x00, 0x00, 0x38, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x1E, //char: 106 'j'
0x00, 0x00, 0x00, 0x03, 0x02, 0x02, 0x02, 0x72, 0x12, 0x0A, 0x16, 0x12, 0x22, 0x77, 0x00, 0x00, //char: 107 'k'
0x00, 0x00, 0x00, 0x0E, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x3E, 0x00, 0x00, //char: 108 'l'
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1F, 0x2A, 0x2A, 0x2A, 0x2A, 0x2A, 0x6B, 0x00, 0x00, //char: 109 'm'
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3B, 0x46, 0x42, 0x42, 0x42, 0x42, 0xE7, 0x00, 0x00, //char: 110 'n'
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x24, 0x42, 0x42, 0x42, 0x24, 0x18, 0x00, 0x00, //char: 111 'o'
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3B, 0x46, 0x42, 0x42, 0x42, 0x46, 0x3A, 0x02, 0x07, //char: 112 'p'
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x5C, 0x62, 0x42, 0x42, 0x42, 0x62, 0x5C, 0x40, 0xE0, //char: 113 'q'
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x77, 0x4C, 0x04, 0x04, 0x04, 0x04, 0x1F, 0x00, 0x00, //char: 114 'r'
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3C, 0x22, 0x02, 0x1C, 0x20, 0x22, 0x1E, 0x00, 0x00, //char: 115 's'
0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x08, 0x3E, 0x08, 0x08, 0x08, 0x08, 0x48, 0x30, 0x00, 0x00, //char: 116 't'
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x63, 0x42, 0x42, 0x42, 0x42, 0x62, 0xDC, 0x00, 0x00, //char: 117 'u'
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x77, 0x22, 0x22, 0x14, 0x14, 0x08, 0x08, 0x00, 0x00, //char: 118 'v'
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x6B, 0x2A, 0x2A, 0x2A, 0x14, 0x14, 0x14, 0x00, 0x00, //char: 119 'w'
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x6E, 0x24, 0x18, 0x18, 0x18, 0x24, 0x76, 0x00, 0x00, //char: 120 'x'
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xE7, 0x42, 0x24, 0x24, 0x18, 0x18, 0x08, 0x08, 0x06, //char: 121 'y'
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7E, 0x22, 0x10, 0x08, 0x08, 0x44, 0x7E, 0x00, 0x00, //char: 122 'z'
0x00, 0x00, 0x60, 0x20, 0x20, 0x20, 0x20, 0x20, 0x10, 0x20, 0x20, 0x20, 0x20, 0x20, 0x60, 0x00, //char: 123 '{'
0x00, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, //char: 124 '|'
0x00, 0x00, 0x06, 0x04, 0x04, 0x04, 0x04, 0x04, 0x08, 0x04, 0x04, 0x04, 0x04, 0x04, 0x06, 0x00, //char: 125 '}'
0x00, 0x04, 0x5A, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, //char: 126 '~'
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, //char: 127 ' '
};
#else
#error "ERR: Font size not supported"
#endif
}; //End Class: Screen
/*********************************************************************************************************************************************************
**********************************************************************************************************************************************************
** CONSTRUCTORS
**********************************************************************************************************************************************************
*********************************************************************************************************************************************************/
/***************************************************************************/
//! @brief empty constructor
//! Screen | void
/***************************************************************************/
//! @details
//! \n Empty constructor
/***************************************************************************/
Screen::Screen( void ) : Longan_nano::Display::Display()
{
DENTER();
//----------------------------------------------------------------
// VARS
//----------------------------------------------------------------
//----------------------------------------------------------------
// BODY
//----------------------------------------------------------------
this -> init_class_vars();
//----------------------------------------------------------------
// RETURN
//----------------------------------------------------------------
DRETURN();
return;
} //End constructor: Screen | void
/*********************************************************************************************************************************************************
**********************************************************************************************************************************************************
** DESTRUCTORS
**********************************************************************************************************************************************************
*********************************************************************************************************************************************************/
/***************************************************************************/
//! @brief empty destructor
//! ~Screen | void |
/***************************************************************************/
//! @details
//! \n Void destructor
/***************************************************************************/
Screen::~Screen( void )
{
DENTER();
//----------------------------------------------------------------
// VARS
//----------------------------------------------------------------
//----------------------------------------------------------------
// BODY
//----------------------------------------------------------------
//----------------------------------------------------------------
// RETURN
//----------------------------------------------------------------
DRETURN();
return;
} //End Destructor: ~Screen | void |
/*********************************************************************************************************************************************************
**********************************************************************************************************************************************************
** PUBLIC INIT
**********************************************************************************************************************************************************
*********************************************************************************************************************************************************/
/***************************************************************************/
//! @brief public init
//! init | void
/***************************************************************************/
//! @return bool | false = OK | true = ERR
//! @details
//! \n call the initializations for the driver
/***************************************************************************/
bool Screen::init( void )
{
DENTER();
//----------------------------------------------------------------
// VARS
//----------------------------------------------------------------
//Return flag
bool f_ret = false;
//----------------------------------------------------------------
// BODY
//----------------------------------------------------------------
//Initialize the display driver. It handles phisical communication with the display and provide methods to write sprites
f_ret = this -> Longan_nano::Display::init();
//Initialize colors
this -> init_default_colors();
//Initialize the frame buffer
f_ret |= this -> init_frame_buffer();
//Initialize default palette
f_ret |= this -> init_palette();
//Initialize update FSM
f_ret |= this -> init_fsm();
//Clear the display to black
this -> Display::clear();
//----------------------------------------------------------------
// RETURN
//----------------------------------------------------------------
DRETURN();
return f_ret;
} //End public init: init | void
/***************************************************************************/
//! @brief public init
//! reset_colors |
/***************************************************************************/
//! @return bool | false = OK | true = ERR
//! @details
//! \n Reset the colors to default
/***************************************************************************/
bool Screen::reset_colors( void )
{
DENTER();
//----------------------------------------------------------------
// VARS
//----------------------------------------------------------------
//Return flag
bool f_ret = false;
//----------------------------------------------------------------
// BODY
//----------------------------------------------------------------
this -> init_default_colors();
f_ret |= this -> init_palette();
this -> Display::clear();
//----------------------------------------------------------------
// RETURN
//----------------------------------------------------------------
DRETURN();
return f_ret; //OK
} //End public init: reset_colors | void
/*********************************************************************************************************************************************************
**********************************************************************************************************************************************************
** PUBLIC SETTER
**********************************************************************************************************************************************************
*********************************************************************************************************************************************************/
/***************************************************************************/
//! @brief public setter
//! set_color | int | int | Color | Color
/***************************************************************************/
//! @param origin_h | int | height position of the sprite
//! @param origin_w | int | width position of the sprite
//! @param background | Color | background color of ths sprite as index from the palette
//! @param foreground | Color | foreground color of ths sprite as index from the palette
//! @return int | <0 ERR | >= 0 number of updated sprites
//! @details
//! \n Set background and foreground color of a given sprite
/***************************************************************************/
inline int Screen::set_color( int origin_h, int origin_w, Color background, Color foreground )
{
DENTER();
//----------------------------------------------------------------
// CHECK
//----------------------------------------------------------------
//If: first character is outside the ascii sprite table
if ((origin_h < 0) || (origin_h >= Config::FRAME_BUFFER_HEIGHT) || (origin_w < 0) || (origin_w >= Config::FRAME_BUFFER_WIDTH))
{
DRETURN_ARG("ERR: out of the sprite table %5d %5d\n", origin_h, origin_w);
return true; //FAIL
}
//If: colors are bad
if ((background >= (Color)Config::PALETTE_SIZE) || (foreground >= (Color)Config::PALETTE_SIZE))
{
DRETURN_ARG("ERR: bad default colors | Back: %3d | Fore: %3d |\n", background, foreground );
return true; //FAIL
}
//----------------------------------------------------------------
// BODY
//----------------------------------------------------------------
//Has the sprite changed?
bool f_changed = false;
//Fetch sprite
Frame_buffer_sprite sprite_tmp = g_frame_buffer[origin_h][origin_w];
//If: sprite uses color and color has changed
if ((this -> is_using_background(sprite_tmp.sprite_index) == true) && (sprite_tmp.background_color != background))
{
//Update colors
sprite_tmp.background_color = background;
f_changed = true;
}
//If: sprite uses color and color has changed
if ((this -> is_using_foreground(sprite_tmp.sprite_index) == true) && (sprite_tmp.foreground_color != foreground))
{
//Update colors
sprite_tmp.foreground_color = foreground;
f_changed = true;
}
//Number of sprites changed
int ret;
//If the sprite has changed
if (f_changed == true)
{
//Update the frame buffer with the new sprite if needed
ret = this -> update_sprite( origin_h, origin_w, sprite_tmp );
}
//----------------------------------------------------------------
// RETURN
//----------------------------------------------------------------
DRETURN();
return ret; //OK
} //End public setter: set_color | int | int | Color | Color
/***************************************************************************/
//! @brief public setter
//! set_default_colors | Color | Color
/***************************************************************************/
//! @param new_background | Color | background color of ths sprite as index from the palette
//! @param new_foreground | Color | foreground color of ths sprite as index from the palette
//! @return int16_t | >=0 Number of sprites changed | < 0 error |
//! @details
//! \n Change the default background and foreground colors
//! \n All existing sprites are automatically updated as needed
/***************************************************************************/
int Screen::set_default_colors( Color new_background, Color new_foreground )
{
DENTER();
//----------------------------------------------------------------
// CHECK
//----------------------------------------------------------------
//If: colors are bad
if ((new_background >= (Color)Config::PALETTE_SIZE) || (new_foreground >= (Color)Config::PALETTE_SIZE))
{
DRETURN_ARG("ERR: bad default colors | Back: %3d | Fore: %3d |\n", new_background, new_foreground );
return -1; //FAIL
}
//----------------------------------------------------------------
// VARS
//----------------------------------------------------------------
//Temp sprite
Frame_buffer_sprite sprite_tmp;
//Num of updated sprites
int ret;
//Compute branch flags
Color old_background = this -> g_default_background_color;
Color old_foreground = this -> g_default_foreground_color;
bool f_background_change = (this -> g_default_background_color != new_background);
bool f_foreground_change = (this -> g_default_foreground_color != new_foreground);
//Number of sprites changed
int num_changed_sprites = 0;
//----------------------------------------------------------------
// BODY
//----------------------------------------------------------------
//If: nothing to do
if ( (f_background_change == false) && (f_foreground_change == false))
{
//Do nothing
}
//If: only background change
if ( (f_background_change == true) && (f_foreground_change == false))
{
//Update defaults
this -> g_default_background_color = new_background;
//For: scan height
for (uint8_t th = 0;th < Config::FRAME_BUFFER_HEIGHT;th++)
{
//For: scan width
for (uint8_t tw = 0;tw < Config::FRAME_BUFFER_WIDTH;tw++)
{
//Fetch sprite
sprite_tmp = this -> g_frame_buffer[ th ][ tw ];
//If: the background is a color to be swapped and the character uses the background
if ( (sprite_tmp.background_color == old_background) && (this -> is_using_background( sprite_tmp.sprite_index )) )
{
//Change the color
sprite_tmp.background_color = new_background;
//Update the sprite
ret = this -> update_sprite( th, tw, sprite_tmp );
//If: failed to update sprite
if ((Config::PEDANTIC_CHECKS == true) && (ret < 0))
{
DRETURN_ARG("ERR: Failed to update sprite\n");
return -1;
}
//If: either zero or one sprite was changed
else
{
//Accumulate number of sprites changed
num_changed_sprites += ret;
}
}
} //End For: scan width
} //For: scan height
} //End If: only background change
//If: only foreground change
else if ( (f_background_change == false) && (f_foreground_change == true))
{
//Update defaults
this -> g_default_foreground_color = new_foreground;
//For: scan height
for (uint8_t th = 0;th < Config::FRAME_BUFFER_HEIGHT;th++)
{
//For: scan width
for (uint8_t tw = 0;tw < Config::FRAME_BUFFER_WIDTH;tw++)
{
//Fetch sprite
sprite_tmp = this -> g_frame_buffer[ th ][ tw ];
//If: the background is a color to be swapped and the character uses the background
if ( (sprite_tmp.foreground_color == old_foreground) && (this -> is_using_foreground( sprite_tmp.sprite_index )) )
{
//Change the color
sprite_tmp.foreground_color = new_foreground;
//Update the sprite
ret = this -> update_sprite( th, tw, sprite_tmp );
//If: failed to update sprite
if ((Config::PEDANTIC_CHECKS == true) && (ret < 0))
{
DRETURN_ARG("ERR: Failed to update sprite\n");
return -1;
}
//If: either zero or one sprite was changed
else
{
//Accumulate number of sprites changed
num_changed_sprites += ret;
}
}
} //End For: scan width
} //For: scan height
} //End If: only foreground change
//If: both colors have changed
else //if ( (f_background_change == true) && (f_foreground_change == true))
{
//Has sprite changed
bool f_changed = false;
//Update defaults
this -> g_default_background_color = new_background;
this -> g_default_foreground_color = new_foreground;
//For: scan height
for (uint8_t th = 0;th < Config::FRAME_BUFFER_HEIGHT;th++)
{
//For: scan width
for (uint8_t tw = 0;tw < Config::FRAME_BUFFER_WIDTH;tw++)
{
//Fetch sprite
sprite_tmp = this -> g_frame_buffer[ th ][ tw ];
//If: the background is a color to be swapped and the character uses the background
if ( (sprite_tmp.background_color == old_background) && (this -> is_using_background( sprite_tmp.sprite_index )) )
{
//Change the color
f_changed = true;
sprite_tmp.background_color = new_background;
}
//If: the background is a color to be swapped and the character uses the background
if ( (sprite_tmp.foreground_color == old_foreground) && (this -> is_using_foreground( sprite_tmp.sprite_index )) )
{
//Change the color
f_changed = true;
sprite_tmp.foreground_color = new_foreground;
}
//If: Sprite has changed
if (f_changed == true)
{
//Mark sprite for update
sprite_tmp.f_update = true;
//Update the sprite
ret = this -> update_sprite( th, tw, sprite_tmp );
//If: failed to update sprite
if ((Config::PEDANTIC_CHECKS == true) && (ret < 0))
{
DRETURN_ARG("ERR: Failed to update sprite\n");
return -1;
}
//If: either zero or one sprite was changed
else
{
//Accumulate number of sprites changed
num_changed_sprites += ret;
}
}
} //End For: scan width
} //For: scan height
} //End If: both colors have changed
//----------------------------------------------------------------
// RETURN
//----------------------------------------------------------------
DRETURN();
return num_changed_sprites; //Number of sprites changed
} //End public setter: set_default_colors | Color | Color
/***************************************************************************/
//! @brief public setter
//! set_palette_color | Color | uint8_t | uint8_t | uint8_t |
/***************************************************************************/
//! @param palette_index | Color | color index of the palette to be changed
//! @param r | uint8_t | red color channel
//! @param g | uint8_t | red color channel
//! @param b | uint8_t | red color channel
//! @return int | >=0 Number of sprites changed | < 0 error |
//! @details
//! \n Change a color in the palette to another RGB color
//! \n Change all sprites that use that color and mark them for update
//! \n Use the conversion function provided by the Display driver to compute the right color space
/***************************************************************************/
int Screen::set_palette_color( Color palette_index, uint8_t r, uint8_t g, uint8_t b )
{
DENTER_ARG("source: %d |\n", palette_index);
//----------------------------------------------------------------
// CHECK
//----------------------------------------------------------------
//If: bad colors
if ((Config::PEDANTIC_CHECKS == true) && (palette_index >= (Color)Config::PALETTE_SIZE))
{
DRETURN_ARG("ERR: bad palette index | source: %d |\n", palette_index );
return -1;
}
//----------------------------------------------------------------
// CHANGE PALETTE
//----------------------------------------------------------------
//Ask the display driver to compute the RGB color to the color supported by the screen
uint16_t new_color = Display::color( r, g, b );
//If: desired color is already in the palette
if (this -> g_palette[ palette_index ] == new_color)
{
DRETURN();
return 0;
}
//Set the color in the palette
this -> g_palette[ palette_index ] = new_color;
//----------------------------------------------------------------
// MARK CHANGED SPRITES FOR UPDATE
//----------------------------------------------------------------
//Temp sprite
Frame_buffer_sprite sprite_tmp;
//Num of updated sprites
int ret;
bool f_sprite_changed;
int num_changed_sprites = 0;
//For: scan height
for (uint8_t th = 0;th < Config::FRAME_BUFFER_HEIGHT;th++)
{
//For: scan width
for (uint8_t tw = 0;tw < Config::FRAME_BUFFER_WIDTH;tw++)
{
//Fetch sprite
sprite_tmp = this -> g_frame_buffer[ th ][ tw ];
//Was sprite changed?
f_sprite_changed = false;
//If: the background is a color to be swapped and the character uses the background
if ( (sprite_tmp.background_color == palette_index) && (this -> is_using_background( sprite_tmp.sprite_index )) )
{
f_sprite_changed = true;
}
//If: the foreground is a color to be swapped and the character uses the foreground
if ( (sprite_tmp.foreground_color == palette_index) && (this -> is_using_foreground( sprite_tmp.sprite_index )) )
{
f_sprite_changed = true;
}
//If: sprite was changed
if (f_sprite_changed == true)
{
//Update the sprite
ret = this -> update_sprite( th, tw, sprite_tmp );
//If: failed to update sprite
if ((Config::PEDANTIC_CHECKS == true) && (ret < 0))
{
DRETURN_ARG("ERR: Failed to update sprite\n");
return -1;
}
//If: either zero or one sprite was changed
else
{
//Accumulate number of sprites changed
num_changed_sprites += ret;
}
}
} //End For: scan width
} //For: scan height
//----------------------------------------------------------------
// RETURN
//----------------------------------------------------------------
DRETURN();
return num_changed_sprites;
} //End public setter: set_palette_color | Color | uint8_t | uint8_t | uint8_t |
/***************************************************************************/
//! @brief public setter
//! set_format | int | Format_align | Format_format |
/***************************************************************************/
//! @param number_size | int | sprites reserved for the number
//! @param align | Format_align | set alignment of the number
//! @param format | Format_format | set display format of the number
//! @return bool | false = OK | true = ERR
//! @details
//! \n With left adjust, the number is print starting from left an origin is the position of the most significant number
//! \n With right adjust, the origin is the position of the least significant number
//! \n LEFT 78901 12.71m
//! \n RIGHT 78901 12.71m
//! \n ORIGIN ^ ^
//! \n The format also specifies full number or enginnering format
/***************************************************************************/
inline bool Screen::set_format( int number_size, Format_align align, Format_format format )
{
DENTER_ARG("number_size: %d |align: %d | format: %d\n", number_size, (int)align, (int)format );
//----------------------------------------------------------------
// CHECK
//----------------------------------------------------------------
//If: bad number size
if (number_size <= 0)
{
DENTER_ARG("ERR: bad number_size: %d\n", number_size );
return true; //ERR
}
//----------------------------------------------------------------
// BODY
//----------------------------------------------------------------
//Configure number format for the next print
this -> g_format_number.size = number_size;
this -> g_format_number.align = align;
this -> g_format_number.format = format;
//----------------------------------------------------------------
// RETURN
//----------------------------------------------------------------
DRETURN();
return false; //OK
} //End public setter: set_format | int | Format_align | Format_format |
/***************************************************************************/
//! @brief public setter
//! set_format | int | Format_align | Format_format | int |
/***************************************************************************/
//! @param number_size | int | sprites reserved for the number
//! @param align | Format_align | set alignment of the number
//! @param format | Format_format | set display format of the number
//! @param exp | int | ENG number exponent
//! @return bool | false = OK | true = ERR
//! @details
//! \n With left adjust, the number is print starting from left an origin is the position of the most significant number
//! \n With right adjust, the origin is the position of the least significant number
//! \n LEFT 78901 12.71m
//! \n RIGHT 78901 12.71m
//! \n ORIGIN ^ ^
//! \n The format also specifies full number or enginnering format
/***************************************************************************/
inline bool Screen::set_format( int number_size, Format_align align, Format_format format, int exp )
{
DENTER_ARG("number_size: %d |align: %d | format: %d | ENG exponent: %d\n", number_size, (int)align, (int)format, exp );
//----------------------------------------------------------------
// CHECK
//----------------------------------------------------------------
//If: bad number size
if (number_size <= 0)
{
DENTER_ARG("ERR: bad number_size: %d\n", number_size );
return true; //ERR
}
//If: bad exponent
if ((exp < -6) || (exp > 6))
{
DENTER_ARG("ERR: bad ENG exponent: %d\n", exp );
return true; //ERR
}
//----------------------------------------------------------------
// BODY
//----------------------------------------------------------------
//Configure number format for the next print
this -> g_format_number.size = number_size;
this -> g_format_number.align = align;
this -> g_format_number.format = format;
//Configure ENG number exponent
this -> g_format_number.eng_exp = exp;
//----------------------------------------------------------------
// RETURN
//----------------------------------------------------------------
DRETURN();
return false; //OK
} //End public setter: set_format | int | Format_align | Format_format | int |
/*********************************************************************************************************************************************************
**********************************************************************************************************************************************************
** PUBLIC GETTERS
**********************************************************************************************************************************************************
*********************************************************************************************************************************************************/
/***************************************************************************/
//! @brief public getter
//! get_pending | void |
/***************************************************************************/
//! @return int | number of sprites pending for update in the frame buffer
//! @details
//! \n return the number of sprites pending for update in the frame buffer
/***************************************************************************/
int Screen::get_pending( void )
{
DENTER(); //Trace Enter
///--------------------------------------------------------------------------
/// RETURN
///--------------------------------------------------------------------------
DRETURN_ARG("Pending: %d", this -> g_pending_cnt ); //Trace Return
return this -> g_pending_cnt ; //OK
} //end public getter: get_pending | void |
/***************************************************************************/
//! @brief public getter
//! get_pending | void |
/***************************************************************************/
//! @return int | number of sprites pending for update in the frame buffer
//! @details
//! \n return the number of sprites pending for update in the frame buffer
/***************************************************************************/
Screen::Error Screen::get_error( void )
{
DENTER(); //Trace Enter
///--------------------------------------------------------------------------
/// RETURN
///--------------------------------------------------------------------------
DRETURN_ARG("ERR%d", (int)this -> g_error_code ); //Trace Return
return this -> g_error_code ; //OK
} //end public getter: get_pending | void |
/*********************************************************************************************************************************************************
**********************************************************************************************************************************************************
** PUBLIC METHODS
**********************************************************************************************************************************************************
*********************************************************************************************************************************************************/
/***************************************************************************/
//! @brief public method
//! update | void
/***************************************************************************/
//! @return bool | false = OK | true = ERR
//! @details
//! FSM that synchronize the frame buffer with the display using the driver
//! The low level driver exposes control steps used by the high level frame buffer driver
/***************************************************************************/
bool Screen::update( void )
{
DENTER();
//----------------------------------------------------------------
// VARS
//----------------------------------------------------------------
//Allows the FSM to run
bool f_continue = true;
//Temp FSM status
Fsm_status status;
//----------------------------------------------------------------
// BODY
//----------------------------------------------------------------
//Snap status of the FSM
status = this -> g_status;
//While: the Screen FSM is allowed to run
while (f_continue == true)
{
DPRINT("exe: %3d | w: %5d | h: %5d | cnt: %3d\n", status.phase, status.scan_w, status.scan_h, status.cnt );
//If: I'm scanning for the next sprite to be updated
if (status.phase == Fsm_state::SCAN_SPRITE)
{
//Fetch the sprite
Frame_buffer_sprite sprite_tmp = this -> g_frame_buffer[ status.scan_h ][ status.scan_w ];
//If: there are no sprites to be updated in the frame buffer
if (this -> g_pending_cnt == 0)
{
//I'm done. Don't wasete time scanning
f_continue = false;
}
//If: the sprite indexed is to be updated
else if (sprite_tmp.f_update == true)
{
//This sprite is not to be updated anymore
this -> g_frame_buffer[ status.scan_h ][ status.scan_w ].f_update = false;
//If: pending counter is already zero
if ((Config::PEDANTIC_CHECKS == true) && (this -> g_pending_cnt == 0))
{
//Algorithmic error
this -> report_error( Screen::Error::PENDING_UNDERFLOW );
DRETURN_ARG("ERR: %d\n", (int)this -> get_error() );
return true;
}
//If: pending ccounter is valid
else
{
//A pending sprite has been processed. This is the only code allowed to reduce workload
this -> g_pending_cnt--;
}
DPRINT("REFRESH sprite h: %5d | w: %5d\n", status.scan_h, status.scan_w );
//Compute the pixel data and try to register the sprite for draw inside the display driver
int ret = this -> register_sprite( status.scan_h, status.scan_w );
//If: no sprites were registered but no errors occurred
if (ret == 0)
{
//Maybe a transparent sprite. Keep scanning for sprites
}
//If: sprite has been registered for draw
else if (ret > 0)
{
//Begin execution of the driver FSM that sends pixel data to the screen
status.phase = Fsm_state::SEND_SPRITE;
//Count number of execution steps of the Display FSM
status.cnt = 0;
}
//If: failed to register sprite
else
{
DPRINT("ERR: Failed to register sprite\n");
//Reset the update FSM
this -> init_fsm();
f_continue = false;
}
//Move on to next sprite
//if: space to advance in width
if (status.scan_w < Config::FRAME_BUFFER_WIDTH -1)
{
//Move cursor right
status.scan_w++;
}
//If: space to advance in height
else if (status.scan_h < Config::FRAME_BUFFER_HEIGHT -1)
{
//Get back left
status.scan_w = 0;
//Move down in height (2° rank of frame buffer vector)
status.scan_h++;
}
//if: scan limit
else
{
//Get back to the top left
status.scan_h = 0;
status.scan_w = 0;
}
} //End If: the sprite indexed is to be updated
//If: I'm allowed to scan for more sprites
else if (status.cnt < Config::SPRITE_SCAN_LIMIT -1)
{
//Move on to next sprite
//if: space to advance in width
if (status.scan_w < Config::FRAME_BUFFER_WIDTH -1)
{
//Move cursor right
status.scan_w++;
}
//If: space to advance in height
else if (status.scan_h < Config::FRAME_BUFFER_HEIGHT -1)
{
//Get back left
status.scan_w = 0;
//Move down in height (2nd rank of frame buffer vector)
status.scan_h++;
}
//if: scan limit
else
{
//Get back to the top left
status.scan_h = 0;
status.scan_w = 0;
}
//I scanned a sprite
status.cnt++;
} //End if: I'm allowed to scan for more sprites
//If: I reached the scan limit
else
{
//Reset the scan sprite counter
status.cnt = 0;
//I'm not allowed to scan more sprite. Release execution of the FSM
f_continue = false;
} //End If: I reached the scan limit
} //End If: I'm scanning for the next sprite to be updated
//If: I'm in the process of sending a sprite
else if (status.phase == Fsm_state::SEND_SPRITE)
{
DPRINT("Execute display FSM: step: %d\n", status.cnt);
//Have the display drivere execute a step in its internal FSM. FSMs are partitioned in logical and physical for future expansion of the screen class.
bool f_ret = this -> Display::update_sprite();
//if: Display driver FSM is busy
if (f_ret == true)
{
//A FSM step was executed
status.cnt++;
//Driver still has work to do. Do not scan for more work.
}
//if: Display driver FSM is IDLE
else
{
DPRINT("Display FSM IDLE\n");
//reset status counter
status.cnt = 0;
//Driver FSM is done. I can scan for more work, if any is available.
status.phase = Fsm_state::SCAN_SPRITE;
}
//
f_continue = false;
} //End If: I'm in the process of sending a sprite
//If: Algorithmic error
else
{
//Reset the update FSM
this -> init_fsm();
f_continue = false;
} //End If: Algorithmic error
} //End While: the Screen FSM is allowed to run
//Write back FSM status
this -> g_status = status;
//----------------------------------------------------------------
// RETURN
//----------------------------------------------------------------
DRETURN_ARG("exe: %3d | w: %5d | h: %5d | cnt: %3d\n", status.phase, status.scan_w, status.scan_h, status.cnt );
return false; //OK
} //End public method: update | void
/***************************************************************************/
//! @brief public method
//! change_color | Color | Color |
/***************************************************************************/
//! @param source | Color | color to be changed
//! @param dest | Color | replacement color
//! @return int | >=0 Number of sprites changed | < 0 error |
//! @details
//! Every sprite that use the "source" palette color as either background or foreground
//! has it swapped for the "dest" palette color. All changed sprites are marked for update
/***************************************************************************/
int Screen::change_color( Color source, Color dest )
{
DENTER_ARG("source: %d | dest: %d |\n", source, dest);
//----------------------------------------------------------------
// CHECK
//----------------------------------------------------------------
//If: bad colors
if ((Config::PEDANTIC_CHECKS == true) && ((source >= (Color)Config::PALETTE_SIZE) || (dest >= (Color)Config::PALETTE_SIZE)) )
{
DRETURN_ARG("ERR: bad colors | source: %d | dest: %d |\n", source, dest );
return -1;
}
//----------------------------------------------------------------
// BODY
//----------------------------------------------------------------
//Temp sprite
Frame_buffer_sprite sprite_tmp;
//Num of updated sprites
int ret = 0;
bool f_sprite_changed;
//For: scan height
for (uint8_t th = 0;th < Config::FRAME_BUFFER_HEIGHT;th++)
{
//For: scan width
for (uint8_t tw = 0;tw < Config::FRAME_BUFFER_WIDTH;tw++)
{
//Fetch sprite
sprite_tmp = this -> g_frame_buffer[ th ][ tw ];
//Was sprite changed?
f_sprite_changed = false;
//If: the background is a color to be swapped and the character uses the background
if ( (sprite_tmp.background_color == source) && (this -> is_using_background( sprite_tmp.sprite_index )) )
{
//Swap the color
sprite_tmp.background_color = dest;
f_sprite_changed = true;
}
//If: the foreground is a color to be swapped and the character uses the foreground
if ( (sprite_tmp.foreground_color == source) && (this -> is_using_foreground( sprite_tmp.sprite_index )) )
{
//Swap the color
sprite_tmp.foreground_color = dest;
f_sprite_changed = true;
}
//If: sprite was changed
if (f_sprite_changed == true)
{
this -> update_sprite( th, tw, sprite_tmp );
}
} //End For: scan width
} //For: scan height
//----------------------------------------------------------------
// RETURN
//----------------------------------------------------------------
DENTER_ARG("sprites updated %d |\n", ret);
return ret; //fail
} //End public method: change_color | Color | Color |
/***************************************************************************/
//! @brief public method
//! clear | void |
/***************************************************************************/
//! @return int | >=0 Number of sprites changed | < 0 error |
//! @details
//! \n Clear the screen by setting a solid color sprite to each element of the sprite frame buffer
/***************************************************************************/
int Screen::clear( void )
{
DENTER();
//----------------------------------------------------------------
// VARS
//----------------------------------------------------------------
//Fast counters
uint8_t th, tw;
//Temp sprite
Frame_buffer_sprite sprite_tmp;
//Number of sprites updated
int num_sprites_updated = 0;
int ret;
//----------------------------------------------------------------
// BODY
//----------------------------------------------------------------
//Construct sprite.
//Special sprite with full background color
sprite_tmp.sprite_index = Config::SPRITE_BLACK;
//Set color. I don't care about background color
sprite_tmp.background_color = Color::BLACK;
sprite_tmp.foreground_color = Color::BLACK;
sprite_tmp.f_update = true;
//For: each frame buffer row (height scan)
for (th = 0;th < Screen::Config::FRAME_BUFFER_HEIGHT;th++)
{
//For: each frame buffer col (width scan)
for (tw = 0;tw < Screen::Config::FRAME_BUFFER_WIDTH;tw++)
{
//Update the frame buffer with the new sprite if needed
ret = this -> update_sprite( th, tw, sprite_tmp );
//If: an error occurred
if ((Config::PEDANTIC_CHECKS == true) && (ret < 0))
{
DRETURN_ARG("ERR: Failed to update sprite\n");
return ret;
}
else
{
//Compute number of updated sprites
num_sprites_updated += ret;
}
} //End For: each frame buffer col (width scan)
} //End For: each frame buffer row (height scan)
//----------------------------------------------------------------
// RETURN
//----------------------------------------------------------------
DRETURN();
return num_sprites_updated;
} //End public method: clear | void |
/***************************************************************************/
//! @brief public method
//! clear | Color |
/***************************************************************************/
//! @param color_tmp | Color | index of the solid sprite in the palette
//! @return int | >=0 Number of sprites changed | < 0 error |
//! @details
//! \n Clear the screen by setting a solid color sprite to each element of the sprite frame buffer
/***************************************************************************/
int Screen::clear( Color color_tmp )
{
DENTER_ARG("color: %d\n", (int)color_tmp);
//----------------------------------------------------------------
// CHECK
//----------------------------------------------------------------
//If: palette index outside the palette
if (color_tmp >= (Color)Screen::Config::PALETTE_SIZE)
{
DRETURN_ARG("ERR: palette index outside the palette\n");
return -1;
}
//----------------------------------------------------------------
// VARS
//----------------------------------------------------------------
//Fast counters
uint8_t th, tw;
//Temp sprite
Frame_buffer_sprite sprite_tmp;
//Number of sprites updated
int num_sprites_updated = 0;
//Temp return
int ret;
//----------------------------------------------------------------
// BODY
//----------------------------------------------------------------
//Construct sprite.
//Special sprite with full background color
sprite_tmp.sprite_index = Config::SPRITE_BACKGROUND;
//Set color. I don't care about background color
sprite_tmp.background_color = color_tmp;
sprite_tmp.foreground_color = color_tmp;
sprite_tmp.f_update = true;
//For: each frame buffer row (height scan)
for (th = 0;th < Screen::Config::FRAME_BUFFER_HEIGHT;th++)
{
//For: each frame buffer col (width scan)
for (tw = 0;tw < Screen::Config::FRAME_BUFFER_WIDTH;tw++)
{
//Update the frame buffer with the new sprite if needed
ret = this -> update_sprite( th, tw, sprite_tmp );
//If: an error occurred
if ((Config::PEDANTIC_CHECKS == true) && (ret < 0))
{
DRETURN_ARG("ERR: Failed to update sprite\n");
return ret;
}
else
{
//Compute number of updated sprites
num_sprites_updated += ret;
}
} //End For: each frame buffer col (width scan)
} //End For: each frame buffer row (height scan)
//----------------------------------------------------------------
// RETURN
//----------------------------------------------------------------
DRETURN();
return num_sprites_updated;
} //End public method: clear | Color |
/***************************************************************************/
//! @brief public method
//! print | int | int | char | Color | Color
/***************************************************************************/
//! @param origin_h | int | height position of the sprite
//! @param origin_w | int | width position of the sprite
//! @param c | char | ascii char to be drawn
//! @param background | Color | background color of ths sprite as index from the palette
//! @param foreground | Color | foreground color of ths sprite as index from the palette
//! @return int | >=0 Number of sprites changed | < 0 error |
//! @todo add special non drawable sprite
//! @details
//! \n Print a character on screen using user defined background and foreground colors
//! \n Mark the sprite for update if needs to be
/***************************************************************************/
int Screen::print( int origin_h, int origin_w, char c, Color background, Color foreground )
{
DENTER_ARG("h: %5d, w: %5d, c: %d %d\n", origin_h, origin_w, (int)background, (int)foreground);
//----------------------------------------------------------------
// CHECK
//----------------------------------------------------------------
//If: character is outside the ascii sprite table
if ((origin_h < 0) || (origin_h >= Config::FRAME_BUFFER_HEIGHT) || (origin_w < 0) || (origin_w >= Config::FRAME_BUFFER_WIDTH))
{
DRETURN_ARG("ERR: out of the sprite table\n");
return -1; //FAIL
}
//If: colors are bad
if ((background >= (Color)Config::PALETTE_SIZE) || (foreground >= (Color)Config::PALETTE_SIZE))
{
DRETURN_ARG("ERR: bad default colors | Back: %3d | Fore: %3d |\n", background, foreground );
return -1; //FAIL
}
//If: character is not stored inside the ascii sprite table
if (is_valid_char( c ) == false)
{
DRETURN_ARG("ERR: character not on the char table\n");
return -1; //FAIL
}
//----------------------------------------------------------------
// VARS
//----------------------------------------------------------------
//Temp sprite
Frame_buffer_sprite sprite_tmp;
//Construct temp sprite
//Sprite index. use its ascii code
sprite_tmp.sprite_index = c;
//Use default background and foreground colors
sprite_tmp.background_color = background;
sprite_tmp.foreground_color = foreground;
//Mark this sprite for update
sprite_tmp.f_update = true;
//----------------------------------------------------------------
// BODY
//----------------------------------------------------------------
//Update the frame buffer with the new sprite if needed
int ret = this -> update_sprite( origin_h, origin_w, sprite_tmp );
//@debug
show_frame_sprite(this -> g_frame_buffer[origin_h][origin_w]);
//----------------------------------------------------------------
// RETURN
//----------------------------------------------------------------
DRETURN();
return ret;
} //End public method: print | int | int | char | Color | Color
/***************************************************************************/
//! @brief public method
//! print | int | int | char | Color
/***************************************************************************/
//! @param origin_h | int | height position of the sprite
//! @param origin_w | int | width position of the sprite
//! @param c | char | ascii char to be drawn
//! @param foreground | Color | foreground color of ths sprite as index from the palette
//! @return int | >=0 Number of sprites changed | < 0 error |
//! @details
//! \n Print a character on screen using default background color but a user defined foreground color
/***************************************************************************/
inline int Screen::print( int origin_h, int origin_w, char c, Color foreground )
{
DENTER();
//----------------------------------------------------------------
// BODY
//----------------------------------------------------------------
//Call the more generic print char method and return its result
int num_changed_sprites = this -> print( origin_h, origin_w, c, this -> g_default_background_color, foreground );
//----------------------------------------------------------------
// RETURN
//----------------------------------------------------------------
DRETURN();
return num_changed_sprites;
} //End public method: print | int | int | char | Color
/***************************************************************************/
//! @brief public method
//! print | int | int | char |
/***************************************************************************/
//! @param origin_h | int | height position of the sprite
//! @param origin_w | int | width position of the sprite
//! @param c | char | ascii char to be drawn
//! @return int16_t | >=0 Number of sprites changed | < 0 error |
//! @details
//! \n print a char inside the sprite frame buffer and mark it for update if needs to be
//! \n uses default background and foreground colors
/***************************************************************************/
inline int Screen::print( int origin_h, int origin_w, char c )
{
DENTER();
//----------------------------------------------------------------
// BODY
//----------------------------------------------------------------
//Call the more generic print char method and return its result
int num_changed_sprites = this -> print( origin_h, origin_w, c, this -> g_default_background_color, this -> g_default_foreground_color );
//----------------------------------------------------------------
// RETURN
//----------------------------------------------------------------
DRETURN();
return num_changed_sprites;
} //End public method: print | int | int | char |
/***************************************************************************/
//! @brief public method
//! print | int | int | const char * | Color | Color |
/***************************************************************************/
//! @param origin_h | int | height position of the sprite
//! @param origin_w | int | width position of the sprite
//! @param str | const char * | string to be drawn. Must be null terminated
//! @param background | Color | background color of ths sprite as index from the palette
//! @param foreground | Color | foreground color of ths sprite as index from the palette
//! @return int16_t | >=0 Number of sprites changed | < 0 error |
//! @details
//! \n Print a string on screen using user defined background and foreground colors
//! \n Strings will not wrap around the screen
//! \n String must be \0 terminated
/***************************************************************************/
int Screen::print( int origin_h, int origin_w, const char *str, Color background, Color foreground )
{
DENTER_ARG("h: %5d, w: %5d, c: %p %s\n", origin_h, origin_w, str, str);
//----------------------------------------------------------------
// CHECK
//----------------------------------------------------------------
//If: first character is outside the ascii sprite table
if ((origin_h < 0) || (origin_h >= Config::FRAME_BUFFER_HEIGHT) || (origin_w < 0) || (origin_w >= Config::FRAME_BUFFER_WIDTH))
{
DRETURN_ARG("ERR: out of the sprite table %5d %5d\n", origin_h, origin_w);
return -1; //FAIL
}
//If: colors are bad
if ((background >= (Color)Config::PALETTE_SIZE) || (foreground >= (Color)Config::PALETTE_SIZE))
{
DRETURN_ARG("ERR: bad default colors | Back: %3d | Fore: %3d |\n", background, foreground );
return -1; //FAIL
}
//If: string is invalid
if (str == nullptr)
{
DRETURN_ARG("ERR: null pointer string\n");
return -1; //FAIL
}
//----------------------------------------------------------------
// VARS
//----------------------------------------------------------------
//Temp sprite
Frame_buffer_sprite sprite_tmp;
//Initialize colors
sprite_tmp.f_update = true;
sprite_tmp.background_color = background;
sprite_tmp.foreground_color = foreground;
//----------------------------------------------------------------
// BODY
//----------------------------------------------------------------
int num_changed_sprites = 0;
//Fast counters
uint8_t t = 0;
uint8_t tw = origin_w;
int ret;
//While: I'm allowed to print, I didn't exceed the string size, and I didn't exceed the screen position
while ((str[t] != '\0') && (tw < Config::FRAME_BUFFER_WIDTH))
{
//Compute sprite ascii char
sprite_tmp.sprite_index = str[t];
DPRINT("t: %5d | tw: %5d | ", t, tw);
//If this is not a printable ascii character
if (this -> is_valid_char(sprite_tmp.sprite_index) == false)
{
//!@todo Use placeholder sprite (special '?')
DPRINT_NOTAB("H: %5d | W: %5d | non printable\n", origin_h, tw );
}
else
{
//Update the frame buffer with the new sprite if needed
ret = this -> update_sprite( origin_h, tw, sprite_tmp );
//If: an error occurred
if ((Config::PEDANTIC_CHECKS == true) && (ret < 0))
{
DRETURN_ARG("ERR: Failed to update sprite\n");
return ret;
}
else
{
//Compute number of updated sprites
num_changed_sprites += ret;
show_frame_sprite(this -> g_frame_buffer[origin_h][tw]);
}
}
//Next character
t++;
tw++;
} //End While: I'm allowed to print, I didn't exceed the string size, and I didn't exceed the screen position
//----------------------------------------------------------------
// RETURN
//----------------------------------------------------------------
DRETURN();
return num_changed_sprites;
} //End public method: print | int | int | const char * | Color | Color |
/***************************************************************************/
//! @brief public method
//! print | int | int | const char* | Color |
/***************************************************************************/
//! @param origin_h | int | height position of the sprite
//! @param origin_w | int | width position of the sprite
//! @param str | const char * | string to be drawn. Must be null terminated
//! @param foreground | Color | foreground color of ths sprite as index from the palette
//! @return int16_t | >=0 Number of sprites changed | < 0 error |
//! @details
//! \n print a char inside the sprite frame buffer and mark it for update if needs to be
//! \n use default background
//! \n wrapper for more general print string function
/***************************************************************************/
inline int Screen::print( int origin_h, int origin_w, const char *str, Color foreground )
{
DENTER(); //Trace enter
//----------------------------------------------------------------
// BODY
//----------------------------------------------------------------
//more general print string function
int num_changed_sprites = print( origin_h, origin_w, str, this -> g_default_background_color, foreground );
//----------------------------------------------------------------
// RETURN
//----------------------------------------------------------------
DRETURN(); //Trace enter
return num_changed_sprites;
} //End public method: print | int | int | const char* | Color |
/***************************************************************************/
//! @brief public method
//! print | int | int | const char* |
/***************************************************************************/
//! @param origin_h | int | height position of the sprite
//! @param origin_w | int | width position of the sprite
//! @param str | const char * | string to be drawn. Must be null terminated
//! @return int16_t | >=0 Number of sprites changed | < 0 error |
//! @details
//! \n print a char inside the sprite frame buffer and mark it for update if needs to be
//! \n uses default background and foreground colors
//! \n wrapper for more general print string function
/***************************************************************************/
inline int Screen::print( int origin_h, int origin_w, const char *str )
{
DENTER(); //Trace enter
//----------------------------------------------------------------
// BODY
//----------------------------------------------------------------
//more general print string function
int num_changed_sprites = print( origin_h, origin_w, str, this -> g_default_background_color, this -> g_default_foreground_color );
//----------------------------------------------------------------
// RETURN
//----------------------------------------------------------------
DRETURN(); //Trace enter
return num_changed_sprites;
} //End public method: print | int | int | char |
/***************************************************************************/
//! @brief public method
//! print | int | int | int | Color | Color |
/***************************************************************************/
//! @param origin_h | int | height position of the first character
//! @param origin_w | int | width position of the first character
//! @param num | int | number to be shown on screen
//! @param background | Color | background color of ths sprite as index from the palette
//! @param foreground | Color | foreground color of ths sprite as index from the palette
//! @return int16_t | >=0 Number of sprites changed | < 0 error |
//! @details
//! \n Print a number on screen. Format configuration is handled by format
//! \n With left asjust, the number is print starting from left an origin is the position of the most significant number
//! \n With right adjust, the origin is the position of the least significant number
//! \n LEFT 78901 12.71m
//! \n RIGHT 78901 12.71m
//! \n ORIGIN ^ ^
//! \n The format also specifies full number or enginnering format
/***************************************************************************/
int Screen::print( int origin_h, int origin_w, int num, Color background, Color foreground )
{
DENTER_ARG("NUM: %d\n", num); //Trace enter
//----------------------------------------------------------------
// CHECK
//----------------------------------------------------------------
// Quit if the number is fully outside the screen for sure
// Avoid wasting CPU time for the conversion
//If: colors are bad
if ((background >= (Color)Config::PALETTE_SIZE) || (foreground >= (Color)Config::PALETTE_SIZE))
{
DRETURN_ARG("ERR: bad default colors | Back: %3d | Fore: %3d |\n", background, foreground );
return -1; //FAIL
}
//If: height fully outside screen
if ((origin_h < 0) || (origin_h >= Config::FRAME_BUFFER_HEIGHT))
{
DRETURN_ARG("ERR: Height out of range: %d\n", origin_h);
return -1;
}
//Fetch the format of the number locally
Format_number format_tmp = this -> g_format_number;
//Compute first sprite and last sprite that can be occupied by the number
uint8_t start_w = ((format_tmp.align == Format_align::ADJ_LEFT)?(origin_w):(origin_w -format_tmp.size +1));
uint8_t stop_w = ((format_tmp.align == Format_align::ADJ_LEFT)?(origin_w +format_tmp.size -1):(origin_w));
DPRINT("start_w: %3d | stop_w: %3d\n", start_w, stop_w );
//If: width fully outside screen in right adjust
if ( (stop_w < 0) || (start_w >= Config::FRAME_BUFFER_WIDTH) )
{
DRETURN_ARG("ERR: Width out of range\n");
return -1;
}
//Clip the start and stop position to be inside the screen
start_w = ((start_w < 0)?(0):(start_w));
stop_w = ((stop_w >= Config::FRAME_BUFFER_WIDTH)?(Config::FRAME_BUFFER_WIDTH -1):(stop_w));
//----------------------------------------------------------------
// NUM -> STRING
//----------------------------------------------------------------
// Execute the conversion
int num_changed_sprites = 0;
//Prepare a string to hold the number
char str[User::String::Config::STRING_SIZE_S32];
//Temp return
uint8_t num_digit;
//Fast counter
uint8_t t;
//if: number format is extended
if (format_tmp.format == Format_format::NUM)
{
//Try to convert the number into a string
num_digit = User::String::num_to_str( (int32_t)num, User::String::Config::STRING_SIZE_S32, str );
//If: fail
if (num_digit == 0)
{
DRETURN_ARG("ERR: Failed to convert NUM number: %d", num);
return -1;
}
}
//If: number format is enginnering
else //if (format_tmp.format = Format_format::ENG)
{
//Try to convert the number into a string
num_digit = User::String::num_to_eng( (int32_t)num, this -> g_format_number.eng_exp, User::String::Config::STRING_SIZE_S32, str );
//If: fail
if (num_digit == 0)
{
DRETURN_ARG("ERR: Failed to convert ENG number: %d", num);
return -1;
}
}
//----------------------------------------------------------------
// NUMBER PARTIALLY OUT OF SCREEN
//----------------------------------------------------------------
// Having just a piece of a number on screen is dangerous
// partially blanked digits need to invalidate the full number and show '#'
// Condition for partially out
// START STOP
// LEFT OW<0 OW+NUM-1>=W
// RIGHT OW-NUM+1<0 OW>=W
int ret;
//Temporary sprite
Frame_buffer_sprite sprite_tmp;
//Build temporary sprite
sprite_tmp.background_color = background;
sprite_tmp.foreground_color = foreground;
sprite_tmp.f_update = true;
//If: number is too big
if (num_digit > format_tmp.size)
{
//Print the invalid number character to show the user the number has no meaning
sprite_tmp.sprite_index = '#';
//For: each number character that can be displayed
for (t = start_w;t <= stop_w;t++)
{
//Update the frame buffer with the new sprite if needed
ret = this -> update_sprite( origin_h, t, sprite_tmp );
//If: an error occurred
if ((Config::PEDANTIC_CHECKS == true) && (ret < 0))
{
DRETURN_ARG("ERR: Failed to update sprite\n");
return ret;
}
else
{
//Compute number of updated sprites
num_changed_sprites += ret;
}
}
DRETURN_ARG("ERR: Width partially out of range | LEFT | start %d | stop %d |\n", start_w, stop_w );
return num_changed_sprites;
}
//If: number partially outside screen in left adjust
else if ( (format_tmp.align == Format_align::ADJ_LEFT) && ((origin_w < 0) || ((origin_w +num_digit -1) >= Config::FRAME_BUFFER_WIDTH)) )
{
//Print the invalid number character to show the user the number has no meaning
sprite_tmp.sprite_index = '#';
//For: each number character that can be displayed
for (t = start_w;t <= stop_w;t++)
{
//Update the frame buffer with the new sprite if needed
ret = this -> update_sprite( origin_h, t, sprite_tmp );
//If: an error occurred
if ((Config::PEDANTIC_CHECKS == true) && (ret < 0))
{
DRETURN_ARG("ERR: Failed to update sprite\n");
return ret;
}
else
{
//Compute number of updated sprites
num_changed_sprites += ret;
}
}
DRETURN_ARG("ERR: Width partially out of range | LEFT | start %d | stop %d |\n", start_w, stop_w );
return num_changed_sprites;
}
//If: number partially outside screen in right adjust
else if ( (format_tmp.align == Format_align::ADJ_RIGHT) && ((origin_w -num_digit +1 < 0) || (origin_w >= Config::FRAME_BUFFER_WIDTH)) )
{
//Print the invalid number character to show the user the number has no meaning
sprite_tmp.sprite_index = '#';
//For: each number character that can be displayed
for (t = start_w;t <= stop_w;t++)
{
//Update the frame buffer with the new sprite if needed
ret = this -> update_sprite( origin_h, t, sprite_tmp );
//If: an error occurred
if ((Config::PEDANTIC_CHECKS == true) && (ret < 0))
{
DRETURN_ARG("ERR: Failed to update sprite\n");
return ret;
}
else
{
//Compute number of updated sprites
num_changed_sprites += ret;
}
}
DRETURN_ARG("ERR: Width partially out of range | RIGHT | start %d | stop %d |\n", start_w, stop_w );
return num_changed_sprites;
}
//----------------------------------------------------------------
// NUMBER FULLY INSIDE SCREEN
//----------------------------------------------------------------
// Number can be shown fully on screen, with at most spaces left out
//If: left alignment
if (format_tmp.align == Format_align::ADJ_LEFT)
{
//Print the number
//For: every number digit
for (t = 0;t < num_digit; t++)
{
//Digit -> Sprite
sprite_tmp.sprite_index = str[t];
//Update the frame buffer with the new sprite if needed
ret = this -> update_sprite( origin_h, t, sprite_tmp );
//If: an error occurred
if ((Config::PEDANTIC_CHECKS == true) && (ret < 0))
{
DRETURN_ARG("ERR: Failed to update sprite\n");
return ret;
}
else
{
//Compute number of updated sprites
num_changed_sprites += ret;
}
} //End For: every digit
//Print spaces to clear the previously printed character
//space sprite
sprite_tmp.sprite_index = ' ';
//For: every number digit
for (;t <= stop_w; t++)
{
//Update the frame buffer with the new sprite if needed
ret = this -> update_sprite( origin_h, t, sprite_tmp );
//If: an error occurred
if ((Config::PEDANTIC_CHECKS == true) && (ret < 0))
{
DRETURN_ARG("ERR: Failed to update sprite\n");
return ret;
}
else
{
//Compute number of updated sprites
num_changed_sprites += ret;
}
}
} //End If: left alignment
//If: right alignment
else //if (format_tmp.align == Format_align::ADJ_RIGHT)
{
//Print the correct number of spaces
//space sprite
sprite_tmp.sprite_index = ' ';
//For: every number digit
for (t = start_w;t < stop_w -num_digit +1; t++)
{
//Update the frame buffer with the new sprite if needed
ret = this -> update_sprite( origin_h, t, sprite_tmp );
//If: an error occurred
if ((Config::PEDANTIC_CHECKS == true) && (ret < 0))
{
DRETURN_ARG("ERR: Failed to update sprite\n");
return ret;
}
else
{
//Compute number of updated sprites
num_changed_sprites += ret;
}
}
//Reset start position
start_w = t;
//For: every number digit
for (t = 0;t < num_digit; t++)
{
//Digit -> Sprite
sprite_tmp.sprite_index = str[t];
//Update the frame buffer with the new sprite if needed
ret = this -> update_sprite( origin_h, start_w +t, sprite_tmp );
//If: an error occurred
if ((Config::PEDANTIC_CHECKS == true) && (ret < 0))
{
DRETURN_ARG("ERR: Failed to update sprite\n");
return ret;
}
else
{
//Compute number of updated sprites
num_changed_sprites += ret;
}
} //End For: every digit
} //End If: right alignment
//----------------------------------------------------------------
// RETURN
//----------------------------------------------------------------
DRETURN();
return num_changed_sprites; //OK
} //End public method: print | int | int | int | Color | Color |
/***************************************************************************/
//! @brief public method
//! print | int | int | int | Color |
/***************************************************************************/
//! @param origin_h | int | height position of the first character
//! @param origin_w | int | width position of the first character
//! @param num | int | number to be shown on screen
//! @param foreground | Color | foreground color of ths sprite as index from the palette
//! @return int16_t | >=0 Number of sprites changed | < 0 error |
//! @details
//! \n Print a number on screen. Overload with default background.
/***************************************************************************/
inline int Screen::print( int origin_h, int origin_w, int num, Color foreground )
{
DENTER_ARG("Num: %d\n", num );
//----------------------------------------------------------------
// RETURN
//----------------------------------------------------------------
//
int num_changed_sprites = print( origin_h, origin_w, num, this -> g_default_background_color, foreground );
DRETURN_ARG("Success: %d\n", num_changed_sprites );
return num_changed_sprites;
} //End public method: print | int | int | int |
/***************************************************************************/
//! @brief public method
//! print | int | int | int |
/***************************************************************************/
//! @param origin_h | int | height position of the first character
//! @param origin_w | int | width position of the first character
//! @param num | int | number to be shown on screen
//! @return int16_t | >=0 Number of sprites changed | < 0 error |
//! @details
//! \n Print a number on screen. Overload with default background and foreground
/***************************************************************************/
inline int Screen::print( int origin_h, int origin_w, int num )
{
DENTER_ARG("Num: %d\n", num );
//----------------------------------------------------------------
// RETURN
//----------------------------------------------------------------
//
int ret = print( origin_h, origin_w, num, this -> g_default_background_color, this -> g_default_foreground_color );
DRETURN_ARG("Success: %d\n", f_ret );
return ret;
} //End public method: print | int | int | int |
/***************************************************************************/
//! @brief public method
//! paint | int | int | Color |
/***************************************************************************/
//! @return int16_t | >=0 Number of sprites changed | < 0 error |
//! @details
//! \n Draw a solid color on the screen
//! \n There are ery few graphics sprite as they ar meant for progress bars and little more
/***************************************************************************/
int Screen::paint( int origin_h, int origin_w, Color color )
{
DENTER_ARG("H: %d, W: %d, color index: %d\n", origin_h, origin_w, (int)color );
//----------------------------------------------------------------
// CHECK
//----------------------------------------------------------------
//If: colors are bad
if (color >= (Color)Config::PALETTE_SIZE)
{
DRETURN_ARG("ERR: bad default colors | Color: %3d |\n", color );
return -1; //FAIL
}
//If: height fully outside screen
if ((origin_h < 0) || (origin_h >= Config::FRAME_BUFFER_HEIGHT))
{
DRETURN_ARG("ERR: Height out of range: %d\n", origin_h);
return -1;
}
//If: width fully outside screen
if ((origin_w < 0) || (origin_w >= Config::FRAME_BUFFER_WIDTH))
{
DRETURN_ARG("ERR: Height out of range: %d\n", origin_w);
return -1;
}
//----------------------------------------------------------------
// Construct Sprite
//----------------------------------------------------------------
//Temporary sprite
Frame_buffer_sprite sprite_tmp;
//Build temporary sprite
sprite_tmp.sprite_index = Config::SPRITE_BACKGROUND;
sprite_tmp.background_color = color;
sprite_tmp.foreground_color = color;
sprite_tmp.f_update = true;
//----------------------------------------------------------------
// Update Frame Buffer
//----------------------------------------------------------------
//Update the frame buffer with the new sprite if needed
int ret = this -> update_sprite( origin_h, origin_w, sprite_tmp );
//----------------------------------------------------------------
// RETURN
//----------------------------------------------------------------
DRETURN();
return ret; //No sprites have been drawn
} //End public method: paint | int | int | Color |
/***************************************************************************/
//! @brief public method
//! print_err | int | int |
/***************************************************************************/
//! @param origin_h | int | height position of the sprite
//! @param origin_w | int | width position of the sprite
//! @return int16_t | >=0 Number of sprites changed | < 0 error |
//! @details
//! \n print a char inside the sprite frame buffer and mark it for update if needs to be
//! \n use default background
//! \n wrapper for more general print string function
/***************************************************************************/
int Screen::print_err( int origin_h, int origin_w )
{
DENTER(); //Trace enter
//----------------------------------------------------------------
// BODY
//----------------------------------------------------------------
//more general print string function
int num_changed_sprites;
//If: OK
if (this -> g_error_code == 0)
{
num_changed_sprites = print( origin_h, origin_w, "OK", Color::GREEN );
}
//If: ERR
else
{
num_changed_sprites = print( origin_h, origin_w, "ERR", Color::RED );
this -> set_format(2, Format_align::ADJ_LEFT, Format_format::NUM );
num_changed_sprites += print( origin_h, origin_w +3, (int)this -> g_error_code, Color::RED );
}
//----------------------------------------------------------------
// RETURN
//----------------------------------------------------------------
DRETURN(); //Trace enter
return num_changed_sprites;
} //End public method: print_err | int | int |
/*********************************************************************************************************************************************************
**********************************************************************************************************************************************************
** PRIVATE INIT
**********************************************************************************************************************************************************
*********************************************************************************************************************************************************/
/***************************************************************************/
//! @brief private init
//! init_class_vars | void |
/***************************************************************************/
//! @return bool | false = OK | true = ERR
//! @details
//! \n Initialize the class vars
/***************************************************************************/
bool Screen::init_class_vars( void )
{
//----------------------------------------------------------------
// VARS
//----------------------------------------------------------------
//----------------------------------------------------------------
// BODY
//----------------------------------------------------------------
//Initialize error code
this -> g_error_code = Screen::Error::OK;
//Initialize default number format
this -> set_format( Screen::Config::FRAME_BUFFER_WIDTH, Format_align::ADJ_LEFT, Format_format::NUM, 0 );
//----------------------------------------------------------------
// RETURN
//----------------------------------------------------------------
return false; //OK
} //End private init: init_class_vars | void |
/***************************************************************************/
//! @brief private init
//! init_frame_buffer | void
/***************************************************************************/
//! @return bool | false = OK | true = ERR
//! @details
//! \n Initialize the sprite frame buffer
/***************************************************************************/
bool Screen::init_frame_buffer()
{
DENTER();
//----------------------------------------------------------------
// VARS
//----------------------------------------------------------------
//Fast counter
uint16_t tw, th;
//Temp sprite
Frame_buffer_sprite sprite_tmp;
//----------------------------------------------------------------
// BODY
//----------------------------------------------------------------
//Force update of this sprite
sprite_tmp.f_update = true;
//Initialize sprite code to FULL BACKGROUND sprite
sprite_tmp.sprite_index = Config::SPRITE_BLACK;
//Initialize colors to defaults Color black and white
sprite_tmp.background_color = Screen::Color::BLACK;
sprite_tmp.foreground_color = Screen::Color::WHITE;
show_frame_sprite( sprite_tmp );
//For: each frame buffer row (height scan)
for (th = 0;th < Screen::Config::FRAME_BUFFER_HEIGHT;th++)
{
//For: each frame buffer col (width scan)
for (tw = 0;tw < Screen::Config::FRAME_BUFFER_WIDTH;tw++)
{
//Save default sprite in the frame buffer
this -> g_frame_buffer[th][tw] = sprite_tmp;
} //End For: each frame buffer col (width scan)
} //End For: each frame buffer row (height scan)
//All sprites require update at the initialization
this -> g_pending_cnt = Config::FRAME_BUFFER_SIZE;
//----------------------------------------------------------------
// RETURN
//----------------------------------------------------------------
DRETURN();
return false; //OK
} //End private init: init_frame_buffer | void
/***************************************************************************/
//! @brief private init
//! init_default_colors | void |
/***************************************************************************/
//! @return void
//! @details
//! \n Initialize the default background and foreground colors
/***************************************************************************/
inline bool Screen::init_default_colors( void )
{
DENTER();
//----------------------------------------------------------------
// BODY
//----------------------------------------------------------------
//Initialize default colors
this -> g_default_background_color = Color::BLACK;
this -> g_default_foreground_color = Color::WHITE;
//----------------------------------------------------------------
// RETURN
//----------------------------------------------------------------
DRETURN();
return false; //OK
} //End private init: init_default_colors | void |
/***************************************************************************/
//! @brief private init
//! init_palette | void
/***************************************************************************/
//! @return bool | false = OK | true = ERR
//! @details
//! \n initialize palette to default values
/***************************************************************************/
bool Screen::init_palette( void )
{
DENTER();
//----------------------------------------------------------------
// BODY
//----------------------------------------------------------------
//Use the CGA default palette (https://en.wikipedia.org/wiki/Color_Graphics_Adapter)
this -> g_palette[Screen::Color::BLACK] = Longan_nano::Display::color( 0x00, 0x00, 0x00 );
this -> g_palette[Screen::Color::BLUE] = Longan_nano::Display::color( 0x00, 0x00, 0xAA );
this -> g_palette[Screen::Color::GREEN] = Longan_nano::Display::color( 0x00, 0xAA, 0x00 );
this -> g_palette[Screen::Color::CYAN] = Longan_nano::Display::color( 0x00, 0xAA, 0xAA );
this -> g_palette[Screen::Color::RED] = Longan_nano::Display::color( 0xAA, 0x00, 0x00 );
this -> g_palette[Screen::Color::MAGENTA] = Longan_nano::Display::color( 0xAA, 0x00, 0xAA );
this -> g_palette[Screen::Color::BROWN] = Longan_nano::Display::color( 0xAA, 0x55, 0x00 );
this -> g_palette[Screen::Color::LGRAY] = Longan_nano::Display::color( 0xAA, 0xAA, 0xAA );
this -> g_palette[Screen::Color::DGRAY] = Longan_nano::Display::color( 0x55, 0x55, 0x55 );
this -> g_palette[Screen::Color::LBLUE] = Longan_nano::Display::color( 0x55, 0x55, 0xFF );
this -> g_palette[Screen::Color::LGREEN] = Longan_nano::Display::color( 0x55, 0xFF, 0x55 );
this -> g_palette[Screen::Color::LCYAN] = Longan_nano::Display::color( 0x55, 0xFF, 0xFF );
this -> g_palette[Screen::Color::LRED] = Longan_nano::Display::color( 0xFF, 0x55, 0x55 );
this -> g_palette[Screen::Color::LMAGENTA] = Longan_nano::Display::color( 0xFF, 0x55, 0xFF );
this -> g_palette[Screen::Color::YELLOW] = Longan_nano::Display::color( 0xFF, 0xFF, 0x55 );
this -> g_palette[Screen::Color::WHITE] = Longan_nano::Display::color( 0xFF, 0xFF, 0xFF );
#ifdef DEBUG_ENABLE
{
DPRINT("TEST: %6x\n", Longan_nano::Display::color( 0xFF, 0xFF, 0xFF ));
this -> show_palette();
}
#endif
//----------------------------------------------------------------
// RETURN
//----------------------------------------------------------------
DRETURN();
return false; //OK
} //End private init: init_palette | void
/***************************************************************************/
//! @brief private init
//! init_fsm | void |
/***************************************************************************/
//! @return bool | false = OK | true = ERR
//! @details
//! \n Initialize the update FSM machine
/***************************************************************************/
bool Screen::init_fsm( void )
{
//----------------------------------------------------------------
// BODY
//----------------------------------------------------------------
//Start from top left
this -> g_status.scan_h = 0;
this -> g_status.scan_w = 0;
//IDLE state
this -> g_status.cnt = 0;
//Update should scan for the next sprite to be updated
this -> g_status.phase = Fsm_state::SCAN_SPRITE;
//----------------------------------------------------------------
// RETURN
//----------------------------------------------------------------
return false; //OK
} //End private init: init_fsm | void |
/*********************************************************************************************************************************************************
**********************************************************************************************************************************************************
** PRIVATE SETTER
**********************************************************************************************************************************************************
*********************************************************************************************************************************************************/
/***************************************************************************/
//! @brief private setter
//! report_error | Error |
/***************************************************************************/
//! @param error_code | Error | error code of the Screen Class
//! @return no return
//! @details
//! Method
/***************************************************************************/
void Screen::report_error( Error error_code )
{
DENTER_ARG("ERR%d\n", (int)error_code ); //Trace Enter
///--------------------------------------------------------------------------
/// CHECK
///--------------------------------------------------------------------------
if ((Config::PEDANTIC_CHECKS == true) && (error_code >= Screen::Error::NUM_ERROR_CODES))
{
this -> g_error_code = Screen::Error::BAD_ERROR_CODE;
}
else
{
this -> g_error_code = error_code;
}
///--------------------------------------------------------------------------
/// RETURN
///--------------------------------------------------------------------------
DRETURN(); //Trace Return
return;
} //end private setter: report_error | Error |
/*********************************************************************************************************************************************************
**********************************************************************************************************************************************************
** PRIVATE TESTERS
**********************************************************************************************************************************************************
*********************************************************************************************************************************************************/
/***************************************************************************/
//! @brief private tester
//! is_valid_char | char |
/***************************************************************************/
//! @return bool | false = INVALID | true =VALID
//! @details
//! return true if the char is stored inside the ascii sprite table
/***************************************************************************/
inline bool Screen::is_valid_char( char c )
{
//----------------------------------------------------------------
// RETURN
//----------------------------------------------------------------
return ((c >= Config::ASCII_START) && (c <= Config::ASCII_STOP));
} //End private tester: is_valid_char | char |
/***************************************************************************/
//! @brief private tester
//! is_using_background | uint8_t |
/***************************************************************************/
//! @return bool | false = sprite do not use background color | true = sprite use background color
//! @details
//! return true if the sprite make use of the background palette color
/***************************************************************************/
inline bool Screen::is_using_background( uint8_t sprite )
{
//----------------------------------------------------------------
// BODY
//----------------------------------------------------------------
//If: special sprite
if (sprite < Config::NUM_SPECIAL_SPRITES)
{
//If: special sprite that use the background
if (sprite == Config::SPRITE_BACKGROUND)
{
//Use background
return true;
}
else
{
//Do not use the background
return false;
}
}
//If: sprite is an ascii character
else if (this -> is_valid_char(sprite) == true)
{
//An ascii character use the background
return true;
}
//----------------------------------------------------------------
// RETURN
//----------------------------------------------------------------
//Non drawable sprite
return false;
} //End private tester: is_using_background | uint8_t |
/***************************************************************************/
//! @brief private tester
//! is_using_foreground | uint8_t |
/***************************************************************************/
//! @return bool | false = sprite do not use foreground color | true = sprite use foreground color
//! @details
//! return true if the sprite make use of the foreground palette color
/***************************************************************************/
inline bool Screen::is_using_foreground( uint8_t sprite )
{
//----------------------------------------------------------------
// BODY
//----------------------------------------------------------------
//If: special sprite
if (sprite < Config::NUM_SPECIAL_SPRITES)
{
//If: special sprite that use the color
if (sprite == Config::SPRITE_FOREGROUND)
{
//Using color
return true;
}
else
{
//Not Using color
return false;
}
}
//If: sprite is an ascii character
else if (this -> is_valid_char(sprite) == true)
{
//An ascii character use the color
return true;
}
//----------------------------------------------------------------
// RETURN
//----------------------------------------------------------------
//Non drawable sprite
return false;
} //End private tester: is_using_foreground | uint8_t |
/***************************************************************************/
//! @brief private tester
//! is_same_sprite | Frame_buffer_sprite | Frame_buffer_sprite |
/***************************************************************************/
//! @return bool | false = different | true = same
//! @details
//! check if sprite_a is functionally the same as sprite_b
//! this is used to decide if mark a sprite for update or leave it as is
//! this function is more nuanced that it appears
//! this function is inside the drivers and assumes all relevant checks have been made by interface functions
//! E.G. >a foreground sprite with with foreground is the same as a background sprite with white background
//! E.G. >writing 'a' red on black is differet from writing 'a' green on black
//! A change of a color in the palette will automatically mark for update all characters that use that palette color, but its taken care by the relevant function
//! Because of that, no function but the update FSM is allowed to deactivate the update flag, so don't be overly aggressive with the result of this function
/***************************************************************************/
bool Screen::is_same_sprite( Frame_buffer_sprite sprite_a, Frame_buffer_sprite sprite_b )
{
DENTER();
//----------------------------------------------------------------
// VARS
//----------------------------------------------------------------
bool f_ascii_a = is_valid_char( sprite_a.sprite_index );
bool f_special_a = (sprite_a.sprite_index < Config::NUM_SPECIAL_SPRITES);
bool f_ascii_b = is_valid_char( sprite_b.sprite_index );
bool f_special_b = (sprite_b.sprite_index < Config::NUM_SPECIAL_SPRITES);
//----------------------------------------------------------------
// BODY
//----------------------------------------------------------------
//If: both characters are special
if ((f_special_a == true) && (f_special_b == true))
{
//If: sprite indexes are the same
if (sprite_a.sprite_index == sprite_b.sprite_index)
{
//If: fixed color sprites (do not use palette)
if ((sprite_a.sprite_index == Config::SPRITE_BLACK) || (sprite_a.sprite_index == Config::SPRITE_WHITE))
{
DRETURN_ARG("Same fixed solid sprites: %d %d\n", sprite_a.sprite_index, sprite_b.sprite_index );
return true; //Same
}
//If: character that use background only
else if (sprite_a.sprite_index == Config::SPRITE_BACKGROUND)
{
//If: background are the same
if (sprite_a.background_color == sprite_b.background_color)
{
DRETURN_ARG("Same solid background sprites: %d\n", sprite_a.background_color );
return true; //Same
}
//If: background are the different
else
{
DRETURN_ARG("Different solid background sprites: %d %d\n", sprite_a.background_color, sprite_b.background_color );
return false; //Different
}
}
//If: character that use foreground only
else if (sprite_a.sprite_index == Config::SPRITE_FOREGROUND)
{
//If: background are the same
if (sprite_a.foreground_color == sprite_b.foreground_color)
{
DRETURN_ARG("Same solid foreground sprites: %d\n", sprite_a.foreground_color );
return true; //Same
}
//If: background are the different
else
{
DRETURN_ARG("Different solid foreground sprites: %d %d\n", sprite_a.foreground_color, sprite_b.foreground_color );
return false; //Different
}
}
//Default:
else
{
//Any other combination is to be treated as different sprites
DRETURN_ARG("Different sprites: %d %d\n", sprite_a.sprite_index, sprite_b.sprite_index );
return false; //Different
}
} //End If: sprite indexes are the same
//End If: sprite indexes are the different
else
{
//@todo: there are combinations of different indexes that yield the same color
DRETURN_ARG("Different sprites: %d %d\n", sprite_a.sprite_index, sprite_b.sprite_index );
return false; //Different
}
} //End If: both characters are special
//If: both are validascii characters
else if ((f_ascii_a == true) && (f_ascii_b == true))
{
//If: sprites have the same index, the same background and the same foreground
if ((sprite_a.sprite_index == sprite_b.sprite_index) && (sprite_a.background_color == sprite_b.background_color) && (sprite_a.foreground_color == sprite_b.foreground_color))
{
DRETURN_ARG("Same ascii characters: %c %c\n", sprite_a.sprite_index, sprite_b.sprite_index );
return true; //Same ascii
}
//If: any property is different
else
{
DRETURN_ARG("Different ascii characters: %c %c\n", sprite_a.sprite_index, sprite_b.sprite_index );
return false; //Different ascii
}
}
//@todo: graphics sprites (Symbols)
//If: every other combination is an invalid sprite
else
{
//Do nothing
}
//----------------------------------------------------------------
// RETURN
//----------------------------------------------------------------
DRETURN();
return false; //Sprites are different
} //End private tester: is_same_sprite | Frame_buffer_sprite | Frame_buffer_sprite |
/*********************************************************************************************************************************************************
**********************************************************************************************************************************************************
** PRIVATE METHODS
**********************************************************************************************************************************************************
*********************************************************************************************************************************************************/
/***************************************************************************/
//! @brief private method
//! register_sprite | uint16_t | uint16_t |
/***************************************************************************/
//! @param index_h | uint16_t | index of the sprite in the frame buffer to build pixel from
//! @param index_w | uint16_t | index of the sprite in the frame buffer to build pixel from
//! @return int8_t | -1 error | >= 0 sprites registered for draw
//! @details
//! \n Register a sprite for draw in the display driver if possible
//! \n Sprite can be xomplex color map or solid color
/***************************************************************************/
int8_t Screen::register_sprite( uint16_t index_h, uint16_t index_w )
{
DENTER_ARG("index_h : %5d | index_w %5d\n", index_h, index_w);
//----------------------------------------------------------------
// VARS
//----------------------------------------------------------------
//Temp sprite data
uint16_t foreground_color, background_color;
//Temp color
uint16_t color;
//false = complex color map | true = solid color
bool f_solid_color;
//----------------------------------------------------------------
// INIT
//----------------------------------------------------------------
//If: bad parameters
if ((Config::PEDANTIC_CHECKS == true) && ((index_w >= Config::FRAME_BUFFER_WIDTH) || (index_h >= Config::FRAME_BUFFER_HEIGHT)) )
{
DRETURN_ARG("ERR: bad parameters\n");
return -1; //FAIL
}
//----------------------------------------------------------------
// DECODE SPRITE
//----------------------------------------------------------------
//Fetch a frame sprite
Frame_buffer_sprite sprite_tmp = g_frame_buffer[index_h][index_w];
show_frame_sprite( sprite_tmp );
//Decode background and foreground colors
background_color = g_palette[ sprite_tmp.background_color ];
foreground_color = g_palette[ sprite_tmp.foreground_color ];
DPRINT("sprite: %d | background color: %6x | foreground_color: %6x |\n", sprite_tmp.sprite_index, background_color, foreground_color );
//Pointer to sprite data
const uint8_t *sprite_ptr = nullptr;
//If: special sprite
if (sprite_tmp.sprite_index < Config::NUM_SPECIAL_SPRITES)
{
//If: Solid black
if (sprite_tmp.sprite_index == Config::SPRITE_BLACK)
{
f_solid_color = true;
color = Display::color(0x00,0x00,0x00);
}
//If: Solid white
else if (sprite_tmp.sprite_index == Config::SPRITE_WHITE)
{
f_solid_color = true;
color = Display::color(0xFF,0xFF,0xFF);
}
//If: Solid background
else if (sprite_tmp.sprite_index == Config::SPRITE_BACKGROUND)
{
f_solid_color = true;
color = background_color;
}
//If: Solid foreground
else if (sprite_tmp.sprite_index == Config::SPRITE_FOREGROUND)
{
f_solid_color = true;
color = foreground_color;
}
//If: Transparent sprite
else if (sprite_tmp.sprite_index == Config::SPRITE_TRANSPARENT)
{
DRETURN_ARG("Transparent Sprite\n");
return 0;
}
//If: unhandled special sprite
else
{
//Signal the error
this -> report_error( Error::REGISTER_SPRITE_FAIL );
DRETURN_ARG("ERR%d: unhandled special sprit\n", this -> get_error() );
return -1;
}
} //End If: special sprite
//If: Handled Ascii Character in the character table
else if (this -> is_valid_char( sprite_tmp.sprite_index ) == true)
{
//If: background and foreground are different
if (background_color != foreground_color)
{
//Full pixel color map
f_solid_color = false;
//Point to the first byte of the ascii sprite
sprite_ptr = &g_ascii_sprites[ (sprite_tmp.sprite_index -Config::ASCII_START) *Config::SPRITE_HEIGHT ];
}
//If: background and foreground are the same
else //if (background_color == foreground_color)
{
//Draw a solid color sprite. Don't bother with computing a redundant pixel color map
f_solid_color = true;
color = background_color;
}
}
//If: Unhandled sprite
else
{
//Signal the error
this -> report_error( Error::REGISTER_SPRITE_FAIL );
DRETURN_ARG("ERR%d: unhandled special sprit\n", this -> get_error() );
return -1;
}
//----------------------------------------------------------------
// BUILD PIXEL MAP
//----------------------------------------------------------------
//Temp return
int ret;
//If: sprite is a complex color map
if (f_solid_color == false)
{
//Fast counter
int tw, th;
//Store a fullbinary width slice (row)
uint32_t sprite_width_slice;
DPRINT("sprite table index: %c %5d | width slice | ", sprite_index, sprite_index-' ' );
//For: Scan height
for (th = 0;th < Config::SPRITE_HEIGHT;th++)
{
//Grab full width slice of data
sprite_width_slice = sprite_ptr[ th ];
DPRINT_NOTAB(" %x |",sprite_width_slice);
//For: Scan width
for (tw = 0;tw < Config::SPRITE_WIDTH;tw++)
{
//Compute color from the binary sprite map | false = background | true = foreground
color = ((sprite_width_slice & 0x01) == 0x00)?(background_color):(foreground_color);
//Shift away the decoded bit
sprite_width_slice = sprite_width_slice >> 1;
//Save pixel
this -> g_pixel_data[((th *Config::SPRITE_WIDTH) +tw)] = color;
} //End For: Scan width
} //End For: Scan height
DPRINT_NOTAB("\n");
//Register the sprite for draw in the Display driver
ret = this -> Display::register_sprite( index_h *Config::SPRITE_HEIGHT, index_w *Config::SPRITE_WIDTH, Config::SPRITE_HEIGHT, Config::SPRITE_WIDTH, g_pixel_data );
//If: failed to register. the register sprite in future can be smaller than the sprite size if trying to register a sprite partially out of screen
if (ret <= 0)
{
//Signal the error
this -> report_error( Error::REGISTER_SPRITE_FAIL );
//Failed to register sprite
ret = -1;
}
//If: success
else
{
//One sprite has been drawn
ret = 1;
}
} //End If: sprite is a complex color map
//If: sprite is a solid color
else //if (f_solid_color == true)
{
//Register the sprite for draw in the Display driver
ret = this -> Display::register_sprite( index_h *Config::SPRITE_HEIGHT, index_w *Config::SPRITE_WIDTH, Config::SPRITE_HEIGHT, Config::SPRITE_WIDTH, color );
//If: failed to register. the register sprite in future can be smaller than the sprite size if trying to register a sprite partially out of screen
if (ret <= 0)
{
//Signal the error
this -> report_error( Error::REGISTER_SPRITE_FAIL );
//Failed to register sprite
ret = -1;
}
//If: success
else
{
//One sprite has been drawn
ret = 1;
}
} //End If: sprite is a solid color
//DEBUG
show_pixels();
//----------------------------------------------------------------
// RETURN
//----------------------------------------------------------------
DRETURN();
return ret;
} //End private method: register_sprite | uint16_t | uint16_t |
/***************************************************************************/
//! @brief private tester
//! update_sprite | uint16_t | uint16_t | Frame_buffer_sprite |
/***************************************************************************/
//! @return ont | <0 = error coccurred | 0 = frame buffer wasn't updated | 1 = frame buffer was updated
//! @details
//! Update a sprite in the frame buffer and mark it for update if required
//! Increase the workload counter if applicable
//! If workload counter is zero, set the scan to the current sprite to quicken the seek
/***************************************************************************/
int8_t Screen::update_sprite( uint16_t index_h, uint16_t index_w, Frame_buffer_sprite new_sprite )
{
DENTER_ARG("H: %d | W: %d |\n", index_h, index_w );
//----------------------------------------------------------------
// CHECK
//----------------------------------------------------------------
//If: invalid coordinates
if ((Config::PEDANTIC_CHECKS == true) && ((index_h >= Config::FRAME_BUFFER_HEIGHT) || (index_w >= Config::FRAME_BUFFER_WIDTH)) )
{
DRETURN_ARG("ERR: bad index H: %d | W: %d |\n", index_h, index_w);
return -1;
}
//----------------------------------------------------------------
// BODY
//----------------------------------------------------------------
int8_t num_updated_sprites;
//Fetch sprite
Frame_buffer_sprite old_sprite = this -> g_frame_buffer[index_h][index_w];
//If: the sprites are the same
if (this -> is_same_sprite(old_sprite, new_sprite) == true)
{
//Do nothing
num_updated_sprites = 0;
}
//If: the sprites are not the same
else //if (old_sprite.f_update == true)
{
//If: old sprite was not marked for update
if (old_sprite.f_update == false)
{
//If: the library workload is full
if ((Config::PEDANTIC_CHECKS == true) && (this -> g_pending_cnt >= Config::FRAME_BUFFER_SIZE))
{
this -> report_error( Screen::Error::PENDING_OVERFLOW );
}
//If: the screen class was IDLE before this call
else if (this -> g_pending_cnt == 0)
{
//Set the scan to this sprite so that the seek is quick
this -> g_status.scan_h = index_h;
this -> g_status.scan_w = index_w;
//Increase workload of the Screen class
this -> g_pending_cnt = 1;
}
//If: Screen class is already busy
else
{
//Increase workload of the Screen class
this -> g_pending_cnt++;
}
}
//Mark for update
new_sprite.f_update = true;
//Update the sprite
this -> g_frame_buffer[index_h][index_w] = new_sprite;
//A sprite was updated
num_updated_sprites = 1;
}
//----------------------------------------------------------------
// RETURN
//----------------------------------------------------------------
DRETURN();
return num_updated_sprites;
} //End private method: update_sprite | uint16_t | uint16_t | Frame_buffer_sprite |
/**********************************************************************************
** NAMESPACE
**********************************************************************************/
} //End Namespace: Longan_nano
#else
#warning "Multiple inclusion of hader file"
#endif


2020-08-02

2020-07-31 Longan Nano GD32VF103 Chrono Class and Scheduler

>>>Longan Nano GD32VF103<<<

Longan Nano GD32VF103 Risc-V 108MHz 32b MCU toolchain, libraries and applications

>>>Longan Nano Chrono Class and Scheduler<<<


Chrono C++ Class uses the 64bit 27MHz systick timer to provide high resolution elapsed time and eaccumulate time HAL methods.

The Demo shows how to implement a fixed function scheduler with overrun and profiling using the Chrono C++ Class.



1Introduction

I use microcontrollers to perform fixed hard real time functions, in which a given list of tasks are executed always at the same rate in the same order, with little variation due to communication with the master.

With the AT4809 and previous microcontroller, I used an internal timer to issue the fast tick as an interrupt service routine, and from that tick, generate all the flags to execute slower tasks.

E.G. The PID running at 4KHz started by a fast tick at 4KHz, the LED running at 2Hz started by prescaling the 4KHz fast tick by 2000.

Illustration 1: Fast Tick issue execution of fixed tasks

With the Longan Nano I have a 64bit 27MHz fast tick timer at my disposal.


2Chrono Class

I decided to create an abstraction layer in class form since I'm practising with C++.

The class embeds two high resolution 64 bit timestamps.

The class uses scoped enums to encapsulate the configurations and definitions inside the namespace and class name e.g. the time unit is: Longan_nano::Chrono::Unit::microseconds


2.1Time Elapsed Mode

One of the core function of the class is to compute how much time has elapsed between two instants of time.

Use:

  • A timer has to be instantiated. Longan_nano::Chrono my_timer;

  • my_timer.start() followed by my_timer.stop() fill the timestamps. my_timer.get_elapsed( Unit ) returns the DeltaT between stop and start

  • my_timer.start() followed by my_timer.stop( Unit ) returns the DeltaT between stop and start


2.2Time Accumulation mode

Another thing the user may want to do is to integrate the time spent doing something to profile execution times or measure something happening in bursts.

The accumulation method has a special flag to use the internal 64 bit timestamps to do this integration at full sub microsecond resolution. This is much better than having the user integrate the rounded results of the elapsed time by hand.

NOTE: the 64bit operation needs a libc call. Use -fno-exceptions compile flag to avoid code size to blow up by 30KB for no reason.

Use:

  • A timer has to be instantiated. Longan_nano::Chrono my_timer;

  • my_timer.start() followed by my_timer.accumulate() fill the timestamps. my_timer.get_accumulator( Unit ) returns all the sum of all DeltaT occurred between start and accumulate

  • my_timer.start() followed by my_timer.accumulate ( Unit ) returns all the sum of all DeltaT occurred between start and accumulate




2.3Chrono Class Source Code



/**********************************************************************************
BSD 3-Clause License
Copyright (c) 2020, Orso Eric
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
3. Neither the name of the copyright holder nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
**********************************************************************************/
/**********************************************************************************
** ENVIROMENT VARIABILE
**********************************************************************************/
#ifndef LONGAN_NANO_CHRONO_H_
#define LONGAN_NANO_CHRONO_H_
/**********************************************************************************
** GLOBAL INCLUDES
**********************************************************************************/
#include <gd32vf103.h>
/**********************************************************************************
** DEFINES
**********************************************************************************/
/**********************************************************************************
** MACROS
**********************************************************************************/
/**********************************************************************************
** NAMESPACE
**********************************************************************************/
//! @namespace Longan_nano namespace encapsulating all related drivers and HAL
namespace Longan_nano
{
/**********************************************************************************
** TYPEDEFS
**********************************************************************************/
/**********************************************************************************
** PROTOTYPE: STRUCTURES
**********************************************************************************/
/**********************************************************************************
** PROTOTYPE: GLOBAL VARIABILES
**********************************************************************************/
/**********************************************************************************
** PROTOTYPE: CLASS
**********************************************************************************/
/************************************************************************************/
//! @class Chrono
/************************************************************************************/
//! @author Orso Eric
//! @version 2020-07-28
//! @brief Deals with busy delays, elapsed time and accumulated time
//! @bug None
//! @copyright BSD 3-Clause License Copyright (c) 2020, Orso Eric
//! @details
//! SysTick
//! Use 64b 27MHz SysTick timer.
//! History Version
//! 2020-07-20
//! Rework Utils library into Chrono library to add start/stop timer
//! "start" snaps the start timestamp at full resolution
//! "stop" snaps the stop timestamp at full resolution
//! "elapsed" returns the elapsed time between stop and start if valid. zero otherwise
//! 2020-07-28
//! I need a method to accumulate execution time and profile how long an activity has taken
//! "accumulate" add stop-start to an internal accumulator at full resolution
//! Add combined "stop" "elapsed" implementation with better performance and fewer calls needed
//! I can combine the stop and accumulator counters since i use one or the other
//! I use a flag to handle initialization and invalid when switching between modes and automatically reset the accumulator
/************************************************************************************/
class Chrono
{
//Visible to all
public:
//--------------------------------------------------------------------------
// ENUM
//--------------------------------------------------------------------------
//Configurations of the SysTick
typedef enum _Config
{
PEDANTIC_CHECKS = false, //Pedantic checks inside the functions
SYSTICK_INVALID = 0xFFFFFFFF, //Invalid timer value
SYSTICK_PRE = 4, //Prescaler for the systick timer
TIME_INVALID = -1, //Return time in case time is invalid
} Config;
//What time unit to use
typedef enum _Unit
{
milliseconds,
microseconds,
} Unit;
//--------------------------------------------------------------------------
// CONSTRUCTORS
//--------------------------------------------------------------------------
/***************************************************************************/
//! @brief Empty Constructor
//! Chrono | void
/***************************************************************************/
//! @return void
//! @details \n
//! Initialize timestamps to invalid
/***************************************************************************/
Chrono( void )
{
//Initialize timestamps to invalid
this -> g_systick_start = Config::SYSTICK_INVALID;
this -> g_systick_stop = Config::SYSTICK_INVALID;
//Regular timer mode
this -> g_f_accumulator_mode = false;
//----------------------------------------------------------------
// RETURN
//----------------------------------------------------------------
return;
} //End Constructor: Chrono | void
//--------------------------------------------------------------------------
// DESTRUCTORS
//--------------------------------------------------------------------------
/***************************************************************************/
//! @brief Empty Destructor
/***************************************************************************/
//! ~Chrono | void
//! @return void
/***************************************************************************/
~Chrono( void )
{
//----------------------------------------------------------------
// RETURN
//----------------------------------------------------------------
return;
}
//--------------------------------------------------------------------------
// OPERATORS
//--------------------------------------------------------------------------
//--------------------------------------------------------------------------
// SETTERS
//--------------------------------------------------------------------------
/***************************************************************************/
//! @brief public setter
//! start | void
/***************************************************************************/
//! @return void
//! @details \n
//! Start the timer
/***************************************************************************/
void start( void )
{
//----------------------------------------------------------------
// BODY
//----------------------------------------------------------------
//Snap timer start
this -> g_systick_start = get_timer_value();
//----------------------------------------------------------------
// RETURN
//----------------------------------------------------------------
return;
} //End setter: start | void
/***************************************************************************/
//! @brief public setter
//! stop | void
/***************************************************************************/
//! @return void
//! @details \n
//! Stop the timer. Snap the stop time
/***************************************************************************/
void stop( void )
{
//----------------------------------------------------------------
// BODY
//----------------------------------------------------------------
//Snap timer start
this -> g_systick_stop = get_timer_value();
//Regular timer mode
this -> g_f_accumulator_mode = false;
//----------------------------------------------------------------
// RETURN
//----------------------------------------------------------------
return;
} //End setter: stop | void
/***************************************************************************/
//! @brief public setter
//! stop | Unit |
/***************************************************************************/
//! @param unit
//! @return void
//! @details \n
//! Stop the timer. Snap the stop time
//! Return the elapsed time between stop and start in the given unit
/***************************************************************************/
int32_t stop( Unit unit )
{
//----------------------------------------------------------------
// CHECK
//----------------------------------------------------------------
//Get start time
uint64_t start_tmp = this -> g_systick_start;
//If: bad timestamp
if ((Config::PEDANTIC_CHECKS == true) && (start_tmp == Config::SYSTICK_INVALID))
{
return Config::TIME_INVALID; //FAIL
}
//----------------------------------------------------------------
// BODY
//----------------------------------------------------------------
//Snap the timestamp
uint64_t stop_tmp = get_timer_value();
//Record the stop timestamp inside the timer
this -> g_systick_stop = stop_tmp;
//Regular timer mode
this -> g_f_accumulator_mode = false;
//----------------------------------------------------------------
// RETURN
//----------------------------------------------------------------
//return elapsed time
return this -> compute_elapsed( start_tmp, stop_tmp, unit );
} //End setter: stop | Unit |
/***************************************************************************/
//! @brief public method
//! accumulate | void |
/***************************************************************************/
//! @return unsigned int | frequency of the SysTick timer
//! @details \n
//! Snap the stop time
//! Accumulate the difference between stop and start inside the accumulator
//! Swap the stop and start, invalidate the stop. Prepare for next cycle
//! Use stop counter as accumulator
//! If timer was in timer mode, reset the accumulator
/***************************************************************************/
bool accumulate( void )
{
//----------------------------------------------------------------
// CHECK
//----------------------------------------------------------------
//Get start time
uint64_t start_tmp = this -> g_systick_start;
//If: bad timestamp
if ((Config::PEDANTIC_CHECKS == true) && (start_tmp == Config::SYSTICK_INVALID))
{
return true; //FAIL
}
//Temp accumulator
uint64_t accumulator_tmp;
//if: Regular timer mode
if (this -> g_f_accumulator_mode == false)
{
//reset the accumulator
accumulator_tmp = 0;
//go into accumulator mode
this -> g_f_accumulator_mode = true;
}
//If: accumulator mode
else
{
//Fetch the current accumulator count
accumulator_tmp = this -> g_systick_stop;
}
//----------------------------------------------------------------
// BODY
//----------------------------------------------------------------
//Snap the timestamp
uint64_t stop_tmp = get_timer_value();
//Record the stop timestamp inside the start timestamp and invalidate the stop timestamp
this -> g_systick_start = stop_tmp;
//Accumulate the DeltaT inside the accumulator at full resolution
accumulator_tmp += stop_tmp -start_tmp;
//Store the accumulator value
this -> g_systick_stop = accumulator_tmp;
//----------------------------------------------------------------
// RETURN
//----------------------------------------------------------------
return false; //OK
} //End public method: accumulate | void |
/***************************************************************************/
//! @brief public method
//! accumulate | Unit unit |
/***************************************************************************/
//! @return unsigned int | frequency of the SysTick timer
//! @details \n
//! Snap the stop time
//! Accumulate the difference between stop and start inside the accumulator
//! Swap the stop and start, invalidate the stop. Prepare for next cycle
/***************************************************************************/
int32_t accumulate( Unit unit )
{
//----------------------------------------------------------------
// CHECK
//----------------------------------------------------------------
//Get start time
uint64_t start_tmp = this -> g_systick_start;
//If: bad timestamp
if ((Config::PEDANTIC_CHECKS == true) && (start_tmp == Config::SYSTICK_INVALID))
{
return true; //FAIL
}
//Temp accumulator
uint64_t accumulator_tmp;
//if: Regular timer mode
if (this -> g_f_accumulator_mode == false)
{
//reset the accumulator
accumulator_tmp = 0;
//go into accumulator mode
this -> g_f_accumulator_mode = true;
}
//If: accumulator mode
else
{
//Fetch the current accumulator count
accumulator_tmp = this -> g_systick_stop;
}
//----------------------------------------------------------------
// BODY
//----------------------------------------------------------------
//Snap the timestamp
uint64_t stop_tmp = get_timer_value();
//Record the stop timestamp inside the start timestamp and invalidate the stop timestamp
this -> g_systick_start = stop_tmp;
//Accumulate the DeltaT inside the accumulator at full resolution
accumulator_tmp += stop_tmp -start_tmp;
//Store the accumulator value
this -> g_systick_stop = accumulator_tmp;
//----------------------------------------------------------------
// RETURN
//----------------------------------------------------------------
return this -> compute_accumulator( accumulator_tmp, unit );
} //End public method: accumulate | void |
/***************************************************************************/
//! @brief public getter
//! get_elapsed | Unit |
/***************************************************************************/
//! @return int | elapsed milliseconds. negative mean the timer was uninitialized
//! @details \n
//! Time elapsed between start and stop
/***************************************************************************/
int32_t get_elapsed( Unit unit )
{
//----------------------------------------------------------------
// VARS
//----------------------------------------------------------------
//Fetch timestamps
uint64_t start_tmp = this -> g_systick_start;
uint64_t stop_tmp = this -> g_systick_stop;
//----------------------------------------------------------------
// CHECKS
//----------------------------------------------------------------
//If: a timetamp is invalid
if ((start_tmp == Config::SYSTICK_INVALID) || (stop_tmp == Config::SYSTICK_INVALID))
{
return Config::TIME_INVALID; //Invalid
}
//If: accumulator mode
if (this -> g_f_accumulator_mode == true)
{
return Config::TIME_INVALID; //Invalid
}
//----------------------------------------------------------------
// RETURN
//----------------------------------------------------------------
//return elapsed time
return compute_elapsed( start_tmp, start_tmp, unit );
} //End public getter: get_elapsed | Unit |
/***************************************************************************/
//! @brief public getter
//! get_accumulator | Unit |
/***************************************************************************/
//! @return int | accumulators. negative mean the timer was uninitialized
//! @details \n
//! return the DeltaT accumulated by the accumulate function in the given time unit
/***************************************************************************/
inline int32_t get_accumulator( Unit unit )
{
//----------------------------------------------------------------
// CHECK
//----------------------------------------------------------------
//If: accumulator mode
if (this -> g_f_accumulator_mode == false)
{
return Config::TIME_INVALID; //Invalid
}
//----------------------------------------------------------------
// RETURN
//----------------------------------------------------------------
//return accumulated time
return this -> compute_accumulator( this -> g_systick_stop, unit );
} //End public getter: get_elapsed | Unit |
/***************************************************************************/
//! @brief public static method
//! get_systick_freq | void
/***************************************************************************/
//! @return unsigned int | frequency of the SysTick timer
//! @details \n
//! The SysTick timer is tied to the CPU clock prescaled by four
/***************************************************************************/
static unsigned int get_systick_freq( void )
{
//----------------------------------------------------------------
// RETURN
//----------------------------------------------------------------
return SystemCoreClock /Config::SYSTICK_PRE;
} //End static method: get_systick_freq | void
//--------------------------------------------------------------------------
// TESTERS
//--------------------------------------------------------------------------
//--------------------------------------------------------------------------
// PUBLIC METHODS
//--------------------------------------------------------------------------
//--------------------------------------------------------------------------
// PUBLIC STATIC METHODS
//--------------------------------------------------------------------------
/***************************************************************************/
//! @brief public static method
//! delay | Unit | unsigned int |
/***************************************************************************/
//! @param delay | unsigned int | how long to wait for in milliseconds
//! @return void |
//! @details \n
//! Use the SysTick timer counter to busy wait for the correct number of microseconds
//! The CPU SysTick timer is clocked by the ABH clock/4 = 27MHz
//! SystemCoreClock defines the frequency of the CPU in Hz
/***************************************************************************/
static bool delay( Unit unit, unsigned int delay_tmp )
{
//----------------------------------------------------------------
// VARS
//----------------------------------------------------------------
//Temp timestamp
uint64_t systick_tmp;
//Compute final timestamp
uint64_t systick_stop;
//Ticks required to count 1mS
uint32_t numticks = compute_tick_per_time_unit( unit );
//If: bad unit
if (numticks == 0)
{
return true; //fail
}
//----------------------------------------------------------------
// BODY
//----------------------------------------------------------------
// Wait for the correct number of ticks
//Snap start
systick_stop = get_timer_value();
//Compute stop time.
systick_stop += numticks *delay_tmp;
//Wait an additional tick for current tick
systick_stop++;
//Busy wait for time to pass
do
{
//Snap timestamp
systick_tmp = get_timer_value();
}
while( systick_tmp < systick_stop );
//----------------------------------------------------------------
// RETURN
//----------------------------------------------------------------
return false; //OK
} //End public static method: delay | Unit | unsigned int |
/***************************************************************************/
//! @brief public static method
//! delay | unsigned int |
/***************************************************************************/
//! @param delay | unsigned int | how long to wait for in milliseconds
//! @return void |
//! @details \n
//! wrapper of delay
//! its assumed that without arguments the user want a millisecond busy delay function
/***************************************************************************/
static inline bool delay( unsigned int delay_tmp )
{
//----------------------------------------------------------------
// RETURN
//----------------------------------------------------------------
return delay( Longan_nano::Chrono::Unit::milliseconds, (unsigned int)delay_tmp );
} //End public static method: delay | unsigned int |
//--------------------------------------------------------------------------
// PUBLIC VARS
//--------------------------------------------------------------------------
//Visible only inside the class
private:
//--------------------------------------------------------------------------
// PRIVATE METHODS
//--------------------------------------------------------------------------
/***************************************************************************/
//! @brief private method
//! compute_tick_per_time_unit | Unit |
/***************************************************************************/
//! @return uint32_t | number of systick counts needed to count one time unit
//! @details
//! Compute the number of systick counts needed to count one time unit
/***************************************************************************/
static inline uint32_t compute_tick_per_time_unit( Unit unit )
{
//----------------------------------------------------------------
// BODY
//----------------------------------------------------------------
// Compute del
//Switch: Time unit
switch( unit )
{
case Unit::milliseconds:
{
return SystemCoreClock /1000 /Config::SYSTICK_PRE;
break;
}
case Unit::microseconds:
{
return SystemCoreClock /1000000 /Config::SYSTICK_PRE;
break;
}
//Unhandled time unit
default:
{
return 0; //Invalid number of systick counts. Using it will yield infinite time
}
}; //End switch: Time unit
//----------------------------------------------------------------
// RETURN
//----------------------------------------------------------------
return 0; //Invalid number of systick counts. Using it will yield infinite time
} //End private method: compute_tick_per_time_unit | Unit |
/***************************************************************************/
//! @brief private method
//! compute_elapsed | uint64_t | uint64_t | Unit |
/***************************************************************************/
//! @return int32_t | negative = invalid | zero or positive = elapsed time in the given time unit
//! @details \n
//! use start and stop timestamp to compute the elapsed time in a given time unit
/***************************************************************************/
inline int32_t compute_elapsed( uint64_t start, uint64_t stop, Unit unit )
{
//----------------------------------------------------------------
// VARS
//----------------------------------------------------------------
//If: causality violation
if ((Config::PEDANTIC_CHECKS == true) && (start > stop))
{
//Hopefully the timestamps are wrong and the universe still works as intended
return Config::TIME_INVALID; //FAIL
}
//----------------------------------------------------------------
// BODY
//----------------------------------------------------------------
//SysTick counts in one time unit
uint32_t numticks_time_unit = this -> compute_tick_per_time_unit( unit );
//If: bad unit was provided
if ((Config::PEDANTIC_CHECKS == true) && (numticks_time_unit == 0))
{
return TIME_INVALID;
}
//Compute DeltaT in system ticks as stop-start
uint64_t deltat = stop -start;
//Compute DeltaT in time units
deltat /= numticks_time_unit;
//Demote
int32_t ret = deltat;
//----------------------------------------------------------------
// RETURN
//----------------------------------------------------------------
return ret;
} //End private method: compute_elapsed | uint64_t | uint64_t | Unit |
/***************************************************************************/
//! @brief private method
//! compute_accumulator | uint64_t | Unit |
/***************************************************************************/
//! @return int32_t | negative = invalid | zero or positive = elapsed time in the given time unit
//! @details \n
//! use start and stop timestamp to compute the elapsed time in a given time unit
/***************************************************************************/
int32_t compute_accumulator( uint64_t accumulator, Unit unit )
{
//----------------------------------------------------------------
// VARS
//----------------------------------------------------------------
//If: accumulator
if ((Config::PEDANTIC_CHECKS == true) && (accumulator == Config::SYSTICK_INVALID))
{
return TIME_INVALID;
}
//----------------------------------------------------------------
// BODY
//----------------------------------------------------------------
//SysTick counts in one time unit
uint32_t numticks_time_unit = this -> compute_tick_per_time_unit( unit );
//If: bad unit was provided
if ((Config::PEDANTIC_CHECKS == true) && (numticks_time_unit == 0))
{
return TIME_INVALID;
}
//Compute DeltaT in time units
accumulator /= numticks_time_unit;
//Demote
int32_t ret = accumulator;
//----------------------------------------------------------------
// RETURN
//----------------------------------------------------------------
return ret;
} //End private method: compute_accumulator | uint64_t | Unit |
//--------------------------------------------------------------------------
// PRIVATE VARS
//--------------------------------------------------------------------------
//true = accumulator mode | false = regular start stop mode
bool g_f_accumulator_mode;
//Systick Timestamps
uint64_t g_systick_start;
//Combined stop and accumulator counter
uint64_t g_systick_stop;
}; //End Class: Chrono
/**********************************************************************************
** NAMESPACE
**********************************************************************************/
} //End Namespace: Longan_nano
#else
#warning "Multiple inclusion of hader file LONGAN_NANO_CHRONO_H_"
#endif




3Demo

This demo shows a practical implementation of an hardwired scheduler that issue the toggle of the RED led at 250mS, and the GREEN led at 750ms, by prescaling the fast tick for the RED led.

This scheduler monitors the overrun of tasks, and light a blue led if error occurs.



Illustration 2: Demo Tasks




Video 1: Demo Tasks


3.1Demo Source Code

Code for the demo to shows the hardwired scheduler, the prescaler, the overrun detection, the uptime measurement and the cpu time measurement.



/****************************************************************************
** OrangeBot Project
*****************************************************************************
** /
** /
** /
** ______ \
** \
** \
*****************************************************************************
** Longan Nano Chrono Scheduler Demo
*****************************************************************************
** Show how to use the Chrono class to build a fixed function scheduler
** and use the Chrono class to measure elapsed time and accumulated time
****************************************************************************/
/****************************************************************************
** INCLUDES
****************************************************************************/
//Longan Nano HAL
#include <gd32vf103.h>
//LED class
#include "longan_nano_led.hpp"
//Time class
#include "longan_nano_chrono.hpp"
/****************************************************************************
** NAMESPACES
****************************************************************************/
/****************************************************************************
** DEFINES
****************************************************************************/
//forever
#define EVER (;;)
/****************************************************************************
** MACROS
****************************************************************************/
/****************************************************************************
** ENUM
****************************************************************************/
//Configurations
typedef enum _Config
{
//Microseconds between led toggles
RED_LED_BLINK_US = 250000,
GREEN_LED_BLINK_US = 750000,
} Config;
/****************************************************************************
** STRUCT
****************************************************************************/
//flags used by the hardwired scheduler
typedef struct _Scheduler
{
bool f_red_led : 1;
bool f_green_led : 1;
bool f_overrun : 1;
} Scheduler;
/****************************************************************************
** PROTOTYPES
****************************************************************************/
/****************************************************************************
** GLOBAL VARIABILES
****************************************************************************/
//Scheduler for the tasks
Scheduler g_scheduler = { 0 };
/****************************************************************************
** FUNCTIONS
****************************************************************************/
/****************************************************************************
** @brief main
** main | void
****************************************************************************/
//! @return int |
//! @details Entry point of program
/***************************************************************************/
int main( void )
{
//----------------------------------------------------------------
// VARS
//----------------------------------------------------------------
//Systick timer used to schedule activities
Longan_nano::Chrono timer_scheduler;
//Systick timers to profile resource use
Longan_nano::Chrono timer_uptime;
Longan_nano::Chrono timer_cpu_use;
//Prescale the fastest task to generate other flags
static uint16_t scheduler_cnt;
//----------------------------------------------------------------
// INIT
//----------------------------------------------------------------
//Initialize LEDs
Longan_nano::Leds::init();
Longan_nano::Leds::set_color( Longan_nano::Leds::Color::BLACK );
//Snap start
timer_scheduler.start();
timer_uptime.start();
//----------------------------------------------------------------
// BODY
//----------------------------------------------------------------
for EVER
{
//----------------------------------------------------------------
// Scheduler
//----------------------------------------------------------------
// Hardwired scheduler to release tasks
// Thanks to the Longan Nano SysTick timer there is no need to use peripherals for timekeeping
//Snap stop and get time since last start in microseconds
int elapsed_us = timer_scheduler.stop( Longan_nano::Chrono::Unit::microseconds );
//If: for some reason, the timing is invalid. Algorithmic error
if (elapsed_us < 0)
{
//Blue led means error
Longan_nano::Leds::set( Longan_nano::Leds::Color::BLUE );
}
//If: enough time has passed between screen executions
else if (elapsed_us >= Config::RED_LED_BLINK_US)
{
//----------------------------------------------------------------
// LED
//----------------------------------------------------------------
// The screen is the fastest task
//If: the previous task was not cleared
if (g_scheduler.f_red_led == true)
{
//There was an overrun. Not enough CPU to keep up
g_scheduler.f_overrun = true;
}
else
{
//Issue a LED BLINK update
g_scheduler.f_red_led = true;
}
//Snap start. Restart the timer
timer_scheduler.start();
//----------------------------------------------------------------
// Prescaler
//----------------------------------------------------------------
// A prescaler is used to schedule the execution of slow tasks without the need of additional timers
//Prescaler counter
scheduler_cnt++;
//----------------------------------------------------------------
// LED Blink
//----------------------------------------------------------------
//If: enough ticks of the prescaler counter have elapsed
if (scheduler_cnt%(Config::GREEN_LED_BLINK_US /Config::RED_LED_BLINK_US) == 0)
{
//If: the previous task was not cleared
if (g_scheduler.f_green_led == true)
{
//There was an overrun. Not enough CPU to keep up
g_scheduler.f_overrun = true;
}
else
{
//Issue a LED BLINK update
g_scheduler.f_green_led = true;
}
}
} //If: enough time has passed between screen executions
//Default
else
{
//Nothing to do
}
//----------------------------------------------------------------
// OVERRUN
//----------------------------------------------------------------
// Triggered when a task is not completed before the next issue
//If: overrun
if (g_scheduler.f_overrun == true)
{
//Clear error
g_scheduler.f_overrun = false;
//Blue led means error
Longan_nano::Leds::set( Longan_nano::Leds::Color::BLUE );
}
//----------------------------------------------------------------
// TASK: RED LED Blink
//----------------------------------------------------------------
// If the RED LED Blink task is authorized to run
if (g_scheduler.f_red_led == true)
{
//Reset flag
g_scheduler.f_red_led = false;
//Snap start to profile the CPU time spent in this task
timer_cpu_use.start();
//Task code: in this calse blink the red led
Longan_nano::Leds::toggle( Longan_nano::Leds::Color::RED );
//Accumulate DeltaT into the CPU timer accumulator
timer_cpu_use.accumulate();
}
//----------------------------------------------------------------
// TASK: GREEN LED Blink
//----------------------------------------------------------------
// If the GREEN LED Blink task is authorized to run
if (g_scheduler.f_green_led == true)
{
//Reset flag
g_scheduler.f_green_led = false;
//Snap start to profile the CPU time spent in this task
timer_cpu_use.start();
//Task code: in this calse blink the red led
Longan_nano::Leds::toggle( Longan_nano::Leds::Color::GREEN );
//Accumulate DeltaT into the CPU timer accumulator
timer_cpu_use.accumulate();
}
//----------------------------------------------------------------
// UPTIME
//----------------------------------------------------------------
// At any moment the application can access to the time since application start
timer_uptime.stop( Longan_nano::Chrono::Unit::microseconds );
//----------------------------------------------------------------
// CPU use
//----------------------------------------------------------------
// At any moment the application can access to the total time the CPU spent running tasks
timer_cpu_use.get_accumulator( Longan_nano::Chrono::Unit::microseconds );
} //End forever
//----------------------------------------------------------------
// RETURN
//----------------------------------------------------------------
return 0;
} //end function: main

4Conclusions

Time measurement and scheduling are fundamental to the operation of a fixed function hard real time microcontroller application.

In this document I laid out my solution and my implementation in the form of the Chrono class and fixed scheduler with execution flags. This solution does not use timer peripherals inside the microcontroller, perform unit conversion and works at full resolution and using C++ constructs.

This architecture will serve as the basis for all my applications based on the Longan nano GD32VF103.




2020-07-06 Longan Nano GD32VF103 PA8 and RTC Interrupts

>>>Longan Nano GD32VF103<<<

Longan Nano GD32VF103 Risc-V 108MHz 32b MCU toolchain, libraries and applications


Illustration 1: Longan Nano test bench


1Introduction

The blink example went up quite painlessly, and I was also able to compile and execute the Bad Apple example that takes a stream of bitmap images from the SD Card and shows them on the screen.

With the basics out of the way, I focused on the interrupts.


1.1Arduino Framework Compatibility

The product page claims compatibility with the Arduino Framework, so I wanted to try the Arduino like interrupt handling.

Early on it was obvious that the Arduino framework has not been completed as the time of writing, with basic HAL functions like the digitalRead() empty. There was no hope of getting Arduino style interrupts to work without doing the framework HAL myself, so I dropped the thing.


2Interrupts

The Risc-V ISA specifies the interrupt handling as part of the core. Details about the ECLIC can be found in this excellent post from Kevin Sangeelee.

The GD32VF103 support an interrupt vector table defined as weak symbols in Start.S in the GD32 framework, it supports tiered priority where high priority interrupts can interrupt low priority ones and has hardware acceleration for them inside the MCU, on top of the special instructions and registers inside the core, as per Risc-V ISA specifications.

The GD32VF103 provides special interrupts from all sort of sources, including a reset vector, an error vector and a watchdog vector, on top of interrupts from almost every peripheral.


2.1Difficulties

Getting the interrupts to work was not easy.

In theory, all I had to do was to define a function with the same name as the symbol in start.S, and the C++ linker should have automagically linked the definitions by overriding the default .weak symbols.

After lots of testing, stack overflow came through with the answer: I need to use the keyword extern “C” to tell the C++ compiler not to change the name of the function and allow the linker to do its job and link the address of the interrupt service routine to the interrupt vector table.


3Example: PA8 EXTI

The Boot button is also wired to the PA8 pin. The EXTI in combination with the ECLIC is used to sense an edge on PA8 and trigger an ISR.

#include <gd32vf103.h>
#define EVER (;;)
void init()
{
//Clock the GPIO banks
rcu_periph_clock_enable(RCU_GPIOA);
rcu_periph_clock_enable(RCU_GPIOC);
//Setup the R, G and B LEDs
gpio_init(GPIOA, GPIO_MODE_OUT_PP, GPIO_OSPEED_50MHZ,GPIO_PIN_1);
gpio_init(GPIOA, GPIO_MODE_OUT_PP, GPIO_OSPEED_50MHZ,GPIO_PIN_2);
gpio_init(GPIOA, GPIO_MODE_IN_FLOATING, GPIO_OSPEED_50MHZ,GPIO_PIN_8);
//Setup the boot button
gpio_init(GPIOC, GPIO_MODE_OUT_PP, GPIO_OSPEED_50MHZ,GPIO_PIN_13);
//Initialize the LEDs to: OFF
gpio_bit_set(GPIOC,GPIO_PIN_13);
gpio_bit_set(GPIOA,GPIO_PIN_1);
gpio_bit_set(GPIOA,GPIO_PIN_2);
//Clock the alternate functions
rcu_periph_clock_enable(RCU_AF);
//Initialize the ECLIC IRQ lines
eclic_priority_group_set(ECLIC_PRIGROUP_LEVEL3_PRIO1);
eclic_irq_enable(EXTI5_9_IRQn, 1, 1);
//Initialize the EXTI. IRQ can be generated from GPIO edge detectors
gpio_exti_source_select(GPIO_PORT_SOURCE_GPIOA, GPIO_PIN_SOURCE_8);
exti_init(EXTI_8, EXTI_INTERRUPT, EXTI_TRIG_BOTH);
//Clear interrupt flag. Ensure no spurious execution at start
exti_interrupt_flag_clear(EXTI_8);
//Enable the interrupts. From now on interrupt handlers can be executed
eclic_global_interrupt_enable();
return;
}
extern "C"
void EXTI5_9_IRQHandler()
{
//If: interrupt from PA8 boot button
if (exti_interrupt_flag_get(EXTI_8) != RESET)
{
//Clear the interrupt from PA8 boot button
exti_interrupt_flag_clear(EXTI_8);
//Toggle the blue led
gpio_bit_write(GPIOA, GPIO_PIN_2, (bit_status)(1-gpio_input_bit_get(GPIOA, GPIO_PIN_2)));
}
//Default: interrupt from an unhandled GPIO
else
{
//Do nothing (should clear the interrupt flags)
}
}
void delay_us(unsigned int us)
{
uint64_t start_mtime, delta_mtime;
uint64_t tmp = get_timer_value();
do
{
start_mtime = get_timer_value();
}
while (start_mtime == tmp);
do
{
delta_mtime = get_timer_value() - start_mtime;
}
while(delta_mtime <(SystemCoreClock/4000000.0 *us ));
return;
}
int main()
{
init();
for EVER
{
//Toggle the RED LED
gpio_bit_write(GPIOC, GPIO_PIN_13, (bit_status)(1-gpio_input_bit_get(GPIOC, GPIO_PIN_13)));
//2Hz blink
delay_us(250000);
}
return 0;
}
view raw main.cpp hosted with ❤ by GitHub

Video1: PA8 interrupt demo in action

4Example: RTC Interrupt

The RTC timer is initialized to emit a “second” interrupt every 250ms. The interrupt toggles the RED LED.

/****************************************************************************
** OrangeBot Project
*****************************************************************************
** /
** /
** /
** ______ \
** \
** \
*****************************************************************************
** Longan Nano Example - Blink RTC IST
*****************************************************************************
** Author: Orso Eric
** Version:
****************************************************************************/
/****************************************************************************
** DESCRIPTION
*****************************************************************************
** Blink the LEDs using the interrupt service rutine generated by the RTC timer
****************************************************************************/
/****************************************************************************
** HYSTORY VERSION
*****************************************************************************
** 2020-07-05
** Interrupt Service Routine working!
** 2020-07-06
** Format source code
****************************************************************************/
/****************************************************************************
** KNOWN BUG
*****************************************************************************
**
****************************************************************************/
/****************************************************************************
** INCLUDE
****************************************************************************/
#include <gd32vf103.h>
//Onboard RBG LEDs of the Longan Nano
#include "longan_nano_led.hpp"
/****************************************************************************
** NAMESPACES
****************************************************************************/
//using namespace std;
/****************************************************************************
** DEFINES
****************************************************************************/
//forEVER
#define EVER (;;)
/****************************************************************************
** GLOBAL VARIABILE
****************************************************************************/
/****************************************************************************
** FUNCTION PROTOTYPES
****************************************************************************/
//Initialize the board
extern bool init( void );
//Busy delay using the SysTick timer
extern void delay_us(unsigned int us);
/****************************************************************************
** FUNCTION
****************************************************************************/
/***************************************************************************/
//! @brief maim
//! main | void
/***************************************************************************/
//! @param delay | unsigned int | how long to wait for in microseconds
//! @return void |
//! @details \n
//! Handles the ISR lines generated by the GPIO lines 5 to 9
/***************************************************************************/
int main( void )
{
//----------------------------------------------------------------
// VARS
//----------------------------------------------------------------
bool f_ret = false;
//----------------------------------------------------------------
// INIT
//----------------------------------------------------------------
//Initialize the board and return success status
f_ret |= init();
//----------------------------------------------------------------
// MAIN LOOP
//----------------------------------------------------------------
for EVER
{
//Toggle the RED LED
//Longan_nano::Leds::toggle( Longan_nano::Led_color::RED );
//2Hz blink
//delay_us(250000);
}
//----------------------------------------------------------------
// RETURN
//----------------------------------------------------------------
return 0;
} //End function: main
/***************************************************************************/
//! @brief utility
//! delay_us
/***************************************************************************/
//! @param delay | unsigned int | how long to wait for in microseconds
//! @return void |
//! @details \n
//! Use the SysTic timer counter to busy wait for the correct number of microseconds
/***************************************************************************/
void delay_us(unsigned int delay)
{
//----------------------------------------------------------------
// VARS
//----------------------------------------------------------------
uint64_t start_mtime, delta_mtime;
//----------------------------------------------------------------
// BODY
//----------------------------------------------------------------
// Wait for the first tick
// Wait for the correct number of ticks
uint64_t tmp = get_timer_value();
do
{
start_mtime = get_timer_value();
}
while (start_mtime == tmp);
do
{
delta_mtime = get_timer_value() - start_mtime;
}
while(delta_mtime <(SystemCoreClock/4000000.0 *delay ));
//----------------------------------------------------------------
// RETURN
//----------------------------------------------------------------
return;
} //End function: main
view raw main.cpp hosted with ❤ by GitHub
/****************************************************************************
** init.cpp
*****************************************************************************
** Board initializations
** >GPIO
** >EXTI
** >ECLIC
****************************************************************************/
/****************************************************************************
** INCLUDE
****************************************************************************/
#include <gd32vf103.h>
//Onboard RBG LEDs of the Longan Nano
#include "longan_nano_led.hpp"
/****************************************************************************
** NAMESPACES
****************************************************************************/
//using namespace std;
/****************************************************************************
** GLOBAL VARIABILE
****************************************************************************/
/****************************************************************************
** FUNCTION PROTOTYPES
****************************************************************************/
//Initialize the board
extern bool init( void );
//Initialize RTC Timer to generate a system tick
extern bool init_rtc( void );
//Initialize the EXTI edge detection interrupt from the GPIO
extern bool init_exti(void);
//Initialize the ECLIC interrupt controller
extern bool init_eclic(void);
/****************************************************************************
** FUNCTION
****************************************************************************/
/***************************************************************************/
//! @brief init
//! init |
/***************************************************************************/
//! @return bool | false = OK | true = ERR
//! @details
//! initialize the longan nano pheriperals
/***************************************************************************/
bool init( void )
{
//----------------------------------------------------------------
// VARS
//----------------------------------------------------------------
//Return
bool f_ret = false;
//----------------------------------------------------------------
// BODY
//----------------------------------------------------------------
// GPIO
// ECLIC
// EXTI
//Initialize the Longan Nano builtin RBG LEDs
f_ret |= Longan_nano::Leds::init();
//Initialize LEDs to OFF
Longan_nano::Leds::set_color( Longan_nano::Led_color::BLACK );
//EXTI GPIO interrupt: PA8 R/F -> EXTI_5_9
f_ret |= init_exti();
//RTC system tick
f_ret |= init_rtc();
//Initialize ECLIC Interrupt controller. Interrupts can now be generated
f_ret |= init_eclic();
//if: initialization failed
if (f_ret == true)
{
}
//----------------------------------------------------------------
// RETURN
//----------------------------------------------------------------
return f_ret;
} //end init: init |
/***************************************************************************/
//! @brief init
//! init_exti |
/***************************************************************************/
//! @return bool | false = OK | true = ERR
//! @details
//! initialize the EXTI edge detection interrupt from the GPIO
//! PA8 R/F -> EXTI_5_9
/***************************************************************************/
bool init_exti(void)
{
//----------------------------------------------------------------
// VARS
//----------------------------------------------------------------
//----------------------------------------------------------------
// BODY
//----------------------------------------------------------------
//Setup the boot button
gpio_init(GPIOA, GPIO_MODE_IN_FLOATING, GPIO_OSPEED_50MHZ,GPIO_PIN_8);
//Clock the alternate functions
rcu_periph_clock_enable(RCU_AF);
//Initialize the EXTI. IRQ can be generated from GPIO edge detectors
gpio_exti_source_select(GPIO_PORT_SOURCE_GPIOA, GPIO_PIN_SOURCE_8);
exti_init(EXTI_8, EXTI_INTERRUPT, EXTI_TRIG_BOTH);
//Clear interrupt flag. Ensure no spurious execution at start
exti_interrupt_flag_clear(EXTI_8);
//----------------------------------------------------------------
// RETURN
//----------------------------------------------------------------
return false; //OK
} //end init: init_exti |
/***************************************************************************/
//! @brief init
//! init_rtc |
/***************************************************************************/
//! @return bool | false = OK | true = ERR
//! @details
//! initialize the RTC Real Time Counter
//! setup to emit a 4Hz SECOND interrupt
//!
//! RTC Interrupts:
//! RTC_INT_SECOND: Generated every time RTC_CNT changes (after the prescaler RTC_PSC)
//! RTC_INT_ALARM: Generated when RTC_CNT equals RTC_ALRM
//! RTC_INT_OV: Generated when the 32b RTC_CNT counter overflows to zero
/***************************************************************************/
bool init_rtc(void)
{
//----------------------------------------------------------------
// VARS
//----------------------------------------------------------------
//----------------------------------------------------------------
// BODY
//----------------------------------------------------------------
//Backup Clock
rcu_periph_clock_enable(RCU_BKPI);
//
rcu_periph_clock_enable(RCU_PMU);
//Enter configuration mode
pmu_backup_write_enable();
//
bkp_deinit();
//Enable LX crystal 32768 [Hz]
rcu_osci_on(RCU_LXTAL);
//Wait for crystal to stabilize
rcu_osci_stab_wait(RCU_LXTAL);
//Clock the RTC with the LX Crystal
rcu_rtc_clock_config(RCU_RTCSRC_LXTAL);
rcu_periph_clock_enable(RCU_RTC);
//wait for RTC registers synchronization
rtc_register_sync_wait();
//wait until last write operation on RTC registers has finished
rtc_lwoff_wait();
//Set the RTC Prescaler
rtc_prescaler_set(8192);
//wait until last write operation on RTC registers has finished
rtc_lwoff_wait();
//Emit an interrupt each time RTC_CNT counts
rtc_interrupt_enable(RTC_INT_SECOND);
//wait until last write operation on RTC registers has finished
rtc_lwoff_wait();
//----------------------------------------------------------------
// RETURN
//----------------------------------------------------------------
return false; //OK
} //end init: init_rtc |
/***************************************************************************/
//! @brief init
//! init_eclic |
/***************************************************************************/
//! @return bool | false = OK | true = ERR
//! @details
//! Initialize the ECLIC interrupt controller
/***************************************************************************/
bool init_eclic(void)
{
//----------------------------------------------------------------
// VARS
//----------------------------------------------------------------
//----------------------------------------------------------------
// BODY
//----------------------------------------------------------------
//Initialize the ECLIC IRQ lines
eclic_priority_group_set(ECLIC_PRIGROUP_LEVEL3_PRIO1);
eclic_irq_enable(EXTI5_9_IRQn, 1, 1);
//Enable the RTC Interrupt
eclic_priority_group_set(ECLIC_PRIGROUP_LEVEL1_PRIO3);
eclic_irq_enable(RTC_IRQn, 1, 0);
//Enable the interrupts. From now on interrupt handlers can be executed
eclic_global_interrupt_enable();
//----------------------------------------------------------------
// RETURN
//----------------------------------------------------------------
return false; //OK
} //end init: init_eclic |
view raw init.cpp hosted with ❤ by GitHub
/****************************************************************************
** int.cpp
*****************************************************************************
** Interrupt Service Routines
** NOTE:
** extern "C" keyword tells the C++ compiler not to mangle the ISR nfunction name
** the Interrupt Vector Table is declared in Start.S and requires special names
** for the ISRs without mangling to link correctly
****************************************************************************/
/****************************************************************************
** Interrupt Vectors
*****************************************************************************
** EXTI5_9_IRQHandler GPIO edge detectors 5 to 9
** RTC_IRQHandler RTC Second
****************************************************************************/
/****************************************************************************
** INCLUDE
****************************************************************************/
#include <gd32vf103.h>
//Onboard RBG LEDs of the Longan Nano
#include "longan_nano_led.hpp"
/***************************************************************************/
//! @brief ISR
//! EXTI5:9 LINES
/***************************************************************************/
//! @return void |
//! @details \n
//! Handles the ISR lines generated by the GPIO lines 5 to 9
/***************************************************************************/
extern "C"
void EXTI5_9_IRQHandler()
{
//----------------------------------------------------------------
// VARS
//----------------------------------------------------------------
//----------------------------------------------------------------
// BODY
//----------------------------------------------------------------
//If: interrupt from PA8 boot button
if (exti_interrupt_flag_get(EXTI_8) != RESET)
{
//Clear the interrupt from PA8 boot button
exti_interrupt_flag_clear(EXTI_8);
//Toggle the green led
Longan_nano::Leds::toggle( Longan_nano::Led_color::GREEN );
}
//Default: interrupt from an unhandled GPIO
else
{
//Do nothing (should clear the interrupt flags)
}
//----------------------------------------------------------------
// RETURN
//----------------------------------------------------------------
} //End ISR: EXTI5_9_IRQHandler
/***************************************************************************/
//! @brief ISR
//! RTC
/***************************************************************************/
//! @return void |
//! @details \n
//! Handles the ISR lines generated by the RTC
//! Includes three vectors:
//! RTC_INT_SECOND: Generated every time RTC_CNT changes (after the prescaler RTC_PSC)
//! RTC_INT_ALARM: Generated when RTC_CNT equals RTC_ALRM
//! RTC_INT_OV: Generated when the 32b RTC_CNT counter overflows to zero
/***************************************************************************/
extern "C"
void RTC_IRQHandler(void)
{
//SECOND interrupt, generated by a change in RTC:CNT
if (rtc_flag_get(RTC_FLAG_SECOND) != RESET)
{
//Clear flag
rtc_flag_clear(RTC_FLAG_SECOND);
Longan_nano::Leds::toggle( Longan_nano::Led_color::RED );
}
//ALARM interrupt, generated when RTC_CNT equals RTC_ALRM
if (rtc_flag_get(RTC_FLAG_ALARM) != RESET)
{
//Clear flag
rtc_flag_clear(RTC_FLAG_ALARM);
}
//----------------------------------------------------------------
// RETURN
//----------------------------------------------------------------
} //End ISR: RTC_IRQHandler
view raw int.cpp hosted with ❤ by GitHub
/**********************************************************************************
** ENVIROMENT VARIABILE
**********************************************************************************/
#ifndef LONGAN_NANO_LED_H_
#define LONGAN_NANO_LED_H_
/**********************************************************************************
** GLOBAL INCLUDES
**********************************************************************************/
#include <gd32vf103.h>
/**********************************************************************************
** DEFINES
**********************************************************************************/
/**********************************************************************************
** MACROS
**********************************************************************************/
/**********************************************************************************
** NAMESPACE
**********************************************************************************/
//! @namespace User My custom namespace
namespace Longan_nano
{
/**********************************************************************************
** TYPEDEFS
**********************************************************************************/
typedef enum _Led_color
{
BLACK,
RED,
GREEN,
BLUE,
WHITE
} Led_color;
typedef enum _Led_gpio
{
RED_GPIO = GPIOC,
GREEN_GPIO = GPIOA,
BLUE_GPIO = GPIOA,
} Led_gpio;
typedef enum _Led_pin
{
RED_PIN = GPIO_PIN_13,
GREEN_PIN = GPIO_PIN_1,
BLUE_PIN = GPIO_PIN_2,
} Led_pin;
/**********************************************************************************
** PROTOTYPE: STRUCTURES
**********************************************************************************/
/**********************************************************************************
** PROTOTYPE: GLOBAL VARIABILES
**********************************************************************************/
/**********************************************************************************
** PROTOTYPE: CLASS
**********************************************************************************/
/************************************************************************************/
//! @class Longan_nano_led
/************************************************************************************/
//! @author Orso Eric
//! @version 0.1 alpha
//! @date 2019/05
//! @brief Dummy Library
//! @details
//! Verbose description \n
//! xxx
//! @pre No prerequisites
//! @bug None
//! @warning No warnings
//! @copyright License ?
//! @todo todo list
/************************************************************************************/
class Leds
{
//Visible to all
public:
//--------------------------------------------------------------------------
// CONSTRUCTORS
//--------------------------------------------------------------------------
//! Default constructor
Leds( void );
//--------------------------------------------------------------------------
// DESTRUCTORS
//--------------------------------------------------------------------------
//!Default destructor
~Leds( void );
//--------------------------------------------------------------------------
// OPERATORS
//--------------------------------------------------------------------------
//--------------------------------------------------------------------------
// SETTERS
//--------------------------------------------------------------------------
//--------------------------------------------------------------------------
// GETTERS
//--------------------------------------------------------------------------
//--------------------------------------------------------------------------
// TESTERS
//--------------------------------------------------------------------------
//--------------------------------------------------------------------------
// PUBLIC METHODS
//--------------------------------------------------------------------------
/***************************************************************************/
//! @brief public static method
//! init |
/***************************************************************************/
//! @return bool | false = OK | true = ERR
//! @details
//! initialize the longan nano GPIO LEDs
/***************************************************************************/
static inline bool init( void )
{
//----------------------------------------------------------------
// VARS
//----------------------------------------------------------------
//----------------------------------------------------------------
// BODY
//----------------------------------------------------------------
//Clock the GPIO banks
rcu_periph_clock_enable(RCU_GPIOA);
rcu_periph_clock_enable(RCU_GPIOC);
//Setup the R (PC13), G (PA1) and B (PA2) LEDs
gpio_init(Led_gpio::RED_GPIO, GPIO_MODE_OUT_PP, GPIO_OSPEED_50MHZ,Led_pin::RED_PIN);
gpio_init(Led_gpio::GREEN_GPIO, GPIO_MODE_OUT_PP, GPIO_OSPEED_50MHZ,Led_pin::GREEN_PIN);
gpio_init(Led_gpio::BLUE_GPIO, GPIO_MODE_OUT_PP, GPIO_OSPEED_50MHZ,Led_pin::BLUE_PIN);
//----------------------------------------------------------------
// RETURN
//----------------------------------------------------------------
return false; //OK
} //End public static method: init
/***************************************************************************/
//! @brief public static method
//! set_color | Led_color
/***************************************************************************/
//! @param Led_color | color of the LED
//! @return bool | false = OK | true = ERR
//! @details
//! initialize the longan nano GPIO LEDs
/***************************************************************************/
static inline bool set_color( Led_color color )
{
//----------------------------------------------------------------
// VARS
//----------------------------------------------------------------
//LED are inverted. SET turn them OFF
bit_status led_r = FlagStatus::SET;
bit_status led_g = FlagStatus::SET;
bit_status led_b = FlagStatus::SET;
//----------------------------------------------------------------
// BODY
//----------------------------------------------------------------
//Switch: Color
switch(color)
{
case (Led_color::BLACK):
{
break;
}
case (Led_color::WHITE):
{
led_r = FlagStatus::RESET;
led_g = FlagStatus::RESET;
led_b = FlagStatus::RESET;
break;
}
default:
{
return true; //FAIL
}
} //End Switch: Color
//Apply the color setting
gpio_bit_write( Led_gpio::RED_GPIO, Led_pin::RED_PIN, (bit_status)led_r );
gpio_bit_write( Led_gpio::GREEN_GPIO, Led_pin::GREEN_PIN, (bit_status)led_g );
gpio_bit_write( Led_gpio::BLUE_GPIO, Led_pin::BLUE_PIN, (bit_status)led_b );
//----------------------------------------------------------------
// RETURN
//----------------------------------------------------------------
return false; //OK
} //End public static method: set_color
/***************************************************************************/
//! @brief public static method
//! toggle |
/***************************************************************************/
//! @return bool | false = OK | true = ERR
//! @details
//! initialize the longan nano GPIO LEDs
/***************************************************************************/
static inline bool toggle( Led_color color )
{
//----------------------------------------------------------------
// VARS
//----------------------------------------------------------------
//----------------------------------------------------------------
// BODY
//----------------------------------------------------------------
//Switch: Color
switch(color)
{
case (Led_color::RED):
{
gpio_bit_write(Led_gpio::RED_GPIO, Led_pin::RED_PIN, (bit_status)(1-gpio_input_bit_get(Led_gpio::RED_GPIO, Led_pin::RED_PIN)));
break;
}
case (Led_color::GREEN):
{
gpio_bit_write(Led_gpio::GREEN_GPIO, Led_pin::GREEN_PIN, (bit_status)(1-gpio_input_bit_get(Led_gpio::GREEN_GPIO, Led_pin::GREEN_PIN)));
break;
}
case (Led_color::BLUE):
{
gpio_bit_write(Led_gpio::BLUE_GPIO, Led_pin::BLUE_PIN, (bit_status)(1-gpio_input_bit_get(Led_gpio::BLUE_GPIO, Led_pin::BLUE_PIN)));
break;
}
default:
{
return true; //FAIL
}
} //End Switch: Color
//----------------------------------------------------------------
// RETURN
//----------------------------------------------------------------
return false; //OK
} //End public static method: toggle
//--------------------------------------------------------------------------
// PUBLIC STATIC METHODS
//--------------------------------------------------------------------------
//--------------------------------------------------------------------------
// PUBLIC VARS
//--------------------------------------------------------------------------
//Visible to derived classes
protected:
//--------------------------------------------------------------------------
// PROTECTED METHODS
//--------------------------------------------------------------------------
//--------------------------------------------------------------------------
// PROTECTED VARS
//--------------------------------------------------------------------------
//Visible only inside the class
private:
//--------------------------------------------------------------------------
// PRIVATE METHODS
//--------------------------------------------------------------------------
//--------------------------------------------------------------------------
// PRIVATE VARS
//--------------------------------------------------------------------------
}; //End Class: Longan_nano_led
/**********************************************************************************
** NAMESPACE
**********************************************************************************/
} //End Namespace: Longan_nano
#else
#warning "Multiple inclusion of hader file"
#endif




5Conclusions

Interrupts are fundamental in an hard real time MCU application and the GD32VF103 is equipped with a refined system to handle interrupts.

This document shows how to generate interrupts from the EXTI and RTC. interrupts can be generated from all sort of sources, and there are even sources for reset, watchdog, errors, etc...




2020-07-05

2020-07-05 Longan Nano GD32VF103 Toolchain






1Abstract

My big 2019/2020 project, the Pi-Wars, was delayed due to Human Malware. With an additional year of development I decided to go back to the drawing board and expand the scope of the platform.

One of the aspects of OrangeBot I wish to improve is the motor controller. Shortcomings:

  • CPU: I hit the upper limit of the AT4809 8b 20MHz MCU running two 16b PID controller with two loops each at 2KHz, limiting the precision of the controls.

  • Current: No current loop in the PID controller. Adding one promises to optimize performance and efficiency

  • Auto-Tune: A PID auto-tuning algorithm based on light machine learning can optimize the precision of the PID. It can also compensate in runtime for changes in parameters to an extent (e.g. gearbox getting looser)

  • Screen: No screen on the motor board to quickly assess power, position and speed. Even error reporting relies on either a LED or the webserver. Clunky for debug

The decision was taken to upgrade to a new microcontroller.


1.1Why the Longan Nano GD32VF103

Given the additional time, I was in the mood to learn something new. Risc-V is gaining in traction, so I searched for a Risc-V based MCU. The 32b architecture already grant a 2X to 8X improvement to an 8b MCU for fixed point arithmetic, ensuring this would fix the lack of CPU power.

After some research, I found the Longan Nano. The performance are amazing for the price:

  • 5$

  • 32b 108 [MHz]

  • RV32IMAC+ECLIC

  • 80x160 screen

  • In-circuit programmer

  • Reset+Button+RGB LED

  • Timer with quadrature decoder

The price to performance of the platform is AMAZING, and the feature set allows for everything I need to include in the motor controller.

One of four advanced timers allow to decode in hardware the encoder reading, without relying on costly ISR decoding, I have ADC channels for current reading, DMA, communication, and much more. Adding a screen for 5$ is incredible. They threw in a cheap plastic case for good measure and a couple of pin strips.

In the end I paid about 10€ per board considering import duty and delivery.



Illustration 1: Longan Nano



2Example

The Longan Nano comes in with a preloaded video player example. It requires an SD card with a video encoded as sequence of BMP images.


Video 1: Longan Video Demo


3Toolchain

With the physical boards arrived and tested, it was time to write and load code.


3.1VSCode + Platform.IO + Longan Nano Toolchain

The toolchain is well explained in this tutorial.

It requires GIT installed if you don't have it already.

Illustration 2: Platform.IO Installed

Unfortunately, that's the only thing explained well and I run into many problems making the toolchain work.

For the bootloader, you need drivers.

That's not all. In order for the board to show up, you need to:

  • Press the boot button (top button)

  • While boot is pressed, press and release the reset button

  • Release the boot button

This makes the MCU enter programming mode, allowing it to show up on the Gigadevice GD32 MCU Tool. Always check verify. The firmware to be uploaded is the .hex stored in the .pio\build\sipeed-longan-nano subfolder of the project.

Illustration 3: DFU Firmware Upload tool

I was unable to make DFU tool work with the one click upload feature of VS Code, but its not a big deal to upload manually. It might be possible to use the serial port and a serial port adapter with a bootloader to use that feature, but I have yet to try, and USB-C is convenient apart from having to use the button combination to get the board in programming mode.


3.2New Project

With the toolchain in place, its time to start coding. On new project, you can select folder and framework. Use a custom project folder and Arduino framework.

Illustration 4: New Project

You have a choice of two framework. The GD32 framework doesn't work and I was unable to get to work. The Arduino framework can run the blink example. And nothing else. Even DigitalRead is not coded in the HAL Arduino library.

What does work is to use the Arduino framework write a main, and just use the functions in gd32vf103.h. From here you can pick my barebone example that should work with interrupts.

From now on, you can compile a project with VS Code and upload with DFU tool on the Longan Nano board.


4User Library

Like I did for other microcontrollers, I'm going to slowly build up upon the base HAL to have convenient driver and file structure for new projects.

I plan to use C++ classes whenever makes sense, and have a basic IO configuration init procedure and examples for how to do stuffs right. Lightweight screen drivers, initialize timers in various modes, hardwired scheduler, etc...


4.1Examples

Unfortunately the Longan Nano has few examples and bad documentation.

There are examples for other evaluation boards and GD32 MCUs to help draw the drivers of the various peripherals, but it takes work to adapt them and make them work.



Video 2: Longan Interrupt Example


4.2Interrupts

The interrupts were very hard to make work.

Here the solution from stack overflow.

The Risc-V has a very refined interrupt controller compared to the MCU I'm used to. The ECLIC is and extension and part of the Risc-V instruction set of the MCU. It provides special registers and instructions.

The ECLIC is interfaced with other controller like the EXTI to provide an extensive tiered interrupt support. There are also vectors to handle exceptions, watchdog, etc...

I feel it'll take time to master its use.




5Conclusions

The Longan Nano is a powerful 32b Risc-V MCU that fits the use case of the OrangeBot control board perfectly. Despite the clunkiness of the toolchain and lack of documentation, the cost and feature set provide great value and I consider it a great entry point to learn 32b MCUs and Risc-V.






2020-05-08

2020-04-18 OrangeBot Motor Swap


>>>Pi Wars 2020<<< 



1OrangeBot Motor Swap

By design, OrangeBot was meant to be powered by four DC motors and have a top speed of 20 [Km/h]. Supply shortages meant that the only motors available were capable of either 10 [Km/h] or 30 [Km/h].
30 [Km/h] motors were ordered and installed, but during the design stage, the torque needed to turn with a four wheeled platform was not accounted for. This caused two severe problems:
  1. Torque was insufficient to turn
  2. Motor gain was too high
>>>Disaster: Turn<<<

1.1Torque

In order to turn a platform with four fixed wheels, a torque is required to make the wheels slide on the terrain sideways. It was impossible to solve this issue even with overvolting the 12 [V] DC motors, and it was impossible to source new slower stronger motors in time for the competition. The scope for the platform was reduced, and OrangeBot is now equipped with two DC Motors and one pivot wheel.
While this allows OrangeBot to turn, the torque was further halved, impeding acceleration and climbing performance.

1.2Motor Gain

The motor had too much gain in terms of [mm/s/V]. PWM controls were unusable, and the PID controller had an impossible job.
Encoders were upgraded from low count magnetic to high count optical, giving the PID a better chance. After months of work, a two loop PID controller with integral speed reference running at 2 [KHz] and consuming all available computational power on the microcontroller was finally able to suppress ringing and instability and achieve a controllable platform even under the effect of the large inertia.
The motors were highly stressed by continuous fine motion inversion, but the performance were Pi-Wars grade.


2Motor Swap

With additional time, it became possible to just source motors better tuned to the task.

Illustration 1 - Motor Stats
Original Motors
The original motors have a speed gain of 613.9 [mm/s/V], a force gain of 14.04 [g/A] and an encoder scale of 15.79 [cnt/mm] thanks to the high resolution replacement quadrature optical encoders.
Replacement Motors
The replacement motors have a gain of 185 [mm/s/V], a force gain of 28.18 [g/A] and an encoder scale of 0.9639 [cnt/mm].
Note that with the original motors, the torque is low enough that the drag significantly affect top speed. With a drag of 0.001, top speed is about 32 [Km/h]; With a drag of 0.02 top speed lowers to 15 [Km/h].




2.1Uninstall Motors

Uninstall old motors:
  • Remove wheels by unscrewing them from the Hub
  • Remove Hub
  • Disconnect the power and the encoder wiring from the Main Motor Board
  • Unscrew the motor from the motor bracket

Illustration 2 - Uninstall Motors


2.2Uninstall Motor Brackets

The replacement motors are longer. Motor Brackets have to be uninstalled and new holes have to be made to fix them to the structure.

Illustration 3 - Interaxis
Interaxis with the original motors was 190 [mm].
The interaxis with the replacement motors is still compatible with Pi-Wars specifications, but is getting dangerously close to the 225 [mm] limit.


2.3Re-Install Motor Brackets

New holes are made for the M3 bolts that hold the motor brackets in place.

Illustration 4 - Re-install motor brackets
Due to overlap between old and new holes, the interaxis is not optimal, and could be reduced from 220 [mm] to 210 [mm]. Doing so would require redoing the Base Plate upon which the platform is built upon.
A task for the future might be to cut new plates using a laser plotter, but for now focus is on achieving the target performance. Optimization of workable if sub-optimal features will be left for later.




2.4Install Replacement Motors

Installing the motor is straightforward. The power wiring of the motors were replaced with higher gauge prior the installation, the encoder wirings was cut to size in this step.

Illustration 5 - Install Replacement Motors



2.5Solder connectors

Power wirings are soldered with a tip. Encoder wirings are soldered with an L 2.54 [mm] pin strip.
Main Motor Board

Illustration 6 - Solder motor and encoder connectors


2.6Assembly

Assembly the hubs, wheels and the platform back together.

Illustration 7- OrangeBot Undercarriage




3Testing and Calibration


3.1Power Test

The platform powers up without problems

Illustration 8- OrangeBot Powered


3.2PWM Remote Control Test

Test PWM controls. Thanks to the reduced speed and increased torque, OrangeBot now controls just fine in PWM mode, with the only drawback of not going in a straight line.



Video 1- OrangeBot PWM Remote Controls




3.3PID Calibration and PID Remote Control Test

A quick calibration of the PID through the remote controls was enough to get good closed loop performance and have the platform move in a straight line.


Illustration 9- PID Calibration


Video 2- Position PID Calibration




4Conclusions

The replacement motors have more torque and less speed, allowing OrangeBot to be stronger and more precise.
There are other design decisions and mistakes made to achieve the deadline of 2020-03-29. With the deadline moved to 2021-03, many of those decisions can be revisited, to achieve an higher quality design.

2020-03-06

2020-03-02 Turret Construction


>>>Pi Wars 2020<<< 



1Turret and Main Weapon

With the completion of the high speed controls and low latency video streaming, the stage is set to proceed with the final phase of construction of the OrangeBot platform.
This is the biggest upgrade since the construction of the core platform, and beyond this point only minor adjustments should be required.
Mechanical upgrades:
  • Component fitting and Pi-Wars specification check
  • Turret
  • Main weapon
  • Rotation and Elevation mechanism
  • Attachments:
    • Pi-Noon Hard Point and Pi-Noon melee weapon servo
    • Lava Palaveer sensors
    • Eco Disaster forklift
  • Battery Strap
  • Maintenance hatches
Electronics:
  • Servos: Electronics, connectors, cable management and power
  • Power: Main switch, distribution, cable management, turret joint routing
  • Raspberry-Pi: Power, I2C and UART proto hat
  • Sensors: I2C network
  • LED swag!
Firmware:
  • Servo driver and messages
  • Main weapon driver and messages
  • Forklift driver and messages
  • Pi-Noon lance driver and messages
Software
  • Key bindings
  • Integration of all remote controls in the client HMI


1.1Disassembly

The platform was disassembled in preparation for the upgrade.

Illustration 1 - OrangeBot Disassembly
Several plates of the platform needs upgrading, other plates have now become obsolete and have to be rebuilt to spec to fix fitting mistakes and support new functionalities.
Electronics need minor modifications and many new cables needs to be routed.

1.2Obsolete Plates

The now obsolete “platform top plate”, “turret base plate” and “turret rear plate” needs retiring as they were place holders.

Illustration 2 - OrangeBot Retired Plates

2Main Electronics

Main board has to be upgraded to support:
  • Three servo motors
  • Switch (Main Weapon Feedback)
  • Third DC Motor (Main Weapon Spring)
The upgrade is mostly painless, requiring an handful of connectors and resistors. Testing was done to ensure the board was operational and would likely not need a second upgrade.
Unfortunately testing revealed that three full sized servos cannot be powered by the main board and require a second independent 5V3A power regulator, that was cannibalized from “Maze Runner”.
Illustration 3 - Main Board Upgrade

2.1Main Weapon Test

A key decision for the next phase of component fitting was whatever the main weapon could be mounted side ways or needed to be mounted up strainght.

Illustration 4 - Main Weapon Side Loader Test (FAIL)
The rest revealed that the side loader has reliability issues, and a top load mechanism is required.

3Turret Construction

With key decisions about the design taken, construction could move to the next phase.

3.1Turret Mockup

A Mockup of the turret mechanism was made to take measurements on the size of various plates and decide on the component placement and fitting.

Illustration 5 - Turret Mockup

3.2Turret Rotation

With the elevation mechanism and turret dimensions sorted out, it was time to go back to the original design and evaluate the place holder rotation mechanism.
The rotation is critical as it has to withstand the inertia and shock of the moving platform and has to be reliable. After careful consideration, it was decided to retire the previous mechanism and redo three plates.

Illustration 6 - Rotation Mechanism Mockup
It became necessary to turn upside down the turret joint to increase clearance for the rotation servo inside the platform and inside the turret to increase the range of the elevator mechanism, also by turning upside down mechanism, the key that connects the rotation joint with the rotation servo no longer needs a clearance with the rotation joint walls, increasing significantly the robustness of the mechanism.
The rotation joint was also moved more toward the rear of the platform. This has the benefit of looking better (swag first!) and granting better clearance to both the components inside the platform and inside the turret.
There were two ways to place the rotation joint. As Murphy dictates, the wrong way was chosen initially!

3.3“Top Plate” and Rotation Joint

A new top plate was constructed to accommodate the rotation joint mounted in reverse. The holes to fix the top plate to the platform were reduced from eight to four, with a fixed bolt and a space below them. This allows for easier maintenance, as removing four bolts allow the turret, the top plate and the rotation mechanism to come off in one piece. An air gap is maintained to get some airflow inside the platform in case thermals become a concern.

Illustration 7 - Top Plate and Flipped Rotation Joint

3.4Rotation Mechanism

It was very important to take extra care in the design and construction of the “Rotation Mechanism” as this servo and joint have to tank the rotational inertia of the turret caused by the motion of the platform.

Illustration 8 - Rotation Mechanism
The Rotation Servo is kept in place by a structure below the turret fixed by long M4x60 bolts.
A “Rotation Key” transmit the motion from the servo, through the hollow hole of the “Rotation Joint” up to the “Rotation Plate”. The key itself can be moved upward to disconnect the servo from the turret quickly for maintenance.
The “Rotation Plate” will feature additional holes for the cables to pass from the turret to the platform.

3.5Turret Component Fitting

Another critical component of the design is the “Turret Base Plate”. It's important to get the dimension and placement of the holes right as all other turret plates and components use this plate as base.

Illustration 9 - Component Fitting - Second Iteration
A second round of component fitting was performed to estimate turret dimensions and placement. In this fitting, it was decided to move the Raspberry Pi to the “Front Elevator Plate” because of the convenience of attaching the RaspiCam flat cable. The Elevation Servo and Elevation Mechanism was also validated in this phase.

Illustration 10 - Turret Plates Masks and Cut
With the dimensions confirmed, the turret plate masks were prepared, and plates were cut to shape. No holes or other cut out were made in this phase. This allowed for a third iteration of component fitting to make sure the plates were cut right

3.6Turret Elevator Plate

Because so much of the fitting depends on the elevator mechanism, the first turret plate to be finalized was the “Turret Elevator Plate”.

Illustration 11 - Turret Elevator Plate Components Fitting
A third iteration of components fitting was performed to decide on the Elevator Joint and the position of the turret components:
  • Raspberry Pi
  • Main Weapon
  • RaspiCam
  • Targeting Laser
  • Elevator Joint

3.7Turret Base Plate

With the design of the turret validated, the Turret Base Plate could finally be prepared and placed in position. The Rotation Joint was quickly tested and proved to perform flawlessly, achieving a great coupling between turret and the rotation servo below.

Illustration 12 - Turret Base Plate
The Rotation Mechanism fits nicely inside the platform with all other components in place.

Illustration 13 - Rotation Mechanism Fitting

3.8Elevator Joint

The elevator joint is constructed from Lego Technic components and M3 bolts. Lego makes it much easier to build a rotating connection. The elevator joint connects the “Turret Elevator Plate” to the “Turret Right Plate” and “Turret left Plate”.

Illustration 14 - Elevator Joint
The elevator plate is connected to the elevator servo at the bottom end point. The motion of the plates makes the butt of the main weapon move up or down. Its limits are the rotation joint and the turret top plate.

Illustration 15 - Elevator Range
The main weapon is not meant to behave as a mortar or long range artillery, so the elevator range does not have to be massive. Something in between 10° of liberty would be more than enough to aim, even when the robot wants to aim at a ground level target while climbing a ramp.
The main weapon is connected to the Turret Elevator Plate by means of an L shaped structural element with two M4 bolts.

3.9Turret Top and Rear Plates

The Top and Rear plates are straightforward. They just needs holes for the L shaped structural elements.

Illustration 16 - Turret Top and Rear Plates

3.10Turret Final Assembly

The “Turret Left Plate” was the last to be constructed, and accommodates the “Elevator Servo”.
The Elevator Servo is connected to the bottom of the “Turret Elevator Plate” with a beam made from a clips and held in place by M3 bolts.

Illustration 17 - Final Assembly Side View

Illustration 18 - Final Assembly Top View
The video below shows how the Rotation and Elevator Joints operate.


Video 1: Rotation and Elevation mechanism

4Conclusions

With the completion of the turret, the platform is finally nearing completion.
The platform dimensions:
Width: 190 [mm]
Length: 260 [mm]
Height: 310 [mm]
Weight: 3811 [g]