Inky Frame: Library + C++ Examples.
This commit is contained in:
parent
9c079bb1e5
commit
ac2fa97e96
|
@ -44,6 +44,7 @@ add_subdirectory(pico_audio)
|
|||
add_subdirectory(pico_wireless)
|
||||
|
||||
add_subdirectory(inky_pack)
|
||||
add_subdirectory(inky_frame)
|
||||
|
||||
add_subdirectory(automation2040w)
|
||||
add_subdirectory(plasma2040)
|
||||
|
|
|
@ -0,0 +1,3 @@
|
|||
include(inky_frame_jpeg_image.cmake)
|
||||
include(inky_frame_sleepy_head.cmake)
|
||||
include(inky_frame_day_planner.cmake)
|
|
@ -0,0 +1,14 @@
|
|||
set(OUTPUT_NAME inky_frame_day_planner)
|
||||
|
||||
add_executable(
|
||||
${OUTPUT_NAME}
|
||||
inky_frame_day_planner.cpp
|
||||
)
|
||||
|
||||
# Pull in pico libraries that we need
|
||||
target_link_libraries(${OUTPUT_NAME} pico_stdlib inky_frame hardware_pwm hardware_spi hardware_i2c hardware_rtc fatfs sdcard pico_graphics)
|
||||
|
||||
pico_enable_stdio_usb(${OUTPUT_NAME} 1)
|
||||
|
||||
# create map/bin/hex file etc.
|
||||
pico_add_extra_outputs(${OUTPUT_NAME})
|
|
@ -0,0 +1,428 @@
|
|||
#include <cstdio>
|
||||
#include <math.h>
|
||||
#include <time.h>
|
||||
#include <stdio.h>
|
||||
#include "pico/stdlib.h"
|
||||
|
||||
#include "libraries/inky_frame/inky_frame.hpp"
|
||||
|
||||
using namespace pimoroni;
|
||||
|
||||
InkyFrame inky;
|
||||
datetime_t now;
|
||||
datetime_t today;
|
||||
|
||||
|
||||
uint32_t time() {
|
||||
absolute_time_t t = get_absolute_time();
|
||||
return to_ms_since_boot(t);
|
||||
}
|
||||
|
||||
|
||||
std::string day_suffix(datetime_t &dt) {
|
||||
std::string result;
|
||||
if(result.back() == '1') {result = "st"; }
|
||||
else if(result.back() == '2') {result = "nd";}
|
||||
else if(result.back() == '3') {result = "rd";}
|
||||
else {result = "th";}
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
int day_of_week(datetime_t &dt) {
|
||||
// based on implementation found at
|
||||
// https://www.tondering.dk/claus/cal/chrweek.php#calcdow
|
||||
uint a = (14 - dt.month) / 12;
|
||||
uint y = dt.year - a;
|
||||
uint m = dt.month + (12 * a) - 2;
|
||||
return (dt.day + y + (y / 4) - (y / 100) + (y / 400) + ((31 * m) / 12)) % 7;
|
||||
}
|
||||
|
||||
|
||||
std::string day_name(datetime_t &dt) {
|
||||
static std::array<std::string, 7> day_names = {
|
||||
"Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"
|
||||
};
|
||||
return day_names[day_of_week(dt)];
|
||||
}
|
||||
|
||||
std::string month_name(datetime_t &dt) {
|
||||
static std::array<std::string, 12> month_names = {
|
||||
"January", "February", "March", "April", "May", "June",
|
||||
"July", "August","September", "October", "November", "December"
|
||||
};
|
||||
return month_names[dt.month - 1];
|
||||
}
|
||||
|
||||
std::string short_day_name(datetime_t &dt) {
|
||||
static std::array<std::string, 7> day_names = {
|
||||
"Su", "Mo", "Tu", "We", "Th", "Fr", "Sa"
|
||||
};
|
||||
return day_names[day_of_week(dt)];
|
||||
}
|
||||
|
||||
std::string year_name(datetime_t &dt) {
|
||||
return std::to_string(dt.year);
|
||||
}
|
||||
|
||||
int days_in_month(datetime_t &dt)
|
||||
{
|
||||
if(dt.month == 2) { // leap year handling
|
||||
return (dt.year % 400 == 0) || (dt.year % 4 == 0 && dt.year % 100 != 0) ? 29 : 28;
|
||||
}
|
||||
|
||||
return (dt.month == 1 || dt.month == 3 || dt.month == 5 ||
|
||||
dt.month == 7 || dt.month == 8 || dt.month == 10 ||
|
||||
dt.month == 12) ? 31 : 30;
|
||||
}
|
||||
|
||||
|
||||
void center_text(std::string message, int y, float scale = 1.0f) {
|
||||
int32_t tw = inky.measure_text(message, scale);
|
||||
inky.text(message, {(600 / 2) - (tw / 2), y}, scale);
|
||||
}
|
||||
|
||||
void center_text(std::string message, int x, int y, int w, float scale = 1.0f) {
|
||||
int32_t tw = inky.measure_text(message, scale);
|
||||
inky.text(message, {(w / 2) - (tw / 2) + x, y}, scale);
|
||||
}
|
||||
|
||||
void render_calendar_view() {
|
||||
int width = 240;
|
||||
|
||||
|
||||
//inky.text_aspect(1.1f);
|
||||
|
||||
inky.set_pen(InkyFrame::RED);
|
||||
inky.rectangle({0, 0, width, 448});
|
||||
|
||||
inky.set_pen(InkyFrame::WHITE);
|
||||
|
||||
// draw the day of the week
|
||||
//inky.thickness(2);
|
||||
//inky.text_tracking(0.9f);
|
||||
center_text(day_name(now), 0, 40, width, 1.4f);
|
||||
|
||||
// draw the day of the month number
|
||||
//inky.thickness(15);
|
||||
//inky.text_tracking(0.95f);
|
||||
center_text(std::to_string(now.day), 0, 120, width, 3.0f);
|
||||
|
||||
// draw the month and year
|
||||
//inky.thickness(1);
|
||||
//inky.text_tracking(0.9f);
|
||||
center_text(month_name(now) + " " + year_name(now), 0, 180, width, 0.9f);
|
||||
|
||||
inky.line({0, 220}, {width, 220});
|
||||
|
||||
/*
|
||||
|
||||
|
||||
|
||||
*/
|
||||
/*
|
||||
inky.set_pen(InkyFrame::WHITE);
|
||||
inky.thickness(1);
|
||||
inky.line(10, 140, 300, 140);
|
||||
int baseline = get_baseline(inky._font);
|
||||
printf("baseline is %d\n", baseline);
|
||||
inky.text("This is a jolly test for great descenders!", 10, 140 + baseline);
|
||||
*/
|
||||
|
||||
datetime_t first_day = {.year = now.year, .month = now.month, .day = 1};
|
||||
int first_dotw = day_of_week(first_day);
|
||||
|
||||
// draw a calendar
|
||||
int xoff = 20;
|
||||
int yoff = 255;
|
||||
int day_width = (width - xoff - xoff) / 7;
|
||||
int row_height = day_width;
|
||||
int dotw = first_dotw;
|
||||
|
||||
//inky.thickness(2);
|
||||
|
||||
datetime_t header_day = first_day; header_day.day += 7 - first_dotw;
|
||||
for(int dotw_header = 0; dotw_header <= 6; dotw_header++) {
|
||||
center_text(short_day_name(header_day), day_width * dotw_header + xoff, yoff, day_width, 0.6f);
|
||||
header_day.day++;
|
||||
}
|
||||
yoff += row_height;
|
||||
|
||||
//inky.thickness(1);
|
||||
|
||||
for(int day = 1; day <= days_in_month(first_day); day++) {
|
||||
if(day == now.day) {
|
||||
inky.set_pen(InkyFrame::WHITE);
|
||||
inky.rectangle({day_width * dotw + xoff, yoff - (row_height / 2), day_width, row_height});
|
||||
inky.set_pen(InkyFrame::RED);
|
||||
center_text(std::to_string(day), day_width * dotw + xoff, yoff, day_width, 0.6f);
|
||||
} else if(dotw == 0 || dotw == 6) {
|
||||
inky.set_pen(InkyFrame::TAUPE);
|
||||
center_text(std::to_string(day), day_width * dotw + xoff, yoff, day_width, 0.6f);
|
||||
} else {
|
||||
inky.set_pen(InkyFrame::WHITE);
|
||||
center_text(std::to_string(day), day_width * dotw + xoff, yoff, day_width, 0.6f);
|
||||
}
|
||||
|
||||
dotw++;
|
||||
if(dotw >= 7) {dotw = 0; yoff += row_height + 5;}
|
||||
}
|
||||
}
|
||||
|
||||
// horrible hacky way to convert strings into numbers because std::string_view
|
||||
// isn't supported by the various std::itoa methods.
|
||||
int parse_number(const char *p, int len) {
|
||||
p += len - 1; // start at end of string
|
||||
int m = 1, r = 0;
|
||||
while(len--) {r += ((*p--) - 48) * m; m *= 10;}
|
||||
return r;
|
||||
}
|
||||
|
||||
bool today_or_after(datetime_t dt1, datetime_t dt2) {
|
||||
return (dt1.year < dt2.year) ? true :
|
||||
(dt1.year == dt2.year && dt1.month < dt2.month) ? true :
|
||||
(dt1.year == dt2.year && dt1.month == dt2.month && dt1.day < dt2.day) ? true : false;
|
||||
}
|
||||
|
||||
uint datetime_to_timestamp(const datetime_t &dt) {
|
||||
struct tm tm = {
|
||||
.tm_sec = dt.sec, .tm_min = dt.min, .tm_hour = dt.hour,
|
||||
.tm_mday = dt.day, .tm_mon = dt.month, .tm_year = dt.year
|
||||
};
|
||||
return mktime(&tm);
|
||||
}
|
||||
|
||||
bool operator==(const datetime_t &dt1, const datetime_t &dt2) {
|
||||
uint ts1 = datetime_to_timestamp(dt1);
|
||||
uint ts2 = datetime_to_timestamp(dt2);
|
||||
return ts1 == ts2;
|
||||
}
|
||||
|
||||
bool operator>(const datetime_t &dt1, const datetime_t &dt2) {
|
||||
uint ts1 = datetime_to_timestamp(dt1);
|
||||
uint ts2 = datetime_to_timestamp(dt2);
|
||||
return ts1 > ts2;
|
||||
}
|
||||
|
||||
datetime_t datetime_from_iso8601(std::string_view s) {
|
||||
datetime_t dt;
|
||||
|
||||
if(s.back() == 'Z') {
|
||||
// has time element, parse that first
|
||||
dt.sec = parse_number(&s[s.length() - 3], 2);
|
||||
dt.min = parse_number(&s[s.length() - 5], 2);
|
||||
dt.hour = parse_number(&s[s.length() - 7], 2);
|
||||
} else {
|
||||
dt.sec = 0;
|
||||
dt.min = 0;
|
||||
dt.hour = 0;
|
||||
|
||||
}
|
||||
|
||||
dt.year = parse_number(&s[0], 4);
|
||||
dt.month = parse_number(&s[4], 2);
|
||||
dt.day = parse_number(&s[6], 2);
|
||||
|
||||
return dt;
|
||||
}
|
||||
|
||||
|
||||
// horrible hacky way to trim whitespace from a std::string_view. why is
|
||||
// dealing with text always so horrible in c++...? :-)
|
||||
void trim(std::string_view &s) {
|
||||
while(std::isspace(s.front()) && s.length() > 0) {s.remove_prefix(1);}
|
||||
while(std::isspace( s.back()) && s.length() > 0) {s.remove_suffix(1);}
|
||||
}
|
||||
|
||||
|
||||
void render_calendar_entries() {
|
||||
|
||||
FATFS fs;
|
||||
FRESULT fr;
|
||||
|
||||
fr = f_mount(&fs, "", 1);
|
||||
if (fr != FR_OK) {
|
||||
// TODO: show error message on screen - missing or bad SD card
|
||||
}
|
||||
|
||||
// TODO: find all (first 6?) calendar files (.ics) on SD card and assign
|
||||
// each one a colour for entries
|
||||
/*FILINFO fileinfo;
|
||||
auto dir = new DIR();
|
||||
f_opendir(dir, "/");
|
||||
while(f_readdir(dir, &fileinfo) == FR_OK && fileinfo.fname[0]) {
|
||||
printf("- %s %lld\n", fileinfo.fname, fileinfo.fsize);
|
||||
}
|
||||
f_closedir(dir);
|
||||
printf("done!\n");*/
|
||||
|
||||
FIL file;
|
||||
f_open(&file, "jon.ics", FA_READ);
|
||||
|
||||
|
||||
struct event_t {
|
||||
std::string location;
|
||||
std::string summary;
|
||||
datetime_t when = {.hour = 0, .min = 0, .sec = 0};
|
||||
};
|
||||
|
||||
std::vector<event_t> events;
|
||||
event_t event;
|
||||
|
||||
char buffer[1024];
|
||||
while(!f_eof(&file)) {
|
||||
f_gets(buffer, 1024, &file);
|
||||
|
||||
std::string_view line(buffer);
|
||||
|
||||
if(line.substr(0, 9) == "LOCATION:") {
|
||||
line = line.substr(9);
|
||||
trim(line);
|
||||
event.location = line;
|
||||
}
|
||||
|
||||
if(line.substr(0, 8) == "SUMMARY:") {
|
||||
line = line.substr(8);
|
||||
trim(line);
|
||||
event.summary = line;
|
||||
}
|
||||
|
||||
if(line.substr(0, 7) == "DTSTART") {
|
||||
line = line.substr(line.find(":") + 1);
|
||||
trim(line);
|
||||
event.when = datetime_from_iso8601(line);
|
||||
}
|
||||
|
||||
if(line.substr(0, 10) == "END:VEVENT") {
|
||||
if(event.when > today) {
|
||||
events.push_back(event);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sort(events.begin(), events.end(), [](const event_t &e1, const event_t &e2) -> bool {
|
||||
return e2.when > e1.when;
|
||||
});
|
||||
|
||||
printf("%d events\n", events.size());
|
||||
|
||||
/*
|
||||
|
||||
// draw a calendar
|
||||
int xoff = 20;
|
||||
int yoff = 220;
|
||||
int day_width = (width - xoff - xoff) / 7;
|
||||
int row_height = day_width;
|
||||
int dotw = first_dotw;
|
||||
|
||||
inky.thickness(2);
|
||||
|
||||
for(int dotw_header = 0; dotw_header <= 6; dotw_header++) {
|
||||
center_text(short_day_name(dotw_header), day_width * dotw_header + xoff, yoff, day_width, 0.7f);
|
||||
}
|
||||
yoff += row_height;
|
||||
|
||||
inky.thickness(1);
|
||||
|
||||
for(int day = 1; day <= days_in_month(first_day); day++) {
|
||||
if(day == now.day) {
|
||||
inky.set_pen(InkyFrame::WHITE);
|
||||
inky.rectangle(day_width * dotw + xoff, yoff - (row_height / 2), day_width, row_height);
|
||||
inky.set_pen(InkyFrame::RED);
|
||||
center_text(std::to_string(day), day_width * dotw + xoff, yoff, day_width, 0.7f);
|
||||
inky.set_pen(InkyFrame::WHITE);
|
||||
}else{
|
||||
center_text(std::to_string(day), day_width * dotw + xoff, yoff, day_width, 0.7f);
|
||||
}
|
||||
|
||||
dotw++;
|
||||
if(dotw >= 7) {dotw = 0; yoff += row_height;}
|
||||
}
|
||||
*/
|
||||
|
||||
int spacing = 5;
|
||||
int xoff = 240 + spacing;
|
||||
int yoff = spacing;
|
||||
int width = 600 - xoff - spacing;
|
||||
int row_height = 50;
|
||||
|
||||
//inky.text_tracking(1.0f);
|
||||
|
||||
for(auto event : events) {
|
||||
static char buf[16];
|
||||
std::string time = "All Day";
|
||||
if(event.when.hour != 0 || event.when.min != 0 || event.when.sec != 0) {
|
||||
sprintf(buf, "%02d:%02d", event.when.hour, event.when.min);
|
||||
time = std::string(buf);
|
||||
}
|
||||
|
||||
printf("%s\n", time.c_str());
|
||||
|
||||
int lozenge_width = inky.measure_text(time, 0.5f) + 10;
|
||||
int lozenge_height = 21;
|
||||
inky.set_pen(InkyFrame::BLACK);
|
||||
inky.circle({xoff + lozenge_height / 2, yoff + lozenge_height / 2}, lozenge_height / 2);
|
||||
inky.circle({xoff + lozenge_width - lozenge_height / 2, yoff + lozenge_height / 2}, lozenge_height / 2);
|
||||
inky.rectangle({xoff + lozenge_height / 2, yoff, lozenge_width - lozenge_height, lozenge_height});
|
||||
inky.set_pen(InkyFrame::WHITE);
|
||||
//inky.thickness(2);
|
||||
center_text(time, xoff, yoff + lozenge_height / 2, lozenge_width, 0.5f);
|
||||
|
||||
inky.set_pen(InkyFrame::BLACK);
|
||||
//inky.thickness(1);
|
||||
inky.text(event.summary, {xoff + spacing + lozenge_width + 10, yoff + spacing + 10}, 0.65f);
|
||||
|
||||
inky.set_pen(InkyFrame::BLUE);
|
||||
//inky.thickness(1);
|
||||
inky.text(event.location, {xoff + spacing, yoff + spacing + 10 + 20}, 0.6f);
|
||||
|
||||
inky.set_pen(InkyFrame::TAUPE);
|
||||
inky.line({xoff, yoff + row_height}, {xoff + width, yoff + row_height});
|
||||
inky.line({xoff, yoff + row_height + 1}, {xoff + width, yoff + row_height + 1});
|
||||
|
||||
yoff += row_height + spacing;
|
||||
|
||||
}
|
||||
|
||||
|
||||
f_close(&file);
|
||||
}
|
||||
|
||||
int main() {
|
||||
inky.init();
|
||||
|
||||
// init usb serial for debugging and give our terminal monitor a bit of
|
||||
// time to connect
|
||||
stdio_init_all();
|
||||
sleep_ms(500);
|
||||
|
||||
printf("\n");
|
||||
printf("\n");
|
||||
printf("\n");
|
||||
|
||||
printf("initialising inky frame.. ");
|
||||
printf("done!\n");
|
||||
|
||||
|
||||
InkyFrame::WakeUpEvent event = inky.get_wake_up_event();
|
||||
printf("%d\n", event);
|
||||
|
||||
// set date to display
|
||||
now = {.year = 2021, .month = 9, .day = 25, .hour = 8, .min = 7, .sec = 12};
|
||||
today = {.year = now.year, .month = now.month, .day = now.day};
|
||||
|
||||
printf("writing to screen.. ");
|
||||
render_calendar_view();
|
||||
|
||||
|
||||
render_calendar_entries();
|
||||
|
||||
inky.update();
|
||||
printf("done!\n");
|
||||
|
||||
// go to sleep and wake up in five minutes - sweet dreams little inky x
|
||||
inky.sleep(5);
|
||||
|
||||
|
||||
return 0;
|
||||
}
|
|
@ -0,0 +1,14 @@
|
|||
set(OUTPUT_NAME inky_frame_jpeg_image)
|
||||
|
||||
add_executable(
|
||||
${OUTPUT_NAME}
|
||||
inky_frame_jpeg_image.cpp
|
||||
)
|
||||
|
||||
# Pull in pico libraries that we need
|
||||
target_link_libraries(${OUTPUT_NAME} pico_stdlib jpegdec inky_frame fatfs hardware_pwm hardware_spi hardware_i2c hardware_rtc fatfs sdcard pico_graphics)
|
||||
|
||||
pico_enable_stdio_usb(${OUTPUT_NAME} 1)
|
||||
|
||||
# create map/bin/hex file etc.
|
||||
pico_add_extra_outputs(${OUTPUT_NAME})
|
|
@ -0,0 +1,182 @@
|
|||
#include <cstdio>
|
||||
#include <math.h>
|
||||
|
||||
#include <stdio.h>
|
||||
#include "pico/stdlib.h"
|
||||
#include "hardware/vreg.h"
|
||||
|
||||
#include "JPEGDEC.h"
|
||||
|
||||
#include "libraries/inky_frame/inky_frame.hpp"
|
||||
|
||||
using namespace pimoroni;
|
||||
|
||||
FATFS fs;
|
||||
FRESULT fr;
|
||||
|
||||
InkyFrame inky;
|
||||
|
||||
|
||||
JPEGDEC jpeg;
|
||||
struct {
|
||||
int x, y, w, h;
|
||||
} jpeg_decode_options;
|
||||
|
||||
void *jpegdec_open_callback(const char *filename, int32_t *size) {
|
||||
FIL *fil = new FIL;
|
||||
if(f_open(fil, filename, FA_READ)) {return nullptr;}
|
||||
*size = f_size(fil);
|
||||
return (void *)fil;
|
||||
}
|
||||
|
||||
void jpegdec_close_callback(void *handle) {
|
||||
f_close((FIL *)handle);
|
||||
delete (FIL *)handle;
|
||||
}
|
||||
|
||||
int32_t jpegdec_read_callback(JPEGFILE *jpeg, uint8_t *p, int32_t c) {
|
||||
uint br; f_read((FIL *)jpeg->fHandle, p, c, &br);
|
||||
return br;
|
||||
}
|
||||
|
||||
int32_t jpegdec_seek_callback(JPEGFILE *jpeg, int32_t p) {
|
||||
return f_lseek((FIL *)jpeg->fHandle, p) == FR_OK ? 1 : 0;
|
||||
}
|
||||
|
||||
int jpegdec_draw_callback(JPEGDRAW *draw) {
|
||||
uint16_t *p = draw->pPixels;
|
||||
|
||||
int xo = jpeg_decode_options.x;
|
||||
int yo = jpeg_decode_options.y;
|
||||
|
||||
for(int y = 0; y < draw->iHeight; y++) {
|
||||
for(int x = 0; x < draw->iWidth; x++) {
|
||||
int sx = ((draw->x + x + xo) * jpeg_decode_options.w) / jpeg.getWidth();
|
||||
int sy = ((draw->y + y + yo) * jpeg_decode_options.h) / jpeg.getHeight();
|
||||
|
||||
if(xo + sx > 0 && xo + sx < inky.bounds.w && yo + sy > 0 && yo + sy < inky.bounds.h) {
|
||||
inky.set_pixel_dither({xo + sx, yo + sy}, RGB(RGB565(*p)));
|
||||
}
|
||||
|
||||
p++;
|
||||
}
|
||||
}
|
||||
|
||||
return 1; // continue drawing
|
||||
}
|
||||
|
||||
void draw_jpeg(std::string filename, int x, int y, int w, int h) {
|
||||
|
||||
// TODO: this is a horrible way to do it but we need to pass some parameters
|
||||
// into the jpegdec_draw_callback() method somehow and the library isn't
|
||||
// setup to allow any sort of user data to be passed around - yuck
|
||||
jpeg_decode_options.x = x;
|
||||
jpeg_decode_options.y = y;
|
||||
jpeg_decode_options.w = w;
|
||||
jpeg_decode_options.h = h;
|
||||
|
||||
jpeg.open(
|
||||
filename.c_str(),
|
||||
jpegdec_open_callback,
|
||||
jpegdec_close_callback,
|
||||
jpegdec_read_callback,
|
||||
jpegdec_seek_callback,
|
||||
jpegdec_draw_callback);
|
||||
|
||||
jpeg.setPixelType(RGB565_BIG_ENDIAN);
|
||||
|
||||
printf("- starting jpeg decode..");
|
||||
int start = millis();
|
||||
jpeg.decode(0, 0, 0);
|
||||
printf("done in %d ms!\n", int(millis() - start));
|
||||
|
||||
jpeg.close();
|
||||
}
|
||||
|
||||
int main() {
|
||||
inky.init();
|
||||
|
||||
inky.led(InkyFrame::LED_ACTIVITY, 0);
|
||||
|
||||
inky.led(InkyFrame::LED_CONNECTION, 0);
|
||||
|
||||
std::string filename;
|
||||
|
||||
if(inky.get_wake_up_event() == InkyFrame::BUTTON_A_EVENT) {
|
||||
filename = "a.jpg";
|
||||
inky.led(InkyFrame::LED_A, 255);
|
||||
}
|
||||
if(inky.get_wake_up_event() == InkyFrame::BUTTON_B_EVENT) {
|
||||
filename = "b.jpg";
|
||||
inky.led(InkyFrame::LED_B, 255);
|
||||
}
|
||||
if(inky.get_wake_up_event() == InkyFrame::BUTTON_C_EVENT) {
|
||||
filename = "c.jpg";
|
||||
inky.led(InkyFrame::LED_C, 255);
|
||||
}
|
||||
if(inky.get_wake_up_event() == InkyFrame::BUTTON_D_EVENT) {
|
||||
filename = "d.jpg";
|
||||
inky.led(InkyFrame::LED_D, 255);
|
||||
}
|
||||
if(inky.get_wake_up_event() == InkyFrame::BUTTON_E_EVENT) {
|
||||
filename = "e.jpg";
|
||||
inky.led(InkyFrame::LED_E, 255);
|
||||
}
|
||||
if(inky.get_wake_up_event() == InkyFrame::EXTERNAL_TRIGGER_EVENT) {
|
||||
uint8_t i = 128;
|
||||
while(true){
|
||||
inky.led(InkyFrame::LED_ACTIVITY, i);
|
||||
i++;
|
||||
}; // Wait for debugger
|
||||
}
|
||||
|
||||
filename = "butterfly-600x448.jpg";
|
||||
|
||||
//inky.led(InkyFrame::LED_E, 255);
|
||||
//sleep_ms(1000);
|
||||
|
||||
if(filename == "") {
|
||||
inky.sleep();
|
||||
}
|
||||
|
||||
inky.led(InkyFrame::LED_ACTIVITY, 255);
|
||||
// turn on led to show we're awake
|
||||
|
||||
|
||||
// init usb serial for debugging and give our terminal monitor a bit of
|
||||
// time to connect
|
||||
stdio_init_all();
|
||||
printf("Woke with file: %s\n", filename.c_str());
|
||||
|
||||
printf("initialising inky frame.. ");
|
||||
|
||||
printf("mounting sd card.. ");
|
||||
fr = f_mount(&fs, "", 1);
|
||||
if (fr != FR_OK) {
|
||||
printf("Failed to mount SD card, error: %d\n", fr);
|
||||
return 0;
|
||||
}
|
||||
printf("done!\n");
|
||||
|
||||
printf("Listing sd card contents..\n");
|
||||
FILINFO file;
|
||||
auto dir = new DIR();
|
||||
f_opendir(dir, "/");
|
||||
while(f_readdir(dir, &file) == FR_OK && file.fname[0]) {
|
||||
printf("- %s %lld\n", file.fname, file.fsize);
|
||||
}
|
||||
f_closedir(dir);
|
||||
printf("done!\n");
|
||||
|
||||
printf("Displaying file: %s\n", filename.c_str());
|
||||
draw_jpeg(filename, 0, 0, 600, 448);
|
||||
printf("done!\n");
|
||||
|
||||
inky.update();
|
||||
|
||||
sleep_ms(5000);
|
||||
inky.led(InkyFrame::LED_CONNECTION, 0);
|
||||
inky.sleep();
|
||||
|
||||
return 0;
|
||||
}
|
|
@ -0,0 +1,14 @@
|
|||
set(OUTPUT_NAME inky_frame_sleepy_head)
|
||||
|
||||
add_executable(
|
||||
${OUTPUT_NAME}
|
||||
inky_frame_sleepy_head.cpp
|
||||
)
|
||||
|
||||
# Pull in pico libraries that we need
|
||||
target_link_libraries(${OUTPUT_NAME} pico_stdlib inky_frame hardware_pwm hardware_spi hardware_i2c hardware_rtc fatfs sdcard pico_graphics)
|
||||
|
||||
pico_enable_stdio_usb(${OUTPUT_NAME} 1)
|
||||
|
||||
# create map/bin/hex file etc.
|
||||
pico_add_extra_outputs(${OUTPUT_NAME})
|
|
@ -0,0 +1,78 @@
|
|||
#include <cstdio>
|
||||
#include <math.h>
|
||||
|
||||
#include <stdio.h>
|
||||
#include "pico/stdlib.h"
|
||||
|
||||
#include "libraries/inky_frame/inky_frame.hpp"
|
||||
|
||||
using namespace pimoroni;
|
||||
|
||||
InkyFrame inky;
|
||||
|
||||
uint32_t time() {
|
||||
absolute_time_t t = get_absolute_time();
|
||||
return to_ms_since_boot(t);
|
||||
}
|
||||
|
||||
void center_text(std::string message, int y, float scale = 1.0f) {
|
||||
int32_t width = inky.measure_text(message, scale);
|
||||
inky.text(message, {(600 / 2) - (width / 2), y}, 600, scale);
|
||||
}
|
||||
|
||||
int main() {
|
||||
inky.init();
|
||||
inky.set_pen(InkyFrame::Pen::WHITE);
|
||||
inky.clear();
|
||||
inky.set_pen(InkyFrame::Pen::BLUE);
|
||||
|
||||
//inky.set_font("sans");
|
||||
|
||||
// init usb serial for debugging and give our terminal monitor a bit of
|
||||
// time to connect
|
||||
stdio_init_all();
|
||||
sleep_ms(500);
|
||||
|
||||
printf("\n");
|
||||
printf("\n");
|
||||
printf("\n");
|
||||
|
||||
printf("initialising inky frame.. ");
|
||||
printf("done!\n");
|
||||
|
||||
InkyFrame::WakeUpEvent event = inky.get_wake_up_event();
|
||||
printf("%d\n", event);
|
||||
|
||||
printf("writing to screen.. ");
|
||||
center_text("i woke up because...", 100, 0.5f);
|
||||
|
||||
center_text("i'm going back to sleep now, leave me alone...", 350, 0.75f);
|
||||
|
||||
std::map<uint8_t, std::string> insults = {
|
||||
{InkyFrame::BUTTON_A_EVENT, "...you pressed A. You monster."},
|
||||
{InkyFrame::BUTTON_B_EVENT, "...some clown fiddled with B."},
|
||||
{InkyFrame::BUTTON_C_EVENT, "...this bozo thought C looked fun."},
|
||||
{InkyFrame::BUTTON_D_EVENT, "...a wise guy couldn't help pushing D."},
|
||||
{InkyFrame::BUTTON_E_EVENT, "...i was poked right in the E(ye)."},
|
||||
{InkyFrame::RTC_ALARM_EVENT, "...my alarm went off. zzz..."},
|
||||
{InkyFrame::EXTERNAL_TRIGGER_EVENT, "...i was externally triggered."}
|
||||
};
|
||||
|
||||
std::string insult = "...of an unexplained event.";
|
||||
|
||||
if(insults.count(event)) {
|
||||
insult = insults.at(event);
|
||||
}
|
||||
|
||||
|
||||
center_text(insult, 180, 1.0f);
|
||||
|
||||
inky.update();
|
||||
printf("done!\n");
|
||||
|
||||
// go to sleep and wake up in five minutes - sweet dreams little inky x
|
||||
inky.sleep(5);
|
||||
|
||||
|
||||
return 0;
|
||||
}
|
|
@ -34,3 +34,4 @@ add_subdirectory(motor2040)
|
|||
add_subdirectory(inventor2040w)
|
||||
add_subdirectory(adcfft)
|
||||
add_subdirectory(jpegdec)
|
||||
add_subdirectory(inky_frame)
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
include(inky_frame.cmake)
|
|
@ -0,0 +1,11 @@
|
|||
set(LIB_NAME inky_frame)
|
||||
add_library(${LIB_NAME} INTERFACE)
|
||||
|
||||
target_sources(${LIB_NAME} INTERFACE
|
||||
${CMAKE_CURRENT_LIST_DIR}/${LIB_NAME}.cpp
|
||||
)
|
||||
|
||||
target_include_directories(${LIB_NAME} INTERFACE ${CMAKE_CURRENT_LIST_DIR})
|
||||
|
||||
# Pull in pico libraries that we need
|
||||
target_link_libraries(${LIB_NAME} INTERFACE hardware_i2c pico_graphics hardware_spi hardware_pwm bitmap_fonts hershey_fonts pico_stdlib sdcard fatfs pcf85063a uc8159 jpegdec)
|
|
@ -0,0 +1,167 @@
|
|||
#include <string.h>
|
||||
#include <math.h>
|
||||
#include <functional>
|
||||
|
||||
#include "hardware/pwm.h"
|
||||
#include "hardware/watchdog.h"
|
||||
|
||||
#include "inky_frame.hpp"
|
||||
|
||||
namespace pimoroni {
|
||||
|
||||
void gpio_configure(uint gpio, bool dir, bool value = false) {
|
||||
gpio_set_function(gpio, GPIO_FUNC_SIO); gpio_set_dir(gpio, dir); gpio_put(gpio, value);
|
||||
}
|
||||
|
||||
void gpio_configure_pwm(uint gpio) {
|
||||
pwm_config cfg = pwm_get_default_config();
|
||||
pwm_set_wrap(pwm_gpio_to_slice_num(gpio), 65535);
|
||||
pwm_init(pwm_gpio_to_slice_num(gpio), &cfg, true);
|
||||
gpio_set_function(gpio, GPIO_FUNC_PWM);
|
||||
}
|
||||
|
||||
void InkyFrame::init() {
|
||||
// keep the pico awake by holding vsys_en high
|
||||
gpio_set_function(HOLD_VSYS_EN, GPIO_FUNC_SIO);
|
||||
gpio_set_dir(HOLD_VSYS_EN, GPIO_OUT);
|
||||
gpio_put(HOLD_VSYS_EN, true);
|
||||
|
||||
// setup the shift register
|
||||
gpio_configure(SR_CLOCK, GPIO_OUT, true);
|
||||
gpio_configure(SR_LATCH, GPIO_OUT, true);
|
||||
gpio_configure(SR_OUT, GPIO_IN);
|
||||
|
||||
// determine wake up event
|
||||
if(read_shift_register_bit(BUTTON_A)) {_wake_up_event = BUTTON_A_EVENT;}
|
||||
if(read_shift_register_bit(BUTTON_B)) {_wake_up_event = BUTTON_B_EVENT;}
|
||||
if(read_shift_register_bit(BUTTON_C)) {_wake_up_event = BUTTON_C_EVENT;}
|
||||
if(read_shift_register_bit(BUTTON_D)) {_wake_up_event = BUTTON_D_EVENT;}
|
||||
if(read_shift_register_bit(BUTTON_E)) {_wake_up_event = BUTTON_E_EVENT;}
|
||||
if(read_shift_register_bit(RTC_ALARM)) {_wake_up_event = RTC_ALARM_EVENT;}
|
||||
if(read_shift_register_bit(EXTERNAL_TRIGGER)) {_wake_up_event = EXTERNAL_TRIGGER_EVENT;}
|
||||
// there are other reasons a wake event can occur: connect power via usb,
|
||||
// connect a battery, or press the reset button. these cannot be
|
||||
// disambiguated so we don't attempt to report them
|
||||
|
||||
// Disable display update busy wait, we'll handle it ourselves
|
||||
uc8159.set_blocking(false);
|
||||
|
||||
// initialise the rtc
|
||||
rtc.init();
|
||||
|
||||
// setup led pwm
|
||||
gpio_configure_pwm(LED_A);
|
||||
gpio_configure_pwm(LED_B);
|
||||
gpio_configure_pwm(LED_C);
|
||||
gpio_configure_pwm(LED_D);
|
||||
gpio_configure_pwm(LED_E);
|
||||
gpio_configure_pwm(LED_ACTIVITY);
|
||||
gpio_configure_pwm(LED_CONNECTION);
|
||||
}
|
||||
|
||||
bool InkyFrame::is_busy() {
|
||||
// check busy flag on shift register
|
||||
bool busy = !read_shift_register_bit(Flags::EINK_BUSY);
|
||||
return busy;
|
||||
}
|
||||
|
||||
void InkyFrame::update(bool blocking) {
|
||||
while(is_busy()) {
|
||||
tight_loop_contents();
|
||||
}
|
||||
uc8159.update((PicoGraphics_PenP4 *)this);
|
||||
while(is_busy()) {
|
||||
tight_loop_contents();
|
||||
}
|
||||
uc8159.power_off();
|
||||
}
|
||||
|
||||
bool InkyFrame::pressed(Button button) {
|
||||
return read_shift_register_bit(button);
|
||||
}
|
||||
|
||||
// set the LED brightness by generating a gamma corrected target value for
|
||||
// the 16-bit pwm channel. brightness values are from 0 to 100.
|
||||
void InkyFrame::led(LED led, uint8_t brightness) {
|
||||
uint16_t value =
|
||||
(uint16_t)(pow((float)(brightness) / 100.0f, 2.8) * 65535.0f + 0.5f);
|
||||
pwm_set_gpio_level(led, value);
|
||||
}
|
||||
|
||||
uint8_t InkyFrame::read_shift_register() {
|
||||
gpio_put(SR_LATCH, false); sleep_us(1);
|
||||
gpio_put(SR_LATCH, true); sleep_us(1);
|
||||
|
||||
uint8_t result = 0;
|
||||
uint8_t bits = 8;
|
||||
while(bits--) {
|
||||
result <<= 1;
|
||||
result |= gpio_get(SR_OUT) ? 1 : 0;
|
||||
|
||||
gpio_put(SR_CLOCK, false); sleep_us(1);
|
||||
gpio_put(SR_CLOCK, true); sleep_us(1);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
bool InkyFrame::read_shift_register_bit(uint8_t index) {
|
||||
return read_shift_register() & (1U << index);
|
||||
}
|
||||
|
||||
void InkyFrame::sleep(int wake_in_minutes) {
|
||||
if(wake_in_minutes != -1) {
|
||||
// set an alarm to wake inky up in wake_in_minutes - the maximum sleep
|
||||
// is 255 minutes or around 4.5 hours which is the longest timer the RTC
|
||||
// supports, to sleep any longer we need to specify a date and time to
|
||||
// wake up
|
||||
rtc.set_timer(wake_in_minutes, PCF85063A::TIMER_TICK_1_OVER_60HZ);
|
||||
rtc.enable_timer_interrupt(true, false);
|
||||
}
|
||||
|
||||
// release the vsys hold pin so that inky can go to sleep
|
||||
gpio_put(HOLD_VSYS_EN, false);
|
||||
while(true){};
|
||||
}
|
||||
|
||||
void InkyFrame::sleep_until(int second, int minute, int hour, int day) {
|
||||
if(second != -1 || minute != -1 || hour != -1 || day != -1) {
|
||||
// set an alarm to wake inky up at the specified time and day
|
||||
rtc.set_alarm(second, minute, hour, day);
|
||||
rtc.enable_alarm_interrupt(true);
|
||||
}
|
||||
|
||||
// release the vsys hold pin so that inky can go to sleep
|
||||
gpio_put(HOLD_VSYS_EN, false);
|
||||
}
|
||||
|
||||
// Display a portion of an image (icon sheet) at dx, dy
|
||||
void InkyFrame::icon(const uint8_t *data, int sheet_width, int icon_size, int index, int dx, int dy) {
|
||||
image(data, sheet_width, icon_size * index, 0, icon_size, icon_size, dx, dy);
|
||||
}
|
||||
|
||||
// Display an image that fills the screen (286*128)
|
||||
void InkyFrame::image(const uint8_t* data) {
|
||||
image(data, WIDTH, 0, 0, WIDTH, HEIGHT, 0, 0);
|
||||
}
|
||||
|
||||
// Display an image smaller than the screen (sw*sh) at dx, dy
|
||||
void InkyFrame::image(const uint8_t *data, int w, int h, int x, int y) {
|
||||
image(data, w, 0, 0, w, h, x, y);
|
||||
}
|
||||
|
||||
void InkyFrame::image(const uint8_t *data, int stride, int sx, int sy, int dw, int dh, int dx, int dy) {
|
||||
for(auto y = 0; y < dh; y++) {
|
||||
for(auto x = 0; x < dw; x++) {
|
||||
|
||||
uint32_t o = ((y + sy) * (stride / 2)) + ((x + sx) / 2);
|
||||
uint8_t d = ((x + sx) & 0b1) ? data[o] >> 4 : data[o] & 0xf;
|
||||
|
||||
// draw the pixel
|
||||
set_pen(d);
|
||||
pixel({dx + x, dy + y});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,125 @@
|
|||
#pragma once
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "drivers/uc8159/uc8159.hpp"
|
||||
#include "drivers/pcf85063a/pcf85063a.hpp"
|
||||
#include "drivers/fatfs/ff.h"
|
||||
|
||||
#include "libraries/pico_graphics/pico_graphics.hpp"
|
||||
|
||||
namespace pimoroni {
|
||||
|
||||
class InkyFrame : public PicoGraphics_Pen3Bit {
|
||||
public:
|
||||
enum Button : uint8_t {
|
||||
BUTTON_A = 0,
|
||||
BUTTON_B = 1,
|
||||
BUTTON_C = 2,
|
||||
BUTTON_D = 3,
|
||||
BUTTON_E = 4
|
||||
};
|
||||
|
||||
enum LED : uint8_t {
|
||||
LED_ACTIVITY = 6,
|
||||
LED_CONNECTION = 7,
|
||||
LED_A = 11,
|
||||
LED_B = 12,
|
||||
LED_C = 13,
|
||||
LED_D = 14,
|
||||
LED_E = 15
|
||||
};
|
||||
|
||||
enum Flags : uint8_t {
|
||||
RTC_ALARM = 5,
|
||||
EXTERNAL_TRIGGER = 6,
|
||||
EINK_BUSY = 7
|
||||
};
|
||||
|
||||
enum WakeUpEvent : uint8_t {
|
||||
UNKNOWN_EVENT = 0,
|
||||
BUTTON_A_EVENT = 1,
|
||||
BUTTON_B_EVENT = 2,
|
||||
BUTTON_C_EVENT = 3,
|
||||
BUTTON_D_EVENT = 4,
|
||||
BUTTON_E_EVENT = 5,
|
||||
RTC_ALARM_EVENT = 6,
|
||||
EXTERNAL_TRIGGER_EVENT = 7,
|
||||
};
|
||||
|
||||
enum Pen : uint8_t {
|
||||
BLACK = 0,
|
||||
WHITE = 1,
|
||||
GREEN = 2,
|
||||
BLUE = 3,
|
||||
RED = 4,
|
||||
YELLOW = 5,
|
||||
ORANGE = 6,
|
||||
CLEAN = 7,
|
||||
TAUPE = 7
|
||||
};
|
||||
|
||||
protected:
|
||||
WakeUpEvent _wake_up_event = UNKNOWN_EVENT;
|
||||
|
||||
enum Pin {
|
||||
HOLD_VSYS_EN = 2,
|
||||
I2C_INT = 3,
|
||||
I2C_SDA = 4,
|
||||
I2C_SCL = 5,
|
||||
SR_CLOCK = 8,
|
||||
SR_LATCH = 9,
|
||||
SR_OUT = 10,
|
||||
MISO = 16,
|
||||
EINK_CS = 17,
|
||||
CLK = 18,
|
||||
MOSI = 19,
|
||||
SD_DAT0 = 19,
|
||||
SD_DAT1 = 20,
|
||||
SD_DAT2 = 21,
|
||||
SD_DAT3 = 22,
|
||||
SD_CS = 22,
|
||||
ADC0 = 26,
|
||||
EINK_RESET = 27,
|
||||
EINK_DC = 28
|
||||
};
|
||||
|
||||
public:
|
||||
UC8159 uc8159;
|
||||
I2C i2c;
|
||||
PCF85063A rtc;
|
||||
|
||||
static const int WIDTH = 600;
|
||||
static const int HEIGHT = 448;
|
||||
|
||||
InkyFrame() :
|
||||
PicoGraphics_Pen3Bit(WIDTH, HEIGHT, nullptr),
|
||||
uc8159(WIDTH, HEIGHT, {spi0, EINK_CS, CLK, MOSI, PIN_UNUSED, EINK_DC, PIN_UNUSED}),
|
||||
i2c(4, 5),
|
||||
rtc(&i2c) {
|
||||
}
|
||||
|
||||
void init();
|
||||
|
||||
// wake/sleep management
|
||||
void sleep(int wake_in_minutes = -1);
|
||||
void sleep_until(int second = -1, int minute = -1, int hour = -1, int day = -1);
|
||||
WakeUpEvent get_wake_up_event() {return _wake_up_event;}
|
||||
|
||||
// screen management
|
||||
void update(bool blocking=false);
|
||||
static bool is_busy();
|
||||
|
||||
// state
|
||||
bool pressed(Button button);
|
||||
static uint8_t read_shift_register();
|
||||
static bool read_shift_register_bit(uint8_t index);
|
||||
void led(LED led, uint8_t brightness);
|
||||
|
||||
void icon(const uint8_t *data, int sheet_width, int icon_size, int index, int dx, int dy);
|
||||
void image(const uint8_t* data);
|
||||
void image(const uint8_t *data, int w, int h, int x, int y);
|
||||
void image(const uint8_t *data, int stride, int sx, int sy, int dw, int dh, int dx, int dy);
|
||||
};
|
||||
|
||||
}
|
Loading…
Reference in New Issue