There are a number of badge-specific and generic APIs among the components of the template app. This section contains a quick list of the APIs and some notes on using them.
1 - APIs: Graphics
PAX Graphics is the default way to draw graphics for the MCH2022 badge. Don’t want the getting started? Complete API can be found here.
Getting started
First, download the template app:
git clone https://github.com/badgeteam/mch2022-template-app my_fancy_app
make install
This will download and install the template app to your badge, showing a colorful “Hello, World!”.
Simply repeat the make install
step every time you want to test your app.
To avoid clutter, remove the graphics from the while loop and make a function containing just the graphics code:
// before main ...
// A neat little graphics function.
void my_fancy_graphics() {
// This fills the screen with blue.
// Color: aarrggbb (like #rrggbb but with 0xff instead of #).
pax_background(&buf, 0xff0000ff);
}
// in main ...
while (1) {
// Call our graphics function.
my_fancy_graphics();
// Draw them to the screen.
disp_flush();
// Await any button press and do another cycle.
// Structure used to receive data.
rp2040_input_message_t message;
// Await forever (because of portMAX_DELAY), a button press.
xQueueReceive(buttonQueue, &message, portMAX_DELAY);
// Is the home button currently pressed?
if (message.input == RP2040_INPUT_BUTTON_HOME && message.state) {
// If home is pressed, exit to launcher.
exit_to_launcher();
}
}
//...
Note that graphics aren’t immediately shown on screen, this is handled by disp_flush()
.
Simple HelloWorld
Let’s start by drawing some white text on the blue background:
//...
// A neat little graphics function.
void my_fancy_graphics() {
// This fills the screen with blue.
// Color: aarrggbb (like #rrggbb but with 0xff instead of #).
pax_background(&buf, 0xff0000ff);
// This draws white text in the top left corner.
float text_x = 0; // Offset from the left.
float text_y = 0; // Offset from the top.
char *my_text = "Hello, World!"; // You can pick any message you'd like.
float text_size = 18; // The normal size for saira regular.
pax_draw_text(&buf, 0xffffffff, pax_font_saira_regular, text_size, text_x, text_y, my_text);
}
//...
Play around with the parameters and see what happens. Try changing text_x
and
text_y
to see where it appears on screen, or maybe change text_font
to (for
example) pax_font_sky
.
Using Images
Using images requires a bit more work, but is still easy to do. First, you
must include #include <pax_codecs.h>
in each file that decodes PNG
images.
Next, find an image that fits in memory (so make it small). Add this
to the main
folder, next to main.c
and include it in CMakeLists.txt
:
idf_component_register(
SRCS
# You source files are here, there might be more than just main.c
"main.c"
INCLUDE_DIRS
# The directories to open header files from are here, again, there might be more.
"." "include"
EMBED_FILES
# This is the location of your image.
${project_dir}/main/my_image.png
)
The EMBED_FILES
directive causes the file’s data to be included mostly as if it were a source file.
You reference the files like so:
//...
extern const uint8_t image_start[] asm("_binary_my_image_png_start");
extern const uint8_t image_end[] asm("_binary_my_image_png_end");
//...
This tells the compiler where to find the image. When embedding files, they will always be named in a similiar manner.
Finally, draw the image using pax_insert_png_buf
. If your image is located
on the SD card or internal filesystem, use pax_insert_png_fd
instead.
//...
// A neat little graphics function.
void my_fancy_graphics() {
// Blue background in case decoding the PNG fails.
pax_background(&buf, 0xff0000ff);
// Draws an image, but does not support transformations.
pax_insert_png_buf(&buf, image_start, image_end-image_start, 0, 0, CODEC_FLAG_OPTIMAL);
}
//...
If your screen turned blue, then the image may have failed to decode.
Try running make monitor
and re-opening the app to see what happened (most
likely, the image is too big to fit in memory). To exit make monitor
, press
CTRL+]
Getting more abstract
Of course, you can do much more than just drawing text! Shown here is an example of drawing a rectangle, a circle and a line:
//...
// Draw a green circle (position is center).
// color x y radius
pax_draw_circle(&buf, 0xff00ff00, 60, 60, 20);
// Draw a transparent red rectangle (position is top left corner).
// color x y width height
pax_draw_rect(&buf, 0xb0ff0000, 40, 10, 70, 50);
// Draw a white line across the entire screen.
// color x1 y1 x2 y2
pax_draw_line(&buf, 0xffffffff, 0, 0, buf.width, buf.height);
//...
PAX (the graphics) also supports matrix transformations.
In short, this feature allows you to stretch, resize, rotate and move around drawing. Consider the following example:
//...
// Save this for later.
pax_push_2d(&buf);
// Modify the translation: shear it.
pax_apply_2d(&buf, matrix_2d_shear(0.5, 0));
// This will no longer have a circular shape.
pax_draw_circle(&buf, 0xff00ff00, 60, 60, 20);
// Restore the matrix.
pax_pop_2d(&buf);
// This will still have a rectangular shape.
pax_draw_rect(&buf, 0xb0ff0000, 40, 10, 70, 50);
//...
Where to Go from Here?
For further details about the library, have a look at the API reference in the library’s repository, robotman2412/pax-graphics
2 - Board Support Package
Most of the board’s peripherals (RP2040 USB and keyboard coprocessor, ICE40 FPGA, ILI9341 LCD controller, BNO055 accelerometer, BME680 air sensor) are initialized and maintained by the board support package. It does not implement each peripheral’s functions, but it provides initialization functions and accessors to the peripheral instances. The BSP is a separate ESP-IDF component that is supposed to be cloned as a git submodule within your project.
At start of your code:
#include "hardware.h"
...
esp_err_t err = bsp_init();
There are additional initialization functions for individual peripherals
(bsp_rp2040_init() ,bsp_ice40_init(), bsp_bno055_init(), bsp_bme680_init()
).
Call them prior to use if you intend to use the specific component. The
ILI9341 display will always be initialized during startup and therefore
does not require separate initialization call.
After initialization, you can use the respective instance accessor
functions to obtain the peripheral’s instance, e.g.
get_ili9341(), get_rp2040(), get_ice40(), get_bno055(), get_bme680()
.
See each function’s documentation in hardware.h
in the component.
In addition, the BSP package gives you defines to the ESP32 pinout. See
mch2022_badge.h
.
3 - WS2812
We all love colorful blinking LEDs, right? There’s a simple API for
accessing the five individually addressable RGB LEDs on the Badge’s
kite. The API is found inside a separate ESP-IDF compontent
ws2812
that is intended as a [git submodule]
(https://github.com/badgeteam/esp32-component-ws2812). If you started
your app development from the [template app]
(https://github.com/badgeteam/mch2022-template-app), it should be
already set up.
There’s a sixth RGB LED on the board (next to the top corner of the display). This LED is controlled via the ICE40 FPGA (see its driver for details).
The LEDs are WS2812-compatible. If you’re not already familiar with these LEDs: Each LED contains red, green and blue LED and a tiny controller that receives 24 bit RGB data from a single serial data line. Further data bits are pushed through to its data output, which is connected to the next LED. This allows many LEDs in a string to be individually controlled.
The LED power supply is switched (together with the SD card). Before using the LEDs, set IO19 (GPIO_SD_PWR) to 1.
Before controlling the LEDs, the driver has to be set up with
the data line connected to the LEDs (GPIO_LED_DATA
). If you
want to control other WS2812 LEDs (e.g. connected to one of the
extension connectors), you can specify a different value.
Setting the LEDs is pretty straightforward: Set up an array of 15 unsigned brightness values (R,G,B for 5 LEDs).
A minimal example to set all LEDs to red:
uint8_t red[] = {0,255,0,0,255,0,0,255,0,0,255,0,0,255,0};
// turn on LED power
gpio_set_direction(GPIO_SD_PWR, GPIO_MODE_OUTPUT);
gpio_set_level(GPIO_SD_PWR, 1);
// initialize WS2812 driver to the appropriate data pin
ws2812_init(GPIO_LED_DATA);
ws2812_send_data(red, sizeof(red));
To animate, change the array values and repeat ws2812_send_data
in regular intervals.