reworking pins, flash doesnt work

sipo
Ondřej Hruška 6 years ago
parent 9a32962c95
commit 393ec1e951
Signed by: MightyPork
GPG Key ID: 2C5FD5035250423D
  1. 3
      gex.mk
  2. 6
      platform/lock_jumper.c
  3. 181
      platform/pin_utils.c
  4. 36
      platform/pin_utils.h
  5. 4
      platform/plat_compat.h
  6. 4
      platform/platform.c
  7. 6
      platform/status_led.c
  8. 265
      units/digital_out/unit_dout.c
  9. 12
      units/digital_out/unit_dout.h
  10. 8
      units/neopixel/unit_neopixel.c
  11. 8
      units/pin/unit_pin.c
  12. 27
      utils/str_utils.c
  13. 65
      utils/str_utils.h

@ -9,6 +9,7 @@ GEX_SRC_DIR = \
User/units/neopixel \
User/units/test \
User/units/pin \
User/units/digital_out \
User/TinyFrame \
User/CWPack \
User/tasks
@ -80,7 +81,7 @@ GEX_CDEFS = $(GEX_CDEFS_BASE) \
-DUSE_FULL_ASSERT=1 \
-DVERBOSE_ASSERT=1 \
-DDEBUG_VFS=0 \
-DDEBUG_FLASH_WRITE=0 \
-DDEBUG_FLASH_WRITE=1 \
-DVERBOSE_HARDFAULT=1 \
-DUSE_STACK_MONITOR=1 \
-DUSE_DEBUG_UART=1

@ -24,15 +24,15 @@ void LockJumper_Init(void)
bool suc = true;
// Resolve and claim resource
Resource rsc = plat_pin2resource(LOCK_JUMPER_PORT, LOCK_JUMPER_PIN, &suc);
Resource rsc = pin2resource(LOCK_JUMPER_PORT, LOCK_JUMPER_PIN, &suc);
assert_param(suc);
suc &= rsc_claim(&UNIT_SYSTEM, rsc);
assert_param(suc);
// Resolve pin
lock_periph = plat_port2periph(LOCK_JUMPER_PORT, &suc);
lock_llpin = plat_pin2ll(LOCK_JUMPER_PIN, &suc);
lock_periph = port2periph(LOCK_JUMPER_PORT, &suc);
lock_llpin = pin2ll(LOCK_JUMPER_PIN, &suc);
assert_param(suc);
// Configure for input

@ -2,6 +2,7 @@
// Created by MightyPork on 2017/11/26.
//
#include <utils/avrlibc.h>
#include "pin_utils.h"
#include "macro.h"
@ -45,7 +46,7 @@ static GPIO_TypeDef * const port_periphs[] = {
COMPILER_ASSERT(PORTS_COUNT == ELEMENTS_IN_ARRAY(port_periphs));
/** Convert pin number to LL bitfield */
uint32_t plat_pin2ll(uint8_t pin_number, bool *suc)
uint32_t pin2ll(uint8_t pin_number, bool *suc)
{
assert_param(suc != NULL);
@ -59,13 +60,12 @@ uint32_t plat_pin2ll(uint8_t pin_number, bool *suc)
}
/** Convert port name (A,B,C...) to peripheral struct pointer */
GPIO_TypeDef *plat_port2periph(char port_name, bool *suc)
GPIO_TypeDef *port2periph(char port_name, bool *suc)
{
assert_param(suc != NULL);
if(port_name < 'A' || port_name >= ('A'+PORTS_COUNT)) {
dbg("Bad port: %c", port_name);
// TODO proper report
dbg("Bad port: %c", port_name); // TODO proper report
*suc = false;
return NULL;
}
@ -75,20 +75,18 @@ GPIO_TypeDef *plat_port2periph(char port_name, bool *suc)
}
/** Convert a pin to resource handle */
Resource plat_pin2resource(char port_name, uint8_t pin_number, bool *suc)
Resource pin2resource(char port_name, uint8_t pin_number, bool *suc)
{
assert_param(suc != NULL);
if(port_name < 'A' || port_name >= ('A'+PORTS_COUNT)) {
dbg("Bad port: %c", port_name);
// TODO proper report
dbg("Bad port: %c", port_name); // TODO proper report
*suc = false;
return R_NONE;
}
if(pin_number >= PINS_COUNT) {
// TODO proper report
dbg("Bad pin: %d", pin_number);
dbg("Bad pin: %d", pin_number); // TODO proper report
*suc = false;
return R_NONE;
}
@ -97,3 +95,168 @@ Resource plat_pin2resource(char port_name, uint8_t pin_number, bool *suc)
return R_PA0 + num*16 + pin_number;
}
/** Parse single pin */
bool parse_pin(const char *value, char *targetName, uint8_t *targetNumber)
{
// discard leading 'P'
if (value[0] == 'P') {
value++;
}
size_t len = strlen(value);
if (len<2||len>3) return false;
*targetName = (uint8_t) value[0];
if (!(*targetName >= 'A' && *targetName <= 'H')) return false;
// lets just hope it's OK
*targetNumber = (uint8_t) avr_atoi(value + 1);
return true;
}
/** Parse port name */
bool parse_port(const char *value, char *targetName)
{
*targetName = (uint8_t) value[0];
if (!(*targetName >= 'A' && *targetName < 'A' + PORTS_COUNT)) return false;
return true;
}
/** Parse a list of pin numbers with ranges and commans/semicolons to a bitmask */
uint16_t parse_pinmask(const char *value, bool *suc)
{
uint32_t bits = 0;
uint32_t acu = 0;
bool inrange = false;
uint32_t rangestart = 0;
// shortcut if none are set
if (value[0] == 0) return 0;
char c;
do {
c = *value++;
if (c == ' ' || c == '\t') {
// skip
}
else if (c >= '0' && c <= '9') {
acu = acu*10 + (c-'0');
}
else if (c == ',' || c == ';' || c == 0) {
// end of number or range
if (!inrange) rangestart = acu;
// swap them if they're in the wrong order
if (acu < rangestart) {
uint32_t swp = acu;
acu = rangestart;
rangestart = swp;
}
for(uint32_t i=rangestart; i<=acu; i++) {
bits |= 1<<i;
}
inrange = false;
rangestart = 0;
acu = 0;
}
else if (c == '-' || c == ':') {
rangestart = acu;
inrange = true;
acu=0;
} else {
*suc = false;
}
} while (c != 0);
if (bits > 0xFFFF) *suc = false;
return (uint16_t) bits;
}
/** Convert a pin bitmask to the ASCII format understood by str_parse_pinmask() */
char * str_pinmask(uint16_t pins, char *buffer)
{
char *b = buffer;
uint32_t start = 0;
bool on = false;
bool first = true;
// shortcut if none are set
if (pins == 0) {
buffer[0] = 0;
return buffer;
}
for (int32_t i = 15; i >= -1; i--) {
bool bit;
if (i == -1) {
bit = false;
} else {
bit = 0 != (pins & 0x8000);
pins <<= 1;
}
if (bit) {
if (!on) {
start = (uint32_t) i;
on = true;
}
} else {
if (on) {
if (!first) {
b += sprintf(b, ", ");
}
if (start == (uint32_t)(i+1)) {
b += sprintf(b, "%"PRIu32, start);
}
else if (start == (uint32_t)(i+2)) {
// exception for 2-long ranges - don't show as range
b += sprintf(b, "%"PRIu32", %"PRIu32, start, i + 1);
}
else {
b += sprintf(b, "%"PRIu32"-%"PRIu32, start, i + 1);
}
first = false;
on = false;
}
}
}
return buffer;
}
/** Spread packed port pins using a mask */
uint16_t port_spread(uint16_t packed, uint16_t mask)
{
uint16_t result = 0;
uint16_t poke = 1;
for (int i = 0; i<16; i++) {
if (mask & (1<<i)) {
if (packed & poke) {
result |= 1<<i;
}
poke <<= 1;
}
}
return result;
}
/** Pack spread port pins using a mask */
uint16_t port_pack(uint16_t spread, uint16_t mask)
{
uint16_t result = 0;
uint16_t poke = 1;
for (int i = 0; i<16; i++) {
if (mask & (1<<i)) {
if (spread & (1<<i)) {
result |= poke;
}
poke <<= 1;
}
}
return result;
}

@ -17,7 +17,7 @@
* @param suc - set to false on failure, left unchanged on success.
* @return LL_GPIO_PIN_x
*/
uint32_t plat_pin2ll(uint8_t pin_number, bool *suc);
uint32_t pin2ll(uint8_t pin_number, bool *suc);
/**
* Convert pin name and number to a resource enum
@ -27,7 +27,7 @@ uint32_t plat_pin2ll(uint8_t pin_number, bool *suc);
* @param suc - set to false on failure, left unchanged on success
* @return the resource, or R_NONE
*/
Resource plat_pin2resource(char port_name, uint8_t pin_number, bool *suc);
Resource pin2resource(char port_name, uint8_t pin_number, bool *suc);
/**
* Convert port name to peripheral instance
@ -36,6 +36,36 @@ Resource plat_pin2resource(char port_name, uint8_t pin_number, bool *suc);
* @param suc - set to false on failure, left unchanged on success.
* @return instance
*/
GPIO_TypeDef *plat_port2periph(char port_name, bool *suc);
GPIO_TypeDef *port2periph(char port_name, bool *suc);
/** Parse a pin name PA0 or A0 to port name and pin number */
bool parse_pin(const char *str, char *targetName, uint8_t *targetNumber);
/** Parse a port name */
bool parse_port(const char *value, char *targetName);
/** Parse a list of pin numbers with ranges and commans/semicolons to a bitmask */
uint16_t parse_pinmask(const char *value, bool *suc);
/** Convert a pin bitmask to the ASCII format understood by str_parse_pinmask() */
char * str_pinmask(uint16_t pins, char *buffer);
/**
* Spread packed port pins using a mask
*
* @param packed - packed bits, right-aligned (eg. 0b1111)
* @param mask - positions of the bits (eg. 0x8803)
* @return - bits spread to their positions (always counting from right)
*/
uint16_t port_spread(uint16_t packed, uint16_t mask);
/**
* Pack spread port pins using a mask
*
* @param spread - bits in a port register (eg. 0xFF02)
* @param mask - mask of the bits we want to pack (eg. 0x8803)
* @return - packed bits, right aligned (eg. 0b1110)
*/
uint16_t port_pack(uint16_t spread, uint16_t mask);
#endif //GEX_PIN_UTILS_H

@ -6,9 +6,9 @@
#define GEX_PLAT_COMPAT_H
// -------- Static buffers ---------
#define TSK_STACK_MAIN 150 // USB / VFS task stack size
#define TSK_STACK_MAIN 200 // USB / VFS task stack size
#define TSK_STACK_MSG 180 // TF message handler task stack size
#define TSK_STACK_JOBRUNNER 100 // Job runner task stack size
#define TSK_STACK_JOBRUNNER 80 // Job runner task stack size
#define BULKREAD_MAX_CHUNK 256 // Bulk read buffer

@ -9,6 +9,7 @@
#include "framework/unit_registry.h"
#include "units/pin/unit_pin.h"
#include "units/digital_out/unit_dout.h"
#include "units/neopixel/unit_neopixel.h"
#include "units/test/unit_test.h"
@ -22,8 +23,9 @@ void plat_init_resources(void)
// --- Common unit drivers ---
ureg_add_type(&UNIT_PIN);
ureg_add_type(&UNIT_DOUT);
#if !DISABLE_TEST_UNIT
#if HAVE_TEST_UNIT
ureg_add_type(&UNIT_TEST);
#endif

@ -17,8 +17,8 @@ void StatusLed_PreInit(void)
{
bool suc = true;
// Resolve pin
led_periph = plat_port2periph(STATUS_LED_PORT, &suc);
led_llpin = plat_pin2ll(STATUS_LED_PIN, &suc);
led_periph = port2periph(STATUS_LED_PORT, &suc);
led_llpin = pin2ll(STATUS_LED_PIN, &suc);
// Configure for output
LL_GPIO_SetPinMode(led_periph, led_llpin, LL_GPIO_MODE_OUTPUT);
@ -34,7 +34,7 @@ void StatusLed_Init(void)
bool suc = true;
// Resolve and claim resource
Resource rsc = plat_pin2resource(STATUS_LED_PORT, STATUS_LED_PIN, &suc);
Resource rsc = pin2resource(STATUS_LED_PORT, STATUS_LED_PIN, &suc);
assert_param(suc);
suc &= rsc_claim(&UNIT_SYSTEM, rsc);

@ -0,0 +1,265 @@
//
// Created by MightyPork on 2017/11/25.
//
#include "comm/messages.h"
#include "unit_base.h"
#include "unit_dout.h"
/** Private data structure */
struct priv {
char port_name;
uint16_t pins; // pin mask
uint16_t initial; // initial pin states
uint16_t open_drain; // open drain pins
GPIO_TypeDef *port;
};
// ------------------------------------------------------------------------
/** Load from a binary buffer stored in Flash */
static void DO_loadBinary(Unit *unit, PayloadParser *pp)
{
struct priv *priv = unit->data;
priv->port_name = pp_char(pp);
priv->pins = pp_u16(pp);
priv->initial = pp_u16(pp);
priv->open_drain = pp_u16(pp);
}
/** Write to a binary buffer for storing in Flash */
static void DO_writeBinary(Unit *unit, PayloadBuilder *pb)
{
struct priv *priv = unit->data;
pb_char(pb, priv->port_name);
pb_u16(pb, priv->pins);
pb_u16(pb, priv->initial);
pb_u16(pb, priv->open_drain);
}
// ------------------------------------------------------------------------
/** Parse a key-value pair from the INI file */
static bool DO_loadIni(Unit *unit, const char *key, const char *value)
{
bool suc = true;
struct priv *priv = unit->data;
if (streq(key, "port")) {
suc = parse_port(value, &priv->port_name);
}
else if (streq(key, "pins")) {
priv->pins = parse_pinmask(value, &suc);
}
else if (streq(key, "initial")) {
priv->initial = parse_pinmask(value, &suc);
}
else if (streq(key, "opendrain")) {
priv->open_drain = parse_pinmask(value, &suc);
}
else {
return false;
}
return suc;
}
/** Generate INI file section for the unit */
static void DO_writeIni(Unit *unit, IniWriter *iw)
{
struct priv *priv = unit->data;
char buf[64];
iw_comment(iw, "Port name");
iw_entry(iw, "port", "%c", priv->port_name);
iw_comment(iw, "Pins (descending, csv, ranges using colon)");
iw_entry(iw, "pins", "%s", str_pinmask(priv->pins, buf));
iw_comment(iw, "Initially high pins");
iw_entry(iw, "initial", "%s", str_pinmask(priv->initial, buf));
iw_comment(iw, "Open-drain pins");
iw_entry(iw, "opendrain", "%s", str_pinmask(priv->open_drain, buf));
}
// ------------------------------------------------------------------------
/** Allocate data structure and set defaults */
static bool DO_preInit(Unit *unit)
{
bool suc = true;
struct priv *priv = unit->data = calloc_ck(1, sizeof(struct priv), &suc);
CHECK_SUC();
// some defaults
priv->port_name = 'A';
priv->pins = 0x0001;
priv->open_drain = 0x0000;
priv->initial = 0x0000;
return true;
}
/** Finalize unit set-up */
static bool DO_init(Unit *unit)
{
bool suc = true;
struct priv *priv = unit->data;
priv->initial &= priv->pins;
priv->open_drain &= priv->pins;
// --- Parse config ---
priv->port = port2periph(priv->port_name, &suc);
if (!suc) {
unit->status = E_BAD_CONFIG;
return false;
}
dbg("Claim rsc");
// Claim all needed pins
for (int i = 0; i < 16; i++) {
if (priv->pins & (1 << i)) {
Resource rsc = pin2resource(priv->port_name, (uint8_t) i, &suc);
if (!suc) {
unit->status = E_BAD_CONFIG;
rsc_teardown(unit);
return false;
}
suc = rsc_claim(unit, rsc);
if (!suc) {
rsc_teardown(unit);
return false;
}
}
}
dbg("Init pins");
uint16_t mask = 1;
for (int i = 0; i < 16; i++, mask <<= 1) {
if (priv->pins & mask) {
uint32_t ll_pin = pin2ll((uint8_t) i, &suc);
// --- Init hardware ---
LL_GPIO_SetPinMode(priv->port, ll_pin, LL_GPIO_MODE_OUTPUT);
LL_GPIO_SetPinOutputType(priv->port, ll_pin, (priv->open_drain & mask) ?
LL_GPIO_OUTPUT_OPENDRAIN :
LL_GPIO_OUTPUT_PUSHPULL);
LL_GPIO_SetPinSpeed(priv->port, ll_pin, LL_GPIO_SPEED_FREQ_HIGH);
}
}
// Set the initial state
priv->port->ODR &= ~priv->pins;
priv->port->ODR |= priv->initial;
return true;
}
/** Tear down the unit */
static void DO_deInit(Unit *unit)
{
struct priv *priv = unit->data;
bool suc = true;
uint16_t mask = 1;
for (int i = 0; i < 16; i++, mask <<= 1) {
if (priv->pins & mask) {
uint32_t ll_pin = pin2ll((uint8_t) i, &suc);
assert_param(suc); // this should never fail if we got this far
// configure the pin as analog
LL_GPIO_SetPinMode(priv->port, ll_pin, LL_GPIO_MODE_ANALOG);
}
}
// Release all resources
rsc_teardown(unit);
// Free memory
free(unit->data);
unit->data = NULL;
}
// ------------------------------------------------------------------------
enum PinCmd_ {
CMD_WRITE = 0,
CMD_SET = 1,
CMD_CLEAR = 2,
CMD_TOGGLE = 3,
};
/** Handle a request message */
static bool DO_handleRequest(Unit *unit, TF_ID frame_id, uint8_t command, PayloadParser *pp)
{
(void)pp;
struct priv *priv = unit->data;
uint16_t packed = pp_u16(pp);
uint16_t mask = priv->pins;
uint16_t spread = port_spread(packed, mask);
uint16_t set, reset;
switch (command) {
case CMD_WRITE:;
set = spread;
reset = (~spread) & mask;
priv->port->BSRR = set | (reset<<16);
break;
case CMD_SET:
priv->port->BSRR = spread;
break;
case CMD_CLEAR:
priv->port->BSRR = (spread<<16);
break;
case CMD_TOGGLE:;
spread = (uint16_t) (~priv->port->ODR) & mask;
set = spread;
reset = (~spread) & mask;
priv->port->BSRR = set | (reset<<16);
break;
default:
com_respond_bad_cmd(frame_id);
return false;
}
return true;
}
// ------------------------------------------------------------------------
/** Unit template */
const UnitDriver UNIT_DOUT = {
.name = "DO",
.description = "Digital output",
// Settings
.preInit = DO_preInit,
.cfgLoadBinary = DO_loadBinary,
.cfgWriteBinary = DO_writeBinary,
.cfgLoadIni = DO_loadIni,
.cfgWriteIni = DO_writeIni,
// Init
.init = DO_init,
.deInit = DO_deInit,
// Function
.handleRequest = DO_handleRequest,
};

@ -0,0 +1,12 @@
//
// Created by MightyPork on 2017/11/25.
//
#ifndef U_DOUT_H
#define U_DOUT_H
#include "unit.h"
extern const UnitDriver UNIT_DOUT;
#endif //U_DOUT_H

@ -49,7 +49,7 @@ static bool Npx_loadIni(Unit *unit, const char *key, const char *value)
struct priv *priv = unit->data;
if (streq(key, "pin")) {
suc = str_parse_pin(value, &priv->port_name, &priv->pin_number);
suc = parse_pin(value, &priv->port_name, &priv->pin_number);
}
else if (streq(key, "pixels")) {
priv->pixels = (uint16_t) avr_atoi(value);
@ -97,9 +97,9 @@ static bool Npx_init(Unit *unit)
struct priv *priv = unit->data;
// --- Parse config ---
priv->ll_pin = plat_pin2ll(priv->pin_number, &suc);
priv->port = plat_port2periph(priv->port_name, &suc);
Resource rsc = plat_pin2resource(priv->port_name, priv->pin_number, &suc);
priv->ll_pin = pin2ll(priv->pin_number, &suc);
priv->port = port2periph(priv->port_name, &suc);
Resource rsc = pin2resource(priv->port_name, priv->pin_number, &suc);
if (!suc) {
unit->status = E_BAD_CONFIG;
return false;

@ -53,7 +53,7 @@ static bool Pin_loadIni(Unit *unit, const char *key, const char *value)
struct priv *priv = unit->data;
if (streq(key, "pin")) {
suc = str_parse_pin(value, &priv->port_name, &priv->pin_number);
suc = parse_pin(value, &priv->port_name, &priv->pin_number);
}
else if (streq(key, "dir")) {
priv->output = str_parse_01(value, "IN", "OUT", &suc);
@ -114,9 +114,9 @@ static bool Pin_init(Unit *unit)
struct priv *priv = unit->data;
// --- Parse config ---
priv->ll_pin = plat_pin2ll(priv->pin_number, &suc);
priv->port = plat_port2periph(priv->port_name, &suc);
Resource rsc = plat_pin2resource(priv->port_name, priv->pin_number, &suc);
priv->ll_pin = pin2ll(priv->pin_number, &suc);
priv->port = port2periph(priv->port_name, &suc);
Resource rsc = pin2resource(priv->port_name, priv->pin_number, &suc);
if (!suc) {
unit->status = E_BAD_CONFIG;
return false;

@ -3,16 +3,19 @@
//
#include "str_utils.h"
#include "platform.h"
#include "avrlibc.h"
bool str_parse_yn(const char *str, bool *suc)
{
// TODO implement strcasecmp without the locale crap from newlib and use it here
if (streq(str, "Y")) return true;
if (streq(str, "1")) return true;
if (streq(str, "YES")) return true;
if (streq(str, "N")) return false;
if (streq(str, "1")) return true;
if (streq(str, "0")) return false;
if (streq(str, "YES")) return true;
if (streq(str, "NO")) return false;
*suc = false;
@ -37,21 +40,3 @@ uint8_t str_parse_012(const char *str, const char *a, const char *b, const char
*suc = false;
return 0;
}
bool str_parse_pin(const char *value, char *targetName, uint8_t *targetNumber)
{
// discard leading 'P'
if (value[0] == 'P') {
value++;
}
size_t len = strlen(value);
if (len<2||len>3) return false;
*targetName = (uint8_t) value[0];
if (!(*targetName >= 'A' && *targetName <= 'H')) return false;
// lets just hope it's OK
*targetNumber = (uint8_t) avr_atoi(value + 1);
return true;
}

@ -5,55 +5,112 @@
#include <string.h>
#include "stringbuilder.h"
/**
* Test equality two strings
*
* @param str1
* @param str2
* @param limit
* @return
*/
static inline bool __attribute__((const))
streq(const char *restrict str1, const char *restrict str2)
{
return strcmp(str1, str2) == 0;
}
/**
* Test equality of two strings, case insensitive
*
* @param str1
* @param str2
* @param limit
* @return
*/
static inline bool __attribute__((const))
strcaseq(const char *restrict str1, const char *restrict str2)
{
return strcasecmp(str1, str2) == 0;
}
/**
* Test if a string starts with another
*
* @param str
* @param prefix
* @param limit
* @return
*/
static inline bool __attribute__((const))
strneq(const char *restrict str1, const char *restrict str2, uint32_t limit)
strneq(const char *restrict str, const char *restrict prefix, uint32_t limit)
{
return strncmp(str1, str2, limit) == 0;
return strncmp(str, prefix, limit) == 0;
}
/**
* Test if a string starts with another, case insensitive
*
* @param str1
* @param str2
* @return match
*/
static inline bool __attribute__((const))
strncaseq(const char *restrict str1, const char *restrict str2, uint32_t limit)
{
return strncasecmp(str1, str2, limit) == 0;
}
/**
* Test if a string starts with another
*
* @param str
* @param prefix
* @return match
*/
static inline bool __attribute__((const))
strstarts(const char *restrict str, const char *restrict prefix)
{
return strncmp(str, prefix, strlen(prefix)) == 0;
}
/**
* Get n-th character from the end
*
* @param str
* @param nth - n-th character (1 means last char)
* @return the char
*/
static inline char __attribute__((const))
last_char_n(const char *str, uint32_t nth)
{
return str[strlen(str) - nth];
}
/**
* Get last character of a string
*
* @param str
* @return last char
*/
static inline char __attribute__((const))
last_char(const char *str)
{
return str[strlen(str) - 1];
}
/** Parse Y/N to bool */
bool str_parse_yn(const char *str, bool *suc);
/** Compare string with two options */
uint8_t str_parse_01(const char *str, const char *a, const char *b, bool *suc);
/** Compare string with three options */
uint8_t str_parse_012(const char *str, const char *a, const char *b, const char *c, bool *suc);
/** Convert bool to one of two options */
#define str_01(cond, a, b) ((cond) ? (b) : (a))
#define str_yn(cond) ((cond) ? ("Y") : ("N"))
bool str_parse_pin(const char *str, char *targetName, uint8_t *targetNumber);
/** Convert bool to Y or N */
#define str_yn(cond) ((cond) ? ("Y") : ("N"))
#endif

Loading…
Cancel
Save