Skip to content
Snippets Groups Projects

Compare revisions

Changes are shown as if the source revision was being merged into the target revision. Learn more about comparing revisions.

Source

Select target project
No results found
Select Git revision
  • add_menu_vibration
  • blinkisync-as-preload
  • ch3/api-speed-eval2
  • ch3/dual-core
  • ch3/genapi-refactor
  • ch3/leds-api
  • ch3/splashscreen
  • dualcore
  • dx/flatten-config-module
  • dx/meh-bdf-to-stm
  • freertos-btle
  • genofire/ble-follow-py
  • koalo/bhi160-works-but-dirty
  • koalo/factory-reset
  • koalo/wip/i2c-for-python
  • master
  • msgctl/faultscreen
  • msgctl/textbuffer_api
  • plaetzchen/ios-workaround
  • rahix/bhi
  • rahix/bluetooth-app-favorite
  • rahix/bma
  • rahix/user-space-ctx
  • renze/hatchery_apps
  • renze/safe_mode
  • schleicher-test
  • schneider/212-reset-hardware-when-entering-repl
  • schneider/ancs
  • schneider/ble-buffers
  • schneider/ble-central
  • schneider/ble-ecg-stream-visu
  • schneider/ble-fixes-2020-3
  • schneider/ble-mini-demo
  • schneider/ble-stability
  • schneider/ble-stability-new-phy
  • schneider/bonding
  • schneider/bonding-fail-if-full
  • schneider/bootloader-update-9a0d158
  • schneider/deepsleep
  • schneider/deepsleep2
  • schneider/deepsleep4
  • schneider/default-main
  • schneider/freertos-list-debug
  • schneider/fundamental-test
  • schneider/iaq-python
  • schneider/ir
  • schneider/max30001
  • schneider/max30001-epicaridum
  • schneider/max30001-pycardium
  • schneider/maxim-sdk-update
  • schneider/mp-exception-print
  • schneider/mp-for-old-bl
  • schneider/png
  • schneider/schleicher-test
  • schneider/sdk-0.2.1-11
  • schneider/sdk-0.2.1-7
  • schneider/sleep-display
  • schneider/spo2-playground
  • schneider/stream-locks
  • schneider/v1.17-changelog
  • bootloader-v1
  • release-1
  • v0.0
  • v1.0
  • v1.1
  • v1.10
  • v1.11
  • v1.12
  • v1.13
  • v1.14
  • v1.15
  • v1.16
  • v1.17
  • v1.18
  • v1.2
  • v1.3
  • v1.4
  • v1.5
  • v1.6
  • v1.7
  • v1.8
  • v1.9
82 results

Target

Select target project
  • card10/firmware
  • annejan/firmware
  • astro/firmware
  • fpletz/firmware
  • gerd/firmware
  • fleur/firmware
  • swym/firmware
  • l/firmware
  • uberardy/firmware
  • wink/firmware
  • madonius/firmware
  • mot/firmware
  • filid/firmware
  • q3k/firmware
  • hauke/firmware
  • Woazboat/firmware
  • pink/firmware
  • mossmann/firmware
  • omniskop/firmware
  • zenox/firmware
  • trilader/firmware
  • Danukeru/firmware
  • shoragan/firmware
  • zlatko/firmware
  • sistason/firmware
  • datenwolf/firmware
  • bene/firmware
  • amedee/firmware
  • martinling/firmware
  • griffon/firmware
  • chris007/firmware
  • adisbladis/firmware
  • dbrgn/firmware
  • jelly/firmware
  • rnestler/firmware
  • mh/firmware
  • ln/firmware
  • penguineer/firmware
  • monkeydom/firmware
  • jens/firmware
  • jnaulty/firmware
  • jeffmakes/firmware
  • marekventur/firmware
  • pete/firmware
  • h2obrain/firmware
  • DooMMasteR/firmware
  • jackie/firmware
  • prof_r/firmware
  • Draradech/firmware
  • Kartoffel/firmware
  • hinerk/firmware
  • abbradar/firmware
  • JustTB/firmware
  • LuKaRo/firmware
  • iggy/firmware
  • ente/firmware
  • flgr/firmware
  • Lorphos/firmware
  • matejo/firmware
  • ceddral7/firmware
  • danb/firmware
  • joshi/firmware
  • melle/firmware
  • fitch/firmware
  • deurknop/firmware
  • sargon/firmware
  • markus/firmware
  • kloenk/firmware
  • lucaswerkmeister/firmware
  • derf/firmware
  • meh/firmware
  • dx/card10-firmware
  • torben/firmware
  • yuvadm/firmware
  • AndyBS/firmware
  • klausdieter1/firmware
  • katzenparadoxon/firmware
  • xiretza/firmware
  • ole/firmware
  • techy/firmware
  • thor77/firmware
  • TilCreator/firmware
  • fuchsi/firmware
  • dos/firmware
  • yrlf/firmware
  • PetePriority/firmware
  • SuperVirus/firmware
  • sur5r/firmware
  • tazz/firmware
  • Alienmaster/firmware
  • flo_h/firmware
  • baldo/firmware
  • mmu_man/firmware
  • Foaly/firmware
  • sodoku/firmware
  • Guinness/firmware
  • ssp/firmware
  • led02/firmware
  • Stormwind/firmware
  • arist/firmware
  • coon/firmware
  • mdik/firmware
  • pippin/firmware
  • royrobotiks/firmware
  • zigot83/firmware
  • mo_k/firmware
106 results
Select Git revision
  • ch3/api-speed-eval2
  • ch3/dual-core
  • ch3/genapi-refactor
  • ch3/leds-api
  • color-2
  • color-html
  • color-html-2
  • color-html-3
  • dualcore
  • freertos-btle
  • master
  • micro-modules
  • patch-1
  • patch-2
  • patch-3
  • rahix/bma
  • schneider/mp-for-old-bl
  • wink/trng
18 results
Show changes
Showing
with 5228 additions and 450 deletions
/**
*
* Implementation of public epic_usb_ interface
*
* Configuration and FLASH-specific implementation of USB mass storage device
* Configuration of USB CDCACM device
*
* USB descriptors for both are defined here.
*
*/
#include "epicardium.h"
#include "fs/filesystem.h"
#include "os/config.h"
#include "usb/cdcacm.h"
#include "usb/mass_storage.h"
#include "usb/descriptors.h"
#include "usb/epc_usb.h"
#include "os/core.h"
#include "mx25lba.h"
#include "msc.h"
#include "mxc_sys.h"
#include "wdt.h"
/* memory access callbacks for the mass storage (FLASH) device */
static int mscmem_init();
static uint32_t mscmem_size(void);
static int mscmem_read(uint32_t lba, uint8_t *buffer);
static int mscmem_write(uint32_t lba, uint8_t *buffer);
static int mscmem_start();
static int mscmem_stop();
static int mscmem_ready();
/* USB descriptors, definition at the end of this file */
static usb_device_descriptor_t device_descriptor_cdcacm
__attribute__((aligned(4)));
static struct config_descriptor_cdcacm config_descriptor_cdcacm
__attribute__((aligned(4)));
static usb_device_descriptor_t device_descriptor_msc
__attribute__((aligned(4)));
static struct config_descriptor_msc config_descriptor_msc
__attribute__((aligned(4)));
/* definitions of config structs */
static const msc_mem_t s_mscCallbacks = {
mscmem_init, mscmem_start, mscmem_stop, mscmem_ready,
mscmem_size, mscmem_read, mscmem_write,
};
static struct esb_device_descriptors s_dd_cdcacm = {
.device = &device_descriptor_cdcacm, .cdcacm = &config_descriptor_cdcacm
};
static struct esb_device_descriptors s_dd_msc = {
.device = &device_descriptor_msc, .msc = &config_descriptor_msc
};
static struct esb_config s_cfg_cdcacm = {
.name = "cdcacm",
.init = esb_cdc_init,
.configure = esb_cdc_configure,
.deconfigure = esb_cdc_deconfigure,
.deinit = NULL,
.descriptors = &s_dd_cdcacm,
.deviceData = NULL,
};
static struct esb_config s_cfg_msc = {
.name = "msc FLASH",
.init = esb_msc_init,
.configure = esb_msc_configure,
.deconfigure = esb_msc_deconfigure,
.deinit = NULL,
.descriptors = &s_dd_msc,
.deviceData = (void *)&s_mscCallbacks,
};
/* function implementations */
static int dirty = 0;
static int mscmem_init()
{
LOG_DEBUG("msc", "mem.init");
fatfs_detach();
return mx25_init();
}
static uint32_t mscmem_size(void)
{
return mx25_size();
}
static int mscmem_read(uint32_t lba, uint8_t *buffer)
{
/* Reset the watchdog as this interrupt might be
* firing back to back for a few seconds. */
WDT_ResetTimer(MXC_WDT0);
return mx25_read(lba, buffer);
}
static int mscmem_write(uint32_t lba, uint8_t *buffer)
{
if (dirty == 0) {
//bootloader_dirty();
}
dirty = 2;
/* Reset the watchdog as this interrupt might be
* firing back to back for a few seconds. */
WDT_ResetTimer(MXC_WDT0);
return mx25_write(lba, buffer);
}
static int mscmem_start()
{
return mx25_start();
}
static int mscmem_stop()
{
LOG_DEBUG("msc", "mem.stop");
int ret = mx25_stop();
fatfs_schedule_attach();
return ret;
}
static int mscmem_ready()
{
if (dirty) {
dirty--;
if (dirty == 0) {
LOG_DEBUG("msc", "sync");
mx25_sync();
//bootloader_clean();
}
}
return mx25_ready();
}
static bool s_fsDetached = false;
int epic_usb_shutdown(void)
{
esb_deinit();
if (s_fsDetached) {
fatfs_attach();
load_config();
}
return 0;
}
int epic_usb_storage(void)
{
esb_deinit();
s_fsDetached = true;
return esb_init(&s_cfg_msc);
}
int epic_usb_cdcacm(void)
{
esb_deinit();
if (s_fsDetached) {
fatfs_attach();
load_config();
}
return esb_init(&s_cfg_cdcacm);
}
/* clang-format off */
static usb_device_descriptor_t device_descriptor_cdcacm = {
.bLength = 0x12,
.bDescriptorType = DT_DEVICE,
.bcdUSB = 0x0110,
.bDeviceClass = CLS_COMM,
.bDeviceSubClass = 0x00,
.bDeviceProtocol = 0x00,
.bMaxPacketSize = 0x40,
.idVendor = VNDR_MAXIM,
.idProduct = 0x003C,
.bcdDevice = 0x0100,
.iManufacturer = 0x01,
.iProduct = 0x02,
.iSerialNumber = 0x00,
.bNumConfigurations = 0x01
};
static struct config_descriptor_cdcacm config_descriptor_cdcacm = {
.config = {
.bLength = 0x09,
.bDescriptorType = DT_CONFIG,
.wTotalLength = 0x0043, /* sizeof(struct config_descriptor_cdcacm) */
.bNumInterfaces = 0x02,
.bConfigurationValue= 0x01,
.iConfiguration = 0x00,
.bmAttributes = 0xE0, /* (self-powered, remote wakeup) */
.bMaxPower = 0x01, /* MaxPower is 2ma (units are 2ma/bit) */
},
.comm_interface = { /* First Interface Descriptor For Comm Class Interface */
.bLength = 0x09,
.bDescriptorType = DT_INTERFACE,
.bInterfaceNumber = 0x00,
.bAlternateSetting = 0x00,
.bNumEndpoints = 0x01,
.bInterfaceClass = CLS_COMM,
.bInterfaceSubClass = SCLS_ACM,
.bInterfaceProtocol = 0x00,
.iInterface = 0x00,
},
.header_functional = {
0x05, /* bFunctionalLength = 5 */
DT_FUNCTIONAL,/* bDescriptorType */
0x00, /* bDescriptorSubtype */
0x10, 0x01, /* bcdCDC */
},
.call_management = {
0x05, /* bFunctionalLength = 5 */
DT_FUNCTIONAL,/* bDescriptorType */
0x01, /* bDescriptorSubtype */
0x03, /* bmCapabilities = Device handles call management itself (0x01), management over data class (0x02) */
0x01, /* bmDataInterface */
},
.acm_functional = {
0x04, /* bFunctionalLength = 4 */
DT_FUNCTIONAL,/* bDescriptorType */
0x02, /* bDescriptorSubtype */
0x02, /* bmCapabilities */
},
.union_functional = {
0x05, /* bFunctionalLength = 5 */
DT_FUNCTIONAL,/* bDescriptorType */
0x06, /* bDescriptorSubtype */
0x00, /* bmMasterInterface */
0x01, /* bmSlaveInterface0 */
},
.endpoint_notify = {
.bLength = 0x07,
.bDescriptorType = DT_ENDPOINT,
.bEndpointAddress = 0x80 | ESB_ENDPOINT_CDCACM_NOTIFY,
.bmAttributes = ATTR_INTERRUPT,
.wMaxPacketSize = 0x0040,
.bInterval = 0xff,
},
.data_interface = {
.bLength = 0x09,
.bDescriptorType = DT_INTERFACE,
.bInterfaceNumber = 0x01,
.bAlternateSetting = 0x00,
.bNumEndpoints = 0x02,
.bInterfaceClass = CLS_DATA,
.bInterfaceSubClass = 0x00,
.bInterfaceProtocol = 0x00,
.iInterface = 0x00, //not 1?
},
.endpoint_out = {
.bLength = 0x07,
.bDescriptorType = DT_ENDPOINT,
.bEndpointAddress = ESB_ENDPOINT_CDCACM_OUT,
.bmAttributes = ATTR_BULK,
.wMaxPacketSize = 0x0040,
.bInterval = 0x00,
},
.endpoint_in = {
.bLength = 0x07,
.bDescriptorType = DT_ENDPOINT,
.bEndpointAddress = 0x80 | ESB_ENDPOINT_CDCACM_IN,
.bmAttributes = ATTR_BULK,
.wMaxPacketSize = 0x0040,
.bInterval = 0x00
}
};
static usb_device_descriptor_t device_descriptor_msc = {
.bLength = 0x12,
.bDescriptorType = DT_DEVICE,
.bcdUSB = 0x0200,
.bDeviceClass = CLS_UNSPECIFIED,
.bDeviceSubClass = 0x00,
.bDeviceProtocol = 0x00,
.bMaxPacketSize = 0x40,
.idVendor = VNDR_MAXIM,
.idProduct = 0x4402,
.bcdDevice = 0x0100,
.iManufacturer = 0x01,
.iProduct = 0x02,
.iSerialNumber = 0x03,
.bNumConfigurations = 0x01
};
static struct config_descriptor_msc config_descriptor_msc =
{
.config = {
.bLength = 0x09,
.bDescriptorType = DT_CONFIG,
.wTotalLength = 0x0020,
.bNumInterfaces = 0x01,
.bConfigurationValue= 0x01,
.iConfiguration = 0x00,
.bmAttributes = 0xC0, /* (self-powered, no remote wakeup) */
.bMaxPower = 0x32,
},
.msc_interface = {
.bLength = 0x09,
.bDescriptorType = DT_INTERFACE,
.bInterfaceNumber = 0x00,
.bAlternateSetting = 0x00,
.bNumEndpoints = 0x02,
.bInterfaceClass = CLS_MASS_STOR,
.bInterfaceSubClass = SCLS_SCSI_CMDS,
.bInterfaceProtocol = PROT_BULK_TRANS,
.iInterface = 0x00,
},
.endpoint_out = {
.bLength = 0x07,
.bDescriptorType = DT_ENDPOINT,
.bEndpointAddress = ESB_ENDPOINT_MSC_OUT,
.bmAttributes = ATTR_BULK,
.wMaxPacketSize = 0x0040,
.bInterval = 0x00,
},
.endpoint_in = {
.bLength = 0x07,
.bDescriptorType = DT_ENDPOINT,
.bEndpointAddress = 0x80 | ESB_ENDPOINT_MSC_IN,
.bmAttributes = ATTR_BULK,
.wMaxPacketSize = 0x0040,
.bInterval = 0x00
}
};
/* clang-format on */
#include "gpio.h"
#include "FreeRTOS.h"
#include "timers.h"
static const gpio_cfg_t motor_pin = {
PORT_0, PIN_8, GPIO_FUNC_OUT, GPIO_PAD_NONE
};
static StaticTimer_t vibra_timer_data;
static TimerHandle_t vibra_timer = NULL;
void epic_vibra_set(int status)
{
if (status) {
GPIO_OutSet(&motor_pin);
} else {
GPIO_OutClr(&motor_pin);
}
}
void vTimerCallback()
{
epic_vibra_set(0);
}
void epic_vibra_vibrate(int millis)
{
int ticks = millis * (configTICK_RATE_HZ / 1000);
if (vibra_timer == NULL) {
vibra_timer = xTimerCreateStatic(
"vibratimer",
1,
pdFALSE, /* one-shot */
0,
vTimerCallback,
&vibra_timer_data);
}
/* Make sure the duration is valid */
if (ticks < 1) {
/* Disable a potentially running motor / timer */
epic_vibra_set(0);
xTimerStop(vibra_timer, 0);
return;
}
if (vibra_timer != NULL) {
epic_vibra_set(1);
xTimerChangePeriod(vibra_timer, ticks, 0);
}
}
#include "os/core.h"
#include "modules/modules.h"
#include "timers.h"
#include "mxc_sys.h"
#include "wdt.h"
static TimerHandle_t clearer_timer;
static StaticTimer_t clearer_timer_buffer;
#define CLEAR_PERIOD pdMS_TO_TICKS(2000)
static void watchdog_clearer_callback()
{
WDT_ResetTimer(MXC_WDT0);
}
void watchdog_init()
{
/*
* Don't enable the the watchdog when a debugger is connected.
*/
if ((CoreDebug->DHCSR & CoreDebug_DHCSR_C_DEBUGEN_Msk) != 0) {
return;
}
sys_cfg_wdt_t wdt_cfg = NULL;
WDT_Init(MXC_WDT0, wdt_cfg);
if (WDT_GetResetFlag(MXC_WDT0)) {
WDT_ClearResetFlag(MXC_WDT0);
LOG_INFO("watchdog", "Last reset was due to watchdog timeout");
}
WDT_Enable(MXC_WDT0, 1);
WDT_SetResetPeriod(
MXC_WDT0,
WDT_PERIOD_2_28); /* Clocked by PCLK at 50MHz, reset at 2^28 ticks = 5.4 seconds */
WDT_EnableReset(MXC_WDT0, 1);
}
void watchdog_clearer_init()
{
WDT_ResetTimer(MXC_WDT0);
clearer_timer = xTimerCreateStatic(
"watchdog_clearer_timer",
CLEAR_PERIOD,
pdTRUE,
NULL,
watchdog_clearer_callback,
&clearer_timer_buffer
);
xTimerStart(clearer_timer, 0);
}
#include "leds.h"
#include "pmic.h"
#include "FreeRTOS.h"
#include "task.h"
#include "epicardium.h"
#include "max32665.h"
#include "gpio.h"
#include "modules/modules.h"
#include "drivers/drivers.h"
#include <stdbool.h>
#define OVERHEAD 33
#define EPIC_WS2812_ZERO_HIGH_TICKS (34 - OVERHEAD)
#define EPIC_WS2812_ZERO_LOW_TICKS (86 - OVERHEAD)
#define EPIC_WS2812_ONE_HIGH_TICKS (86 - OVERHEAD)
#define EPIC_WS2812_ONE_LOW_TICKS (34 - OVERHEAD)
#define EPIC_WS2812_RESET_TCKS (4800 - OVERHEAD)
static volatile uint32_t counter = 0;
static inline __attribute__((always_inline)) void
epic_ws2812_delay_ticks(uint32_t ticks)
{
counter = ticks;
while (--counter) {
};
}
static inline __attribute__((always_inline)) void
epic_ws2812_transmit_bit(gpio_cfg_t *pin, uint8_t bit)
{
if (bit != 0) {
GPIO_OutSet(pin);
epic_ws2812_delay_ticks(EPIC_WS2812_ONE_HIGH_TICKS);
GPIO_OutClr(pin);
epic_ws2812_delay_ticks(EPIC_WS2812_ONE_LOW_TICKS);
} else {
GPIO_OutSet(pin);
epic_ws2812_delay_ticks(EPIC_WS2812_ZERO_HIGH_TICKS);
GPIO_OutClr(pin);
epic_ws2812_delay_ticks(EPIC_WS2812_ZERO_LOW_TICKS);
}
}
static inline __attribute__((always_inline)) void
epic_ws2812_transmit_byte(gpio_cfg_t *pin, uint8_t byte)
{
epic_ws2812_transmit_bit(pin, byte & 0b10000000);
epic_ws2812_transmit_bit(pin, byte & 0b01000000);
epic_ws2812_transmit_bit(pin, byte & 0b00100000);
epic_ws2812_transmit_bit(pin, byte & 0b00010000);
epic_ws2812_transmit_bit(pin, byte & 0b00001000);
epic_ws2812_transmit_bit(pin, byte & 0b00000100);
epic_ws2812_transmit_bit(pin, byte & 0b00000010);
epic_ws2812_transmit_bit(pin, byte & 0b00000001);
}
void epic_ws2812_write(uint8_t pin, uint8_t *pixels, uint32_t n_bytes)
{
uint8_t *pixels_end = pixels + n_bytes;
gpio_cfg_t *pin_cfg = &gpio_configs[pin];
taskENTER_CRITICAL();
/*
* If the pin was not previously configured as an output, the
* `epic_gpio_set_pin_mode()` call will pull it down which the first
* neopixel interprets as the color `0x008000`. To fix this, wait a bit
* after mode-setting and then write the new values.
*/
if ((epic_gpio_get_pin_mode(pin) & EPIC_GPIO_MODE_OUT) == 0) {
epic_gpio_set_pin_mode(pin, EPIC_GPIO_MODE_OUT);
}
GPIO_OutClr(pin_cfg);
epic_ws2812_delay_ticks(EPIC_WS2812_RESET_TCKS);
do {
epic_ws2812_transmit_byte(pin_cfg, *pixels);
} while (++pixels != pixels_end);
taskEXIT_CRITICAL();
}
#include <math.h>
static const uint8_t pride_colors[6][3] = {
{ 0xe4, 0x02, 0x02 }, { 0xff, 0x8c, 0x00 }, { 0xff, 0xed, 0x00 },
{ 0x00, 0x80, 0x26 }, { 0x00, 0x4d, 0xff }, { 0x75, 0x06, 0x87 },
};
static void epic_frame(Ctx *epicardium_ctx, float frame)
{
int num_colors = sizeof(pride_colors) / sizeof(pride_colors[0]);
for (int color = 0; color < num_colors; color++) {
ctx_rgba8_stroke(
epicardium_ctx,
pride_colors[color][0],
pride_colors[color][1],
pride_colors[color][2],
0xff
);
ctx_line_width(epicardium_ctx, 10.0);
ctx_line_cap(epicardium_ctx, CTX_CAP_ROUND);
float width =
expf(-pow(frame / 2.0 - 5 + color / 2.0, 2)) * 55.0 +
5.0;
float ypos = color * 10.0 + 40.0 - (num_colors - 1) * 5.0;
ctx_move_to(epicardium_ctx, 80.0 - width, ypos);
ctx_line_to(epicardium_ctx, 80.0 + width, ypos);
ctx_stroke(epicardium_ctx);
}
ctx_save(epicardium_ctx);
ctx_font_size(epicardium_ctx, 20.0f);
ctx_text_baseline(epicardium_ctx, CTX_TEXT_BASELINE_BOTTOM);
ctx_rgba8(epicardium_ctx, 0xff, 0xff, 0xff, 0xff);
ctx_text_align(epicardium_ctx, CTX_TEXT_ALIGN_CENTER);
ctx_move_to(epicardium_ctx, 80.0f, 58.0f);
ctx_text(epicardium_ctx, "Epicardium");
if (strcmp(CARD10_VERSION, "v1.18") != 0) {
ctx_text_align(epicardium_ctx, CTX_TEXT_ALIGN_LEFT);
ctx_move_to(epicardium_ctx, 2.0f, 78.0f);
ctx_text(epicardium_ctx, CARD10_VERSION);
} else {
ctx_move_to(epicardium_ctx, 80.0f, 78.0f);
ctx_text(epicardium_ctx, "Queer Quinoa");
}
ctx_restore(epicardium_ctx);
}
...@@ -5,10 +5,12 @@ ...@@ -5,10 +5,12 @@
#include <errno.h> #include <errno.h>
#ifndef __SPHINX_DOC #ifndef __SPHINX_DOC
/* stddef.h is not recognized by hawkmoth for some odd reason */ /* Some headers are not recognized by hawkmoth for some odd reason */
#include <stddef.h> #include <stddef.h>
#include <stdbool.h>
#else #else
typedef unsigned int size_t; typedef unsigned int size_t;
typedef _Bool bool;
#endif /* __SPHINX_DOC */ #endif /* __SPHINX_DOC */
/* /*
...@@ -27,27 +29,171 @@ typedef unsigned int size_t; ...@@ -27,27 +29,171 @@ typedef unsigned int size_t;
*/ */
/* clang-format off */ /* clang-format off */
#define API_UART_WRITE 0x1 #define API_SYSTEM_EXIT 0x1
#define API_UART_READ 0x2 #define API_SYSTEM_EXEC 0x2
#define API_LEDS_SET 0x3 #define API_SYSTEM_RESET 0x3
#define API_VIBRA_SET 0x4 #define API_BATTERY_VOLTAGE 0x4
#define API_VIBRA_VIBRATE 0x5 #define API_SLEEP 0x5
#define API_STREAM_READ 0x6
#define API_INTERRUPT_ENABLE 0x7 #define API_INTERRUPT_ENABLE 0xA
#define API_INTERRUPT_DISABLE 0x8 #define API_INTERRUPT_DISABLE 0xB
#define API_LIGHT_SENSOR_RUN 0x9 #define API_INTERRUPT_IS_ENABLED 0xC
#define API_LIGHT_SENSOR_GET 0xa
#define API_LIGHT_SENSOR_STOP 0xb #define API_UART_WRITE_STR 0x10
#define API_UART_READ_CHAR 0x11
#define API_DISP_OPEN 0x10 #define API_UART_READ_STR 0x12
#define API_DISP_CLOSE 0x11
#define API_DISP_PRINT 0x12 #define API_STREAM_READ 0x1F
#define API_DISP_CLEAR 0x13
#define API_DISP_UPDATE 0x14 #define API_DISP_OPEN 0x20
#define API_DISP_LINE 0x15 #define API_DISP_CLOSE 0x21
#define API_DISP_RECT 0x16 #define API_DISP_PRINT 0x22
#define API_DISP_CIRC 0x17 #define API_DISP_CLEAR 0x23
#define API_DISP_PIXEL 0x18 #define API_DISP_UPDATE 0x24
#define API_DISP_LINE 0x25
#define API_DISP_RECT 0x26
#define API_DISP_CIRC 0x27
#define API_DISP_PIXEL 0x28
#define API_DISP_FRAMEBUFFER 0x29
#define API_DISP_BACKLIGHT 0x2a
#define API_DISP_PRINT_ADV 0x2b
#define API_DISP_BLIT 0x2d
/* API_BATTERY_VOLTAGE 0x30 */
#define API_BATTERY_CURRENT 0x31
#define API_CHARGEIN_VOLTAGE 0x32
#define API_CHARGEIN_CURRENT 0x33
#define API_SYSTEM_VOLTAGE 0x34
#define API_THERMISTOR_VOLTAGE 0x35
#define API_FILE_OPEN 0x40
#define API_FILE_CLOSE 0x41
#define API_FILE_READ 0x42
#define API_FILE_WRITE 0x44
#define API_FILE_FLUSH 0x45
#define API_FILE_SEEK 0x46
#define API_FILE_TELL 0x47
#define API_FILE_STAT 0x48
#define API_FILE_OPENDIR 0x49
#define API_FILE_READDIR 0x4a
#define API_FILE_UNLINK 0x4b
#define API_FILE_RENAME 0x4c
#define API_FILE_MKDIR 0x4d
#define API_FILE_FS_ATTACHED 0x4e
#define API_RTC_GET_SECONDS 0x50
#define API_RTC_SCHEDULE_ALARM 0x51
#define API_RTC_SET_MILLISECONDS 0x52
#define API_RTC_GET_MILLISECONDS 0x53
#define API_RTC_GET_MONOTONIC_SECONDS 0x54
#define API_RTC_GET_MONOTONIC_MILLISECONDS 0x55
#define API_LEDS_SET 0x60
#define API_LEDS_SET_HSV 0x61
#define API_LEDS_PREP 0x62
#define API_LEDS_PREP_HSV 0x63
#define API_LEDS_UPDATE 0x64
#define API_LEDS_SET_POWERSAVE 0x65
#define API_LEDS_SET_ROCKET 0x66
#define API_LEDS_SET_FLASHLIGHT 0x67
#define API_LEDS_DIM_TOP 0x68
#define API_LEDS_DIM_BOTTOM 0x69
#define API_LEDS_SET_ALL 0x6a
#define API_LEDS_SET_ALL_HSV 0x6b
#define API_LEDS_SET_GAMMA_TABLE 0x6c
#define API_LEDS_CLEAR_ALL 0x6d
#define API_LEDS_GET_ROCKET 0x6e
#define API_LEDS_GET 0x6f
#define API_LEDS_FLASH_ROCKET 0x72
#define API_VIBRA_SET 0x70
#define API_VIBRA_VIBRATE 0x71
#define API_LIGHT_SENSOR_RUN 0x80
#define API_LIGHT_SENSOR_GET 0x81
#define API_LIGHT_SENSOR_STOP 0x82
#define API_LIGHT_SENSOR_READ 0x83
#define API_BUTTONS_READ 0x90
#define API_GPIO_SET_PIN_MODE 0xA0
#define API_GPIO_GET_PIN_MODE 0xA1
#define API_GPIO_WRITE_PIN 0xA2
#define API_GPIO_READ_PIN 0xA3
#define API_TRNG_READ 0xB0
#define API_CSPRNG_READ 0XB1
#define API_PERSONAL_STATE_SET 0xc0
#define API_PERSONAL_STATE_GET 0xc1
#define API_PERSONAL_STATE_IS_PERSISTENT 0xc2
#define API_BME680_INIT 0xD0
#define API_BME680_DEINIT 0xD1
#define API_BME680_GET_DATA 0xD2
#define API_BSEC_GET_DATA 0xD3
#define API_BHI160_ENABLE 0xe0
#define API_BHI160_DISABLE 0xe1
#define API_BHI160_DISABLE_ALL 0xe2
#define API_MAX30001_ENABLE 0xf0
#define API_MAX30001_DISABLE 0xf1
#define API_MAX86150_ENABLE 0x0100
#define API_MAX86150_DISABLE 0x0101
#define API_USB_SHUTDOWN 0x110
#define API_USB_STORAGE 0x111
#define API_USB_CDCACM 0x112
#define API_WS2812_WRITE 0x0120
#define API_CONFIG_GET_STRING 0x130
#define API_CONFIG_GET_INTEGER 0x131
#define API_CONFIG_GET_BOOLEAN 0x132
#define API_CONFIG_SET_STRING 0x133
#define API_BLE_GET_COMPARE_VALUE 0x140
#define API_BLE_COMPARE_RESPONSE 0x141
#define API_BLE_SET_MODE 0x142
#define API_BLE_GET_EVENT 0x143
#define API_BLE_GET_SCAN_REPORT 0x144
#define API_BLE_GET_LAST_PAIRING_NAME 0x145
#define API_BLE_GET_PEER_DEVICE_NAME 0x146
#define API_BLE_FREE_EVENT 0x147
#define API_BLE_HID_SEND_REPORT 0x150
#define API_BLE_ATTS_DYN_CREATE_GROUP 0x160
#define API_BLE_ATTS_DYN_DELETE_GROUP 0x161
#define API_BLE_ATTS_DYN_DELETE_GROUPS 0x169
#define API_BLE_ATTS_DYN_ADD_CHARACTERISTIC 0x16B
#define API_BLE_ATTS_DYN_ADD_DESCRIPTOR 0x163
#define API_BLE_ATTS_SET_BUFFER 0x16A
#define API_BLE_ATTS_SEND_SERVICE_CHANGED_IND 0x168
#define API_BLE_ATTS_SET_ATTR 0x170
#define API_BLE_ATTS_HANDLE_VALUE_NTF 0x171
#define API_BLE_ATTS_HANDLE_VALUE_IND 0x172
#define API_BLE_CLOSE_CONNECTION 0x180
#define API_BLE_IS_CONNECTION_OPEN 0x181
#define API_BLE_SET_DEVICE_NAME 0x182
#define API_BLE_GET_DEVICE_NAME 0x183
#define API_BLE_GET_ADDRESS 0x184
#define API_BLE_ADVERTISE 0x185
#define API_BLE_ADVERTISE_STOP 0x186
#define API_BLE_DISCOVER_PRIMARY_SERVICES 0x187
#define API_BLE_DISCOVER_CHARACTERISTICS 0x188
#define API_BLE_DISCOVER_DESCRIPTORS 0x189
#define API_BLE_ATTC_READ 0x18A
#define API_BLE_ATTC_WRITE_NO_RSP 0x18B
#define API_BLE_ATTC_WRITE 0x18C
#define API_BLE_INIT 0x190
#define API_BLE_DEINIT 0x191
/* clang-format on */ /* clang-format on */
typedef uint32_t api_int_id_t; typedef uint32_t api_int_id_t;
...@@ -59,6 +205,17 @@ typedef uint32_t api_int_id_t; ...@@ -59,6 +205,17 @@ typedef uint32_t api_int_id_t;
* the other direction. These interrupts can be enabled/disabled * the other direction. These interrupts can be enabled/disabled
* (masked/unmasked) using :c:func:`epic_interrupt_enable` and * (masked/unmasked) using :c:func:`epic_interrupt_enable` and
* :c:func:`epic_interrupt_disable`. * :c:func:`epic_interrupt_disable`.
*
* These interrupts work similar to hardware interrupts: You will only get a
* single interrupt, even if multiple events occured since the ISR last ran
* (*this behavior is new since version 1.16*).
*
* .. warning::
*
* Never attempt to call the API from inside an ISR. This might trigger an
* assertion if a call is already being made from thread context. We plan to
* lift this restriction at some point, but for the time being, this is how
* it is.
*/ */
/** /**
...@@ -75,24 +232,164 @@ API(API_INTERRUPT_ENABLE, int epic_interrupt_enable(api_int_id_t int_id)); ...@@ -75,24 +232,164 @@ API(API_INTERRUPT_ENABLE, int epic_interrupt_enable(api_int_id_t int_id));
*/ */
API(API_INTERRUPT_DISABLE, int epic_interrupt_disable(api_int_id_t int_id)); API(API_INTERRUPT_DISABLE, int epic_interrupt_disable(api_int_id_t int_id));
/**
* Check if an API interrupt is enabled.
*
* :param int int_id: The interrupt to be checked
* :param bool* enabled: ``true`` will be stored here if the interrupt is enabled.
* ``false`` otherwise.
*
* :return: 0 on success, ``-EINVAL`` if the interrupt is unknown.
*
* .. versionadded:: 1.16
*/
API(API_INTERRUPT_IS_ENABLED, int epic_interrupt_is_enabled(api_int_id_t int_id, bool *enabled));
/** /**
* The following interrupts are defined: * The following interrupts are defined:
*/ */
/* clang-format off */ /* clang-format off */
/** Reset Handler? **TODO** */ /** Reset Handler */
#define EPIC_INT_RESET 0 #define EPIC_INT_RESET 0
/** ``^C`` interrupt. See :c:func:`epic_isr_ctrl_c` for details. */ /** ``^C`` interrupt. See :c:func:`epic_isr_ctrl_c` for details. */
#define EPIC_INT_CTRL_C 1 #define EPIC_INT_CTRL_C 1
/* Debug interrupt, please ignore */ /** UART Receive interrupt. See :c:func:`epic_isr_uart_rx`. */
#define EPIC_INT_BHI160_TEST 2 #define EPIC_INT_UART_RX 2
API_ISR(EPIC_INT_BHI160_TEST, epic_isr_bhi160_test); /** RTC Alarm interrupt. See :c:func:`epic_isr_rtc_alarm`. */
#define EPIC_INT_RTC_ALARM 3
/** BHI160 Accelerometer. See :c:func:`epic_isr_bhi160_accelerometer`. */
#define EPIC_INT_BHI160_ACCELEROMETER 4
/** BHI160 Orientation Sensor. See :c:func:`epic_isr_bhi160_orientation`. */
#define EPIC_INT_BHI160_ORIENTATION 5
/** BHI160 Gyroscope. See :c:func:`epic_isr_bhi160_gyroscope`. */
#define EPIC_INT_BHI160_GYROSCOPE 6
/** MAX30001 ECG. See :c:func:`epic_isr_max30001_ecg`. */
#define EPIC_INT_MAX30001_ECG 7
/** BHI160 Magnetometer. See :c:func:`epic_isr_bhi160_magnetometer`. */
#define EPIC_INT_BHI160_MAGNETOMETER 8
/** MAX86150 ECG and PPG sensor. See :c:func:`epic_isr_max86150`. */
#define EPIC_INT_MAX86150 9
/** Bluetooth Low Energy event. See :c:func:`epic_isr_ble`. */
#define EPIC_INT_BLE 10
#define EPIC_INT_MAX86150_PROX 11
/* Number of defined interrupts. */ /* Number of defined interrupts. */
#define EPIC_INT_NUM 3 #define EPIC_INT_NUM 12
/* clang-format on */ /* clang-format on */
API_ISR(EPIC_INT_RESET, epic_isr_reset); /*
* "Reset Handler*. This isr is implemented by the API caller and is used to
* reset the core for loading a new payload.
*
* Just listed here for completeness. You don't need to implement this yourself.
*/
API_ISR(EPIC_INT_RESET, __epic_isr_reset);
/**
* Core API
* ========
* The following functions control execution of code on core 1.
*/
/**
* Stop execution of the current payload and return to the menu.
*
* :param int ret: Return code.
* :return: :c:func:`epic_exit` will never return.
*/
void epic_exit(int ret) __attribute__((noreturn));
/*
* The actual epic_exit() function is not an API call because it needs special
* behavior. The underlying call is __epic_exit() which returns. After calling
* this API function, epic_exit() will enter the reset handler.
*/
API(API_SYSTEM_EXIT, void __epic_exit(int ret));
/**
* Stop execution of the current payload and immediately start another payload.
*
* :param char* name: Name (path) of the new payload to start. This can either
* be:
*
* - A path to an ``.elf`` file (l0dable).
* - A path to a ``.py`` file (will be loaded using Pycardium).
* - A path to a directory (assumed to be a Python module, execution starts
* with ``__init__.py`` in this folder).
*
* :return: :c:func:`epic_exec` will only return in case loading went wrong.
* The following error codes can be returned:
*
* - ``-ENOENT``: File not found.
* - ``-ENOEXEC``: File not a loadable format.
*/
int epic_exec(char *name);
/*
* Underlying API call for epic_exec(). The function is not an API call itself
* because it needs special behavior when loading a new payload.
*/
API(API_SYSTEM_EXEC, int __epic_exec(char *name));
/**
* Reset/Restart card10
*/
API(API_SYSTEM_RESET, void epic_system_reset(void));
/**
* Sleep for the specified amount of time.
*
* This call will block for at most the specified amount of time. It allows epicardium to
* reduce clock speed of the system until this call is finished.
*
* This call returns early if an interrupt is signaled from epicardium.
*
* The clock source of epicardium has a limited amount of accuracy. Tolerances
* of +- 10% have been observed. This means that the sleep time also has a
* tolarance of at least +- 10%. The exact amount varies from device to device and
* also with temperature. You should take this into consideration when selecting
* the time you want to sleep.
*
* :param ms: Time to wait in milliseconds
* :returns: 0 if no interrupt happened, ``INT_MAX`` if an interrupt happened and the sleep ended early.
*/
API(API_SLEEP, int epic_sleep(uint32_t ms));
/**
* PMIC API
* ===============
*/
/**
* Read the current battery voltage.
*/
API(API_BATTERY_VOLTAGE, int epic_read_battery_voltage(float *result));
/**
* Read the current battery current.
*/
API(API_BATTERY_CURRENT, int epic_read_battery_current(float *result));
/**
* Read the current charge voltage.
*/
API(API_CHARGEIN_VOLTAGE, int epic_read_chargein_voltage(float *result));
/**
* Read the current charge current.
*/
API(API_CHARGEIN_CURRENT, int epic_read_chargein_current(float *result));
/**
* Read the current system voltage.
*/
API(API_SYSTEM_VOLTAGE, int epic_read_system_voltage(float *result));
/**
* Read the current thermistor voltage.
*/
API(API_THERMISTOR_VOLTAGE, int epic_read_thermistor_voltage(float *result));
/** /**
...@@ -110,22 +407,64 @@ API_ISR(EPIC_INT_RESET, epic_isr_reset); ...@@ -110,22 +407,64 @@ API_ISR(EPIC_INT_RESET, epic_isr_reset);
* :param str: String to write. Does not necessarily have to be NULL-terminated. * :param str: String to write. Does not necessarily have to be NULL-terminated.
* :param length: Amount of bytes to print. * :param length: Amount of bytes to print.
*/ */
API(API_UART_WRITE, void epic_uart_write_str(const char *str, intptr_t length)); API(API_UART_WRITE_STR, void epic_uart_write_str(
const char *str, size_t length
));
/** /**
* Blocking read a single character from any connected serial device. * Try reading a single character from any connected serial device.
* ``epic_uart_read_chr`` only returns once one byte has been read.
* *
* .. todo:: * If nothing is available, :c:func:`epic_uart_read_char` returns ``(-1)``.
*
* :return: The byte or ``(-1)`` if no byte was available.
*/
API(API_UART_READ_CHAR, int epic_uart_read_char(void));
/**
* Read as many characters as possible from the UART queue.
*
* :c:func:`epic_uart_read_str` will not block if no new data is available. For
* an example, see :c:func:`epic_isr_uart_rx`.
*
* :param char* buf: Buffer to be filled with incoming data.
* :param size_t cnt: Size of ``buf``.
* :returns: Number of bytes read. Can be ``0`` if no data was available.
* Might be a negative value if an error occured.
*/
API(API_UART_READ_STR, int epic_uart_read_str(char *buf, size_t cnt));
/**
* **Interrupt Service Routine** for :c:data:`EPIC_INT_UART_RX`
*
* UART receive interrupt. This interrupt is triggered whenever a new character
* becomes available on any connected UART device. This function is weakly
* aliased to :c:func:`epic_isr_default` by default.
* *
* This API function is currently in violation of the API rules. * **Example**:
*
* .. code-block:: cpp
*
* void epic_isr_uart_rx(void)
* {
* char buffer[33];
* int n = epic_uart_read_str(&buffer, sizeof(buffer) - 1);
* buffer[n] = '\0';
* printf("Got: %s\n", buffer);
* }
*
* int main(void)
* {
* epic_interrupt_enable(EPIC_INT_UART_RX);
* *
* :return: The byte. * while (1) {
* __WFI();
* }
* }
*/ */
API(API_UART_READ, char epic_uart_read_chr(void)); API_ISR(EPIC_INT_UART_RX, epic_isr_uart_rx);
/** /**
* **Interrupt Service Routine** * **Interrupt Service Routine** for :c:data:`EPIC_INT_CTRL_C`
* *
* A user-defineable ISR which is triggered when a ``^C`` (``0x04``) is received * A user-defineable ISR which is triggered when a ``^C`` (``0x04``) is received
* on any serial input device. This function is weakly aliased to * on any serial input device. This function is weakly aliased to
...@@ -140,303 +479,2288 @@ API(API_UART_READ, char epic_uart_read_chr(void)); ...@@ -140,303 +479,2288 @@ API(API_UART_READ, char epic_uart_read_chr(void));
API_ISR(EPIC_INT_CTRL_C, epic_isr_ctrl_c); API_ISR(EPIC_INT_CTRL_C, epic_isr_ctrl_c);
/** /**
* LEDs * Buttons
* ==== * =======
*
*/ */
/** Button IDs */
enum epic_button {
/** ``1``, Bottom left button (bit 0). */
BUTTON_LEFT_BOTTOM = 1,
/** ``2``, Bottom right button (bit 1). */
BUTTON_RIGHT_BOTTOM = 2,
/** ``4``, Top right button (bit 2). */
BUTTON_RIGHT_TOP = 4,
/** ``8``, Top left (power) button (bit 3). */
BUTTON_LEFT_TOP = 8,
/** ``8``, Top left (power) button (bit 3). */
BUTTON_RESET = 8,
};
/** /**
* Set one of card10's RGB LEDs to a certain color. * Read buttons.
*
* :c:func:`epic_buttons_read` will read all buttons specified in ``mask`` and
* return set bits for each button which was reported as pressed.
*
* .. note::
*
* The reset button cannot be unmapped from reset functionality. So, while
* you can read it, it cannot be used for app control.
*
* **Example**:
*
* .. code-block:: cpp
*
* #include "epicardium.h"
* *
* :param led: Which led to set. 0-10 are the leds on the top and 11-14 are the 4 "ambient" leds. * uint8_t pressed = epic_buttons_read(BUTTON_LEFT_BOTTOM | BUTTON_RIGHT_BOTTOM);
* :param r: Red component of the color. *
* :param g: Green component of the color. * if (pressed & BUTTON_LEFT_BOTTOM) {
* :param b: Blue component of the color. * // Bottom left button is pressed
* }
*
* if (pressed & BUTTON_RIGHT_BOTTOM) {
* // Bottom right button is pressed
* }
*
* :param uint8_t mask: Mask of buttons to read. The 4 LSBs correspond to the 4
* buttons:
*
* ===== ========= ============ ===========
* ``3`` ``2`` ``1`` ``0``
* ----- --------- ------------ -----------
* Reset Right Top Right Bottom Left Bottom
* ===== ========= ============ ===========
*
* Use the values defined in :c:type:`epic_button` for masking, as shown in
* the example above.
* :return: Returns nonzero value if unmasked buttons are pushed.
*/ */
API(API_LEDS_SET, void epic_leds_set(int led, uint8_t r, uint8_t g, uint8_t b)); API(API_BUTTONS_READ, uint8_t epic_buttons_read(uint8_t mask));
/** /**
* Sensor Data Streams * Wristband GPIO
* =================== * ==============
* A few of card10's sensors can do continuous measurements. To allow
* performant access to their data, the following function is made for generic
* access to streams.
*/ */
/** GPIO pins IDs */
enum gpio_pin {
/** ``1``, Wristband connector 1 */
EPIC_GPIO_WRISTBAND_1 = 1,
/** ``2``, Wristband connector 2 */
EPIC_GPIO_WRISTBAND_2 = 2,
/** ``3``, Wristband connector 3 */
EPIC_GPIO_WRISTBAND_3 = 3,
/** ``4``, Wristband connector 4 */
EPIC_GPIO_WRISTBAND_4 = 4,
};
/** GPIO pin modes */
enum gpio_mode {
/** Configure the pin as input */
EPIC_GPIO_MODE_IN = (1<<0),
/** Configure the pin as output */
EPIC_GPIO_MODE_OUT = (1<<1),
EPIC_GPIO_MODE_ADC = (1<<2),
/** Enable the internal pull-up resistor */
EPIC_GPIO_PULL_UP = (1<<6),
/** Enable the internal pull-down resistor */
EPIC_GPIO_PULL_DOWN = (1<<7),
};
/** /**
* Read sensor data into a buffer. ``epic_stream_read()`` will read as many * Set the mode of a card10 GPIO pin.
* sensor samples into the provided buffer as possible and return the number of
* samples written. If no samples are available, ``epic_stream_read()`` will
* return ``0`` immediately.
* *
* ``epic_stream_read()`` expects the provided buffer to have a size which is a * :c:func:`epic_gpio_set_pin_mode` will set the pin specified by ``pin`` to the mode ``mode``.
* multiple of the sample size for the given stream. For the sample-format and * If the specified pin ID is not valid this function will do nothing.
* size, please consult the sensors documentation.
* *
* Before reading the internal sensor sample queue, ``epic_stream_read()`` will * **Example:**
* call a sensor specific *poll* function to allow the sensor driver to fetch
* new samples from its hardware. This should, however, never take a long
* amount of time.
* *
* :param int sd: Sensor Descriptor. You get sensor descriptors as return * .. code-block:: cpp
* values when activating the respective sensors.
* :param void* buf: Buffer where sensor data should be read into.
* :param size_t count: How many bytes to read at max. Note that fewer bytes
* might be read. In most cases, this should be ``sizeof(buf)``.
* :return: Number of data packets read (**not** number of bytes) or a negative
* error value. Possible errors:
* *
* - ``-ENODEV``: Sensor is not currently available. * #include "epicardium.h"
* - ``-EBADF``: The given sensor descriptor is unknown.
* - ``-EINVAL``: ``count`` is not a multiple of the sensor's sample size.
* - ``-EBUSY``: The descriptor table lock could not be acquired.
* *
* **Example**: * // Configure wristband pin 1 as output.
* if (epic_gpio_set_pin_mode(GPIO_WRISTBAND_1, GPIO_MODE_OUT)) {
* // Do your error handling here...
* }
*
* :param uint8_t pin: ID of the pin to configure. Use on of the IDs defined in :c:type:`gpio_pin`.
* :param uint8_t mode: Mode to be configured. Use a combination of the :c:type:`gpio_mode` flags.
* :returns: ``0`` if the mode was set, ``-EINVAL`` if ``pin`` is not valid or the mode could not be set.
*/
API(API_GPIO_SET_PIN_MODE, int epic_gpio_set_pin_mode(
uint8_t pin, uint8_t mode
));
/**
* Get the mode of a card10 GPIO pin.
*
* :c:func:`epic_gpio_get_pin_mode` will get the current mode of the GPIO pin specified by ``pin``.
*
* **Example:**
* *
* .. code-block:: cpp * .. code-block:: cpp
* *
* #include "epicardium.h" * #include "epicardium.h"
* *
* struct foo_measurement sensor_data[16]; * // Get the mode of wristband pin 1.
* int foo_sd, n; * int mode = epic_gpio_get_pin_mode(GPIO_WRISTBAND_1);
* if (mode < 0) {
* // Do your error handling here...
* } else {
* // Do something with the queried mode information
* }
* *
* foo_sd = epic_foo_sensor_enable(9001); * :param uint8_t pin: ID of the pin to get the configuration of. Use on of the IDs defined in :c:type:`gpio_pin`.
* :returns: Configuration byte for the specified pin or ``-EINVAL`` if the pin is not valid.
*/
API(API_GPIO_GET_PIN_MODE, int epic_gpio_get_pin_mode(uint8_t pin));
/**
* Write value to a card10 GPIO pin,
* *
* while (1) { * :c:func:`epic_gpio_write_pin` will set the value of the GPIO pin described by ``pin`` to either on or off depending on ``on``.
* n = epic_stream_read(
* foo_sd,
* &sensor_data,
* sizeof(sensor_data)
* );
* *
* // Print out the measured sensor samples * **Example:**
* for (int i = 0; i < n; i++) { *
* printf("Measured: %?\n", sensor_data[i]); * .. code-block:: cpp
*
* #include "epicardium.h"
*
* // Get the mode of wristband pin 1.
* int mode = epic_gpio_get_pin_mode(GPIO_WRISTBAND_1);
* if (mode < 0) {
* // Do your error handling here...
* } else {
* // Do something with the queried mode information
* } * }
*
* :param uint8_t pin: ID of the pin to get the configuration of. Use on of the IDs defined in :c:type:`gpio_pin`.
* :param bool on: Sets the pin to either true (on/high) or false (off/low).
* :returns: ``0`` on succcess, ``-EINVAL`` if ``pin`` is not valid or is not configured as an output.
*/
API(API_GPIO_WRITE_PIN, int epic_gpio_write_pin(uint8_t pin, bool on));
/**
* Read value of a card10 GPIO pin.
*
* :c:func:`epic_gpio_read_pin` will get the value of the GPIO pin described by ``pin``.
*
* **Example:**
*
* .. code-block:: cpp
*
* #include "epicardium.h"
*
* // Get the current value of wristband pin 1.
* uint32_t value = epic_gpio_read_pin(GPIO_WRISTBAND_1);
* if (mode == -EINVAL) {
* // Do your error handling here...
* } else {
* // Do something with the current value
* } * }
*
* :param uint8_t pin: ID of the pin to get the configuration of. Use on of the IDs defined in :c:type:`gpio_pin`.
* :returns: ``-EINVAL`` if ``pin`` is not valid, an integer value otherwise.
*/ */
API(API_STREAM_READ, int epic_stream_read(int sd, void *buf, size_t count)); API(API_GPIO_READ_PIN, int epic_gpio_read_pin(uint8_t pin));
/** /**
* Misc * LEDs
* ==== * ====
*/ */
/** /**
* Turn vibration motor on or off * Set one of card10's RGB LEDs to a certain color in RGB format.
* *
* :param status: 1 to turn on, 0 to turn off. * This function is rather slow when setting multiple LEDs, use
* :c:func:`leds_set_all` or :c:func:`leds_prep` + :c:func:`leds_update`
* instead.
*
* :param int led: Which LED to set. 0-10 are the LEDs on the top and 11-14
* are the 4 "ambient" LEDs.
* :param uint8_t r: Red component of the color.
* :param uint8_t g: Green component of the color.
* :param uint8_t b: Blue component of the color.
*/ */
API(API_VIBRA_SET, void epic_vibra_set(int status)); API(API_LEDS_SET, void epic_leds_set(int led, uint8_t r, uint8_t g, uint8_t b));
/** /**
* Turn vibration motor on for a given time * Get one of card10's RGB LEDs in format of RGB.
* *
* :param millis: number of milliseconds to run the vibration motor. * :c:func:`epic_leds_get_rgb` will get the value of a RGB LED described by ``led``.
*
* :param int led: Which LED to get. 0-10 are the LEDs on the top and 11-14
* are the 4 "ambient" LEDs.
* :param uint8_t * rgb: need tree byte array to get the value of red, green and blue.
* :returns: ``0`` on success or ``-EPERM`` if the LED is blocked by personal-state.
*
* .. versionadded:: 1.10
*/ */
API(API_VIBRA_VIBRATE, void epic_vibra_vibrate(int millis)); API(API_LEDS_GET, int epic_leds_get_rgb(int led, uint8_t * rgb));
/** /**
* Display * Set one of the rockets to flash for a certain time.
* ======= *
* :c:func:`epic_leds_flash_rocket` will set a timer for the flash of a rocket.
*
* :param int led: Number of the rocket that sould flash
* :param uint8_t value: brightness of the 'on'-state of this rocket ( 0 < value < 32)
* :param int millis: time in milliseconds defining the duration of the flash (i.e. how long is the rocket 'on')
*
* .. versionadded:: 1.16
*/ */
API(API_LEDS_FLASH_ROCKET, void epic_leds_flash_rocket(int led, uint8_t valiue, int millis));
/** Line-Style */ /**
enum disp_linestyle { * Set one of card10's RGB LEDs to a certain color in HSV format.
/** */ *
LINESTYLE_FULL = 0, * This function is rather slow when setting multiple LEDs, use
/** */ * :c:func:`leds_set_all_hsv` or :c:func:`leds_prep_hsv` + :c:func:`leds_update`
LINESTYLE_DOTTED = 1 * instead.
}; *
* :param int led: Which LED to set. 0-10 are the LEDs on the top and 11-14 are the 4 "ambient" LEDs.
/** Fill-Style */ * :param float h: Hue component of the color. (0 <= h < 360)
enum disp_fillstyle { * :param float s: Saturation component of the color. (0 <= s <= 1)
/** */ * :param float v: Value/Brightness component of the color. (0 <= v <= 0)
FILLSTYLE_EMPTY = 0, */
/** */ API(API_LEDS_SET_HSV, void epic_leds_set_hsv(
FILLSTYLE_FILLED = 1 int led, float h, float s, float v
}; ));
/** /**
* Locks the display. * Set multiple of card10's RGB LEDs to a certain color in RGB format.
* *
* :return: ``0`` on success or a negative value in case of an error: * The first ``len`` leds are set, the remaining ones are not modified.
* *
* - ``-EBUSY``: Display was already locked from another task. * :param uint8_t[len][r,g,b] pattern: Array with RGB Values with 0 <= len <=
* 15. 0-10 are the LEDs on the top and 11-14 are the 4 "ambient" LEDs.
* :param uint8_t len: Length of 1st dimension of ``pattern``, see above.
*/ */
API(API_DISP_OPEN, int epic_disp_open()); API(API_LEDS_SET_ALL, void epic_leds_set_all(uint8_t *pattern, uint8_t len));
/** /**
* Unlocks the display again. * Set multiple of card10's RGB LEDs to a certain color in HSV format.
* *
* :return: ``0`` on success or a negative value in case of an error: * The first ``len`` led are set, the remaining ones are not modified.
* *
* - ``-EBUSY``: Display was already locked from another task. * :param uint8_t[len][h,s,v] pattern: Array of format with HSV Values with 0
* <= len <= 15. 0-10 are the LEDs on the top and 11-14 are the 4 "ambient"
* LEDs. (0 <= h < 360, 0 <= s <= 1, 0 <= v <= 1)
* :param uint8_t len: Length of 1st dimension of ``pattern``, see above.
*/ */
API(API_DISP_CLOSE, int epic_disp_close()); API(API_LEDS_SET_ALL_HSV, void epic_leds_set_all_hsv(
float *pattern, uint8_t len
));
/**
* Prepare one of card10's RGB LEDs to be set to a certain color in RGB format.
*
* Use :c:func:`leds_update` to apply changes.
*
* :param int led: Which LED to set. 0-10 are the LEDs on the top and 11-14
* are the 4 "ambient" LEDs.
* :param uint8_t r: Red component of the color.
* :param uint8_t g: Green component of the color.
* :param uint8_t b: Blue component of the color.
*/
API(API_LEDS_PREP, void epic_leds_prep(
int led, uint8_t r, uint8_t g, uint8_t b
));
/**
* Prepare one of card10's RGB LEDs to be set to a certain color in HSV format.
*
* Use :c:func:`leds_update` to apply changes.
*
* :param int led: Which LED to set. 0-10 are the LEDs on the top and 11-14
* are the 4 "ambient" LEDs.
* :param uint8_t h: Hue component of the color. (float, 0 <= h < 360)
* :param uint8_t s: Saturation component of the color. (float, 0 <= s <= 1)
* :param uint8_t v: Value/Brightness component of the color. (float, 0 <= v <= 0)
*/
API(API_LEDS_PREP_HSV, void epic_leds_prep_hsv(
int led, float h, float s, float v
));
/**
* Set global brightness for top RGB LEDs.
*
* Aside from PWM, the RGB LEDs' overall brightness can be controlled with a
* current limiter independently to achieve a higher resolution at low
* brightness which can be set with this function.
*
* :param uint8_t value: Global brightness of top LEDs. (1 <= value <= 8, default = 1)
*/
API(API_LEDS_DIM_BOTTOM, void epic_leds_dim_bottom(uint8_t value));
/**
* Set global brightness for bottom RGB LEDs.
*
* Aside from PWM, the RGB LEDs' overall brightness can be controlled with a
* current limiter independently to achieve a higher resolution at low
* brightness which can be set with this function.
*
* :param uint8_t value: Global brightness of bottom LEDs. (1 <= value <= 8, default = 8)
*/
API(API_LEDS_DIM_TOP, void epic_leds_dim_top(uint8_t value));
/**
* Enables or disables powersave mode.
*
* Even when set to zero, the RGB LEDs still individually consume ~1mA.
* Powersave intelligently switches the supply power in groups. This introduces
* delays in the magnitude of ~10µs, so it can be disabled for high speed
* applications such as POV.
*
* :param bool eco: Activates powersave if true, disables it when false. (default = True)
*/
API(API_LEDS_SET_POWERSAVE, void epic_leds_set_powersave(bool eco));
/**
* Updates the RGB LEDs with changes that have been set with :c:func:`leds_prep`
* or :c:func:`leds_prep_hsv`.
*
* The LEDs can be only updated in bulk, so using this approach instead of
* :c:func:`leds_set` or :c:func:`leds_set_hsv` significantly reduces the load
* on the corresponding hardware bus.
*/
API(API_LEDS_UPDATE, void epic_leds_update(void));
/**
* Set the brightness of one of the rocket LEDs.
*
* :param int led: Which LED to set.
*
* +-------+--------+----------+
* | ID | Color | Location |
* +=======+========+==========+
* | ``0`` | Blue | Left |
* +-------+--------+----------+
* | ``1`` | Yellow | Top |
* +-------+--------+----------+
* | ``2`` | Green | Right |
* +-------+--------+----------+
* :param uint8_t value: Brightness of LED (value between 0 and 31).
*/
API(API_LEDS_SET_ROCKET, void epic_leds_set_rocket(int led, uint8_t value));
/**
* Get the brightness of one of the rocket LEDs.
*
* :param int led: Which LED to get.
*
* +-------+--------+----------+
* | ID | Color | Location |
* +=======+========+==========+
* | ``0`` | Blue | Left |
* +-------+--------+----------+
* | ``1`` | Yellow | Top |
* +-------+--------+----------+
* | ``2`` | Green | Right |
* +-------+--------+----------+
* :returns value: Brightness of LED (value between 0 and 31) or ``-EINVAL`` if the LED/rocket does not exists.
*
* .. versionadded:: 1.10
*/
API(API_LEDS_GET_ROCKET, int epic_leds_get_rocket(int led));
/**
* Turn on the bright side LED which can serve as a flashlight if worn on the left wrist or as a rad tattoo illuminator if worn on the right wrist.
*
*:param bool power: Side LED on if true.
*/
API(API_LEDS_SET_FLASHLIGHT, void epic_set_flashlight(bool power));
/**
* Set gamma lookup table for individual rgb channels.
*
* Since the RGB LEDs' subcolor LEDs have different peak brightness and the
* linear scaling introduced by PWM is not desireable for color accurate work,
* custom lookup tables for each individual color channel can be loaded into the
* Epicardium's memory with this function.
*
* :param uint8_t rgb_channel: Color whose gamma table is to be updated, 0->Red, 1->Green, 2->Blue.
* :param uint8_t[256] gamma_table: Gamma lookup table. (default = 4th order power function rounded up)
*/
API(API_LEDS_SET_GAMMA_TABLE, void epic_leds_set_gamma_table(
uint8_t rgb_channel, uint8_t *gamma_table
));
/**
* Set all LEDs to a certain RGB color.
*
* :param uint8_t r: Value for the red color channel.
* :param uint8_t g: Value for the green color channel.
* :param uint8_t b: Value for the blue color channel.
*/
API(API_LEDS_CLEAR_ALL, void epic_leds_clear_all(
uint8_t r, uint8_t g, uint8_t b
));
/**
* BME680
* ======
*
* .. versionadded:: 1.4
*/
/**
* BME680 Sensor Data
*/
struct bme680_sensor_data {
/** Temperature in degree celsius */
float temperature;
/** Humidity in % relative humidity */
float humidity;
/** Pressure in hPa */
float pressure;
/** Gas resistance in Ohms */
float gas_resistance;
};
/**
* Initialize the BM680 sensor.
*
* .. versionadded:: 1.4
*
* :return: 0 on success or ``-Exxx`` on error. The following
* errors might occur:
*
* - ``-EFAULT``: On NULL-pointer.
* - ``-EINVAL``: Invalid configuration.
* - ``-EIO``: Communication with the device failed.
* - ``-ENODEV``: Device was not found.
*/
API(API_BME680_INIT, int epic_bme680_init());
/**
* De-Initialize the BM680 sensor.
*
* .. versionadded:: 1.4
*
* :return: 0 on success or ``-Exxx`` on error. The following
* errors might occur:
*
* - ``-EFAULT``: On NULL-pointer.
* - ``-EINVAL``: Invalid configuration.
* - ``-EIO``: Communication with the device failed.
* - ``-ENODEV``: Device was not found.
*/
API(API_BME680_DEINIT, int epic_bme680_deinit());
/**
* Get the current BME680 data.
*
* .. versionadded:: 1.4
*
* :param data: Where to store the environmental data.
* :return: 0 on success or ``-Exxx`` on error. The following
* errors might occur:
*
* - ``-EFAULT``: On NULL-pointer.
* - ``-EINVAL``: Sensor not initialized.
* - ``-EIO``: Communication with the device failed.
* - ``-ENODEV``: Device was not found.
*/
API(API_BME680_GET_DATA, int epic_bme680_read_sensors(
struct bme680_sensor_data *data
));
/**
* .. _bsec_api:
*
* BSEC
* ----
* The Bosch Sensortec Environmental Cluster (BSEC) library
* allows to estimate an indoor air qualtiy (IAQ) metric as
* well as CO2 and VOC content equivalents using the gas sensor
* of the BME680.
*
* As it is a proprietary binary blob, it has to be enabled using
* the ``bsec_enable`` configuration option (see :ref:`card10_cfg`).
*
* Please also have a look at the BME680 datasheet and some of
* the BSEC documentation:
*
* https://git.card10.badge.events.ccc.de/card10/hardware/-/blob/master/datasheets/bosch/BST-BME680-DS001.pdf
*
* https://git.card10.badge.events.ccc.de/card10/firmware/-/blob/master/lib/vendor/Bosch/BSEC/integration_guide/BST-BME680-Integration-Guide-AN008-48.pdf
*/
/**
* BSEC Sensor Data
*/
struct bsec_sensor_data {
/** Compensated temperature in degree celsius */
float temperature;
/** Compensated humidity in % relative humidity */
float humidity;
/** Pressure in hPa */
float pressure;
/** Gas resistance in Ohms */
float gas_resistance;
/** Timestamp in of the measurement in UNIX time (seconds since
* 1970-01-01 00:00:00 UTC)*/
uint32_t timestamp;
/** Accuracy of IAQ, CO2 equivalent and breath VOC equivalent:
*
* 0: Stabilization / run-in ongoing:
* This means that the sensor still needs to warm up. Takes about
* 5 min after activation of BSEC / reboot.
*
* 1: Low accuracy:
* The sensor has not yet been calibrated. BSEC needs to collect
* more data to calibrate the sensor. This can take multiple
* hours.
*
* BSEC documentation: To reach high accuracy(3) please expose
* sensor once to good air (e.g. outdoor air) and bad air (e.g.
* box with exhaled breath) for auto-trimming
*
* 2: Medium accuracy: auto-trimming ongoing
* BSEC has detected that it needs to recalibrate the sensor.
* This is an automatic process and usally finishes after tens
* of minutes. Can happen every now and then.
*
* 3: High accuracy:
* The sensor has warmed up and is calibrated.
*
* From BSEC documentation:
* IAQ accuracy indicator will notify the user when they should
* initiate a calibration process. Calibration is performed automatically
* in the background if the sensor is exposed to clean and polluted air
* for approximately 30 minutes each.
*
* See also:
* https://community.bosch-sensortec.com/t5/MEMS-sensors-forum/BME680-IAQ-accuracy-definition/m-p/5931/highlight/true#M10
*/
uint8_t accuracy;
/** Indoor Air Quality with range 0 to 500
*
* Statement from the Bosch BSEC library:
*
* Indoor-air-quality (IAQ) gives an indication of the relative change
* in ambient TVOCs detected by BME680.
*
* The IAQ scale ranges from 0 (clean air) to 500 (heavily polluted air).
* During operation, algorithms automatically calibrate and adapt
* themselves to the typical environments where the sensor is operated
* (e.g., home, workplace, inside a car, etc.).This automatic background
* calibration ensures that users experience consistent IAQ performance.
* The calibration process considers the recent measurement history (typ.
* up to four days) to ensure that IAQ=25 corresponds to typical good air
* and IAQ=250 indicates typical polluted air.
*
* Please also consult the BME680 datsheet (pages 9 and 21) as well:
* https://git.card10.badge.events.ccc.de/card10/hardware/-/blob/master/datasheets/bosch/BST-BME680-DS001.pdf
*
*/
int32_t indoor_air_quality;
/** Unscaled IAQ value.
*
* See this post for details:
* https://community.bosch-sensortec.com/t5/MEMS-sensors-forum/BME680-strange-IAQ-and-CO2-values/m-p/9667/highlight/true#M1505
*/
int32_t static_indoor_air_quality;
/** Estimation of equivalant CO2 content in the air in ppm. */
float co2_equivalent;
/** Estimation of equivalant breath VOC content in the air in ppm. */
float breath_voc_equivalent;
};
/**
*
* Get the current BME680 data filtered by Bosch BSEC library
*
* As it is a proprietary binary blob, it has to be enabled using
* the ``bsec_enable`` configuration option (see :ref:`card10_cfg`).
*
* The sample rate is currently fixed to one sample every 3 seconds.
* Querying the sensor more often will return cached data.
*
* After the libary has been activated it starts to calibrate the
* sensor. This can take multiple hours.
* After a reset/power on it takes about 5 minutes to stabilize
* the sensor if it was calibrated before.
*
* The BSEC library regularly recalibrates the sensor during operation.
* The ``accuracy`` field of the return data indicates the calibration
* status of the sensor. Please take it into consideration when
* using / displaying the IAQ.
*
* Please refer to the description of :c:type:`bsec_sensor_data` for more
* information about how to interpret its content.
*
* .. versionadded:: 1.x
*
* :param data: Where to store the environmental data.
* :return: 0 on success or ``-Exxx`` on error. The following
* errors might occur:
*
* - ``-EFAULT``: On NULL-pointer.
* - ``-EINVAL``: No data available from the sensor.
* - ``-ENODEV``: BSEC libray is not running.
*/
API(API_BSEC_GET_DATA, int epic_bsec_read_sensors(
struct bsec_sensor_data *data
));
/**
* MAX86150
* ========
*/
/**
* Configuration for a MAX86150 sensor.
*
* This struct is used when enabling a sensor using
* :c:func:`epic_max86150_enable_sensor`.
*/
struct max86150_sensor_config {
/**
* Number of samples Epicardium should keep for this sensor. Do not set
* this number too high as the sample buffer will eat RAM.
*/
size_t sample_buffer_len;
/**
* Sample rate for PPG from the sensor in Hz. Maximum data rate is limited
* to 200 Hz for all sensors though some might be limited at a lower
* rate.
*
* Possible values are 10, 20, 50, 84, 100, 200.
*/
uint16_t ppg_sample_rate;
};
/**
* MAX86150 Sensor Data
*/
struct max86150_sensor_data {
/** Red LED data */
uint32_t red;
/** IR LED data */
uint32_t ir;
/** ECG data */
int32_t ecg;
};
/**
* Enable a MAX86150 PPG and ECG sensor.
*
* Calling this function will instruct the MAX86150 to collect a
* data from the sensor. You can then retrieve the samples using
* :c:func:`epic_stream_read`.
*
* :param max86150_sensor_config* config: Configuration for this sensor.
* :param size_t config_size: Size of ``config``.
* :returns: A sensor descriptor which can be used with
* :c:func:`epic_stream_read` or a negative error value:
*
* - ``-ENOMEM``: The MAX86150 driver failed to create a stream queue.
* - ``-ENODEV``: The MAX86150 driver failed due to physical connectivity problem
* (broken wire, unpowered, etc).
* - ``-EINVAL``: config->ppg_sample_rate is not one of 10, 20, 50, 84, 100, 200
* or config_size is not size of config.
*
* .. versionadded:: 1.16
*/
API(API_MAX86150_ENABLE, int epic_max86150_enable_sensor(struct max86150_sensor_config *config, size_t config_size));
/**
* Disable the MAX86150 sensor.
*
* :returns: 0 in case of success or forward negative error value from stream_deregister.
*
* .. versionadded:: 1.16
*/
API(API_MAX86150_DISABLE, int epic_max86150_disable_sensor());
/**
* **Interrupt Service Routine** for :c:data:`EPIC_INT_MAX86150`
*
* :c:func:`epic_isr_max86150` is called whenever the MAX86150
* PPG sensor has new data available.
*/
API_ISR(EPIC_INT_MAX86150, epic_isr_max86150);
/**
* Personal State
* ==============
* Card10 can display your personal state.
*
* If a personal state is set the top-left LED on the bottom side of the
* harmonics board is directly controlled by epicardium and it can't be
* controlled by pycardium.
*
* To re-enable pycardium control the personal state has to be cleared. To do
* that simply set it to ``STATE_NONE``.
*
* The personal state can be set to be persistent which means it won't get reset
* on pycardium application change/restart.
*/
/** Possible personal states. */
enum personal_state {
/** ``0``, No personal state - LED is under regular application control. */
STATE_NONE = 0,
/** ``1``, "no contact, please!" - I am overloaded. Please leave me be - red led, continuously on. */
STATE_NO_CONTACT = 1,
/** ``2``, "chaos" - Adventure time - blue led, short blink, long blink. */
STATE_CHAOS = 2,
/** ``3``, "communication" - want to learn something or have a nice conversation - yellow led, long blinks. */
STATE_COMMUNICATION = 3,
/** ``4``, "camp" - I am focussed on self-, camp-, or community maintenance - green led, fade on and off. */
STATE_CAMP = 4,
/** STATE_MAX gives latest value and count of possible STATEs**/
STATE_MAX = 5,
};
/**
* Set the users personal state.
*
* Using :c:func:`epic_personal_state_set` an application can set the users personal state.
*
* :param uint8_t state: The users personal state. Must be one of :c:type:`personal_state`.
* :param bool persistent: Indicates whether the configured personal state will remain set and active on pycardium application restart/change.
* :returns: ``0`` on success, ``-EINVAL`` if an invalid state was requested.
*/
API(API_PERSONAL_STATE_SET, int epic_personal_state_set(
uint8_t state, bool persistent
));
/**
* Get the users personal state.
*
* Using :c:func:`epic_personal_state_get` an application can get the currently set personal state of the user.
*
* :returns: A value with exactly one value of :c:type:`personal_state` set.
*/
API(API_PERSONAL_STATE_GET, int epic_personal_state_get());
/**
* Get whether the users personal state is persistent.
*
* Using :c:func:`epic_personal_state_is_persistent` an app can find out whether the users personal state is persistent or transient.
*
* :returns: ``1`` if the state is persistent, ``0`` otherwise.
*/
API(API_PERSONAL_STATE_IS_PERSISTENT, int epic_personal_state_is_persistent());
/**
* Sensor Data Streams
* ===================
* A few of card10's sensors can do continuous measurements. To allow
* performant access to their data, the following function is made for generic
* access to streams.
*/
/**
* Read sensor data into a buffer. ``epic_stream_read()`` will read as many
* sensor samples into the provided buffer as possible and return the number of
* samples written. If no samples are available, ``epic_stream_read()`` will
* return ``0`` immediately.
*
* ``epic_stream_read()`` expects the provided buffer to have a size which is a
* multiple of the sample size for the given stream. For the sample-format and
* size, please consult the sensors documentation.
*
* Before reading the internal sensor sample queue, ``epic_stream_read()`` will
* call a sensor specific *poll* function to allow the sensor driver to fetch
* new samples from its hardware. This should, however, never take a long
* amount of time.
*
* :param int sd: Sensor Descriptor. You get sensor descriptors as return
* values when activating the respective sensors.
* :param void* buf: Buffer where sensor data should be read into.
* :param size_t count: How many bytes to read at max. Note that fewer bytes
* might be read. In most cases, this should be ``sizeof(buf)``.
* :return: Number of data packets read (**not** number of bytes) or a negative
* error value. Possible errors:
*
* - ``-ENODEV``: Sensor is not currently available.
* - ``-EBADF``: The given sensor descriptor is unknown.
* - ``-EINVAL``: ``count`` is not a multiple of the sensor's sample size.
* - ``-EBUSY``: The descriptor table lock could not be acquired.
*
* **Example**:
*
* .. code-block:: cpp
*
* #include "epicardium.h"
*
* struct foo_measurement sensor_data[16];
* int foo_sd, n;
*
* foo_sd = epic_foo_sensor_enable(9001);
*
* while (1) {
* n = epic_stream_read(
* foo_sd,
* &sensor_data,
* sizeof(sensor_data)
* );
*
* // Print out the measured sensor samples
* for (int i = 0; i < n; i++) {
* printf("Measured: %?\n", sensor_data[i]);
* }
* }
*/
API(API_STREAM_READ, int epic_stream_read(int sd, void *buf, size_t count));
/**
* BHI160 Sensor Fusion
* ====================
* card10 has a BHI160 onboard which is used as an IMU. BHI160 exposes a few
* different sensors which can be accessed using Epicardium API.
*
* .. versionadded:: 1.4
*
* **Example**:
*
* .. code-block:: cpp
*
* #include "epicardium.h"
*
* // Configure a sensor & enable it
* struct bhi160_sensor_config cfg = {0};
* cfg.sample_buffer_len = 40;
* cfg.sample_rate = 4; // Hz
* cfg.dynamic_range = 2; // g
*
* int sd = epic_bhi160_enable_sensor(BHI160_ACCELEROMETER, &cfg);
*
* // Read sensor data
* while (1) {
* struct bhi160_data_vector buf[10];
*
* int n = epic_stream_read(sd, buf, sizeof(buf));
*
* for (int i = 0; i < n; i++) {
* printf("X: %6d Y: %6d Z: %6d\n",
* buf[i].x,
* buf[i].y,
* buf[i].z);
* }
* }
*
* // Disable the sensor
* epic_bhi160_disable_sensor(BHI160_ACCELEROMETER);
*/
/**
* BHI160 Sensor Types
* -------------------
*/
/**
* BHI160 virtual sensor type.
*/
enum bhi160_sensor_type {
/**
* Accelerometer
*
* - Data type: :c:type:`bhi160_data_vector`
* - Dynamic range: g's (1x Earth Gravity, ~9.81m*s^-2)
*/
BHI160_ACCELEROMETER = 0,
/**
* Magnetometer
*
* - Data type: :c:type:`bhi160_data_vector`
* - Dynamic range: -1000 to 1000 microtesla
*/
BHI160_MAGNETOMETER = 1,
/** Orientation */
BHI160_ORIENTATION = 2,
/** Gyroscope */
BHI160_GYROSCOPE = 3,
/** Gravity (**Unimplemented**) */
BHI160_GRAVITY = 4,
/** Linear acceleration (**Unimplemented**) */
BHI160_LINEAR_ACCELERATION = 5,
/** Rotation vector (**Unimplemented**) */
BHI160_ROTATION_VECTOR = 6,
/** Uncalibrated magnetometer (**Unimplemented**) */
BHI160_UNCALIBRATED_MAGNETOMETER = 7,
/** Game rotation vector (whatever that is supposed to be) */
BHI160_GAME_ROTATION_VECTOR = 8,
/** Uncalibrated gyroscrope (**Unimplemented**) */
BHI160_UNCALIBRATED_GYROSCOPE = 9,
/** Geomagnetic rotation vector (**Unimplemented**) */
BHI160_GEOMAGNETIC_ROTATION_VECTOR = 10,
};
enum bhi160_data_type {
BHI160_DATA_TYPE_VECTOR
};
/**
* BHI160 Sensor Data Types
* ------------------------
*/
/**
* Vector Data. The scaling of these values is dependent on the chosen dynamic
* range. See the individual sensor's documentation for details.
*/
struct bhi160_data_vector {
enum bhi160_data_type data_type;
/** X */
int16_t x;
/** Y */
int16_t y;
/** Z */
int16_t z;
/** Status */
uint8_t status;
};
/**
* BHI160 API
* ----------
*/
/**
* Configuration for a BHI160 sensor.
*
* This struct is used when enabling a sensor using
* :c:func:`epic_bhi160_enable_sensor`.
*/
struct bhi160_sensor_config {
/**
* Number of samples Epicardium should keep for this sensor. Do not set
* this number too high as the sample buffer will eat RAM.
*/
size_t sample_buffer_len;
/**
* Sample rate for the sensor in Hz. Maximum data rate is limited
* to 200 Hz for all sensors though some might be limited at a lower
* rate.
*/
uint16_t sample_rate;
/**
* Dynamic range. Interpretation of this value depends on
* the sensor type. Please refer to the specific sensor in
* :c:type:`bhi160_sensor_type` for details.
*/
uint16_t dynamic_range;
/** Always zero. Reserved for future parameters. */
uint8_t _padding[8];
};
/**
* Enable a BHI160 virtual sensor. Calling this function will instruct the
* BHI160 to collect data for this specific virtual sensor. You can then
* retrieve the samples using :c:func:`epic_stream_read`.
*
* :param bhi160_sensor_type sensor_type: Which sensor to enable.
* :param bhi160_sensor_config* config: Configuration for this sensor.
* :returns: A sensor descriptor which can be used with
* :c:func:`epic_stream_read` or a negative error value:
*
* - ``-EBUSY``: The BHI160 driver is currently busy with other tasks and
* could not be acquired for enabling a sensor.
*
* .. versionadded:: 1.4
*/
API(API_BHI160_ENABLE, int epic_bhi160_enable_sensor(
enum bhi160_sensor_type sensor_type,
struct bhi160_sensor_config *config
));
/**
* Disable a BHI160 sensor.
*
* :param bhi160_sensor_type sensor_type: Which sensor to disable.
*
* .. versionadded:: 1.4
*/
API(API_BHI160_DISABLE, int epic_bhi160_disable_sensor(
enum bhi160_sensor_type sensor_type
));
/**
* Disable all BHI160 sensors.
*
* .. versionadded:: 1.4
*/
API(API_BHI160_DISABLE_ALL, void epic_bhi160_disable_all_sensors());
/**
* BHI160 Interrupt Handlers
* -------------------------
*/
/**
* **Interrupt Service Routine** for :c:data:`EPIC_INT_BHI160_ACCELEROMETER`
*
* :c:func:`epic_isr_bhi160_accelerometer` is called whenever the BHI160
* accelerometer has new data available.
*/
API_ISR(EPIC_INT_BHI160_ACCELEROMETER, epic_isr_bhi160_accelerometer);
/**
* **Interrupt Service Routine** for :c:data:`EPIC_INT_BHI160_MAGNETOMETER`
*
* :c:func:`epic_isr_bhi160_magnetometer` is called whenever the BHI160
* magnetometer has new data available.
*/
API_ISR(EPIC_INT_BHI160_MAGNETOMETER, epic_isr_bhi160_magnetometer);
/**
* **Interrupt Service Routine** for :c:data:`EPIC_INT_BHI160_ORIENTATION`
*
* :c:func:`epic_isr_bhi160_orientation` is called whenever the BHI160
* orientation sensor has new data available.
*/
API_ISR(EPIC_INT_BHI160_ORIENTATION, epic_isr_bhi160_orientation);
/**
* **Interrupt Service Routine** for :c:data:`EPIC_INT_BHI160_GYROSCOPE`
*
* :c:func:`epic_isr_bhi160_orientation` is called whenever the BHI160
* gyroscrope has new data available.
*/
API_ISR(EPIC_INT_BHI160_GYROSCOPE, epic_isr_bhi160_gyroscope);
/**
* Vibration Motor
* ===============
*/
/**
* Turn vibration motor on or off
*
* :param status: 1 to turn on, 0 to turn off.
*/
API(API_VIBRA_SET, void epic_vibra_set(int status));
/**
* Turn vibration motor on for a given time
*
* :param millis: number of milliseconds to run the vibration motor.
*/
API(API_VIBRA_VIBRATE, void epic_vibra_vibrate(int millis));
/**
* Display
* =======
* The card10 has an LCD screen that can be accessed from user code.
*
* There are two ways to access the display:
*
* - *immediate mode*, where you ask Epicardium to draw shapes and text for
* you. Most functions in this subsection are related to *immediate mode*.
* - *framebuffer mode*, where you provide Epicardium with a memory range where
* you already drew graphics whichever way you wanted and Epicardium will
* copy them to the display. To use *framebuffer mode*, use the
* :c:func:`epic_disp_framebuffer` function.
*/
/** Line-Style */
enum disp_linestyle {
/** */
LINESTYLE_FULL = 0,
/** */
LINESTYLE_DOTTED = 1
};
/** Fill-Style */
enum disp_fillstyle {
/** */
FILLSTYLE_EMPTY = 0,
/** */
FILLSTYLE_FILLED = 1
};
/** Width of display in pixels */
#define DISP_WIDTH 160
/** Height of display in pixels */
#define DISP_HEIGHT 80
/**
* Framebuffer
*
* The frambuffer stores pixels as RGB565, but byte swapped. That is, for every ``(x, y)`` coordinate, there are two ``uint8_t``\ s storing 16 bits of pixel data.
*
* .. todo::
*
* Document (x, y) in relation to chirality.
*
* **Example**: Fill framebuffer with red
*
* .. code-block:: cpp
*
* union disp_framebuffer fb;
* uint16_t red = 0b1111100000000000;
* for (int y = 0; y < DISP_HEIGHT; y++) {
* for (int x = 0; x < DISP_WIDTH; x++) {
* fb.fb[y][x][0] = red >> 8;
* fb.fb[y][x][1] = red & 0xFF;
* }
* }
* epic_disp_framebuffer(&fb);
*/
union disp_framebuffer {
/** Coordinate based access (as shown in the example above). */
uint8_t fb[DISP_HEIGHT][DISP_WIDTH][2];
/** Raw byte-indexed access. */
uint8_t raw[DISP_HEIGHT*DISP_WIDTH*2];
};
/**
* Locks the display.
*
* :return: ``0`` on success or a negative value in case of an error:
*
* - ``-EBUSY``: Display was already locked from another task.
*/
API(API_DISP_OPEN, int epic_disp_open());
/**
* Unlocks the display again.
*
* :return: ``0`` on success or a negative value in case of an error:
*
* - ``-EBUSY``: Display was already locked from another task.
*/
API(API_DISP_CLOSE, int epic_disp_close());
/** /**
* Causes the changes that have been written to the framebuffer * Causes the changes that have been written to the framebuffer
* to be shown on the display * to be shown on the display
* :return: ``0`` on success or a negative value in case of an error:
*
* - ``-EBUSY``: Display was already locked from another task.
*/
API(API_DISP_UPDATE, int epic_disp_update());
/**
* Prints a string into the display framebuffer
*
* :param posx: x position to print to.
* :param posy: y position to print to.
* :param pString: string to print
* :param fg: foreground color in rgb565
* :param bg: background color in rgb565
* :return: ``0`` on success or a negative value in case of an error:
*
* - ``-EBUSY``: Display was already locked from another task.
*/
API(API_DISP_PRINT,
int epic_disp_print(
int16_t posx,
int16_t posy,
const char *pString,
uint16_t fg,
uint16_t bg)
);
/*
* Font Selection
*/
enum disp_font_name {
DISP_FONT8 = 0,
DISP_FONT12 = 1,
DISP_FONT16 = 2,
DISP_FONT20 = 3,
DISP_FONT24 = 4,
};
/*
* Image data type
*/
enum epic_rgb_format {
EPIC_RGB8 = 0,
EPIC_RGBA8 = 1,
EPIC_RGB565 = 2,
EPIC_RGBA5551 = 3,
};
/**
* Prints a string into the display framebuffer with font type selectable
*
* :param fontName: number of font, use FontName enum
* :param posx: x position to print to.
* :param posy: y position to print to.
* :param pString: string to print
* :param fg: foreground color in rgb565
* :param bg: background color in rgb565, no background is drawn if bg==fg
* :return: ``0`` on success or a negative value in case of an error:
*
* - ``-EBUSY``: Display was already locked from another task.
*/
API(API_DISP_PRINT_ADV, int epic_disp_print_adv(
uint8_t font,
int16_t posx,
int16_t posy,
const char *pString,
uint16_t fg,
uint16_t bg
));
/**
* Fills the whole screen with one color
*
* :param color: fill color in rgb565
* :return: ``0`` on success or a negative value in case of an error:
*
* - ``-EBUSY``: Display was already locked from another task.
*/
API(API_DISP_CLEAR, int epic_disp_clear(uint16_t color));
/**
* Draws a pixel on the display
*
* :param x: x position;
* :param y: y position;
* :param color: pixel color in rgb565
* :return: ``0`` on success or a negative value in case of an error:
*
* - ``-EBUSY``: Display was already locked from another task.
*/
API(API_DISP_PIXEL, int epic_disp_pixel(
int16_t x, int16_t y, uint16_t color
));
/**
* Blits an image buffer to the display
*
* :param x: x position
* :param y: y position
* :param w: Image width
* :param h: Image height
* :param img: Image data
* :param format: Format of the image data. One of :c:type:`epic_rgb_format`.
*/
API(API_DISP_BLIT, int epic_disp_blit(
int16_t x,
int16_t y,
int16_t w,
int16_t h,
void *img,
enum epic_rgb_format format
));
/**
* Draws a line on the display
*
* :param xstart: x starting position
* :param ystart: y starting position
* :param xend: x ending position
* :param yend: y ending position
* :param color: line color in rgb565
* :param linestyle: 0 for solid, 1 for dottet (almost no visual difference)
* :param pixelsize: thickness of the line; 1 <= pixelsize <= 8
* :return: ``0`` on success or a negative value in case of an error:
*
* - ``-EBUSY``: Display was already locked from another task.
*/
API(API_DISP_LINE, int epic_disp_line(
int16_t xstart,
int16_t ystart,
int16_t xend,
int16_t yend,
uint16_t color,
enum disp_linestyle linestyle,
uint16_t pixelsize
));
/**
* Draws a rectangle on the display
*
* :param xstart: x coordinate of top left corner
* :param ystart: y coordinate of top left corner
* :param xend: x coordinate of bottom right corner
* :param yend: y coordinate of bottom right corner
* :param color: line color in rgb565
* :param fillstyle: 0 for empty, 1 for filled
* :param pixelsize: thickness of the rectangle outline; 1 <= pixelsize <= 8
* :return: ``0`` on success or a negative value in case of an error:
*
* - ``-EBUSY``: Display was already locked from another task.
*/
API(API_DISP_RECT, int epic_disp_rect(
int16_t xstart,
int16_t ystart,
int16_t xend,
int16_t yend,
uint16_t color,
enum disp_fillstyle fillstyle,
uint16_t pixelsize
));
/**
* Draws a circle on the display
*
* :param x: x coordinate of the center; 0 <= x <= 160
* :param y: y coordinate of the center; 0 <= y <= 80
* :param rad: radius of the circle
* :param color: fill and outline color of the circle (rgb565)
* :param fillstyle: 0 for empty, 1 for filled
* :param pixelsize: thickness of the circle outline; 1 <= pixelsize <= 8
* :return: ``0`` on success or a negative value in case of an error:
*
* - ``-EBUSY``: Display was already locked from another task.
*/
API(API_DISP_CIRC, int epic_disp_circ(
int16_t x,
int16_t y,
uint16_t rad,
uint16_t color,
enum disp_fillstyle fillstyle,
uint16_t pixelsize
));
/**
* Immediately send the contents of a framebuffer to the display. This overrides
* anything drawn by immediate mode graphics and displayed using ``epic_disp_update``.
*
* :param fb: framebuffer to display
* :return: ``0`` on success or negative value in case of an error:
*
* - ``-EBUSY``: Display was already locked from another task.
*/
API(API_DISP_FRAMEBUFFER, int epic_disp_framebuffer(
union disp_framebuffer *fb
));
/**
* Light Sensor
* ============
*/
/**
* Set the backlight brightness.
*
* Note that this function does not require acquiring the display.
*
* :param brightness: brightness from 0 - 100
* :return: ``0`` on success or negative value in case of an error
*/
API(API_DISP_BACKLIGHT, int epic_disp_backlight(uint16_t brightness));
/**
* Start continuous readout of the light sensor. Will read light level
* at preconfigured interval and make it available via :c:func:`epic_light_sensor_get`.
*
* If the continuous readout was already running, this function will silently pass.
*
*
* :return: ``0`` if the start was successful or a negative error value
* if an error occured. Possible errors:
*
* - ``-EBUSY``: The timer could not be scheduled.
*/
API(API_LIGHT_SENSOR_RUN, int epic_light_sensor_run());
/**
* Get the last light level measured by the continuous readout.
*
* :param uint16_t* value: where the last light level should be written.
* :return: ``0`` if the readout was successful or a negative error
* value. Possible errors:
*
* - ``-ENODATA``: Continuous readout not currently running.
*/
API(API_LIGHT_SENSOR_GET, int epic_light_sensor_get(uint16_t* value));
/**
* Stop continuous readout of the light sensor.
*
* If the continuous readout wasn't running, this function will silently pass.
*
* :return: ``0`` if the stop was sucessful or a negative error value
* if an error occured. Possible errors:
*
* - ``-EBUSY``: The timer stop could not be scheduled.
*/
API(API_LIGHT_SENSOR_STOP, int epic_light_sensor_stop());
/**
* Get the light level directly.
*
* Each call has an intrinsic delay of about 240us, I recommend another
* 100-300us delay between calls. Whether or not the IR LED is fast enough is
* another issue.
*
* :return: Light level
*
* .. versionadded:: 1.8
*/
API(API_LIGHT_SENSOR_READ, uint16_t epic_light_sensor_read(void));
/**
* File
* ====
* Except for :c:func:`epic_file_open`, which models C stdio's ``fopen``
* function, ``close``, ``read`` and ``write`` model `close(2)`_, `read(2)`_ and
* `write(2)`_. All file-related functions return >= ``0`` on success and
* ``-Exyz`` on failure, with error codes from errno.h (``EIO``, ``EINVAL``
* etc.)
*
* .. _close(2): http://man7.org/linux/man-pages/man2/close.2.html
* .. _read(2): http://man7.org/linux/man-pages/man2/read.2.html
* .. _write(2): http://man7.org/linux/man-pages/man2/write.2.html
*/
/** */
API(API_FILE_OPEN, int epic_file_open(
const char* filename, const char* modeString
));
/** */
API(API_FILE_CLOSE, int epic_file_close(int fd));
/** */
API(API_FILE_READ, int epic_file_read(int fd, void* buf, size_t nbytes));
/**
* Write bytes to a file.
*
* :param int fd: Descriptor returned by :c:func:`epic_file_open`.
* :param void* buf: Data to write.
* :param size_t nbytes: Number of bytes to write.
*
* :return: ``< 0`` on error, ``nbytes`` on success. (Partial writes don't occur on success!)
*
*/
API(API_FILE_WRITE, int epic_file_write(
int fd, const void* buf, size_t nbytes
));
/** */
API(API_FILE_FLUSH, int epic_file_flush(int fd));
/** */
API(API_FILE_SEEK, int epic_file_seek(int fd, long offset, int whence));
/** */
API(API_FILE_TELL, int epic_file_tell(int fd));
/** */
enum epic_stat_type {
/**
* Basically ``ENOENT``. Although :c:func:`epic_file_stat` returns an
* error for 'none', the type will still be set to none additionally.
*
* This is also used internally to track open FS objects, where we use
* ``EPICSTAT_NONE`` to mark free objects.
*/
EPICSTAT_NONE,
/** normal file */
EPICSTAT_FILE,
/** directory */
EPICSTAT_DIR,
};
/**
* Maximum length of a path string (=255).
*/
#define EPICSTAT_MAX_PATH 255
/* conveniently the same as FF_MAX_LFN */
/** */
struct epic_stat {
/** Entity Type: file, directory or none */
enum epic_stat_type type;
/*
* Note about padding & placement of uint32_t size:
*
* To accomodate for future expansion, we want padding at the end of
* this struct. Since sizeof(enum epic_stat_type) can not be assumed
* to be have a certain size, we're placing uint32_t size here so we
* can be sure it will be at offset 4, and therefore the layout of the
* other fields is predictable.
*/
/** Size in bytes. */
uint32_t size;
/** File Name. */
char name[EPICSTAT_MAX_PATH + 1];
uint8_t _reserved[12];
};
/**
* stat path
*
* :param char* filename: path to stat
* :param epic_stat* stat: pointer to result
*
* :return: ``0`` on success, negative on error
*/
API(API_FILE_STAT, int epic_file_stat(
const char* path, struct epic_stat* stat
));
/**
* Open a directory, for enumerating its contents.
*
* Use :c:func:`epic_file_readdir` to iterate over the directories entries.
*
* **Example**:
*
* .. code-block:: cpp
*
* #include "epicardium.h"
*
* int fd = epic_file_opendir("/path/to/dir");
*
* struct epic_stat entry;
* for (;;) {
* epic_file_readdir(fd, &entry);
*
* if (entry.type == EPICSTAT_NONE) {
* // End
* break;
* }
*
* printf("%s\n", entry.name);
* }
*
* epic_file_close(fd);
*
* :param char* path: Directory to open.
*
* :return: ``> 0`` on success, negative on error
*/
API(API_FILE_OPENDIR, int epic_file_opendir(const char* path));
/**
* Read one entry from a directory.
*
* Call :c:func:`epic_file_readdir` multiple times to iterate over all entries
* of a directory. The end of the entry list is marked by returning
* :c:data:`EPICSTAT_NONE` as the :c:member:`epic_stat.type`.
*
* :param int fd: Descriptor returned by :c:func:`epic_file_opendir`.
* :param epic_stat* stat: Pointer where to store the result. Pass NULL to
* reset iteration offset of ``fd`` back to the beginning.
*
* :return: ``0`` on success, negative on error
*/
API(API_FILE_READDIR, int epic_file_readdir(int fd, struct epic_stat* stat));
/**
* Unlink (remove) a file.
*
* :param char* path: file to delete
*
* :return: ``0`` on success, negative on error
*/
API(API_FILE_UNLINK, int epic_file_unlink(const char* path));
/**
* Rename a file or directory.
*
* :param char* oldp: old name
* :param char* newp: new name
*
* :return: ``0`` on success, negative on error
*/
API(API_FILE_RENAME, int epic_file_rename(const char *oldp, const char* newp));
/**
* Create directory in CWD
*
* :param char* dirname: directory name
*
* :return: ``0`` on success, negative on error
*/
API(API_FILE_MKDIR, int epic_file_mkdir(const char *dirname));
/**
* Check whether the filesystem is currently attached and a call like
* :c:func:`epic_file_open` has a chance to succeed.
*
* :return: ``true`` if the filesystem is attached and ``false`` if the
* filesystem is not attached.
*/
API(API_FILE_FS_ATTACHED, bool epic_fs_is_attached(void));
/**
* RTC
* ===
*/
/**
* Get the monotonic time in seconds.
*
* :return: monotonic time in seconds
*
* .. versionadded:: 1.11
*/
API(API_RTC_GET_MONOTONIC_SECONDS,
uint32_t epic_rtc_get_monotonic_seconds(void)
);
/**
* Get the monotonic time in ms.
*
* :return: monotonic time in milliseconds
*
* .. versionadded:: 1.11
*/ */
API(API_DISP_UPDATE, int epic_disp_update()); API(API_RTC_GET_MONOTONIC_MILLISECONDS,
uint64_t epic_rtc_get_monotonic_milliseconds(void)
);
/** /**
* Prints a string into the display framebuffer * Read the current RTC value.
* *
* :param posx: x position to print to. 0 <= x <= 160 * :return: Unix time in seconds
* :param posy: y position to print to. 0 <= y <= 80 */
* :param pString: string to print API(API_RTC_GET_SECONDS, uint32_t epic_rtc_get_seconds(void));
* :param fg: foreground color in rgb565
* :param bg: background color in rgb565 /**
* :return: ``0`` on success or a negative value in case of an error: * Read the current RTC value in ms.
* *
* - ``-EBUSY``: Display was already locked from another task. * :return: Unix time in milliseconds
*/ */
API(API_DISP_PRINT, API(API_RTC_GET_MILLISECONDS, uint64_t epic_rtc_get_milliseconds(void));
int epic_disp_print(
uint16_t posx,
uint16_t posy,
const char *pString,
uint16_t fg,
uint16_t bg)
);
/** /**
* Fills the whole screen with one color * Sets the current RTC time in milliseconds
*/
API(API_RTC_SET_MILLISECONDS, void epic_rtc_set_milliseconds(
uint64_t milliseconds
));
/**
* Schedule the RTC alarm for the given timestamp.
* *
* :param color: fill color in rgb565 * :param uint32_t timestamp: When to schedule the IRQ
* :return: ``0`` on success or a negative value in case of an error: * :return: ``0`` on success or a negative value if an error occured. Possible
* errors:
* *
* - ``-EBUSY``: Display was already locked from another task. * - ``-EINVAL``: RTC is in a bad state
*/ */
API(API_DISP_CLEAR, int epic_disp_clear(uint16_t color)); API(API_RTC_SCHEDULE_ALARM, int epic_rtc_schedule_alarm(uint32_t timestamp));
/** /**
* Draws a pixel on the display * **Interrupt Service Routine** for :c:data:`EPIC_INT_RTC_ALARM`
* *
* :param x: x position; 0 <= x <= 160 * ``epic_isr_rtc_alarm()`` is called when the RTC alarm triggers. The RTC alarm
* :param y: y position; 0 <= y <= 80 * can be scheduled using :c:func:`epic_rtc_schedule_alarm`.
* :param color: pixel color in rgb565 */
* :return: ``0`` on success or a negative value in case of an error: API_ISR(EPIC_INT_RTC_ALARM, epic_isr_rtc_alarm);
/**
* RNG
* ====
*/
/**
* Read random bytes from the TRNG.
* *
* - ``-EBUSY``: Display was already locked from another task. * Be aware that this function returns raw unprocessed bytes from
* the TRNG. They might be biased or have other kinds of imperfections.
*
* Use :c:func:`epic_csprng_read` for cryptographically safe random
* numbers instead.
*
* .. warning::
*
* The exact behaviour of the TRNG is not well understood. Its
* distribution and other parameters are unknown. Only use this
* function if you really want the unmodified values from the
* hardware TRNG to experiment with it.
*
* :param uint8_t * dest: Destination buffer
* :param size: Number of bytes to read.
* :return: ``0`` on success or a negative value if an error occured. Possible
* errors:
*
* - ``-EFAULT``: Invalid destination address.
*/ */
API(API_DISP_PIXEL, API(API_TRNG_READ, int epic_trng_read(uint8_t *dest, size_t size));
int epic_disp_pixel(
uint16_t x,
uint16_t y,
uint16_t color)
);
/** /**
* Draws a line on the display * Read random bytes from the CSPRNG.
* *
* :param xstart: x starting position; 0 <= x <= 160 * The random bytes returned are safe to be used for cryptography.
* :param ystart: y starting position; 0 <= y <= 80
* :param xend: x ending position; 0 <= x <= 160
* :param yend: y ending position; 0 <= y <= 80
* :param color: line color in rgb565
* :param linestyle: 0 for solid, 1 for dottet (almost no visual difference)
* :param pixelsize: thickness of the line; 1 <= pixelsize <= 8
* :return: ``0`` on success or a negative value in case of an error:
* *
* - ``-EBUSY``: Display was already locked from another task. * :param uint8_t * dest: Destination buffer
* :param size: Number of bytes to read.
* :return: ``0`` on success or a negative value if an error occured. Possible
* errors:
*
* - ``-EFAULT``: Invalid destination address.
*/ */
API(API_DISP_LINE, API(API_CSPRNG_READ, int epic_csprng_read(uint8_t *dest, size_t size));
int epic_disp_line(
uint16_t xstart,
uint16_t ystart,
uint16_t xend,
uint16_t yend,
uint16_t color,
enum disp_linestyle linestyle,
uint16_t pixelsize)
);
/** /**
* Draws a rectangle on the display * MAX30001
* ========
*/
/**
* Configuration for a MAX30001 sensor.
* *
* :param xstart: x coordinate of top left corner; 0 <= x <= 160 * This struct is used when enabling the sensor using
* :param ystart: y coordinate of top left corner; 0 <= y <= 80 * :c:func:`epic_max30001_enable_sensor`.
* :param xend: x coordinate of bottom right corner; 0 <= x <= 160 */
* :param yend: y coordinate of bottom right corner; 0 <= y <= 80 struct max30001_sensor_config {
* :param color: line color in rgb565 /**
* :param fillstyle: 0 for empty, 1 for filled * Number of samples Epicardium should keep for this sensor. Do not set
* :param pixelsize: thickness of the rectangle outline; 1 <= pixelsize <= 8 * this number too high as the sample buffer will eat RAM.
* :return: ``0`` on success or a negative value in case of an error: */
size_t sample_buffer_len;
/**
* Sample rate for the sensor in Hz.
*/
uint16_t sample_rate;
/**
* Set to true if the second lead comes from USB-C
*/
bool usb;
/**
* Set to true if the interal lead bias of the MAX30001 is to be used.
*/
bool bias;
/** Always zero. Reserved for future parameters. */
uint8_t _padding[8];
};
/**
* Enable a MAX30001 ECG sensor.
* *
* - ``-EBUSY``: Display was already locked from another task. * Calling this function will instruct the MAX30001 to collect data for this
* sensor. You can then retrieve the samples using :c:func:`epic_stream_read`.
*
* :param max30001_sensor_config* config: Configuration for this sensor.
* :returns: A sensor descriptor which can be used with
* :c:func:`epic_stream_read` or a negative error value:
*
* - ``-EBUSY``: The MAX30001 driver is currently busy with other tasks and
* could not be acquired for enabling a sensor.
*
* .. versionadded:: 1.6
*/ */
API(API_DISP_RECT, API(API_MAX30001_ENABLE, int epic_max30001_enable_sensor(
int epic_disp_rect( struct max30001_sensor_config *config
uint16_t xstart, ));
uint16_t ystart,
uint16_t xend,
uint16_t yend,
uint16_t color,
enum disp_fillstyle fillstyle,
uint16_t pixelsize)
);
/** /**
* Draws a circle on the display * Disable MAX30001
* *
* :param x: x coordinate of the center; 0 <= x <= 160 * .. versionadded:: 1.6
* :param y: y coordinate of the center; 0 <= y <= 80 */
* :param rad: radius of the circle API(API_MAX30001_DISABLE, int epic_max30001_disable_sensor());
* :param color: fill and outline color of the circle (rgb565)
* :param fillstyle: 0 for empty, 1 for filled /**
* :param pixelsize: thickness of the circle outline; 1 <= pixelsize <= 8 * **Interrupt Service Routine** for :c:data:`EPIC_INT_MAX30001_ECG`
* :return: ``0`` on success or a negative value in case of an error:
* *
* - ``-EBUSY``: Display was already locked from another task. * This interrupt handler is called whenever the MAX30001 ECG has new data
* available.
*/ */
API(API_DISP_CIRC, API_ISR(EPIC_INT_MAX30001_ECG, epic_isr_max30001_ecg);
int epic_disp_circ(
uint16_t x,
uint16_t y,
uint16_t rad,
uint16_t color,
enum disp_fillstyle fillstyle,
uint16_t pixelsize)
);
/** /**
* Start continuous readout of the light sensor. Will read light level * USB
* at preconfigured interval and make it available via `epic_light_sensor_get()`. * ===
*/
/**
* De-initialize the currently configured USB device (if any)
* *
* If the continuous readout was already running, this function will silently pass. */
API(API_USB_SHUTDOWN, int epic_usb_shutdown(void));
/**
* Configure the USB peripheral to export the internal FLASH
* as a Mass Storage device.
*/
API(API_USB_STORAGE, int epic_usb_storage(void));
/**
* Configure the USB peripheral to provide card10's stdin/stdout
* on a USB CDC-ACM device.
*/
API(API_USB_CDCACM, int epic_usb_cdcacm(void));
/**
* WS2812
* ======
*/
/**
* Takes a gpio pin specified with the gpio module and transmits
* the led data. The format ``GG:RR:BB`` is expected.
* *
* :param uint8_t pin: The gpio pin to be used for data.
* :param uint8_t * pixels: The buffer, in which the pixel data is stored.
* :param uint32_t n_bytes: The size of the buffer.
* *
* :return: `0` if the start was successful or a negative error value * .. versionadded:: 1.10
* if an error occured. Possible errors: */
API(API_WS2812_WRITE, void epic_ws2812_write(uint8_t pin, uint8_t *pixels, uint32_t n_bytes));
/**
* Configuration
* =============
*/
/**
* Read an integer from the configuration file
* *
* - ``-EBUSY``: The timer could not be scheduled. * :param char* key: Name of the option to read
* :param int* value: Place to read the value into
* :return: ``0`` on success or a negative value if an error occured. Possible
* errors:
*
* - ``-ENOENT``: Value can not be read
*
* .. versionadded:: 1.13
*/ */
API(API_LIGHT_SENSOR_RUN, int epic_light_sensor_run()); API(API_CONFIG_GET_INTEGER, int epic_config_get_integer(const char *key, int *value));
/** /**
* Get the last light level measured by the continuous readout. * Read a boolean from the configuration file
* *
* :param uint16_t* value: where the last light level should be written. * :param char* key: Name of the option to read
* :return: `0` if the readout was successful or a negative error * :param bool* value: Place to read the value into
* value. Possible errors: * :return: ``0`` on success or a negative value if an error occured. Possible
* errors:
* *
* - ``-ENODATA``: Continuous readout not currently running. * - ``-ENOENT``: Value can not be read
*
* .. versionadded:: 1.13
*/ */
API(API_LIGHT_SENSOR_GET, int epic_light_sensor_get(uint16_t* value)); API(API_CONFIG_GET_BOOLEAN, int epic_config_get_boolean(const char *key, bool *value));
/**
* Read a string from the configuration file.
*
* If the buffer supplied is too small for the config option,
* no error is reported and the first ``buf_len - 1`` characters
* are returned (0 terminated).
*
* :param char* key: Name of the option to read
* :param char* buf: Place to read the string into
* :param size_t buf_len: Size of the provided buffer
* :return: ``0`` on success or a negative value if an error occured. Possible
* errors:
*
* - ``-ENOENT``: Value can not be read
*
* .. versionadded:: 1.13
*/
API(API_CONFIG_GET_STRING, int epic_config_get_string(const char *key, char *buf, size_t buf_len));
/** /**
* Stop continuous readout of the light sensor. * Write a string to the configuration file.
* *
* If the continuous readout wasn't running, this function will silently pass. * :param char* key: Name of the option to write
* :param char* value: The value to write
* :return: ``0`` on success or a negative value if an error occured. Possivle
* errors:
*
* - ``-EINVAL``: Parameters out of range
* - ``-ENOENT``: Key already exists but can not be read
* - ``-EIO`` : Unspecified I/O error
* - Any fopen/fread/fwrite/fclose related error code
*
* .. versionadded:: 1.16
*/
API(API_CONFIG_SET_STRING, int epic_config_set_string(const char *key, const char *value));
/**
* Bluetooth Low Energy (BLE)
* ==========================
*/
/**
* BLE event type
*/
enum epic_ble_event_type {
/** No event pending */
BLE_EVENT_NONE = 0,
/** Numeric comparison requested */
BLE_EVENT_HANDLE_NUMERIC_COMPARISON = 1,
/** A pairing procedure has failed */
BLE_EVENT_PAIRING_FAILED = 2,
/** A pairing procedure has successfully completed */
BLE_EVENT_PAIRING_COMPLETE = 3,
/** New scan data is available */
BLE_EVENT_SCAN_REPORT = 4,
BLE_EVENT_ATT_EVENT = 5,
BLE_EVENT_ATT_WRITE = 6,
BLE_EVENT_DM_EVENT = 7,
};
/**
* MicroPython Bluetooth support data types. Please
* do not use them until they are stabilized.
*/
typedef uint8_t bdAddr_t[6];
struct epic_wsf_header
{
/** General purpose parameter passed to event handler */
uint16_t param;
/** General purpose event value passed to event handler */
uint8_t event;
/** General purpose status value passed to event handler */
uint8_t status;
};
struct epic_att_event
{
/** Header structure */
struct epic_wsf_header hdr;
/** Value */
uint8_t *pValue;
/** Value length */
uint16_t valueLen;
/** Attribute handle */
uint16_t handle;
/** TRUE if more response packets expected */
uint8_t continuing;
/** Negotiated MTU value */
uint16_t mtu;
};
struct epic_hciLeConnCmpl_event
{ /** Event header */
struct epic_wsf_header hdr;
/** Status. */
uint8_t status;
/** Connection handle. */
uint16_t handle;
/** Local connection role. */
uint8_t role;
/** Peer address type. */
uint8_t addrType;
/** Peer address. */
bdAddr_t peerAddr;
/** Connection interval */
uint16_t connInterval;
/** Connection latency. */
uint16_t connLatency;
/** Supervision timeout. */
uint16_t supTimeout;
/** Clock accuracy. */
uint8_t clockAccuracy;
/** enhanced fields */
/** Local RPA. */
bdAddr_t localRpa;
/** Peer RPA. */
bdAddr_t peerRpa;
};
/*! \brief Disconnect complete event */
struct epic_hciDisconnectCmpl_event
{
/** Event header */
struct epic_wsf_header hdr;
/** Disconnect complete status. */
uint8_t status;
/** Connect handle. */
uint16_t handle;
/** Reason. */
uint8_t reason;
};
struct epic_dm_event
{
union {
/** LE connection complete. */
struct epic_hciLeConnCmpl_event leConnCmpl;
/** Disconnect complete. */
struct epic_hciDisconnectCmpl_event disconnectCmpl;
};
};
struct epic_att_write
{
/** Header structure */
struct epic_wsf_header hdr;
/** Value length */
uint16_t valueLen;
/** Attribute handle */
uint16_t handle;
uint8_t operation;
uint16_t offset;
void *buffer;
};
struct epic_ble_event {
enum epic_ble_event_type type;
union {
void *data;
struct epic_att_event *att_event;
struct epic_dm_event *dm_event;
struct epic_att_write *att_write;
};
};
/**
* Scan report data. Based on ``hciLeAdvReportEvt_t`` from BLE stack.
*
* TODO: 64 bytes for data is an arbitrary number ATM */
struct epic_scan_report
{
/** advertising or scan response data. */
uint8_t data[64];
/** length of advertising or scan response data. */
uint8_t len;
/** RSSI. */
int8_t rssi;
/** Advertising event type. */
uint8_t eventType;
/** Address type. */
uint8_t addrType;
/** Device address. */
uint8_t addr[6];
/** direct fields */
/** Direct advertising address type. */
uint8_t directAddrType;
/** Direct advertising address. */
uint8_t directAddr[6];
};
/**
* **Interrupt Service Routine** for :c:data:`EPIC_INT_BLE`
*
* :c:func:`epic_isr_ble` is called when the BLE stack wants to signal an
* event to the application. You can use :c:func:`epic_ble_get_event` to obtain
* the event which triggered this interrupt.
*
* Currently supported events:
*
* :c:data:`BLE_EVENT_HANDLE_NUMERIC_COMPARISON`:
* An ongoing pairing procedure requires a numeric comparison to complete.
* The compare value can be retreived using :c:func:`epic_ble_get_compare_value`.
*
* :c:data:`BLE_EVENT_PAIRING_FAILED`:
* A pairing procedure failed. The stack automatically went back advertising
* and accepting new pairings.
*
* :c:data:`BLE_EVENT_PAIRING_COMPLETE`:
* A pairing procedure has completed sucessfully.
* The stack automatically persists the pairing information, creating a bond.
*
* .. versionadded:: 1.16
*/
API_ISR(EPIC_INT_BLE, epic_isr_ble);
/**
* Retreive the event which triggered :c:func:`epic_isr_ble`
*
* .. versionadded:: 1.16
* .. versionchanged:: 1.17
*/
API(API_BLE_GET_EVENT, int epic_ble_get_event(struct epic_ble_event *e));
/**
* Retrieve the compare value of an ongoing pairing procedure.
*
* If no pairing procedure is ongoing, the returned value is undefined.
*
* :return: 6 digit long compare value
*
* .. versionadded:: 1.16
*/
API(API_BLE_GET_COMPARE_VALUE, uint32_t epic_ble_get_compare_value(void));
/**
* Retrieve the (file) name of the last pairing which was successful.
*
* :return: ``0`` on success or a negative value if an error occured. Possible
* errors:
*
* - ``-ENOENT``: There was no successful pairing yet.
*
* .. versionadded:: 1.16
*/
API(API_BLE_GET_LAST_PAIRING_NAME, int epic_ble_get_last_pairing_name(char *buf, size_t buf_size));
/**
* Retrieve the name of the peer to which we are connected
*
* The name might be empty if the peer device does not expose it or
* if it has not yet been read from it.
*
* :return: ``0`` on success or a negative value if an error occured. Possible
* errors:
*
* - ``-ENOENT``: There is no active connection at the moment.
*
* .. versionadded:: 1.16
*/
API(API_BLE_GET_PEER_DEVICE_NAME, int epic_ble_get_peer_device_name(char *buf, size_t buf_size));
/**
* Indicate wether the user confirmed the compare value.
*
* If a pariring procedure involving a compare value is ongoing and this
* function is called with confirmed set to ``true``, it will try to
* proceed and complete the pairing process. If called with ``false``, the
* pairing procedure will be aborted.
*
* :param bool confirmed: ``true`` if the user confirmed the compare value.
*
* .. versionadded:: 1.16
*/
API(API_BLE_COMPARE_RESPONSE, void epic_ble_compare_response(bool confirmed));
/**
* Set the desired mode of the BLE stack.
*
* There are three allowed modes:
*
* - Peripheral which is not bondable (bondable = ``false``, scanner = ``false``).
* - Peripheral which is bondable (bondable = ``true``, scanner = ``false``).
* - Observer which scans for advertisements (bondable = ``false``, scanner = ``true``).
*
* By default the card10 will not allow new bondings to be made. New
* bondings have to explicitly allowed by calling this function.
*
* While bondable the card10 will change its advertisements to
* indicate to scanning hosts that it is available for discovery.
*
* When scanning is active, :c:data:`BLE_EVENT_SCAN_REPORT` events will be sent
* and the scan reports can be fetched using :c:func:`epic_ble_get_scan_report`.
* *
* :return: `0` if the stop was sucessful or a negative error value * When switching applications new bondings are automatically
* disallowed and scanning is stopped.
*
* :param bool bondable: ``true`` if new bondings should be allowed.
* :param bool scanner: ``true`` if scanning should be turned on.
*
* .. versionadded:: 1.16
*/
API(API_BLE_SET_MODE, void epic_ble_set_mode(bool bondable, bool scanner));
/**
* Retrieve a scan report from the queue of scan reports.
*
* :param struct\ epic_scan_report* rpt: Pointer where the report will be stored.
*
* :return: ``0`` on success or a negative value if an error occured. Possible
* errors:
*
* - ``-ENOENT``: No scan report available
*
*/
API(API_BLE_GET_SCAN_REPORT, int epic_ble_get_scan_report(struct epic_scan_report *rpt));
/**
* Send an input report to the host.
*
* :param uint8_t report_id: The id of the report to use. 1: keyboard, 2: mouse, 3: consumer control
* :param uint8_t* data: Data to be reported.
* :param uint8_t len: Length in bytes of the data to be reported. Maximum length is 8 bytes.
*
* :return: ``0`` on success, ``1`` if the report is queued or a negative value
* if an error occured. Possible errors: * if an error occured. Possible errors:
* *
* - ``-EBUSY``: The timer stop could not be scheduled. * - ``-EIO``: There is no host device connected or BLE HID is not enabled.
* - ``-EAGAIN``: There is no space in the queue available. Try again later.
* - ``-EINVAL``: Either the report_id is out of range or the data is too long.
*
*/
API(API_BLE_HID_SEND_REPORT, int epic_ble_hid_send_report(uint8_t report_id, uint8_t *data, uint8_t len));
/**
* MicroPython BLE Support API
* ---------------------------
* The following API calls are to be used for MicroPython BLE support.
*
* .. warning::
*
* The following epic-calls are **not** part of the stable public API and
* thus **no** guarantee about stability or behavior is made. Do not use
* these outside of Pycardium unless you can live with sudden breakage!!
*
* They are only documented here for completeness and as a reference for
* firmware hackers, not for common usage.
*/ */
API(API_LIGHT_SENSOR_STOP, int epic_light_sensor_stop());
/** Private API call for Pycardium BLE support. */
API(API_BLE_INIT, int epic_ble_init(void));
/** Private API call for Pycardium BLE support. */
API(API_BLE_DEINIT, int epic_ble_deinit(void));
/** Private API call for Pycardium BLE support. */
API(API_BLE_ATTS_DYN_CREATE_GROUP, int epic_atts_dyn_create_service(const uint8_t *uuid, uint8_t uuid_len, uint16_t group_size, void **pSvcHandle));
//API(API_BLE_ATTS_DYN_DELETE_GROUP, void AttsDynDeleteGroup(void *pSvcHandle));
/** Private API call for Pycardium BLE support. */
API(API_BLE_ATTS_DYN_DELETE_GROUPS, int epic_ble_atts_dyn_delete_groups(void));
/** Private API call for Pycardium BLE support. */
API(API_BLE_ATTS_DYN_ADD_CHARACTERISTIC, int epic_atts_dyn_add_characteristic(void *pSvcHandle, const uint8_t *uuid, uint8_t uuid_len, uint8_t flags, uint16_t maxLen, uint16_t *value_handle));
/** Private API call for Pycardium BLE support. */
API(API_BLE_ATTS_DYN_ADD_DESCRIPTOR, int epic_ble_atts_dyn_add_descriptor(void *pSvcHandle, const uint8_t *uuid, uint8_t uuid_len, uint8_t flags, const uint8_t *value, uint16_t value_len, uint16_t maxLen, uint16_t *descriptor_handle));
/** Private API call for Pycardium BLE support. */
API(API_BLE_ATTS_SEND_SERVICE_CHANGED_IND, int epic_atts_dyn_send_service_changed_ind(void));
/** Private API call for Pycardium BLE support. */
API(API_BLE_ATTS_SET_ATTR, int epic_ble_atts_set_attr(uint16_t handle, const uint8_t *value, uint16_t value_len));
/** Private API call for Pycardium BLE support. */
API(API_BLE_ATTS_HANDLE_VALUE_NTF, int epic_ble_atts_handle_value_ntf(uint8_t connId, uint16_t handle, uint16_t valueLen, uint8_t *pValue));
/** Private API call for Pycardium BLE support. */
API(API_BLE_ATTS_HANDLE_VALUE_IND, int epic_ble_atts_handle_value_ind(uint8_t connId, uint16_t handle, uint16_t valueLen, uint8_t *pValue));
/** Private API call for Pycardium BLE support. */
API(API_BLE_ATTS_SET_BUFFER, int epic_ble_atts_set_buffer(uint16_t value_handle, size_t len, bool append));
/** Private API call for Pycardium BLE support. */
API(API_BLE_FREE_EVENT, int epic_ble_free_event(struct epic_ble_event *e));
/** Private API call for Pycardium BLE support. */
API(API_BLE_CLOSE_CONNECTION, void epic_ble_close_connection(uint8_t connId));
/** Private API call for Pycardium BLE support. */
API(API_BLE_IS_CONNECTION_OPEN, int epic_ble_is_connection_open(void));
/** Private API call for Pycardium BLE support. */
API(API_BLE_SET_DEVICE_NAME, int epic_ble_set_device_name(const uint8_t *buf, uint16_t len));
/** Private API call for Pycardium BLE support. */
API(API_BLE_GET_DEVICE_NAME, int epic_ble_get_device_name(uint8_t **buf, uint16_t *len));
/** Private API call for Pycardium BLE support. */
API(API_BLE_GET_ADDRESS, void epic_ble_get_address(uint8_t *addr));
/** Private API call for Pycardium BLE support. */
API(API_BLE_ADVERTISE, int epic_ble_advertise(int interval_us, const uint8_t *adv_data, size_t adv_data_len, const uint8_t *sr_data, size_t sr_data_len, bool connectable));
/** Private API call for Pycardium BLE support. */
API(API_BLE_ADVERTISE_STOP, int epic_ble_advertise_stop(void));
/** Private API call for Pycardium BLE support. */
API(API_BLE_DISCOVER_PRIMARY_SERVICES, int epic_ble_attc_discover_primary_services(uint8_t connId, const uint8_t *uuid, uint8_t uuid_len));
/** Private API call for Pycardium BLE support. */
API(API_BLE_DISCOVER_CHARACTERISTICS, int epic_ble_attc_discover_characteristics(uint8_t connId, uint16_t start_handle, uint16_t end_handle));
/** Private API call for Pycardium BLE support. */
API(API_BLE_DISCOVER_DESCRIPTORS, int epic_ble_attc_discover_descriptors(uint8_t connId, uint16_t start_handle, uint16_t end_handle));
/** Private API call for Pycardium BLE support. */
API(API_BLE_ATTC_READ, int epic_ble_attc_read(uint8_t connId, uint16_t value_handle));
/** Private API call for Pycardium BLE support. */
API(API_BLE_ATTC_WRITE_NO_RSP, int epic_ble_attc_write_no_rsp(uint8_t connId, uint16_t value_handle, const uint8_t *value, uint16_t value_len));
/** Private API call for Pycardium BLE support. */
API(API_BLE_ATTC_WRITE, int epic_ble_attc_write(uint8_t connId, uint16_t value_handle, const uint8_t *value, uint16_t value_len));
#endif /* _EPICARDIUM_H */ #endif /* _EPICARDIUM_H */
/**
* Implemention of the epicardium epic_file_ api that operates on
* the global EpicFileSystem instance
*
* All functions lock & unlock the global FS object
*
*/
#include "fs/internal.h"
#if defined(__GNUC__) && \
((__GNUC__ > 4) || (__GNUC__ == 4 && __GNUC_MINOR__ >= 6))
#define HAVE_STATIC_ASSERT 1
#elif defined(__clang__)
#define HAVE_STATIC_ASSERT 1
#endif
#if HAVE_STATIC_ASSERT
_Static_assert(sizeof(struct epic_stat) == 276, "");
#endif
int epic_file_open(const char *filename, const char *mode)
{
EpicFileSystem *fs;
int res = efs_lock_global(&fs);
if (res == 0) {
res = efs_open(fs, filename, mode);
efs_unlock_global(fs);
}
return res;
}
bool epic_fs_is_attached(void)
{
EpicFileSystem *fs;
if (efs_lock_global(&fs) == 0) {
efs_unlock_global(fs);
return true;
} else {
return false;
}
}
int epic_file_close(int fd)
{
EpicFileSystem *fs;
int res = efs_lock_global(&fs);
if (res == 0) {
res = efs_close(fs, fd);
efs_unlock_global(fs);
}
return res;
}
int epic_file_read(int fd, void *buf, size_t nbytes)
{
EpicFileSystem *fs;
int res = efs_lock_global(&fs);
if (res == 0) {
res = efs_read(fs, fd, buf, nbytes);
efs_unlock_global(fs);
}
return res;
}
int epic_file_write(int fd, const void *buf, size_t nbytes)
{
EpicFileSystem *fs;
int res = efs_lock_global(&fs);
if (res == 0) {
res = efs_write(fs, fd, buf, nbytes);
efs_unlock_global(fs);
}
return res;
}
int epic_file_flush(int fd)
{
EpicFileSystem *fs;
int res = efs_lock_global(&fs);
if (res == 0) {
res = efs_flush(fs, fd);
efs_unlock_global(fs);
}
return res;
}
int epic_file_seek(int fd, long offset, int whence)
{
EpicFileSystem *fs;
int res = efs_lock_global(&fs);
if (res == 0) {
res = efs_seek(fs, fd, offset, whence);
efs_unlock_global(fs);
}
return res;
}
int epic_file_tell(int fd)
{
EpicFileSystem *fs;
int res = efs_lock_global(&fs);
if (res == 0) {
res = efs_tell(fs, fd);
efs_unlock_global(fs);
}
return res;
}
int epic_file_stat(const char *filename, struct epic_stat *stat)
{
EpicFileSystem *fs;
int res = efs_lock_global(&fs);
if (res == 0) {
res = efs_stat(fs, filename, stat);
efs_unlock_global(fs);
}
return res;
}
int epic_file_opendir(const char *path)
{
EpicFileSystem *fs;
int res = efs_lock_global(&fs);
if (res == 0) {
res = efs_opendir(fs, path);
efs_unlock_global(fs);
}
return res;
}
int epic_file_readdir(int fd, struct epic_stat *stat)
{
EpicFileSystem *fs;
int res = efs_lock_global(&fs);
if (res == 0) {
res = efs_readdir(fs, fd, stat);
efs_unlock_global(fs);
}
return res;
}
int epic_file_unlink(const char *path)
{
EpicFileSystem *fs;
int res = efs_lock_global(&fs);
if (res == 0) {
res = efs_unlink(fs, path);
efs_unlock_global(fs);
}
return res;
}
int epic_file_rename(const char *oldp, const char *newp)
{
EpicFileSystem *fs;
int res = efs_lock_global(&fs);
if (res == 0) {
res = efs_rename(fs, oldp, newp);
efs_unlock_global(fs);
}
return res;
}
int epic_file_mkdir(const char *dirname)
{
EpicFileSystem *fs;
int res = efs_lock_global(&fs);
if (res == 0) {
res = efs_mkdir(fs, dirname);
efs_unlock_global(fs);
}
return res;
}
#ifndef EPICARDIUM_MODULE_FILESYSTEM_INCLUDED
#define EPICARDIUM_MODULE_FILESYSTEM_INCLUDED
/* ---------- FAT fs ------------------------------------------------------ */
#include <stdbool.h>
#include "epicardium.h"
/**
* module initialization - to be called once at startup before any FreeRTOS tasks
* have been started
*
* calls fatfs_attach
*/
void fatfs_init(void);
/**
* initialize and mount the FLASH storage
*
* NOTE: not safe to be called from an ISR
*/
int fatfs_attach(void);
/**
* asynchronously attach the FLASH storage
*
* safe to be called from an ISR
*/
void fatfs_schedule_attach(void);
/** close all opened FDs, sync and deinitialize FLASH layer */
void fatfs_detach(void);
/** close all onpened FDs
* TODO: add ability to close FDs opened by core0/core1 only
*/
#define EPICARDIUM_COREMASK_0 0x01
#define EPICARDIUM_COREMASK_1 0x02
#define EPICARDIUM_COREMASK_BOTH 0x03
void fatfs_close_all(int coreMask);
#endif//EPICARDIUM_MODULE_FILESYSTEM_INCLUDED
/*
* Implementation of efs_ API functions for a FatFS specific
* EpicFileSystem
*/
#include <errno.h>
#include <stddef.h>
#include <stdio.h>
#include <stdbool.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <ff.h>
#include <diskio.h>
#include <FreeRTOS.h>
#include <semphr.h>
#include <timers.h>
#include "fs/internal.h"
#include "fs/filesystem.h"
#include "user_core/user_core.h"
#include "epicardium.h"
#include "card10.h"
#include "os/core.h"
#include "modules/modules.h"
#include "api/common.h"
#include "os/mutex.h"
#define SSLOG_DEBUG(...) LOG_DEBUG("fatfs", __VA_ARGS__)
#define SSLOG_INFO(...) LOG_INFO("fatfs", __VA_ARGS__)
#define SSLOG_ERR(...) LOG_ERR("fatfs", __VA_ARGS__)
/* clang-format off */
#define EPIC_FAT_MAX_OPENED (1 << (EPIC_FAT_FD_INDEX_BITS))
#define EPIC_FAT_FD_GENERATION_BITS (31 - (EPIC_FAT_FD_INDEX_BITS))
#define EPIC_FAT_FD_INDEX_MASK (uint32_t)((1u << EPIC_FAT_FD_INDEX_BITS) - 1)
#define EPIC_FAT_FD_INDEX(fd) ((uint32_t)(fd)&EPIC_FAT_FD_INDEX_MASK)
#define EPIC_FAT_FD_GENERATION(fd) ((uint32_t)(fd) >> EPIC_FAT_FD_INDEX_BITS)
#define EPIC_FAT_FD_MAX_GENERATION (uint32_t)((1u << EPIC_FAT_FD_GENERATION_BITS) - 1)
#define EPIC_FAT_FD(idx, gen) (int)(((uint32_t)(gen) << EPIC_FAT_FD_INDEX_BITS) \
| ((uint32_t)(idx)&EPIC_FAT_FD_INDEX_MASK))
/* clang-format on */
struct FatObject {
uint32_t generation;
int coreMask;
enum epic_stat_type type;
union {
FIL file;
DIR dir;
};
};
struct EpicFileSystem {
struct FatObject pool[EPIC_FAT_MAX_OPENED];
uint32_t generationCount;
bool attached;
FATFS FatFs;
int lockCoreMask;
};
// this table converts from FRESULT to POSIX errno
static const int s_libffToErrno[20];
static const char *f_get_rc_string(FRESULT rc);
static void efs_close_all(EpicFileSystem *fs, int coreMask);
/**
* if EPICSTAT_NONE is passed to `expected`, the type is not checked.
*/
static bool efs_get_opened(
EpicFileSystem *fs,
int i,
enum epic_stat_type expected,
struct FatObject **res,
int *rc
);
static bool
efs_get_new(EpicFileSystem *fs, uint32_t *idx, struct FatObject **obj, int *rc);
static int efs_obj_init(
EpicFileSystem *fs,
struct FatObject *obj,
uint32_t index,
enum epic_stat_type type
);
static void efs_obj_deinit(EpicFileSystem *fs, struct FatObject *obj);
static void efs_init_stat(struct epic_stat *stat, FILINFO *finfo);
static EpicFileSystem s_globalFileSystem;
static struct mutex fatfs_lock = { 0 };
static void cb_attachTimer(void *a, uint32_t b)
{
fatfs_attach();
}
void fatfs_init()
{
static volatile bool s_initCalled = false;
//this has to be called vefore any tasks have been started!
// ...not 100% water-tight though, since tick count might be zero even after vTaskStartScheduler
// has been called...
assert(xTaskGetTickCount() == configINITIAL_TICK_COUNT);
assert(!s_initCalled);
s_initCalled = true;
mutex_create(&fatfs_lock);
s_globalFileSystem.generationCount = 1;
fatfs_attach();
}
/*
* NOTE about attach/detach:
*
* while in detach, we're calling diskio_deinitialize (a function that is
* originally not present in libff's diskio.h), we do not need to call
* diskio_initialize in attach, since it will implicitly be called by
* any f_ operation, via libff's find_volume for volumes that have not
* been mounted yet.
*
*/
int fatfs_attach()
{
FRESULT ff_res;
int rc = 0;
mutex_lock(&fatfs_lock);
EpicFileSystem *fs = &s_globalFileSystem;
if (!fs->attached) {
ff_res = f_mount(&fs->FatFs, "/", 0);
if (ff_res == FR_OK) {
fs->attached = true;
SSLOG_INFO("attached\n");
} else {
SSLOG_ERR(
"f_mount error %s\n", f_get_rc_string(ff_res)
);
rc = -s_libffToErrno[ff_res];
}
}
mutex_unlock(&fatfs_lock);
return rc;
}
void fatfs_schedule_attach(void)
{
//if we're running in thread context, cont't call the *FromISR version
if (xPortIsInsideInterrupt()) {
xTimerPendFunctionCallFromISR(cb_attachTimer, NULL, 0, NULL);
} else {
xTimerPendFunctionCall(
cb_attachTimer, NULL, 0, 1); //wait 1 tick
}
}
void fatfs_detach()
{
FRESULT ff_res;
EpicFileSystem *fs;
if (efs_lock_global(&fs) == 0) {
if (fs->attached) {
efs_close_all(fs, EPICARDIUM_COREMASK_BOTH);
//unmount by passing NULL as fs object, will destroy our sync object via ff_del_syncobj
ff_res = f_mount(NULL, "/", 0);
if (ff_res != FR_OK) {
SSLOG_ERR(
"f_mount (unmount) error %s\n",
f_get_rc_string(ff_res)
);
}
fs->attached = false;
disk_deinitialize();
SSLOG_INFO("detached\n");
}
efs_unlock_global(fs);
}
}
void fatfs_close_all(int coreMask)
{
EpicFileSystem *fs;
if (efs_lock_global(&fs) == 0) {
efs_close_all(fs, coreMask);
efs_unlock_global(fs);
}
}
static const char *f_get_rc_string(FRESULT rc)
{
static const TCHAR *rcstrings =
_T("OK\0DISK_ERR\0INT_ERR\0NOT_READY\0NO_FILE\0NO_PATH\0INVALID_NAME\0")
_T("DENIED\0EXIST\0INVALID_OBJECT\0WRITE_PROTECTED\0INVALID_DRIVE\0")
_T("NOT_ENABLED\0NO_FILESYSTEM\0MKFS_ABORTED\0TIMEOUT\0LOCKED\0")
_T("NOT_ENOUGH_CORE\0TOO_MANY_OPEN_FILES\0INVALID_PARAMETER\0");
FRESULT i;
const char *p = rcstrings;
for (i = 0; i != rc && *p; i++) {
while (*p++)
;
}
return p;
}
int efs_lock_global(EpicFileSystem **fs)
{
*fs = NULL;
mutex_lock(&fatfs_lock);
if (!s_globalFileSystem.attached) {
mutex_unlock(&fatfs_lock);
return -ENODEV;
}
*fs = &s_globalFileSystem;
if (xTaskGetCurrentTaskHandle() == dispatcher_task_id) {
s_globalFileSystem.lockCoreMask = EPICARDIUM_COREMASK_1;
} else {
s_globalFileSystem.lockCoreMask = EPICARDIUM_COREMASK_0;
}
return 0;
}
void efs_unlock_global(EpicFileSystem *fs)
{
(void)fs;
mutex_unlock(&fatfs_lock);
}
static bool efs_get_opened(
EpicFileSystem *fs,
int fd,
enum epic_stat_type expected,
struct FatObject **obj,
int *rc
) {
uint32_t index = EPIC_FAT_FD_INDEX(fd);
uint32_t generation = EPIC_FAT_FD_GENERATION(fd);
*obj = NULL;
*rc = -EBADF;
if (index >= EPIC_FAT_MAX_OPENED ||
generation >= EPIC_FAT_FD_MAX_GENERATION) {
return false;
}
if (fs->pool[index].generation != generation) {
return false;
}
if (expected != EPICSTAT_NONE && fs->pool[index].type != expected) {
return false;
}
*obj = &fs->pool[index];
*rc = 0;
return true;
}
static bool
efs_get_new(EpicFileSystem *fs, uint32_t *idx, struct FatObject **obj, int *rc)
{
uint32_t index;
*obj = NULL;
*rc = 0;
*idx = 0;
//find free object to use
for (index = 0; index < EPIC_FAT_MAX_OPENED; ++index) {
if (fs->pool[index].type == EPICSTAT_NONE) {
break;
}
}
if (index == EPIC_FAT_MAX_OPENED) {
*rc = -s_libffToErrno[FR_TOO_MANY_OPEN_FILES];
return false;
}
*obj = &fs->pool[index];
*idx = index;
return true;
}
static int efs_obj_init(
EpicFileSystem *fs,
struct FatObject *obj,
uint32_t index,
enum epic_stat_type type
) {
uint32_t generation;
generation = fs->generationCount++;
if (generation == EPIC_FAT_FD_MAX_GENERATION) {
fs->generationCount = 1;
}
obj->type = type;
obj->generation = generation;
obj->coreMask = fs->lockCoreMask;
return EPIC_FAT_FD(index, generation);
}
static void efs_obj_deinit(EpicFileSystem *fs, struct FatObject *obj)
{
obj->type = EPICSTAT_NONE;
obj->generation = 0;
obj->coreMask = 0;
}
/* here we're trying to mirror glibc's behaviour:
* any combination of rwax parses but only the first of those flags wins:
* - rw, ra, rr all open read-only
* a `+` at any position but the first turns into read-write
* any other character at any position yields EINVAL
*/
static inline bool parse_mode(const char *mstring, int *mode)
{
switch (mstring[0]) {
case 'r':
*mode = FA_READ;
break;
case 'w':
*mode = FA_CREATE_ALWAYS | FA_WRITE;
break;
case 'x':
//in constrast to FA_CREATE_ALWAYS, FA_CREATE_NEW fails for existing files
*mode = FA_WRITE | FA_CREATE_NEW;
break;
case 'a':
//in constrast to FA_CREATE_ALWAYS, FA_CREATE_NEW fails for existing files
*mode = FA_WRITE | FA_OPEN_APPEND;
break;
default:
return false;
}
while (*mstring) {
switch (*mstring++) {
case '+': //turns any of r,w,x into read&write
*mode |= FA_READ | FA_WRITE;
break;
case 'r': //fallthrough intentional
case 'w': //fallthrough intentional
case 'a': //fallthrough intentional
case 'x': //fallthrough intentional
case 'b': //fallthrough intentional
break;
default:
return false;
}
}
return true;
}
int efs_open(EpicFileSystem *fs, const char *filename, const char *modeString)
{
struct FatObject *o = NULL;
uint32_t index;
int mode = 0;
int res;
if (!parse_mode(modeString, &mode)) {
return -EINVAL;
}
if (!efs_get_new(fs, &index, &o, &res)) {
return res;
}
res = f_open(&o->file, filename, mode);
if (res != FR_OK) {
return -s_libffToErrno[res];
}
// for 'a' mode, we must begin at the end of the file
if ((mode & FA_OPEN_APPEND) != 0) {
f_lseek(&o->file, f_size(&o->file));
}
return efs_obj_init(fs, o, index, EPICSTAT_FILE);
}
int efs_close(EpicFileSystem *fs, int fd)
{
int res;
struct FatObject *o;
if (efs_get_opened(fs, fd, EPICSTAT_NONE, &o, &res)) {
if (o->type == EPICSTAT_FILE) {
res = f_close(&o->file);
} else {
res = f_closedir(&o->dir);
}
if (res != FR_OK) {
return -s_libffToErrno[res];
}
efs_obj_deinit(fs, o);
}
return res;
}
void efs_close_all(EpicFileSystem *fs, int coreMask)
{
assert(coreMask != 0);
for (int i = 0; i < EPIC_FAT_MAX_OPENED; ++i) {
if (!(fs->pool[i].coreMask & coreMask)) {
continue;
}
switch (fs->pool[i].type) {
case EPICSTAT_FILE:
f_close(&fs->pool[i].file);
break;
case EPICSTAT_DIR:
f_closedir(&fs->pool[i].dir);
break;
case EPICSTAT_NONE:
break;
}
efs_obj_deinit(fs, &fs->pool[i]);
}
}
int efs_read(EpicFileSystem *fs, int fd, void *buf, size_t nbytes)
{
unsigned int nread = 0;
int res;
struct FatObject *o;
if (efs_get_opened(fs, fd, EPICSTAT_FILE, &o, &res)) {
res = f_read(&o->file, buf, nbytes, &nread);
if (res != FR_OK) {
return -s_libffToErrno[res];
}
res = (int)nread;
}
return res;
}
int efs_write(EpicFileSystem *fs, int fd, const void *buf, size_t nbytes)
{
unsigned int nwritten = 0;
int res;
struct FatObject *o;
if (efs_get_opened(fs, fd, EPICSTAT_FILE, &o, &res)) {
res = f_write(&o->file, buf, nbytes, &nwritten);
if (res != FR_OK) {
res = -s_libffToErrno[res];
} else {
res = (int)nwritten;
}
}
return res;
}
int efs_flush(EpicFileSystem *fs, int fd)
{
int res = 0;
struct FatObject *o;
if (efs_get_opened(fs, fd, EPICSTAT_FILE, &o, &res)) {
res = f_sync(&o->file);
if (res != FR_OK) {
res = -s_libffToErrno[res];
}
}
return res;
}
int efs_seek(EpicFileSystem *fs, int fd, long offset, int whence)
{
int res = 0;
struct FatObject *o;
if (efs_get_opened(fs, fd, EPICSTAT_FILE, &o, &res)) {
switch (whence) {
case SEEK_SET:
res = f_lseek(&o->file, offset);
break;
case SEEK_CUR:
res = f_lseek(&o->file, f_tell(&o->file) + offset);
break;
case SEEK_END:
res = f_lseek(&o->file, f_size(&o->file) + offset);
break;
default:
return -EINVAL;
}
res = -s_libffToErrno[res];
}
return res;
}
int efs_tell(EpicFileSystem *fs, int fd)
{
int res;
struct FatObject *o;
if (efs_get_opened(fs, fd, EPICSTAT_FILE, &o, &res)) {
//f_tell simply accesses fp->fptr so no errors are expected - return directly
res = f_tell(&o->file);
}
return res;
}
static void efs_init_stat(struct epic_stat *stat, FILINFO *finfo)
{
if (finfo->fname[0] != 0) {
if (finfo->fattrib & AM_DIR) {
stat->type = EPICSTAT_DIR;
} else {
stat->type = EPICSTAT_FILE;
}
strncpy(stat->name, finfo->fname, EPICSTAT_MAX_PATH);
} else {
stat->name[0] = 0;
stat->type = EPICSTAT_NONE;
}
}
int efs_stat(EpicFileSystem *fs, const char *filename, struct epic_stat *stat)
{
int res = 0;
static FILINFO finfo;
res = f_stat(filename, &finfo);
if (res == 0) {
efs_init_stat(stat, &finfo);
}
return -s_libffToErrno[res];
}
int efs_opendir(EpicFileSystem *fs, const char *path)
{
int res;
struct FatObject *o;
uint32_t index;
if (efs_get_new(fs, &index, &o, &res)) {
res = f_opendir(&o->dir, path);
if (res != FR_OK) {
return -s_libffToErrno[res];
}
return efs_obj_init(fs, o, index, EPICSTAT_DIR);
}
return res;
}
int efs_readdir(EpicFileSystem *fs, int fd, struct epic_stat *stat)
{
int res;
struct FatObject *o;
if (efs_get_opened(fs, fd, EPICSTAT_DIR, &o, &res)) {
FILINFO finfo;
res = f_readdir(&o->dir, stat ? &finfo : NULL);
if (res == FR_OK && stat) {
efs_init_stat(stat, &finfo);
}
}
return res;
}
int efs_unlink(EpicFileSystem *fs, const char *path)
{
int res = f_unlink(path);
return -s_libffToErrno[res];
}
int efs_rename(EpicFileSystem *fs, const char *oldp, const char *newp)
{
int res = f_rename(oldp, newp);
return -s_libffToErrno[res];
}
int efs_mkdir(EpicFileSystem *fs, const char *dirname)
{
int res = f_mkdir(dirname);
return -s_libffToErrno[res];
}
static const int s_libffToErrno[20] = {
[FR_OK] = 0,
[FR_DISK_ERR] = EIO,
[FR_INT_ERR] = EIO,
[FR_NOT_READY] = EBUSY,
[FR_NO_FILE] = ENOENT,
[FR_NO_PATH] = ENOENT,
[FR_INVALID_NAME] = EINVAL,
[FR_DENIED] = EACCES,
[FR_EXIST] = EEXIST,
[FR_INVALID_OBJECT] = EINVAL,
[FR_WRITE_PROTECTED] = EROFS,
[FR_INVALID_DRIVE] = ENODEV,
[FR_NOT_ENABLED] = ENODEV,
[FR_NO_FILESYSTEM] = ENODEV,
[FR_MKFS_ABORTED] = EIO,
[FR_TIMEOUT] = EIO,
[FR_LOCKED] = EIO,
[FR_NOT_ENOUGH_CORE] = ENOMEM,
[FR_TOO_MANY_OPEN_FILES] = EMFILE,
[FR_INVALID_PARAMETER] = EINVAL,
};
#include "fs_util.h"
#include "epicardium.h"
#include <stdint.h>
#include <string.h>
int fs_read_file(char *filename, void *data, int len)
{
int fd = epic_file_open(filename, "r");
if (fd < 0) {
return fd;
}
int res = epic_file_read(fd, data, len);
epic_file_close(fd);
return res;
}
int fs_read_text_file(char *filename, char *data, int len)
{
int readbytes;
if (len < 1)
return -1;
readbytes = fs_read_file(filename, data, len - 1);
if (readbytes < 0) {
data[0] = 0;
return readbytes;
};
data[readbytes] = 0;
while (readbytes > 0 && data[readbytes - 1] < 0x20) {
data[--readbytes] = 0;
};
return readbytes;
}
int fs_write_file(char *filename, const void *data, int len)
{
int fd = epic_file_open(filename, "w");
if (fd < 0) {
return fd;
}
int res = epic_file_write(fd, data, len);
epic_file_close(fd);
return res;
}
int fs_get_file_size(char *filename)
{
struct epic_stat stat;
int res = epic_file_stat(filename, &stat);
return res < 0 ? res : (int)stat.size;
}
#if 0
#include "ff.h"
FATFS FatFs; /* File system object for logical drive */
FS_USAGE FsUsage;
/* TODO: Port functions from r0ket/rad10 libs */
int fs_info(FATFS *fs)
{
memcpy(fs, &FatFs, sizeof(FATFS));
return 0;
}
int fs_usage(FATFS *fs, FS_USAGE *fs_usage)
{
FRESULT res;
DWORD tot_clust, fre_clust, sec_size;
res = f_getfree("/", &fre_clust, &fs);
if(res != FR_OK)
return -res;
// sectore size = sectors per cluster * sector size
#if FF_MAX_SS == FF_MIN_SS
sec_size = fs->csize * FF_MAX_SS;
#else
sec_size = fs->csize * fs.ssize;
#endif
// total/free sectors * sectore size
tot_clust = fs->n_fatent - 2;
fs_usage->total = tot_clust * sec_size; //FatFs.ssize;
fs_usage->free = fre_clust * sec_size; //FatFs.ssize;
return 0;
}
#endif
#pragma once
#include <ff.h>
typedef struct {
DWORD total;
DWORD free;
} FS_USAGE;
int fs_info(FATFS *fs);
int fs_usage(FATFS *fs, FS_USAGE *fs_usage);
int fs_read_file(char * filename, void * data, int len);
int fs_read_text_file(char * filename, char * data, int len);
int fs_write_file(char * filename, const void * data, int len);
int fs_get_file_size(char * filename);
#ifndef EPICARCIUM_FS_INTERNAL_H_INCLUDED
#define EPICARCIUM_FS_INTERNAL_H_INCLUDED
#include <stdint.h>
#include <stddef.h>
#include <stdbool.h>
#include "epicardium.h"
/* Number of bits to use for indexing into our internal pool of files/directories
* This indirectly specifies the size of the pool as 1^EPIC_FAT_FD_INDEX_BITS
* Increase if number of open file descriptors is not enough, but be aware of
* memory usage of the pool!
*/
#define EPIC_FAT_FD_INDEX_BITS 4
#define EPIC_FAT_STATIC_SEMAPHORE 1
// forward declaration, actual definition is in filesystem_fat.c
typedef struct EpicFileSystem EpicFileSystem;
int efs_open(EpicFileSystem *fs, const char *filename, const char *modeString);
int efs_close(EpicFileSystem *fs, int fd);
int efs_read(EpicFileSystem *fs, int fd, void *buf, size_t nbytes);
int efs_write(EpicFileSystem *fs, int fd, const void *buf, size_t nbytes);
int efs_flush(EpicFileSystem *fs, int fd);
int efs_seek(EpicFileSystem *fs, int fd, long offset, int whence);
int efs_tell(EpicFileSystem *fs, int fd);
int efs_stat(EpicFileSystem *fs, const char *filename, struct epic_stat *stat);
int efs_opendir(EpicFileSystem *fs, const char *path);
int efs_readdir(EpicFileSystem *fs, int fd, struct epic_stat *stat);
int efs_unlink(EpicFileSystem *fs, const char *path);
int efs_rename(EpicFileSystem *fs, const char *oldp, const char *newp);
int efs_mkdir(EpicFileSystem *fs, const char *dirname);
/**
* lock global filesystem
*
* locks the global mutex and, if the global EpicFileSystem has been initialized correctly
* passes it to *fs
*
* Upon successful return, the filesystem has to be re-locked with epic_fs_unlock_global
* In case of error, the filesystem will be left in a locked state.
*/
int efs_lock_global(EpicFileSystem** fs);
void efs_unlock_global(EpicFileSystem* fs);
#endif// EPICARCIUM_FS_INTERNAL_H_INCLUDED
fs_sources = files(
'fileops.c',
'filesystem_fat.c',
'fs_util.c',
)
#pragma once
/*
* 32-bit ELF structures.
*
* ref: Tool Interface Standard (TIS) Executable and Linking Format (ELF) Specification
* Version 1.2, May 1995
* http://refspecs.linuxbase.org/elf/elf.pdf
*
* ref: ELF for the ARM Architecture
* ARM IHI 0044F, current through ABI release 2.10, 24th November 2015
* http://infocenter.arm.com/help/topic/com.arm.doc.ihi0044f/IHI0044F_aaelf.pdf
*
*/
#include <stdint.h>
typedef uint32_t Elf32_Addr;
typedef uint16_t Elf32_Half;
typedef uint32_t Elf32_Off;
typedef int32_t Elf32_Sword;
typedef uint32_t Elf32_Word;
#define EI_NIDENT 16
typedef struct {
unsigned char e_ident[EI_NIDENT];
Elf32_Half e_type;
Elf32_Half e_machine;
Elf32_Word e_version;
Elf32_Addr e_entry;
Elf32_Off e_phoff;
Elf32_Off e_shoff;
Elf32_Word e_flags;
Elf32_Half e_ehsize;
Elf32_Half e_phentsize;
Elf32_Half e_phnum;
Elf32_Half e_shentsize;
Elf32_Half e_shnum;
Elf32_Half e_shstrndx;
} Elf32_Ehdr;
#define ET_DYN 3 // Shared object file or PIE binary
#define EM_ARM 40
#define EV_CURRENT 1
#define ELFMAG0 0x7f
#define ELFMAG1 'E'
#define ELFMAG2 'L'
#define ELFMAG3 'F'
#define ELFCLASS32 1
#define ELFDATA2LSB 1
typedef struct {
Elf32_Word sh_name;
Elf32_Word sh_type;
Elf32_Word sh_flags;
Elf32_Addr sh_addr;
Elf32_Off sh_offset;
Elf32_Word sh_size;
Elf32_Word sh_link;
Elf32_Word sh_info;
Elf32_Word sh_addralign;
Elf32_Word sh_entsize;
} Elf32_Shdr;
#define SHT_RELA 4
#define SHT_REL 9
#define SHT_DYNSYM 11
typedef struct {
Elf32_Word st_name;
Elf32_Addr st_value;
Elf32_Word st_size;
unsigned char st_info;
unsigned char st_other;
Elf32_Half st_shndx;
} Elf32_Sym;
#define ELF32_ST_BIND(i) ((i)>>4)
#define ELF32_ST_TYPE(i) ((i)&0xf)
#define STB_WEAK 2
typedef struct {
Elf32_Addr r_offset;
Elf32_Word r_info;
} Elf32_Rel;
#define ELF32_R_SYM(i) ((i)>>8)
#define ELF32_R_TYPE(i) ((unsigned char)(i))
#define R_ARM_RELATIVE 0x17
typedef struct {
Elf32_Word p_type;
Elf32_Off p_offset;
Elf32_Addr p_vaddr;
Elf32_Addr p_paddr;
Elf32_Word p_filesz;
Elf32_Word p_memsz;
Elf32_Word p_flags;
Elf32_Word p_align;
} Elf32_Phdr;
#define PT_LOAD 1
#define PT_INTERP 3
#include "l0der/l0der.h"
#include <alloca.h>
#include <stdio.h>
#include <string.h>
#include "epicardium.h"
#include "l0der/elf.h"
#include "os/core.h"
/*
* l0der is, in reality, a boneless operating-system style ELF loader.
*
* To implement it, we parse an ELF file somewhat defensively, trying to
* not DoS ourselves by overallocating RAM (no heap allocations, no recursion).
*
* Currently we support only relocatable, PIE binaries. Adding support for
* static ELFs would be trivial, however we want to keep the possibility to
* shuffle around memory areas in future versions of card10 (possibly giving
* l0dables more RAM than 256k) without having to recompile all l0dables. We
* are also keeping the opportunity to have separate loading schemes in the
* future, for instance:
* - l0dables running next to pycardium, without unloading it
* - multiple l0dables running next to each other (TSR-style)
*
* Thus, we use PIE l0dables to keep these possibilities open and not write down
* a memory map in stone.
*/
#define WEAK_SYMBOL_MAX 128
struct _pie_load_info {
/// Populated by _load_pie
// Addresses within ELF file.
uint32_t image_start;
uint32_t image_limit;
// Highest alignment request for a segment.
uint32_t strictest_alignment;
/// Populated by _parse_dynamic_symbols
// List of weak symbols for which relocations can be ignored.
uint32_t weak_symbols[WEAK_SYMBOL_MAX];
uint32_t weak_symbol_count;
/// Populated by _get_load_addr
// Load address of ELF file.
uint32_t load_address;
// Addresses within memory of ELF file.
uint32_t image_load_start;
uint32_t image_load_limit;
// Stack top.
uint32_t stack_top;
};
/*
* Read an ELF header, check E_IDENT.
*/
static int _read_elf_header(int fd, Elf32_Ehdr *hdr)
{
int res;
epic_file_seek(fd, 0, SEEK_SET);
if ((res = epic_file_read(fd, hdr, sizeof(Elf32_Ehdr))) !=
sizeof(Elf32_Ehdr)) {
LOG_ERR("l0der", "_read_elf_header: read failed: %d", res);
return res;
}
if (hdr->e_ident[0] != ELFMAG0 || hdr->e_ident[1] != ELFMAG1 ||
hdr->e_ident[2] != ELFMAG2 || hdr->e_ident[3] != ELFMAG3) {
LOG_ERR("l0der", "_read_elf_header: not an ELF file");
return -ENOEXEC;
}
if (hdr->e_ident[4] != ELFCLASS32) {
LOG_ERR("l0der", "_read_elf_header: not a 32-bit ELF");
return -ENOEXEC;
}
if (hdr->e_ident[5] != ELFDATA2LSB) {
LOG_ERR("l0der", "_read_elf_header: not a little-endian ELF");
return -ENOEXEC;
}
if (hdr->e_ident[6] != EV_CURRENT) {
LOG_ERR("l0der", "_read_elf_header: not a v1 ELF");
return -ENOEXEC;
}
if (hdr->e_ehsize < sizeof(Elf32_Ehdr)) {
LOG_ERR("l0der", "_raed_elf_header: header too small");
return -ENOEXEC;
}
return 0;
}
/*
* Read bytes from file at a given offset.
*
* :param int fd: file pointer to read from
* :param uint32_t address: address from which to read
* :param void *data: buffer into which to read
* :param size_t count: amount of bytes to read
* :returns: ``0`` on success or a negative value on error. Possible errors:
*
* - ``-EIO``: Could not read from FAT - address out of bounds of not enough bytes available.
*/
static int _seek_and_read(int fd, uint32_t address, void *data, size_t count)
{
int res;
if ((res = epic_file_seek(fd, address, SEEK_SET)) != 0) {
LOG_ERR("l0der",
"_seek_and_read: could not seek to 0x%lx: %d",
address,
res);
return res;
}
if ((size_t)(res = epic_file_read(fd, data, count)) != count) {
LOG_ERR("l0der", "_seek_and_read: could not read: %d", res);
return res;
}
return 0;
}
/*
* Read an ELF program header header.
*/
static int _read_program_header(int fd, uint32_t phdr_addr, Elf32_Phdr *phdr)
{
return _seek_and_read(fd, phdr_addr, phdr, sizeof(Elf32_Phdr));
}
/*
* Read an ELF section header header.
*/
static int _read_section_header(int fd, uint32_t shdr_addr, Elf32_Shdr *shdr)
{
return _seek_and_read(fd, shdr_addr, shdr, sizeof(Elf32_Shdr));
}
/*
* Check an ELF program header.
*
* This function ensures basic memory sanity of a program header / segment.
* It ensures that it points to a file region that is contained within the file fully.
*/
static int _check_program_header(int fd, size_t size, Elf32_Phdr *phdr)
{
// Check file size/offset.
uint32_t file_start = phdr->p_offset;
uint32_t file_limit = phdr->p_offset + phdr->p_filesz;
if (file_limit < file_start) {
LOG_ERR("l0der", "_check_program_header: file size overflow");
return -ENOEXEC;
}
if (file_limit > size) {
LOG_ERR("l0der",
"_check_program_header: extends past end of file");
return -ENOEXEC;
}
if (phdr->p_type == PT_LOAD) {
// Check mem/file size.
if (phdr->p_filesz > phdr->p_memsz) {
LOG_ERR("l0der",
"_check_program_header: file size larger than memory size");
return -ENOEXEC;
}
uint32_t mem_start = phdr->p_vaddr;
uint32_t mem_limit = phdr->p_vaddr + phdr->p_memsz;
if (mem_limit < mem_start) {
LOG_ERR("l0der",
"_check_program_header: mem size overflow");
return -ENOEXEC;
}
}
return 0;
}
/*
* Check an ELF section header.
*
* This function ensures basic memory sanity of a section header.
* It ensures that it points to a file region that is contained within the file fully.
*/
static int _check_section_header(int fd, size_t size, Elf32_Shdr *shdr)
{
// Check file size/offset.
uint32_t file_start = shdr->sh_offset;
uint32_t file_limit = shdr->sh_offset + shdr->sh_size;
if (file_limit < file_start) {
LOG_ERR("l0der", "_check_section_header: file size overflow");
return -ENOEXEC;
}
if (file_limit > size) {
LOG_ERR("l0der",
"_check_section_header: extends past end of file");
return -ENOEXEC;
}
return 0;
}
/*
* Interpreter expected in l0dable PIE binaries.
*/
static const char *_interpreter = "card10-l0dable";
/*
* Check that the given INTERP program header contains the correct interpreter string.
*/
static int _check_interp(int fd, Elf32_Phdr *phdr)
{
int res;
uint32_t buffer_size = strlen(_interpreter) + 1;
char *interp = alloca(buffer_size);
memset(interp, 0, buffer_size);
if ((res = _seek_and_read(fd, phdr->p_offset, interp, buffer_size)) !=
0) {
return res;
}
if (strncmp(interp, _interpreter, strlen(_interpreter)) != 0) {
LOG_ERR("l0der",
"_check_interp: invalid interpreter, want card10-l0dable");
return -1;
}
return 0;
}
/*
* Calculate address at which binary should be loaded.
*
* Currently this means trying to fit it into core1 RAM.
*/
static int _get_load_addr(struct _pie_load_info *li)
{
uint32_t image_size = li->image_limit - li->image_start;
// ref: Documentation/memorymap.rst
uint32_t core1_mem_start = 0x20040000;
uint32_t core1_mem_limit = 0x20080000;
uint32_t core1_mem_size = core1_mem_limit - core1_mem_start;
if (image_size > core1_mem_size) {
LOG_ERR("l0der",
"_get_load_addr: image too large (need 0x%08lx bytes, have %08lx",
image_size,
core1_mem_size);
return -ENOMEM;
}
// Place image at bottom of core1 memory range.
li->load_address = core1_mem_start;
li->image_load_start = li->load_address + li->image_start;
li->image_load_limit = li->load_address + li->image_limit;
// Ensure within alignment requests.
if ((li->load_address % li->strictest_alignment) != 0) {
LOG_ERR("l0der",
"_get_load_addr: too strict alignment request for %ld bytes",
li->strictest_alignment);
return -ENOEXEC;
}
// Place stack at top of core1 memory range.
li->stack_top = core1_mem_limit;
// Check that there is enough stack space.
uint32_t stack_space = li->stack_top - li->image_load_limit;
if (stack_space < 8192) {
LOG_WARN(
"l0der",
"_get_load_addr: low stack space (%ld bytes)",
stack_space
);
} else if (stack_space < 256) {
LOG_ERR("l0der",
"_get_load_addr: low stack space (%ld bytes), cannot continue",
stack_space);
return -ENOMEM;
}
LOG_INFO(
"l0der",
"Stack at %08lx, %ld bytes available",
li->stack_top,
stack_space
);
return 0;
}
/*
* Load a program segment into memory.
*
* Segment must be a LOAD segment.
*/
static int _load_segment(int fd, struct _pie_load_info *li, Elf32_Phdr *phdr)
{
uint32_t segment_start = li->load_address + phdr->p_vaddr;
uint32_t segment_limit = segment_start + phdr->p_memsz;
LOG_INFO(
"l0der",
"Segment %08lx-%08lx: 0x%lx bytes from file",
segment_start,
segment_limit,
phdr->p_filesz
);
memset((void *)segment_start, 0, phdr->p_memsz);
return _seek_and_read(
fd, phdr->p_offset, (void *)segment_start, phdr->p_filesz
);
}
/*
* Parse dynamic symbol sections.
*/
static int _parse_dynamic_symbols(
int fd, size_t size, struct _pie_load_info *li, Elf32_Ehdr *hdr
) {
int res;
Elf32_Shdr shdr;
Elf32_Sym sym;
// Go through all dynamic symbol sections.
for (int i = 0; i < hdr->e_shnum; i++) {
uint32_t shdr_addr = hdr->e_shoff + (i * hdr->e_shentsize);
if ((res = _read_section_header(fd, shdr_addr, &shdr)) != 0) {
return res;
}
if (shdr.sh_type != SHT_DYNSYM) {
continue;
}
if ((res = _check_section_header(fd, size, &shdr)) != 0) {
return res;
}
if ((shdr.sh_size % sizeof(Elf32_Sym)) != 0) {
LOG_ERR("l0der",
"_parse_dynamic_symbols: SHT_DYN section with invalid size: %ld",
shdr.sh_size);
return -EIO;
}
uint32_t sym_count = shdr.sh_size / sizeof(Elf32_Sym);
// Read symbols one by one.
if ((res = epic_file_seek(fd, shdr.sh_offset, SEEK_SET)) != 0) {
LOG_ERR("l0der",
"_parse_dynamic_symbols: seek to first relocation (at 0x%lx) failed",
shdr.sh_offset);
return res;
}
for (uint32_t j = 0; j < sym_count; j++) {
if ((res = epic_file_read(
fd, &sym, sizeof(Elf32_Sym))) !=
sizeof(Elf32_Sym)) {
LOG_ERR("l0der",
"__parse_dynamic_symbols: symbol read failed: %d",
res);
return res;
}
uint32_t bind = ELF32_ST_BIND(sym.st_info);
if (bind != STB_WEAK) {
continue;
}
if (li->weak_symbol_count >= WEAK_SYMBOL_MAX) {
LOG_ERR("l0der",
"__parse_dynamic_symbols: too many weak symbols (limit: %d)",
WEAK_SYMBOL_MAX);
return -ENOMEM;
}
li->weak_symbols[li->weak_symbol_count++] = j;
}
}
return 0;
}
/*
* Apply dynamic relocations from ELF.
*
* Currently, we only support R_ARM_RELATIVE relocations. These seem to be
* the only one used when making 'standard' PIE binaries on RAM. However, other
* kinds might have to be implemented in the future.
*/
static int _run_relocations(
int fd, size_t size, struct _pie_load_info *li, Elf32_Ehdr *hdr
) {
int res;
Elf32_Shdr shdr;
Elf32_Rel rel;
// Go through all relocation sections.
for (int i = 0; i < hdr->e_shnum; i++) {
uint32_t shdr_addr = hdr->e_shoff + (i * hdr->e_shentsize);
if ((res = _read_section_header(fd, shdr_addr, &shdr)) != 0) {
return res;
}
// We don't support RELA (relocation with addend) sections (yet?).
if (shdr.sh_type == SHT_RELA) {
LOG_ERR("l0der",
"_run_relocations: found unsupported SHT_RELA section, bailing");
return -ENOEXEC;
}
if (shdr.sh_type != SHT_REL) {
continue;
}
if ((res = _check_section_header(fd, size, &shdr)) != 0) {
return res;
}
if ((shdr.sh_size % sizeof(Elf32_Rel)) != 0) {
LOG_ERR("l0der",
"_run_relocations: SHT_REL section with invalid size: %ld",
shdr.sh_size);
return -ENOEXEC;
}
uint32_t reloc_count = shdr.sh_size / sizeof(Elf32_Rel);
// Read relocations one by one.
if ((res = epic_file_seek(fd, shdr.sh_offset, SEEK_SET)) != 0) {
LOG_ERR("l0der",
"_run_relocations: seek to first relocation (at 0x%lx) failed",
shdr.sh_offset);
return res;
}
for (uint32_t j = 0; j < reloc_count; j++) {
if ((res = epic_file_read(
fd, &rel, sizeof(Elf32_Rel))) !=
sizeof(Elf32_Rel)) {
LOG_ERR("l0der",
"_run_relocations: relocation read failed: %d",
res);
return res;
}
uint32_t sym = ELF32_R_SYM(rel.r_info);
uint8_t type = ELF32_R_TYPE(rel.r_info);
// Skip relocations that are for weak symbols.
// (ie., do not resolve relocation - they default to a safe NULL)
uint8_t skip = 0;
if (sym != 0) {
for (uint32_t k = 0; k < li->weak_symbol_count;
k++) {
if (li->weak_symbols[k] == sym) {
skip = 1;
break;
}
}
}
if (skip) {
continue;
}
switch (type) {
case R_ARM_RELATIVE:
// Relocate.
if ((rel.r_offset % 4) != 0) {
LOG_ERR("l0der",
"_run_relocations: R_ARM_RELATIVE address must be 4-byte aligned");
return -ENOEXEC;
}
volatile uint32_t *addr =
(uint32_t
*)(rel.r_offset + li->load_address);
if ((uint32_t)addr < li->image_load_start ||
(uint32_t)addr >= li->image_load_limit) {
LOG_ERR("l0der",
"_run_relocations: R_ARM_RELATIVE address (%08lx) is outside image boundaries",
(uint32_t)addr);
return -ENOEXEC;
}
*addr += li->load_address;
break;
default:
LOG_ERR("l0der",
"_run_relocations: unsupported relocation type %d",
type);
return -ENOEXEC;
}
}
}
return 0;
}
/*
* Load a l0dable PIE binary.
*/
static int
_load_pie(int fd, size_t size, Elf32_Ehdr *hdr, struct l0dable_info *info)
{
int res;
struct _pie_load_info li = { 0 };
// First pass over program headers: sanity check sizes, calculate image
// size bounds, check alignment.
li.image_start = 0xffffffff;
li.image_limit = 0x0;
Elf32_Phdr phdr;
int status_interp = -1;
for (int i = 0; i < hdr->e_phnum; i++) {
uint32_t phdr_addr = hdr->e_phoff + (i * hdr->e_phentsize);
if ((res = _read_program_header(fd, phdr_addr, &phdr)) != 0) {
return res;
}
if ((res = _check_program_header(fd, size, &phdr)) != 0) {
return res;
}
if (phdr.p_type == PT_INTERP) {
status_interp = _check_interp(fd, &phdr);
continue;
}
if (phdr.p_type == PT_LOAD) {
// Check alignment request.
if ((phdr.p_offset % phdr.p_align) !=
(phdr.p_vaddr % phdr.p_align)) {
LOG_ERR("l0der",
"_load_pie: phdr %d alignment too strict",
i);
return -ENOEXEC;
}
if (phdr.p_align > li.strictest_alignment) {
li.strictest_alignment = phdr.p_align;
}
uint32_t mem_start = phdr.p_vaddr;
uint32_t mem_limit = phdr.p_vaddr + phdr.p_memsz;
// Record memory usage.
if (mem_start < li.image_start) {
li.image_start = mem_start;
}
if (mem_limit > li.image_limit) {
li.image_limit = mem_limit;
}
}
}
if (status_interp != 0) {
// Expected interpreter string was not found.
LOG_ERR("l0der", "_load_pie: not a card10 l0dable");
return -ENOEXEC;
}
if (li.image_limit < li.image_start) {
// We didn't find any LOAD segment.
LOG_ERR("l0der", "_load_pie: no loadable segments");
return -ENOEXEC;
}
LOG_INFO(
"l0der",
"Image bounds %08lx - %08lx",
li.image_start,
li.image_limit
);
if ((res = _get_load_addr(&li)) != 0) {
return res;
}
LOG_INFO("l0der", "Loading at %08lx", li.load_address);
// Second pass through program headers: load all LOAD segments.
for (int i = 0; i < hdr->e_phnum; i++) {
uint32_t phdr_addr = hdr->e_phoff + (i * hdr->e_phentsize);
if ((res = _read_program_header(fd, phdr_addr, &phdr)) != 0) {
return res;
}
if (phdr.p_type != PT_LOAD) {
continue;
}
if ((res = _load_segment(fd, &li, &phdr)) != 0) {
return res;
}
}
// Load dynamic symbols.
if ((res = _parse_dynamic_symbols(fd, size, &li, hdr)) != 0) {
return res;
}
// Run relocations.
if ((res = _run_relocations(fd, size, &li, hdr)) != 0) {
return res;
}
uint32_t image_entrypoint = li.load_address + hdr->e_entry;
LOG_INFO("l0der", "Entrypoint (ISR Vector) at %08lx", image_entrypoint);
// Setup stack
uint32_t *isr = (uint32_t *)image_entrypoint;
isr[0] = li.stack_top;
info->isr_vector = (void *)image_entrypoint;
return 0;
}
int l0der_load_path(const char *path, struct l0dable_info *info)
{
int fd, res;
if ((fd = epic_file_open(path, "rb")) < 0) {
LOG_ERR("l0der",
"l0der_load_path: could not open ELF file %s: %d",
path,
fd);
return fd;
}
if ((res = epic_file_seek(fd, 0, SEEK_END)) != 0) {
return res;
}
if ((res = epic_file_tell(fd)) < 0) {
return res;
}
size_t size = res;
if ((res = epic_file_seek(fd, 0, SEEK_SET)) != 0) {
return res;
}
// Load ELF header and ensure it's somewhat sane.
Elf32_Ehdr hdr;
if ((res = _read_elf_header(fd, &hdr)) != 0) {
goto done;
}
// Sanitize segments.
uint32_t ph_start = hdr.e_phoff;
uint32_t ph_limit = hdr.e_phoff + (hdr.e_phnum * hdr.e_phentsize);
if (ph_limit < ph_start) {
LOG_ERR("l0der",
"l0der_load_path: invalid program header count/size: overflow");
return -ENOEXEC;
}
if (ph_limit - ph_start == 0) {
LOG_ERR("l0der", "l0der_load_path: no segments");
return -ENOEXEC;
}
if (ph_limit > size) {
LOG_ERR("l0der",
"l0der_load_path: program header table extends past end of file");
return -ENOEXEC;
}
if (hdr.e_phentsize < sizeof(Elf32_Phdr)) {
LOG_ERR("l0der",
"l0der_load_path: invalid program header table entry size");
return -ENOEXEC;
}
// Sanitize sections.
uint32_t sh_start = hdr.e_shoff;
uint32_t sh_limit = hdr.e_shoff + (hdr.e_shnum + hdr.e_shentsize);
if (sh_limit < sh_start) {
LOG_ERR("l0der",
"l0der_load_path: invalid section header count/size: overflow");
return -ENOEXEC;
}
if (sh_limit > size) {
LOG_ERR("l0der",
"l0der_load_path: section header table extends past end of file");
return -ENOEXEC;
}
if (hdr.e_shentsize < sizeof(Elf32_Shdr)) {
LOG_ERR("l0der",
"l0der_load_path: invalid section header table entry size");
return -ENOEXEC;
}
// Check whether it's something that we can load.
if (hdr.e_type == ET_DYN && hdr.e_machine == EM_ARM &&
hdr.e_version == EV_CURRENT) {
LOG_INFO("l0der", "Loading PIE l0dable %s ...", path);
res = _load_pie(fd, size, &hdr, info);
goto done;
} else {
LOG_ERR("l0der",
"l0der_load_path: %s: not an ARM PIE, cannot load.",
path);
res = -ENOEXEC;
goto done;
}
done:
epic_file_close(fd);
return res;
}
#pragma once
/*
* l0der, the l0dable loader.
*
* l0der is the ELF loader responsible for retrieving a l0dable from FAT and
* into memory for core1 to execute.
*
* l0dables are PIE ELF binaries. They can be loaded anywhere into memory,
* although for now we load them at a static address (but that might change
* with address space evolution and/or multi-app / resident app support.
*
*/
struct l0dable_info {
/** The address of the entry ISR vector. */
void *isr_vector;
};
/**
* Load a l0dable into memory.
*
* :param const char *path: Path of l0dable on FAT filesystem.
* :param l0dable_info l0dable: Information about loaded l0dable.
* :returns: ``0`` on success or a negative value on error. Possible errors:
*
* - ``-ENOENT``: l0dable not present at given path.
* - ``-EIO``: Read failed: l0dable corrupted or truncated.
* - ``-ENOEXEC``: Corrupted/invalid l0dable.
* - ``-ENOMEM``: l0dable too large to fit in RAM.
*/
int l0der_load_path(const char *path, struct l0dable_info *l0dable);
l0der_sources = files(
'l0der.c',
)
#include <stdio.h>
#include <stdlib.h>
#include "max32665.h"
#include "uart.h"
#include "cdcacm.h"
#include "card10.h"
#include "pmic.h"
#include "leds.h"
#include "api/dispatcher.h"
#include "modules/modules.h" #include "modules/modules.h"
#include "modules/log.h" #include "os/core.h"
#include "modules/stream.h" #include "os/work_queue.h"
#include "api/interrupt-sender.h" #include "fs/filesystem.h"
#include "drivers/drivers.h"
#include "user_core/user_core.h"
#include "os/config.h"
#include "card10-version.h"
#include "user_core/interrupts.h"
#include "drivers/display/epic_ctx.h"
#include <Heart.h> #include "leds.h"
#include "GUI_Paint.h" #include "version-splash.h"
#include "FreeRTOS.h" #include "FreeRTOS.h"
#include "task.h" #include "task.h"
#include "mxc_delay.h"
TaskHandle_t dispatcher_task_id; #include <stdlib.h>
#include <string.h>
#include <machine/endian.h>
/* #include "epic_boot.c"
* API dispatcher task. This task will sleep until an API call is issued and
* then wake up to dispatch it.
*/
void vApiDispatcher(void *pvParameters)
{
LOG_DEBUG("dispatcher", "Ready.");
while (1) {
if (api_dispatcher_poll()) {
api_dispatcher_exec();
}
ulTaskNotifyTake(pdTRUE, portMAX_DELAY);
}
}
int main(void) int main(void)
{ {
watchdog_init();
LOG_INFO("startup", "Epicardium startup ..."); LOG_INFO("startup", "Epicardium startup ...");
LOG_INFO("startup", "Version " CARD10_VERSION);
LOG_DEBUG("startup", "Initializing hardware ...");
hardware_early_init();
load_config();
migration_delete_app_launchers();
//LED feedback in case of dead display
epic_leds_set(11, 0, 0, 1);
epic_leds_set(12, 0, 0, 1);
epic_leds_set(13, 0, 0, 1);
epic_leds_set(14, 0, 0, 1);
epic_disp_clear(0x0000);
#if 0 /* reenable for future releases */
epic_leds_set_rocket(0, 31);
epic_leds_set_rocket(1, 31);
epic_leds_set_rocket(2, 31);
// TODO: Use blit function here
#if BYTE_ORDER == LITTLE_ENDIAN
for (size_t i = 0; i < sizeof(epicardium_ctx_fb); i += 2) {
epicardium_ctx_fb[i] = version_splash[i + 1];
epicardium_ctx_fb[i + 1] = version_splash[i];
}
#else
memcpy(epicardium_ctx_fb, version_splash, sizeof(epicardium_ctx_fb));
#endif
card10_init(); if (strcmp(CARD10_VERSION, "v1.17") != 0) {
card10_diag(); ctx_font_size(epicardium_ctx, 20.0f);
ctx_text_baseline(epicardium_ctx, CTX_TEXT_BASELINE_BOTTOM);
ctx_rgba8(epicardium_ctx, 0xff, 0xc6, 0x00, 0xff);
Paint_DrawImage(Heart, 0, 0, 160, 80); ctx_text_align(epicardium_ctx, CTX_TEXT_ALIGN_CENTER);
LCD_Update(); ctx_move_to(epicardium_ctx, 80.0f, 58.0f);
ctx_text(epicardium_ctx, "Epicardium");
/* TODO: Move this to its own function */ ctx_text_align(epicardium_ctx, CTX_TEXT_ALIGN_LEFT);
SCB->SCR |= SCB_SCR_SEVONPEND_Msk; ctx_move_to(epicardium_ctx, 2.0f, 78.0f);
ctx_text(epicardium_ctx, CARD10_VERSION);
if (cdcacm_init() < 0) { /* re-init for the first app */
LOG_ERR("startup", "USB-Serial unavailable"); disp_ctx_reinit();
}
epic_disp_update();
mxc_delay(2000000);
#else
for (uint32_t frame = 0; frame < 15; frame++) {
switch (frame) {
case 0:
epic_leds_set_rocket(0, 31);
break;
case 5:
epic_leds_set_rocket(1, 31);
break;
case 10:
epic_leds_set_rocket(2, 31);
break;
} }
fatfs_init(); epic_disp_clear(0x0000);
api_interrupt_init(); epic_frame(epicardium_ctx, frame);
stream_init(); epic_disp_update();
}
/* re-init for the first app */
disp_ctx_reinit();
#endif
epic_leds_clear_all(0, 0, 0);
LOG_INFO("startup", "Initializing tasks ..."); LOG_DEBUG("startup", "Initializing tasks ...");
/* Serial */ /* Serial */
if (xTaskCreate( if (xTaskCreate(
vSerialTask, vSerialTask,
(const char *)"Serial", (const char *)"Serial",
configMINIMAL_STACK_SIZE, configMINIMAL_STACK_SIZE * 2,
NULL, NULL,
tskIDLE_PRIORITY + 1, tskIDLE_PRIORITY + 3,
NULL) != pdPASS) { NULL) != pdPASS) {
LOG_CRIT("startup", "Failed to create %s task!", "Serial"); panic("Failed to create %s task!", "Serial");
abort();
} }
/* PMIC */ /* PMIC */
if (xTaskCreate( if (xTaskCreate(
vPmicTask, vPmicTask,
(const char *)"PMIC", (const char *)"PMIC",
configMINIMAL_STACK_SIZE, configMINIMAL_STACK_SIZE * 3,
NULL,
tskIDLE_PRIORITY + 4,
NULL) != pdPASS) {
panic("Failed to create %s task!", "PMIC");
}
/* BHI160 */
if (xTaskCreate(
vBhi160Task,
(const char *)"BHI160 Driver",
configMINIMAL_STACK_SIZE * 2,
NULL,
tskIDLE_PRIORITY + 1,
NULL) != pdPASS) {
panic("Failed to create %s task!", "BHI160");
}
/* MAX30001 */
if (xTaskCreate(
vMAX30001Task,
(const char *)"MAX30001 Driver",
configMINIMAL_STACK_SIZE * 2,
NULL, NULL,
tskIDLE_PRIORITY + 1, tskIDLE_PRIORITY + 1,
NULL) != pdPASS) { NULL) != pdPASS) {
LOG_CRIT("startup", "Failed to create %s task!", "PMIC"); panic("Failed to create %s task!", "MAX30001");
}
/* MAX86150 */
if (xTaskCreate(
vMAX86150Task,
(const char *)"MAX86150 Driver",
configMINIMAL_STACK_SIZE * 2,
NULL,
tskIDLE_PRIORITY + 1,
NULL) != pdPASS) {
panic("Failed to create %s task!", "MAX86150");
}
/* BSEC */
if (bsec_activate() == 0) {
if (xTaskCreate(
vBSECTask,
(const char *)"BSEC",
configMINIMAL_STACK_SIZE * 5,
NULL,
tskIDLE_PRIORITY + 1,
NULL) != pdPASS) {
LOG_CRIT(
"startup", "Failed to create %s task!", "BSEC"
);
abort(); abort();
} }
}
/* API */ /* API */
if (xTaskCreate( if (xTaskCreate(
...@@ -92,19 +183,62 @@ int main(void) ...@@ -92,19 +183,62 @@ int main(void)
NULL, NULL,
tskIDLE_PRIORITY + 2, tskIDLE_PRIORITY + 2,
&dispatcher_task_id) != pdPASS) { &dispatcher_task_id) != pdPASS) {
LOG_CRIT("startup", "Failed to create %s task!", "API Dispatcher"); panic("Failed to create %s task!", "API Dispatcher");
abort(); }
/* Interrupts */
if (xTaskCreate(
vInterruptsTask,
(const char *)"Interrupt Dispatcher",
configMINIMAL_STACK_SIZE,
NULL,
tskIDLE_PRIORITY + 2,
NULL) != pdPASS) {
panic("Failed to create %s task!", "Interrupt Dispatcher");
} }
LOG_INFO("startup", "Initializing dispatcher ..."); /* BLE */
api_dispatcher_init(); if (ble_is_enabled()) {
if (xTaskCreate(
vBleTask,
(const char *)"BLE",
configMINIMAL_STACK_SIZE * 10,
NULL,
tskIDLE_PRIORITY + 3,
NULL) != pdPASS) {
panic("Failed to create %s task!", "BLE");
}
}
LOG_INFO("startup", "Starting core1 payload ..."); /* Lifecycle */
core1_start(); if (xTaskCreate(
vLifecycleTask,
(const char *)"Lifecycle",
configMINIMAL_STACK_SIZE * 4,
NULL,
tskIDLE_PRIORITY + 3,
NULL) != pdPASS) {
panic("Failed to create %s task!", "Lifecycle");
}
LOG_INFO("startup", "Starting FreeRTOS ..."); /* Work Queue */
if (xTaskCreate(
vWorkQueueTask,
(const char *)"Work Queue",
configMINIMAL_STACK_SIZE * 4,
NULL,
tskIDLE_PRIORITY + 1,
NULL) != pdPASS) {
panic("Failed to create %s task!", "Work Queue");
}
workqueue_init();
/*
* Initialize serial driver data structures.
*/
serial_init();
LOG_DEBUG("startup", "Starting FreeRTOS ...");
vTaskStartScheduler(); vTaskStartScheduler();
LOG_CRIT("startup", "FreeRTOS did not start due to unknown error!"); panic("FreeRTOS did not start due to unknown error!");
abort();
} }
...@@ -37,8 +37,9 @@ api_dispatcher_lib = static_library( ...@@ -37,8 +37,9 @@ api_dispatcher_lib = static_library(
'api-dispatcher', 'api-dispatcher',
'api/dispatcher.c', 'api/dispatcher.c',
'api/interrupt-sender.c', 'api/interrupt-sender.c',
'api/control.c',
api[1], # Dispatcher api[1], # Dispatcher
dependencies: periphdriver, dependencies: [libcard10, periphdriver],
) )
########################################################################## ##########################################################################
...@@ -65,20 +66,58 @@ freertos = static_library( ...@@ -65,20 +66,58 @@ freertos = static_library(
########################################################################## ##########################################################################
subdir('modules/') subdir('modules/')
subdir('drivers/')
subdir('user_core/')
subdir('ble/')
subdir('os/')
subdir('fs/')
subdir('l0der/')
epicardium_cargs = ['-D_POSIX_C_SOURCE=200809']
if get_option('jailbreak_card10')
epicardium_cargs += [
'-DJAILBREAK_CARD10=1',
]
endif
version_screen = custom_target(
'version-splash.h',
input: 'version-splash.png',
output: 'version-splash.h',
command: [
python3,
meson.current_source_dir() + '../tools/version-image.py',
'@INPUT@',
'@OUTPUT@',
],
)
elf = executable( elf = executable(
name + '.elf', name + '.elf',
'cdcacm.c', 'usb/epc_usb.c',
'usb/cdcacm.c',
'usb/mass_storage.c',
'main.c', 'main.c',
'support.c', 'support.c',
module_sources, module_sources,
dependencies: [libcard10, max32665_startup_core0, maxusb, libff13], os_sources,
user_core_sources,
driver_sources,
fs_sources,
l0der_sources,
ble_sources,
version_hdr,
version_screen,
dependencies: [libcard10, max32665_startup_core0, maxusb, libff13, ble, bhy1, libcrypto, bsec, libctx],
link_with: [api_dispatcher_lib, freertos], link_with: [api_dispatcher_lib, freertos],
link_whole: [max32665_startup_core0_lib, board_card10_lib], link_whole: [max32665_startup_core0_lib, board_card10_lib, newlib_heap_lib],
include_directories: [freertos_includes], include_directories: [freertos_includes],
link_args: [ link_args: [
'-Wl,-Map=' + meson.current_build_dir() + '/' + name + '.map', '-Wl,-Map=' + meson.current_build_dir() + '/' + name + '.map',
'-Wl,--defsym=_write=_write_epicardium',
], ],
c_args: epicardium_cargs,
) )
epicardium_bin = custom_target( epicardium_bin = custom_target(
......
#include "epicardium.h"
#include "tmr_utils.h"
#include "gpio.h"
#include "GUI_DEV/GUI_Paint.h"
#include "Fonts/fonts.h"
#include "tmr.h"
#include "FreeRTOS.h"
#include "task.h"
static TaskHandle_t lock = NULL;
static int check_lock()
{
TaskHandle_t task = xTaskGetCurrentTaskHandle();
if (task != lock) {
return -EBUSY;
} else {
return 0;
}
}
int epic_disp_print(
uint16_t posx,
uint16_t posy,
const char *pString,
uint16_t fg,
uint16_t bg
) {
int cl = check_lock();
if (cl < 0) {
return cl;
} else {
Paint_DrawString_EN(posx, posy, pString, &Font20, bg, fg);
return 0;
}
}
int epic_disp_clear(uint16_t color)
{
int cl = check_lock();
if (cl < 0) {
return cl;
} else {
LCD_Clear(color);
return 0;
}
}
int epic_disp_pixel(uint16_t x, uint16_t y, uint16_t color)
{
int cl = check_lock();
if (cl < 0) {
return cl;
} else {
Paint_SetPixel(x, y, color);
return 0;
}
}
int epic_disp_line(
uint16_t xstart,
uint16_t ystart,
uint16_t xend,
uint16_t yend,
uint16_t color,
enum disp_linestyle linestyle,
uint16_t pixelsize
) {
int cl = check_lock();
if (cl < 0) {
return cl;
} else {
Paint_DrawLine(
xstart, ystart, xend, yend, color, linestyle, pixelsize
);
return 0;
}
}
int epic_disp_rect(
uint16_t xstart,
uint16_t ystart,
uint16_t xend,
uint16_t yend,
uint16_t color,
enum disp_fillstyle fillstyle,
uint16_t pixelsize
) {
int cl = check_lock();
if (cl < 0) {
return cl;
} else {
Paint_DrawRectangle(
xstart, ystart, xend, yend, color, fillstyle, pixelsize
);
return 0;
}
}
int epic_disp_circ(
uint16_t x,
uint16_t y,
uint16_t rad,
uint16_t color,
enum disp_fillstyle fillstyle,
uint16_t pixelsize
) {
int cl = check_lock();
if (cl < 0) {
return cl;
} else {
Paint_DrawCircle(x, y, rad, color, fillstyle, pixelsize);
return 0;
}
}
int epic_disp_update()
{
int cl = check_lock();
if (cl < 0) {
return cl;
} else {
LCD_Update();
return 0;
}
}
int epic_disp_open()
{
TaskHandle_t task = xTaskGetCurrentTaskHandle();
if (lock == task) {
return 0;
} else if (lock == NULL) {
lock = task;
return 0;
} else {
return -EBUSY;
}
}
int epic_disp_close()
{
if (check_lock() < 0 && lock != NULL) {
return -EBUSY;
} else {
lock = NULL;
return 0;
}
}
void disp_forcelock()
{
TaskHandle_t task = xTaskGetCurrentTaskHandle();
lock = task;
}