mirror of
https://github.com/RIOT-OS/RIOT.git
synced 2024-12-29 04:50:03 +01:00
17045: sys/coding: add XOR based coding module r=benpicco a=benpicco 19243: cpu/gd32v: add periph_gpio_ll and periph_gpio_ll_irq support r=benpicco a=gschorcht ### Contribution description This PR provides the `periph_gpio_ll` and `periph_gpio_ll_irq` support for GD32VF103. Level triggered interrupts are emulated. `periph_gpio_ll_irq` could be split off from this PR as a separate PR if necessary. ### Testing procedure Use any GD32V board and connect PA0 -> PB0 and PA1 -> PB1 where PA is the output port and PB the input port. With these connections `tests/periph_gpio_ll` should work. ``` BOARD=sipeed-longan-nano make -j8 -C tests/periph_gpio_ll flash term ``` If necessary, change the input and output pins by setting the environment variables and connect the corresponding pins, for example for `seeedstudio-gd32` PA1 -> PB8 and PA8 -> PB9: ``` PIN_OUT_0=1 PIN_OUT_1=8 PIN_IN_0=8 PIN_IN_1=9 BOARD=seedstudio-gd32 make -j8 -C tests/periph_gpio_ll flash term ``` ### Issues/PRs references Co-authored-by: Benjamin Valentin <benjamin.valentin@ml-pa.com> Co-authored-by: Gunar Schorcht <gunar@schorcht.net>
This commit is contained in:
commit
f341ad6c9c
@ -13,6 +13,10 @@ config CPU_FAM_GD32V
|
||||
select HAS_PERIPH_CLIC
|
||||
select HAS_PERIPH_GPIO
|
||||
select HAS_PERIPH_GPIO_IRQ
|
||||
select HAS_PERIPH_GPIO_LL
|
||||
select HAS_PERIPH_GPIO_LL_IRQ
|
||||
select HAS_PERIPH_GPIO_LL_IRQ_LEVEL_TRIGGERED_HIGH
|
||||
select HAS_PERIPH_GPIO_LL_IRQ_LEVEL_TRIGGERED_LOW
|
||||
select HAS_PERIPH_FLASHPAGE
|
||||
select HAS_PERIPH_FLASHPAGE_IN_ADDRESS_SPACE
|
||||
select HAS_PERIPH_FLASHPAGE_PAGEWISE
|
||||
|
@ -4,6 +4,10 @@ FEATURES_PROVIDED += arch_nuclei
|
||||
FEATURES_PROVIDED += periph_clic
|
||||
FEATURES_PROVIDED += periph_gpio
|
||||
FEATURES_PROVIDED += periph_gpio_irq
|
||||
FEATURES_PROVIDED += periph_gpio_ll
|
||||
FEATURES_PROVIDED += periph_gpio_ll_irq
|
||||
FEATURES_PROVIDED += periph_gpio_ll_irq_level_triggered_high
|
||||
FEATURES_PROVIDED += periph_gpio_ll_irq_level_triggered_low
|
||||
FEATURES_PROVIDED += periph_rtc
|
||||
FEATURES_PROVIDED += periph_rtc_mem
|
||||
FEATURES_PROVIDED += periph_rtt
|
||||
|
116
cpu/gd32v/include/gpio_ll_arch.h
Normal file
116
cpu/gd32v/include/gpio_ll_arch.h
Normal file
@ -0,0 +1,116 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Gunar Schorcht <gunar@schorcht.net>
|
||||
*
|
||||
* This file is subject to the terms and conditions of the GNU Lesser
|
||||
* General Public License v2.1. See the file LICENSE in the top level
|
||||
* directory for more details.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @ingroup cpu_gd32v
|
||||
* @ingroup drivers_periph_gpio_ll
|
||||
* @{
|
||||
*
|
||||
* @file
|
||||
* @brief GPIO Low-level API implementation for the GD32V GPIO peripheral
|
||||
*
|
||||
* @author Gunar Schorcht <gunar@schorcht.net>
|
||||
*/
|
||||
|
||||
#ifndef GPIO_LL_ARCH_H
|
||||
#define GPIO_LL_ARCH_H
|
||||
|
||||
#include "architecture.h"
|
||||
#include "periph/gpio_ll.h"
|
||||
#include "periph_cpu.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#ifndef DOXYGEN /* hide implementation specific details from Doxygen */
|
||||
|
||||
/**
|
||||
* @brief Number of ports available on GD32VF103
|
||||
*/
|
||||
#define GPIO_PORT_NUMOF 5
|
||||
|
||||
/**
|
||||
* @brief Get a GPIO port by number
|
||||
*/
|
||||
#define GPIO_PORT(num) (GPIOA_BASE + ((num) << 10))
|
||||
|
||||
/**
|
||||
* @brief Get a GPIO port number by gpio_t value
|
||||
*/
|
||||
#define GPIO_PORT_NUM(port) (((port) - GPIOA_BASE) >> 10)
|
||||
|
||||
static inline uword_t gpio_ll_read(gpio_port_t port)
|
||||
{
|
||||
return ((GPIO_Type *)port)->ISTAT;
|
||||
}
|
||||
|
||||
static inline uword_t gpio_ll_read_output(gpio_port_t port)
|
||||
{
|
||||
return ((GPIO_Type *)port)->OCTL;
|
||||
}
|
||||
|
||||
static inline void gpio_ll_set(gpio_port_t port, uword_t mask)
|
||||
{
|
||||
((GPIO_Type *)port)->BOP = mask;
|
||||
}
|
||||
|
||||
static inline void gpio_ll_clear(gpio_port_t port, uword_t mask)
|
||||
{
|
||||
((GPIO_Type *)port)->BOP = mask << 16;
|
||||
}
|
||||
|
||||
static inline void gpio_ll_toggle(gpio_port_t port, uword_t mask)
|
||||
{
|
||||
unsigned irq_state = irq_disable();
|
||||
((GPIO_Type *)port)->OCTL ^= mask;
|
||||
irq_restore(irq_state);
|
||||
}
|
||||
|
||||
static inline void gpio_ll_write(gpio_port_t port, uword_t value)
|
||||
{
|
||||
((GPIO_Type *)port)->OCTL = value;
|
||||
}
|
||||
|
||||
static inline gpio_port_t gpio_get_port(gpio_t pin)
|
||||
{
|
||||
return pin & 0xfffffff0UL;
|
||||
}
|
||||
|
||||
static inline uint8_t gpio_get_pin_num(gpio_t pin)
|
||||
{
|
||||
return pin & 0xfUL;
|
||||
}
|
||||
|
||||
static inline gpio_port_t gpio_port_pack_addr(void *addr)
|
||||
{
|
||||
return (gpio_port_t)addr;
|
||||
}
|
||||
|
||||
static inline void * gpio_port_unpack_addr(gpio_port_t port)
|
||||
{
|
||||
if (port < GPIOA_BASE) {
|
||||
return (void *)port;
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static inline bool is_gpio_port_num_valid(uint_fast8_t num)
|
||||
{
|
||||
return num < GPIO_PORT_NUMOF;
|
||||
}
|
||||
|
||||
#endif /* DOXYGEN */
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* GPIO_LL_ARCH_H */
|
||||
/** @} */
|
@ -183,6 +183,36 @@ void gpio_init_af(gpio_t pin, gpio_af_t af);
|
||||
*/
|
||||
void gpio_init_analog(gpio_t pin);
|
||||
|
||||
/* Hide this from Doxygen to avoid merging implementation details into
|
||||
* public view on type */
|
||||
#ifndef DOXYGEN
|
||||
|
||||
#define HAVE_GPIO_PULL_STRENGTH_T
|
||||
typedef enum {
|
||||
GPIO_PULL_WEAKEST = 0,
|
||||
GPIO_PULL_WEAK = 0,
|
||||
GPIO_PULL_STRONG = 0,
|
||||
GPIO_PULL_STRONGEST = 0
|
||||
} gpio_pull_strength_t;
|
||||
|
||||
#define HAVE_GPIO_DRIVE_STRENGTH_T
|
||||
typedef enum {
|
||||
GPIO_DRIVE_WEAKEST = 0,
|
||||
GPIO_DRIVE_WEAK = 0,
|
||||
GPIO_DRIVE_STRONG = 0,
|
||||
GPIO_DRIVE_STRONGEST = 0
|
||||
} gpio_drive_strength_t;
|
||||
|
||||
#define HAVE_GPIO_SLEW_T
|
||||
typedef enum {
|
||||
GPIO_SLEW_SLOWEST = 0,
|
||||
GPIO_SLEW_SLOW = 1,
|
||||
GPIO_SLEW_FAST = 2,
|
||||
GPIO_SLEW_FASTEST = 2,
|
||||
} gpio_slew_t;
|
||||
|
||||
#endif /* !DOXYGEN */
|
||||
|
||||
/**
|
||||
* @brief Available number of ADC devices
|
||||
*/
|
||||
|
154
cpu/gd32v/periph/gpio_ll.c
Normal file
154
cpu/gd32v/periph/gpio_ll.c
Normal file
@ -0,0 +1,154 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Gunar Schorcht <gunar@schorcht.net>
|
||||
*
|
||||
* This file is subject to the terms and conditions of the GNU Lesser General
|
||||
* Public License v2.1. See the file LICENSE in the top level directory for more
|
||||
* details.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @ingroup cpu_gd32v
|
||||
* @ingroup drivers_periph_gpio_ll
|
||||
* @{
|
||||
*
|
||||
* @file
|
||||
* @brief GPIO Low-level API implementation for the GD32V GPIO peripheral
|
||||
*
|
||||
* @author Gunar Schorcht <gunar@schorcht.net>
|
||||
* @}
|
||||
*/
|
||||
|
||||
#include <errno.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "cpu.h"
|
||||
#include "bitarithm.h"
|
||||
#include "periph/gpio_ll.h"
|
||||
|
||||
#define ENABLE_DEBUG 0
|
||||
#include "debug.h"
|
||||
|
||||
uint16_t pin_used[GPIO_PORT_NUMOF] = {};
|
||||
|
||||
int gpio_ll_init(gpio_port_t port, uint8_t pin, const gpio_conf_t *conf)
|
||||
{
|
||||
if ((conf->pull == GPIO_PULL_KEEP) ||
|
||||
(conf->state == GPIO_OUTPUT_OPEN_SOURCE) ||
|
||||
((conf->state != GPIO_INPUT) && (conf->pull != GPIO_FLOATING))) {
|
||||
return -ENOTSUP;
|
||||
}
|
||||
|
||||
unsigned state = irq_disable();
|
||||
|
||||
periph_clk_en(APB2, (RCU_APB2EN_PAEN_Msk << GPIO_PORT_NUM(port)));
|
||||
|
||||
volatile uint32_t *ctrl = (pin < 8) ? &((GPIO_Type *)port)->CTL0
|
||||
: &((GPIO_Type *)port)->CTL1;
|
||||
volatile uint32_t *octl = &((GPIO_Type *)port)->OCTL;
|
||||
unsigned pos = ((pin % 8) * 4);
|
||||
|
||||
/* reset configuration CTLx[1:0], MDx[1:0] (analogue, input mode) */
|
||||
*ctrl &= ~(0xf << pos);
|
||||
|
||||
switch (conf->state) {
|
||||
case GPIO_DISCONNECT:
|
||||
*ctrl |= 0x1 << (pos + 2);
|
||||
pin_used[GPIO_PORT_NUM(port)] &= ~(1 << pin);
|
||||
if (pin_used[GPIO_PORT_NUM(port)] == 0) {
|
||||
periph_clk_dis(APB2, (RCU_APB2EN_PAEN_Msk << GPIO_PORT_NUM(port)));
|
||||
}
|
||||
break;
|
||||
case GPIO_INPUT:
|
||||
pin_used[GPIO_PORT_NUM(port)] |= 1 << pin;
|
||||
if (conf->pull == GPIO_FLOATING) {
|
||||
*ctrl |= 0x1 << (pos + 2);
|
||||
}
|
||||
else {
|
||||
*ctrl |= 0x2 << (pos + 2);
|
||||
if (conf->pull == GPIO_PULL_UP) {
|
||||
*octl |= 1 << pin;
|
||||
}
|
||||
else {
|
||||
*octl &= ~(1 << pin);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case GPIO_OUTPUT_PUSH_PULL:
|
||||
case GPIO_OUTPUT_OPEN_DRAIN:
|
||||
pin_used[GPIO_PORT_NUM(port)] |= 1 << pin;
|
||||
*ctrl |= (conf->slew_rate + 1) << pos;
|
||||
*ctrl |= (conf->state == GPIO_OUTPUT_OPEN_DRAIN ? 0x1 : 0x0) << (pos + 2);
|
||||
if (conf->initial_value) {
|
||||
gpio_ll_set(port, 1UL << pin);
|
||||
}
|
||||
else {
|
||||
gpio_ll_clear(port, 1UL << pin);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
irq_restore(state);
|
||||
return -ENOTSUP;
|
||||
}
|
||||
|
||||
irq_restore(state);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void gpio_ll_query_conf(gpio_conf_t *dest, gpio_port_t port, uint8_t pin)
|
||||
{
|
||||
assert(dest);
|
||||
|
||||
unsigned state = irq_disable();
|
||||
|
||||
memset(dest, 0, sizeof(*dest));
|
||||
|
||||
volatile uint32_t *ctrl_reg = (pin < 8) ? &((GPIO_Type *)port)->CTL0
|
||||
: &((GPIO_Type *)port)->CTL1;
|
||||
|
||||
unsigned pos = ((pin % 8) * 4);
|
||||
|
||||
uint32_t mode = (*ctrl_reg >> pos) & 0x3;
|
||||
uint32_t ctrl = (*ctrl_reg >> (pos + 2)) & 0x3;
|
||||
|
||||
if (mode == 0) {
|
||||
dest->state = GPIO_INPUT;
|
||||
switch (ctrl) {
|
||||
case 0:
|
||||
dest->state = GPIO_USED_BY_PERIPHERAL;
|
||||
break;
|
||||
case 1:
|
||||
dest->pull = GPIO_FLOATING;
|
||||
break;
|
||||
case 2:
|
||||
dest->pull = (((GPIO_Type *)port)->OCTL & (1UL << pin)) ? GPIO_PULL_UP
|
||||
: GPIO_PULL_DOWN;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
else {
|
||||
dest->slew_rate = mode - 1;
|
||||
dest->pull = GPIO_FLOATING;
|
||||
switch (ctrl) {
|
||||
case 0:
|
||||
dest->state = GPIO_OUTPUT_PUSH_PULL;
|
||||
break;
|
||||
case 1:
|
||||
dest->state = GPIO_OUTPUT_OPEN_DRAIN;
|
||||
break;
|
||||
default:
|
||||
dest->state = GPIO_USED_BY_PERIPHERAL;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (dest->state == GPIO_INPUT) {
|
||||
dest->initial_value = (gpio_ll_read(port) >> pin) & 1UL;
|
||||
}
|
||||
else {
|
||||
dest->initial_value = (gpio_ll_read_output(port) >> pin) & 1UL;
|
||||
}
|
||||
irq_restore(state);
|
||||
}
|
192
cpu/gd32v/periph/gpio_ll_irq.c
Normal file
192
cpu/gd32v/periph/gpio_ll_irq.c
Normal file
@ -0,0 +1,192 @@
|
||||
/*
|
||||
* Copyright (C) 2023 Gunar Schorcht <gunar@schorcht.net>
|
||||
*
|
||||
* This file is subject to the terms and conditions of the GNU Lesser General
|
||||
* Public License v2.1. See the file LICENSE in the top level directory for more
|
||||
* details.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @ingroup cpu_gd32v
|
||||
* @ingroup drivers_periph_gpio_ll_irq
|
||||
* @{
|
||||
*
|
||||
* @file
|
||||
* @brief IRQ implementation of the GPIO Low-Level API for GD32V
|
||||
*
|
||||
* @author Gunar Schorcht <gunar@schorcht.net>
|
||||
* @}
|
||||
*/
|
||||
|
||||
#include <errno.h>
|
||||
|
||||
#include "cpu.h"
|
||||
#include "bitarithm.h"
|
||||
#include "periph/gpio_ll_irq.h"
|
||||
|
||||
#define ENABLE_DEBUG 0
|
||||
#include "debug.h"
|
||||
|
||||
/**
|
||||
* @brief Number of available external interrupt lines
|
||||
*/
|
||||
#define GPIO_ISR_CHAN_NUMOF (16U)
|
||||
#define GPIO_ISR_CHAN_MASK (0xFFFF)
|
||||
|
||||
struct _gpio_isr_ctx {
|
||||
gpio_ll_cb_t cb;
|
||||
void *arg;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Allocate memory for one callback and argument per EXTI channel
|
||||
*/
|
||||
static struct _gpio_isr_ctx _exti_ctx[GPIO_ISR_CHAN_NUMOF];
|
||||
|
||||
/* keep the state of level triggered interrupt activation using 32-bit values
|
||||
* since the registers require 32-bit access */
|
||||
static uint32_t _h_level_triggered = 0;
|
||||
static uint32_t _l_level_triggered = 0;
|
||||
|
||||
void gpio_ll_irq_mask(gpio_port_t port, uint8_t pin)
|
||||
{
|
||||
(void)port;
|
||||
EXTI->INTEN &= ~(1 << pin);
|
||||
}
|
||||
|
||||
void gpio_ll_irq_unmask_and_clear(gpio_port_t port, uint8_t pin)
|
||||
{
|
||||
(void)port;
|
||||
EXTI->PD = (1 << pin);
|
||||
EXTI->INTEN |= (1 << pin);
|
||||
}
|
||||
|
||||
static inline unsigned _irq_num(unsigned pin)
|
||||
{
|
||||
if (pin < 5) {
|
||||
return EXTI0_IRQn + pin;
|
||||
}
|
||||
if (pin < 10) {
|
||||
return EXTI5_9_IRQn;
|
||||
}
|
||||
return EXTI10_15_IRQn;
|
||||
}
|
||||
|
||||
/* Forward declaration of ISR */
|
||||
static void _gpio_isr(unsigned irqn);
|
||||
|
||||
int gpio_ll_irq(gpio_port_t port, uint8_t pin, gpio_irq_trig_t trig, gpio_ll_cb_t cb, void *arg)
|
||||
{
|
||||
unsigned irq_state = irq_disable();
|
||||
int port_num = GPIO_PORT_NUM(port);
|
||||
|
||||
/* set callback */
|
||||
_exti_ctx[pin].cb = cb;
|
||||
_exti_ctx[pin].arg = arg;
|
||||
|
||||
/* enable alternate function clock for the GPIO module */
|
||||
periph_clk_en(APB2, RCU_APB2EN_AFEN_Msk);
|
||||
|
||||
/* configure the EXTI channel */
|
||||
volatile uint32_t *afio_exti_ss = &AFIO->EXTISS0 + (pin >> 2);
|
||||
|
||||
*afio_exti_ss &= ~(0xfUL << ((pin & 0x03) * 4));
|
||||
*afio_exti_ss |= (uint32_t)port_num << ((pin & 0x03) * 4);
|
||||
|
||||
uint32_t pin_mask = 1UL << pin;
|
||||
|
||||
_h_level_triggered &= ~pin_mask;
|
||||
_l_level_triggered &= ~pin_mask;
|
||||
EXTI->RTEN &= ~pin_mask;
|
||||
EXTI->FTEN &= ~pin_mask;
|
||||
|
||||
/* configure the active flank, level interrupts are emulated */
|
||||
switch (trig) {
|
||||
case GPIO_TRIGGER_EDGE_RISING:
|
||||
EXTI->RTEN |= pin_mask;
|
||||
break;
|
||||
case GPIO_TRIGGER_EDGE_FALLING:
|
||||
EXTI->FTEN |= pin_mask;
|
||||
break;
|
||||
case GPIO_TRIGGER_EDGE_BOTH:
|
||||
EXTI->RTEN |= pin_mask;
|
||||
EXTI->FTEN |= pin_mask;
|
||||
break;
|
||||
case GPIO_TRIGGER_LEVEL_HIGH:
|
||||
EXTI->RTEN |= pin_mask;
|
||||
_h_level_triggered |= pin_mask;
|
||||
break;
|
||||
case GPIO_TRIGGER_LEVEL_LOW:
|
||||
EXTI->FTEN |= pin_mask;
|
||||
_l_level_triggered |= pin_mask;
|
||||
break;
|
||||
}
|
||||
|
||||
/* clear pending interrupts */
|
||||
EXTI->PD = pin_mask;
|
||||
|
||||
/* enable global pin interrupt */
|
||||
unsigned irqn = _irq_num(pin);
|
||||
|
||||
clic_set_handler(irqn, _gpio_isr);
|
||||
clic_enable_interrupt(irqn, CPU_DEFAULT_IRQ_PRIO);
|
||||
|
||||
/* unmask the pins interrupt channel */
|
||||
EXTI->INTEN |= pin_mask;
|
||||
|
||||
/* emulate a level interrupt if the pin already has the level */
|
||||
uint32_t level = gpio_ll_read(port) & pin_mask;
|
||||
|
||||
if ((trig == GPIO_TRIGGER_LEVEL_HIGH) && level) {
|
||||
EXTI->SWIEV |= pin_mask;
|
||||
}
|
||||
else if ((trig == GPIO_TRIGGER_LEVEL_LOW) && !level) {
|
||||
EXTI->SWIEV |= pin_mask;
|
||||
}
|
||||
|
||||
irq_restore(irq_state);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void _gpio_isr(unsigned irqn)
|
||||
{
|
||||
(void)irqn;
|
||||
|
||||
/* read pending interrupts */
|
||||
uint32_t pending = (EXTI->PD & GPIO_ISR_CHAN_MASK);
|
||||
|
||||
/* clear pending interrupts */
|
||||
EXTI->PD = pending;
|
||||
|
||||
/* generate soft interrupts for lines which have their interrupt enabled */
|
||||
pending &= EXTI->INTEN;
|
||||
|
||||
/* iterate over all set bits */
|
||||
uint8_t pin = 0;
|
||||
|
||||
while (pending) {
|
||||
/* get next pin with interrupt */
|
||||
pending = bitarithm_test_and_clear(pending, &pin);
|
||||
/* call registered callball function */
|
||||
_exti_ctx[pin].cb(_exti_ctx[pin].arg);
|
||||
|
||||
/* emulate level triggered IRQs by asserting the IRQ again in software */
|
||||
uint32_t pin_mask = 1UL << pin;
|
||||
|
||||
if ((_h_level_triggered & pin_mask) || (_l_level_triggered & pin_mask)) {
|
||||
/* determine the port of triggered interrupt */
|
||||
volatile uint32_t *afio_exti_ss = &AFIO->EXTISS0 + (pin >> 2);
|
||||
gpio_port_t port = GPIO_PORT(((*afio_exti_ss >> ((pin & 0x03) * 4)) & 0xfUL));
|
||||
|
||||
/* trigger software interrupt if the pin has the according level */
|
||||
uint32_t level = gpio_ll_read(port) & pin_mask;
|
||||
if ((_h_level_triggered & pin_mask) && level) {
|
||||
EXTI->SWIEV |= pin_mask;
|
||||
}
|
||||
else if ((_l_level_triggered & pin_mask) && !level) {
|
||||
EXTI->SWIEV |= pin_mask;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -454,7 +454,7 @@ static inline bool is_gpio_port_num_valid(uint_fast8_t num);
|
||||
* value will be chosen instead and `0` is returned.
|
||||
* @warning Note that hardware GPIO peripherals may have shared building
|
||||
* blocks. Those *SHOULD* be handed out by the implementation in
|
||||
* first-come-fist-served fashion. (E.g. if there is only one pull up
|
||||
* first-come-first-served fashion. (E.g. if there is only one pull up
|
||||
* resistor per port that can be connected to any pin of that port,
|
||||
* typically the first pin on the port configured as pull up will
|
||||
* succeed and subsequent configuration as pull ups for other pins on
|
||||
|
@ -117,9 +117,10 @@ void gpio_ll_irq_mask(gpio_port_t port, uint8_t pin);
|
||||
/**
|
||||
* @brief Unmask IRQs on the given GPIO pin
|
||||
*
|
||||
* Same as @ref gpio_ll_irq_unmask_and_clear except that IRQs that came in during
|
||||
* Same as @ref gpio_ll_irq_unmask_and_clear except that IRQs that came in while
|
||||
* masked are not lost.
|
||||
*
|
||||
* @warning On same MCUs (most notably STM32) this is impossible to implement.
|
||||
* @warning On some MCUs (most notably STM32) this is impossible to implement.
|
||||
* The feature `periph_gpio_ll_irq_unmask` is provided, if this
|
||||
* function is available.
|
||||
*
|
||||
|
@ -33,6 +33,10 @@ ifneq (,$(filter auto_init_sock_dns,$(USEMODULE)))
|
||||
endif
|
||||
endif
|
||||
|
||||
ifneq (,$(filter coding,$(USEMODULE)))
|
||||
USEMODULE += bitfield
|
||||
endif
|
||||
|
||||
ifneq (,$(filter congure_%,$(USEMODULE)))
|
||||
USEMODULE += congure
|
||||
endif
|
||||
|
1
sys/coding/Makefile
Normal file
1
sys/coding/Makefile
Normal file
@ -0,0 +1 @@
|
||||
include $(RIOTBASE)/Makefile.base
|
16
sys/coding/doc.txt
Normal file
16
sys/coding/doc.txt
Normal file
@ -0,0 +1,16 @@
|
||||
/*
|
||||
* Copyright (C) 2021 Benjamin Valentin <benjamin.valentin@ml-pa.com>
|
||||
*
|
||||
* This file is subject to the terms and conditions of the GNU Lesser
|
||||
* General Public License v2.1. See the file LICENSE in the top level
|
||||
* directory for more details.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @defgroup sys_coding Error correction codes
|
||||
* @ingroup sys
|
||||
* @brief Error correction function libraries
|
||||
*
|
||||
* This module provides functions to generate redundancy data for error
|
||||
* correction.
|
||||
*/
|
203
sys/coding/xor.c
Normal file
203
sys/coding/xor.c
Normal file
@ -0,0 +1,203 @@
|
||||
/*
|
||||
* Copyright (C) 2021 Benjamin Valentin <benjamin.valentin@ml-pa.com>
|
||||
*
|
||||
* This file is subject to the terms and conditions of the GNU Lesser
|
||||
* General Public License v2.1. See the file LICENSE in the top level
|
||||
* directory for more details.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @ingroup sys_coding_xor
|
||||
* @brief XOR coding algorithm
|
||||
*
|
||||
* @{
|
||||
*
|
||||
* @file
|
||||
* @brief XOR coding implementation
|
||||
*
|
||||
* @author Benjamin Valentin <benjamin.valentin@ml-pa.com>
|
||||
*/
|
||||
|
||||
#include <string.h>
|
||||
#include "bitfield.h"
|
||||
#include "coding/xor.h"
|
||||
|
||||
#define ENABLE_DEBUG 0
|
||||
#include "debug.h"
|
||||
|
||||
static void _gen_parity(const void *data, size_t len, uint8_t *out)
|
||||
{
|
||||
const uint8_t *in = data;
|
||||
|
||||
memset(out, 0, CODING_XOR_PARITY_LEN(len));
|
||||
for (unsigned i = 0; i < len; ++i) {
|
||||
out[i / CONFIG_CODING_XOR_CHECK_BYTES] ^= in[i];
|
||||
}
|
||||
}
|
||||
|
||||
static inline size_t _transpose_idx(size_t i, size_t width, size_t height)
|
||||
{
|
||||
size_t x = i % width;
|
||||
size_t y = i / width;
|
||||
|
||||
return x * height + y;
|
||||
}
|
||||
|
||||
static inline void _swap(uint8_t *a, uint8_t *b)
|
||||
{
|
||||
uint8_t tmp = *a;
|
||||
*a = *b;
|
||||
*b = tmp;
|
||||
}
|
||||
|
||||
/* https://www.geeksforgeeks.org/inplace-m-x-n-size-matrix-transpose/ */
|
||||
static void _transpose(uint8_t *data, size_t len, size_t height)
|
||||
{
|
||||
BITFIELD(visited, len);
|
||||
memset(visited, 0, sizeof(visited));
|
||||
|
||||
/* A[0] and A[size-1] won't move */
|
||||
size_t i = 1;
|
||||
size_t last = len -1;
|
||||
bf_set(visited, 0);
|
||||
bf_set(visited, last);
|
||||
|
||||
while (i < last) {
|
||||
size_t cycle_begin = i;
|
||||
uint8_t tmp = data[i];
|
||||
do {
|
||||
/* Input matrix [r x c]
|
||||
* Output matrix
|
||||
* i_new = (i*r)%(N-1)
|
||||
*/
|
||||
size_t next = (i * height) % last;
|
||||
_swap(&data[next], &tmp);
|
||||
bf_set(visited, i);
|
||||
i = next;
|
||||
} while (i != cycle_begin);
|
||||
|
||||
/* Get Next Move (what about querying random location?) */
|
||||
i = bf_find_first_unset(visited, len);
|
||||
}
|
||||
}
|
||||
|
||||
static inline void _mix(void *data, size_t len)
|
||||
{
|
||||
_transpose(data, len, len / CONFIG_CODING_XOR_CHECK_BYTES);
|
||||
}
|
||||
|
||||
static inline void _unmix(void *data, size_t len)
|
||||
{
|
||||
_transpose(data, len, CONFIG_CODING_XOR_CHECK_BYTES);
|
||||
}
|
||||
|
||||
static bool _recover_byte(const uint8_t *in, size_t width, uint8_t height,
|
||||
const uint8_t *parity, size_t idx, uint8_t *bitfield,
|
||||
size_t block_size, uint8_t *out)
|
||||
{
|
||||
uint8_t res = parity[idx / CONFIG_CODING_XOR_CHECK_BYTES];
|
||||
size_t start = idx - idx % CONFIG_CODING_XOR_CHECK_BYTES;
|
||||
|
||||
for (unsigned i = start; i < start + CONFIG_CODING_XOR_CHECK_BYTES; ++i) {
|
||||
/* skip the lost byte */
|
||||
if (i == idx) {
|
||||
continue;
|
||||
}
|
||||
|
||||
/* get index of neighbor byte in transposed matrix */
|
||||
size_t idx_in = _transpose_idx(i, height, width);
|
||||
if (!bf_isset(bitfield, idx_in / block_size)) {
|
||||
DEBUG("missing chunk %u\n", idx_in / block_size);
|
||||
return false;
|
||||
}
|
||||
res ^= in[idx_in];
|
||||
}
|
||||
|
||||
*out = res;
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool _recover_blocks(void *data, size_t len, const uint8_t *parity,
|
||||
uint8_t *bitfield, size_t block_size)
|
||||
{
|
||||
uint8_t *in = data;
|
||||
|
||||
const uint8_t height = CONFIG_CODING_XOR_CHECK_BYTES;
|
||||
const size_t width = len / height;
|
||||
const uint8_t num_data_blocks = len / block_size;
|
||||
bool success = true;
|
||||
|
||||
for (size_t i = 0; i < len; i += block_size) {
|
||||
/* block is present, nothing to do */
|
||||
if (bf_isset(bitfield, i / block_size)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
DEBUG("try to recover chunk %u / %u\n", i / block_size, num_data_blocks);
|
||||
for (size_t j = i; j < i + block_size; ++j) {
|
||||
|
||||
/* get original byte position */
|
||||
size_t idx = _transpose_idx(j, width, height);
|
||||
|
||||
/* we can only recover the byte if we have the matching parity block */
|
||||
size_t parity_block = idx / (CONFIG_CODING_XOR_CHECK_BYTES * block_size);
|
||||
if (!bf_isset(bitfield, num_data_blocks + parity_block)) {
|
||||
DEBUG("missing parity block %u\n", parity_block);
|
||||
success = false;
|
||||
goto next_block;
|
||||
}
|
||||
|
||||
/* try to recover lost byte from parity */
|
||||
if (!_recover_byte(in, width, height, parity, idx,
|
||||
bitfield, block_size, &in[j])) {
|
||||
success = false;
|
||||
goto next_block;
|
||||
}
|
||||
}
|
||||
bf_set(bitfield, i / block_size);
|
||||
|
||||
next_block:
|
||||
/* try to recover another block */ ;
|
||||
}
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
void coding_xor_generate(void *data, size_t len, uint8_t *parity)
|
||||
{
|
||||
_gen_parity(data, len, parity);
|
||||
_mix(data, len);
|
||||
}
|
||||
|
||||
bool coding_xor_recover(void *data, size_t len, uint8_t *parity,
|
||||
uint8_t *bitfield, size_t block_size, bool recover_parity)
|
||||
{
|
||||
size_t num_data_chunks = len / block_size;
|
||||
size_t num_parity_chunks = CODING_XOR_PARITY_LEN(len) / block_size;
|
||||
|
||||
if (!_recover_blocks(data, len, parity, bitfield, block_size)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
_unmix(data, len);
|
||||
|
||||
if (!recover_parity) {
|
||||
return true;
|
||||
}
|
||||
|
||||
/* recover lost parity blocks */
|
||||
for (size_t i = 0; i < num_parity_chunks; ++i) {
|
||||
if (bf_isset(bitfield, num_data_chunks + i)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
DEBUG("regenerate parity block %u\n", i);
|
||||
size_t data_len = block_size * CONFIG_CODING_XOR_CHECK_BYTES;
|
||||
_gen_parity((uint8_t *)data + i * data_len,
|
||||
data_len, parity + i * block_size);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/** @} */
|
93
sys/include/coding/xor.h
Normal file
93
sys/include/coding/xor.h
Normal file
@ -0,0 +1,93 @@
|
||||
/*
|
||||
* Copyright (C) 2021 Benjamin Valentin <benjamin.valentin@ml-pa.com>
|
||||
*
|
||||
* This file is subject to the terms and conditions of the GNU Lesser
|
||||
* General Public License v2.1. See the file LICENSE in the top level
|
||||
* directory for more details.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @defgroup sys_coding_xor XOR code
|
||||
* @ingroup sys_coding
|
||||
* @brief Simple XOR based coding algorithms
|
||||
*
|
||||
* @experimental This is a very basic implementation, it can only recover
|
||||
* 1 lost block in 3 and only has a 33% chance of recovering
|
||||
* two consecutive lost blocks.
|
||||
* API / Algorithm might change if that means we can do better.
|
||||
*
|
||||
* @{
|
||||
*
|
||||
* @file
|
||||
* @brief XOR coding definitions
|
||||
*
|
||||
* @author Benjamin Valentin <benjamin.valentin@ml-pa.com>
|
||||
*/
|
||||
|
||||
#ifndef CODING_XOR_H
|
||||
#define CODING_XOR_H
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @brief Number of payload bytes per parity byte
|
||||
*/
|
||||
#ifndef CONFIG_CODING_XOR_CHECK_BYTES
|
||||
#define CONFIG_CODING_XOR_CHECK_BYTES 3U
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @brief Get the size of the needed parity buffer for a given payload size
|
||||
*
|
||||
* @param in Payload length
|
||||
*/
|
||||
#define CODING_XOR_PARITY_LEN(in) (((in) + CONFIG_CODING_XOR_CHECK_BYTES - 1) \
|
||||
/ CONFIG_CODING_XOR_CHECK_BYTES)
|
||||
|
||||
/**
|
||||
* @brief Generate parity and mix data buffer
|
||||
*
|
||||
* This generates parity data to recover one in @ref CONFIG_CODING_XOR_CHECK_BYTES
|
||||
* bytes.
|
||||
* The data buffer is then mixed to distribute bytes amongst transfer blocks, so
|
||||
* that the chance for consecutive bytes to be in the same block is lowered.
|
||||
*
|
||||
* @param[in,out] data The data buffer to be processed
|
||||
* @param[in] len Size of the data buffer
|
||||
* @param[out] parity Buffer to hold parity data.
|
||||
* Must be at least `CODING_XOR_PARITY_LEN(len)` bytes
|
||||
*/
|
||||
void coding_xor_generate(void *data, size_t len, uint8_t *parity);
|
||||
|
||||
/**
|
||||
* @brief Restore and unmix buffer
|
||||
*
|
||||
*
|
||||
* @param[in, out] data The data buffer to be restored
|
||||
* @param[in] len Size of the data buffer
|
||||
* @param[in,out] parity Buffer with parity data.
|
||||
* Must be at least `CODING_XOR_PARITY_LEN(len)` bytes
|
||||
* @param[in,out] blocks Bitfieled to indicate which blocks were received.
|
||||
* This indicates the presence of both data and parity
|
||||
* blocks. Parity blocks are appended after the last
|
||||
* data block.
|
||||
* If a block was restored it's bit will be set.
|
||||
* @param[in] block_size Size of a data/payload block
|
||||
* @param[in] recover_parity If true, missing parity blocks will be re-generated
|
||||
* from data blocks.
|
||||
*
|
||||
* @return True if all data blocks were recovered
|
||||
*/
|
||||
bool coding_xor_recover(void *data, size_t len, uint8_t *parity,
|
||||
uint8_t *blocks, size_t block_size, bool recover_parity);
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* CODING_XOR_H */
|
||||
/** @} */
|
1
tests/unittests/tests-coding/Makefile
Normal file
1
tests/unittests/tests-coding/Makefile
Normal file
@ -0,0 +1 @@
|
||||
include $(RIOTBASE)/Makefile.base
|
1
tests/unittests/tests-coding/Makefile.include
Normal file
1
tests/unittests/tests-coding/Makefile.include
Normal file
@ -0,0 +1 @@
|
||||
USEMODULE += coding
|
190
tests/unittests/tests-coding/tests-coding-xor.c
Normal file
190
tests/unittests/tests-coding/tests-coding-xor.c
Normal file
@ -0,0 +1,190 @@
|
||||
/*
|
||||
* Copyright (C) 2021 Benjamin Valentin <benjamin.valentin@ml-pa.com>
|
||||
*
|
||||
* This file is subject to the terms and conditions of the GNU Lesser
|
||||
* General Public License v2.1. See the file LICENSE in the top level
|
||||
* directory for more details.
|
||||
*/
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include "embUnit/embUnit.h"
|
||||
|
||||
#include "bitfield.h"
|
||||
#include "coding/xor.h"
|
||||
#include "tests-coding.h"
|
||||
|
||||
static void test_coding_xor_building_blocks(void)
|
||||
{
|
||||
char string[] = "0123456789AB";
|
||||
uint8_t parity[CODING_XOR_PARITY_LEN(sizeof(string) - 1)];
|
||||
|
||||
coding_xor_generate(string, sizeof(string) - 1, parity);
|
||||
|
||||
TEST_ASSERT_EQUAL_INT(0x33, parity[0]);
|
||||
TEST_ASSERT_EQUAL_INT(0x32, parity[1]);
|
||||
TEST_ASSERT_EQUAL_INT(0x39, parity[2]);
|
||||
TEST_ASSERT_EQUAL_INT(0x3A, parity[3]);
|
||||
TEST_ASSERT_EQUAL_STRING("0369147A258B", string);
|
||||
|
||||
uint8_t blocks = 0x80;
|
||||
bool success = coding_xor_recover(string, sizeof(string) - 1, parity,
|
||||
&blocks, sizeof(string) - 1, false);
|
||||
TEST_ASSERT(success);
|
||||
TEST_ASSERT_EQUAL_STRING("0123456789AB", string);
|
||||
}
|
||||
|
||||
static void test_coding_xor_recovery(void)
|
||||
{
|
||||
char string[] = "0123456789AB";
|
||||
char string_rx[sizeof(string)];
|
||||
uint8_t parity[CODING_XOR_PARITY_LEN(sizeof(string) - 1)];
|
||||
const uint8_t chunk_size = 4;
|
||||
const uint8_t num_chunks = sizeof(string) / chunk_size;
|
||||
const uint8_t num_parity_chunks = sizeof(parity) / chunk_size;
|
||||
|
||||
BITFIELD(chunks, num_chunks + num_parity_chunks);
|
||||
memset(chunks, 0, sizeof(chunks));
|
||||
memset(string_rx, 0, sizeof(string_rx));
|
||||
|
||||
coding_xor_generate(string, sizeof(string) - 1, parity);
|
||||
|
||||
for (unsigned i = 0; i < num_chunks; ++i) {
|
||||
/* lose a single chunk / packet */
|
||||
if (i == 1) {
|
||||
continue;
|
||||
}
|
||||
|
||||
memcpy(string_rx + i * chunk_size,
|
||||
string + i * chunk_size,
|
||||
chunk_size);
|
||||
bf_set(chunks, i);
|
||||
}
|
||||
/* we have all parity chunks */
|
||||
for (unsigned i = 0; i < num_parity_chunks; ++i) {
|
||||
bf_set(chunks, num_chunks + i);
|
||||
}
|
||||
|
||||
bool success = coding_xor_recover(string_rx, sizeof(string_rx) - 1,
|
||||
parity, chunks, chunk_size, false);
|
||||
TEST_ASSERT(success);
|
||||
TEST_ASSERT_EQUAL_STRING("0123456789AB", string_rx);
|
||||
}
|
||||
|
||||
static void test_coding_xor_recovery_failed(void)
|
||||
{
|
||||
char string[] = "0123456789AB";
|
||||
char string_rx[sizeof(string)];
|
||||
uint8_t parity[CODING_XOR_PARITY_LEN(sizeof(string) - 1)];
|
||||
const uint8_t chunk_size = 4;
|
||||
const uint8_t num_chunks = sizeof(string) / chunk_size;
|
||||
const uint8_t num_parity_chunks = sizeof(parity) / chunk_size;
|
||||
|
||||
BITFIELD(chunks, num_chunks + num_parity_chunks);
|
||||
memset(chunks, 0, sizeof(chunks));
|
||||
memset(string_rx, 0, sizeof(string_rx));
|
||||
|
||||
coding_xor_generate(string, sizeof(string) - 1, parity);
|
||||
|
||||
for (unsigned i = 0; i < num_chunks; ++i) {
|
||||
/* lose two chunks / packets */
|
||||
if (i == 1 || i == 2) {
|
||||
continue;
|
||||
}
|
||||
|
||||
memcpy(string_rx + i * chunk_size,
|
||||
string + i * chunk_size,
|
||||
chunk_size);
|
||||
bf_set(chunks, i);
|
||||
}
|
||||
/* we have all parity chunks */
|
||||
for (unsigned i = 0; i < num_parity_chunks; ++i) {
|
||||
bf_set(chunks, num_chunks + i);
|
||||
}
|
||||
|
||||
bool success = coding_xor_recover(string_rx, sizeof(string_rx) - 1,
|
||||
parity, chunks, chunk_size, false);
|
||||
TEST_ASSERT(!success);
|
||||
}
|
||||
|
||||
static void test_coding_xor_recovery_parity(void)
|
||||
{
|
||||
char string[] = "0123456789AB";
|
||||
uint8_t parity[CODING_XOR_PARITY_LEN(sizeof(string) - 1)];
|
||||
uint8_t parity_rx[CODING_XOR_PARITY_LEN(sizeof(string) - 1)];
|
||||
const uint8_t chunk_size = 4;
|
||||
const uint8_t num_chunks = sizeof(string) / chunk_size;
|
||||
const uint8_t num_parity_chunks = sizeof(parity) / chunk_size;
|
||||
|
||||
BITFIELD(chunks, num_chunks + num_parity_chunks);
|
||||
memset(chunks, 0, sizeof(chunks));
|
||||
|
||||
coding_xor_generate(string, sizeof(string) - 1, parity);
|
||||
|
||||
/* we have all data chunks */
|
||||
for (unsigned i = 0; i < num_chunks; ++i) {
|
||||
bf_set(chunks, i);
|
||||
}
|
||||
|
||||
bool success = coding_xor_recover(string, sizeof(string) - 1, parity_rx,
|
||||
chunks, chunk_size, true);
|
||||
TEST_ASSERT(success);
|
||||
TEST_ASSERT_EQUAL_STRING("0123456789AB", string);
|
||||
TEST_ASSERT(memcmp(parity, parity_rx, sizeof(parity)) == 0);
|
||||
}
|
||||
|
||||
static void test_coding_xor_recovery_large(void)
|
||||
{
|
||||
uint8_t txbuf[1024];
|
||||
const size_t data_len = 768;
|
||||
|
||||
const size_t parity_len = CODING_XOR_PARITY_LEN(data_len);
|
||||
uint8_t *parity = &txbuf[data_len];
|
||||
|
||||
const uint8_t chunk_size = 64;
|
||||
const size_t num_chunks = (data_len + parity_len) / chunk_size;
|
||||
BITFIELD(chunks, num_chunks);
|
||||
memset(chunks, 0, sizeof(chunks));
|
||||
|
||||
TEST_ASSERT_EQUAL_INT(sizeof(txbuf), data_len + parity_len);
|
||||
|
||||
/* fill TX buffer with known pattern */
|
||||
for (unsigned i = 0; i < data_len; ++i) {
|
||||
txbuf[i] = i & 0xFF;
|
||||
}
|
||||
coding_xor_generate(txbuf, data_len, parity);
|
||||
|
||||
for (unsigned i = 0; i < num_chunks; ++i) {
|
||||
uint8_t *data = &txbuf[i * chunk_size];
|
||||
|
||||
/* lose some chunks */
|
||||
if (i == 3 || i == 10 || i == 13) {
|
||||
memset(data, 0, chunk_size);
|
||||
} else {
|
||||
bf_set(chunks, i);
|
||||
}
|
||||
}
|
||||
|
||||
bool success = coding_xor_recover(txbuf, data_len, parity, chunks,
|
||||
chunk_size, true);
|
||||
TEST_ASSERT(success);
|
||||
for (unsigned i = 0; i < data_len; ++i) {
|
||||
TEST_ASSERT_EQUAL_INT(i & 0xFF, txbuf[i]);
|
||||
}
|
||||
}
|
||||
|
||||
Test *tests_coding_xor_tests(void)
|
||||
{
|
||||
EMB_UNIT_TESTFIXTURES(fixtures) {
|
||||
new_TestFixture(test_coding_xor_building_blocks),
|
||||
new_TestFixture(test_coding_xor_recovery),
|
||||
new_TestFixture(test_coding_xor_recovery_failed),
|
||||
new_TestFixture(test_coding_xor_recovery_parity),
|
||||
new_TestFixture(test_coding_xor_recovery_large),
|
||||
};
|
||||
|
||||
EMB_UNIT_TESTCALLER(coding_xor_tests, NULL, NULL, fixtures);
|
||||
|
||||
return (Test *)&coding_xor_tests;
|
||||
}
|
14
tests/unittests/tests-coding/tests-coding.c
Normal file
14
tests/unittests/tests-coding/tests-coding.c
Normal file
@ -0,0 +1,14 @@
|
||||
/*
|
||||
* Copyright 2021 Benjamin Valentin <benjamin.valentin@ml-pa.com>
|
||||
*
|
||||
* This file is subject to the terms and conditions of the GNU Lesser
|
||||
* General Public License v2.1. See the file LICENSE in the top level
|
||||
* directory for more details.
|
||||
*/
|
||||
|
||||
#include "tests-coding.h"
|
||||
|
||||
void tests_coding(void)
|
||||
{
|
||||
TESTS_RUN(tests_coding_xor_tests());
|
||||
}
|
44
tests/unittests/tests-coding/tests-coding.h
Normal file
44
tests/unittests/tests-coding/tests-coding.h
Normal file
@ -0,0 +1,44 @@
|
||||
/*
|
||||
* Copyright 2021 Benjamin Valentin <benjamin.valentin@ml-pa.com>
|
||||
*
|
||||
* This file is subject to the terms and conditions of the GNU Lesser
|
||||
* General Public License v2.1. See the file LICENSE in the top level
|
||||
* directory for more details.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @addtogroup unittests
|
||||
* @{
|
||||
*
|
||||
* @file
|
||||
* @brief Unittests for the ``coding`` module
|
||||
*
|
||||
* @author Benjamin Valentin <benjamin.valentin@ml-pa.com>
|
||||
*/
|
||||
#ifndef TESTS_CODING_H
|
||||
#define TESTS_CODING_H
|
||||
|
||||
#include "embUnit.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @brief The entry point of this test suite.
|
||||
*/
|
||||
void tests_coding(void);
|
||||
|
||||
/**
|
||||
* @brief Generates tests for coding/xor.h
|
||||
*
|
||||
* @return embUnit tests if successful, NULL if not.
|
||||
*/
|
||||
Test *tests_coding_xor_tests(void);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* TESTS_CODING_H */
|
||||
/** @} */
|
Loading…
Reference in New Issue
Block a user