From a1dc923315514ed6f23da5f2c0950830975299f8 Mon Sep 17 00:00:00 2001 From: jaseg Date: Mon, 16 Mar 2020 19:19:34 +0100 Subject: Fix serial --- .gitmodules | 3 + controller/fw/Makefile | 15 ++++- controller/fw/src/adc.c | 20 +++--- controller/fw/src/con_usart.c | 33 +++++++++ controller/fw/src/con_usart.h | 16 +++++ controller/fw/src/dma_util.c | 46 +++++++++++++ controller/fw/src/dma_util.h | 10 +++ controller/fw/src/freq_meas.c | 8 ++- controller/fw/src/main.c | 80 +++++++++++++++------- controller/fw/src/protocol.c | 10 ++- controller/fw/src/serial.c | 153 ++++++++++++++++++++++++++++++++++++++++++ controller/fw/src/serial.h | 82 ++++++++++++++++++++++ controller/fw/src/sr_global.h | 13 ++++ controller/fw/tinyprintf | 1 + 14 files changed, 448 insertions(+), 42 deletions(-) create mode 100644 controller/fw/src/con_usart.c create mode 100644 controller/fw/src/con_usart.h create mode 100644 controller/fw/src/dma_util.c create mode 100644 controller/fw/src/dma_util.h create mode 100644 controller/fw/src/serial.c create mode 100644 controller/fw/src/serial.h create mode 160000 controller/fw/tinyprintf diff --git a/.gitmodules b/.gitmodules index 755dc2f..9da9307 100644 --- a/.gitmodules +++ b/.gitmodules @@ -19,3 +19,6 @@ [submodule "controller/fw/crypto-algorithms"] path = controller/fw/crypto-algorithms url = https://github.com/B-Con/crypto-algorithms +[submodule "controller/fw/tinyprintf"] + path = controller/fw/tinyprintf + url = https://github.com/cjlano/tinyprintf diff --git a/controller/fw/Makefile b/controller/fw/Makefile index 0fea8ae..50d162d 100644 --- a/controller/fw/Makefile +++ b/controller/fw/Makefile @@ -42,8 +42,18 @@ PRESIG_KEYFILE ?= presig_test_key.secret # Sources ######################################################################################################################## -C_SOURCES := src/main.c src/mspdebug_wrapper.c src/spi_flash.c src/freq_meas.c src/dsss_demod.c src/adc.c \ - src/protocol.c +C_SOURCES := src/main.c +C_SOURCES += src/mspdebug_wrapper.c +C_SOURCES += src/spi_flash.c +C_SOURCES += src/freq_meas.c +C_SOURCES += src/dsss_demod.c +C_SOURCES += src/adc.c +C_SOURCES += src/protocol.c +C_SOURCES += src/serial.c +C_SOURCES += src/con_usart.c +C_SOURCES += src/dma_util.c +C_SOURCES += tinyprintf/tinyprintf.c + C_SOURCES += $(MSPDEBUG_DIR)/drivers/jtaglib.c CMSIS_SOURCES += $(CMSIS_DIR)/CMSIS/DSP/Source/TransformFunctions/arm_rfft_fast_init_f32.c @@ -121,6 +131,7 @@ SYSTEM_FLAGS ?= -nostdlib -ffreestanding -nostartfiles COMMON_CFLAGS += -Imspdebug/util -Imspdebug/drivers -Ilevmarq COMMON_CFLAGS += -I$(CMSIS_DIR_ABS)/CMSIS/DSP/Include -I$(CMSIS_DIR_ABS)/CMSIS/Core/Include CFLAGS += -I$(abspath musl_include_shims) +CFLAGS += -Itinyprintf COMMON_CFLAGS += -I$(BUILDDIR) -Isrc -Itinyaes CFLAGS += -I$(CUBE_DIR)/Drivers/CMSIS/Device/ST/STM32F4xx/Include diff --git a/controller/fw/src/adc.c b/controller/fw/src/adc.c index 74f0aa9..81f5ac1 100644 --- a/controller/fw/src/adc.c +++ b/controller/fw/src/adc.c @@ -1,8 +1,7 @@ +#include #include - -#include -#include +#include #include "adc.h" #include "sr_global.h" @@ -58,9 +57,10 @@ void adc_init() { TIM1->CR2 = (2<CCMR1 = (6<CCER = TIM_CCER_CC1E; - TIM1->PSC = 84-1; /* 1us ticks @ f_APB2=84MHz */ - TIM1->ARR = 1000-1; /* 1ms period */ - TIM1->CCR1 = 500-1; + assert(apb2_timer_speed%1000000 == 0); + TIM1->PSC = 1000-1; + TIM1->ARR = (apb2_timer_speed/1000000)-1; /* 1ms period */ + TIM1->CCR1 = 1; TIM1->BDTR = TIM_BDTR_MOE; /* DEBUG */ @@ -85,14 +85,12 @@ void TIM1_CC_IRQHandler(void) { void DMA2_Stream0_IRQHandler(void) { uint8_t isr = (DMA2->LISR >> DMA_LISR_FEIF0_Pos) & 0x3f; - - GPIOA->ODR ^= 1<<7; - GPIOA->BSRR = 1<<10; + GPIOA->BSRR = 1<<11; if (isr & DMA_LISR_TCIF0) { /* Transfer complete */ /* Check we're done processing the old buffer */ if (adc_fft_buf_ready_idx != -1) { /* FIXME DEBUG */ - GPIOA->BSRR = 1<<10<<16; + GPIOA->BSRR = 1<<11<<16; /* clear all flags */ adc_dma->LIFCR = isr<BSRR = 1<<10<<16; + GPIOA->BSRR = 1<<11<<16; /* clear all flags */ adc_dma->LIFCR = isr< +#include "con_usart.h" + +volatile struct usart_desc con_usart = { + .le_usart = USART1, + .le_usart_irqn = USART1_IRQn, + .tx_dmas = DMA2_Stream7, + .tx_dma_sn = 7, + .tx_dma_ch = 4, + .tx_dma = DMA2, + .tx_dma_irqn = DMA2_Stream7_IRQn, +}; + +void con_usart_init() { + RCC->AHB1ENR |= RCC_AHB1ENR_GPIOAEN | RCC_AHB1ENR_DMA2EN; + RCC->APB2ENR |= RCC_APB2ENR_USART1EN; + + /* GPIO config: A9 (TX), A10 (RX) */ + GPIOA->MODER &= ~GPIO_MODER_MODER9_Msk & ~GPIO_MODER_MODER10_Msk; + GPIOA->MODER |= (2<OSPEEDR &= ~GPIO_OSPEEDR_OSPEED9_Msk & ~GPIO_OSPEEDR_OSPEED10_Msk; + GPIOA->OSPEEDR |= (2<AFR[1] &= ~GPIO_AFRH_AFSEL9_Msk & ~GPIO_AFRH_AFSEL10_Msk; + GPIOA->AFR[1] |= (7< + +#include "dma_util.h" + +uint8_t dma_get_isr_and_clear(DMA_TypeDef *dma, int ch) { + uint8_t isr_val; + switch(ch) { + case 0: + isr_val = dma->LISR & 0x3f; + dma->LIFCR = isr_val; + return isr_val; + case 1: + isr_val = dma->LISR>>6 & 0x3f; + dma->LIFCR = isr_val<<6; + return isr_val; + case 2: + isr_val = dma->LISR>>16 & 0x3f; + dma->LIFCR = isr_val<<16; + return isr_val; + case 3: + isr_val = dma->LISR>>6>>16 & 0x3f; + dma->LIFCR = isr_val<<6<<16; + return isr_val; + case 4: + isr_val = dma->HISR & 0x3f; + dma->HIFCR = isr_val; + return isr_val; + case 5: + isr_val = dma->HISR>>6 & 0x3f; + dma->HIFCR = isr_val<<6; + return isr_val; + case 6: + isr_val = dma->HISR>>16 & 0x3f; + dma->HIFCR = isr_val<<16; + return isr_val; + case 7: + isr_val = dma->HISR>>6>>16 & 0x3f; + dma->HIFCR = isr_val<<6<<16; + return isr_val; + default: + assert(0); + return 0; + } +} + diff --git a/controller/fw/src/dma_util.h b/controller/fw/src/dma_util.h new file mode 100644 index 0000000..5ea2676 --- /dev/null +++ b/controller/fw/src/dma_util.h @@ -0,0 +1,10 @@ +#ifndef __DMA_UTIL_H__ +#define __DMA_UTIL_H__ + +#include + +#include "sr_global.h" + +uint8_t dma_get_isr_and_clear(DMA_TypeDef *dma, int ch); + +#endif /* __DMA_UTIL_H__ */ diff --git a/controller/fw/src/freq_meas.c b/controller/fw/src/freq_meas.c index 020e12d..6b9b54e 100644 --- a/controller/fw/src/freq_meas.c +++ b/controller/fw/src/freq_meas.c @@ -136,7 +136,13 @@ int adc_buf_measure_freq(uint16_t adc_buf[FMEAS_FFT_LEN], float *out) { DEBUG_PRINT("done."); */ - *out = (par[1] + first_bin) * binsize_hz; + float res = (par[1] + first_bin) * binsize_hz; + if (res < 5 || res > 150) { + *out = NAN; + return -1; + } + + *out = res; return 0; } diff --git a/controller/fw/src/main.c b/controller/fw/src/main.c index fa5d50b..b100b16 100644 --- a/controller/fw/src/main.c +++ b/controller/fw/src/main.c @@ -6,15 +6,25 @@ #include #include +#include #include "sr_global.h" #include "adc.h" #include "spi_flash.h" #include "freq_meas.h" #include "dsss_demod.h" +#include "con_usart.h" static struct spi_flash_if spif; +unsigned int sysclk_speed = 0; +unsigned int apb1_speed = 0; +unsigned int apb2_speed = 0; +unsigned int auxclk_speed = 0; +unsigned int apb1_timer_speed = 0; +unsigned int apb2_timer_speed = 0; + +struct leds leds; void __libc_init_array(void) { /* we don't need this. */ } void __assert_func (unused_a const char *file, unused_a int line, unused_a const char *function, unused_a const char *expr) { @@ -24,14 +34,14 @@ void __assert_func (unused_a const char *file, unused_a int line, unused_a const static void clock_setup(void) { - /* 8MHz HSE clock as PLL source. - * - * Divide by 8 -> 1 MHz */ + /* 8MHz HSE clock as PLL source. */ +#define HSE_SPEED 8000000 + /* Divide by 8 -> 1 MHz */ #define PLL_M 8 /* Multiply by 336 -> 336 MHz VCO frequency */ #define PLL_N 336 /* Divide by 4 -> 84 MHz (max freq for our chip) */ -#define PLL_P 4 +#define PLL_P 2 /* Aux clock for USB OTG, SDIO, RNG: divide VCO frequency (336 MHz) by 7 -> 48 MHz (required by USB OTG) */ #define PLL_Q 7 @@ -54,10 +64,10 @@ static void clock_setup(void) /* set AHB prescaler to /1 (CFGR:bits 7:4) */ RCC->CFGR |= (0 << RCC_CFGR_HPRE_Pos); - /* set ABP1 prescaler to 2 -> 42MHz */ - RCC->CFGR |= (4 << RCC_CFGR_PPRE1_Pos); - /* set ABP2 prescaler to 1 -> 84MHz */ - RCC->CFGR |= (0 << RCC_CFGR_PPRE2_Pos); + /* set ABP1 prescaler to 4 -> 42MHz */ + RCC->CFGR |= (5 << RCC_CFGR_PPRE1_Pos); + /* set ABP2 prescaler to 2 -> 84MHz */ + RCC->CFGR |= (4 << RCC_CFGR_PPRE2_Pos); if (RCC->CR & RCC_CR_PLLON) asm volatile ("bkpt"); @@ -79,6 +89,13 @@ static void clock_setup(void) | RCC_PLLCFGR_PLLSRC; /* select HSE as PLL source */ RCC->CR |= RCC_CR_PLLON; + sysclk_speed = HSE_SPEED / PLL_M * PLL_N / PLL_P; + auxclk_speed = HSE_SPEED / PLL_M * PLL_N / PLL_Q; + apb1_speed = sysclk_speed / 4; + apb1_timer_speed = apb1_speed * 2; + apb2_speed = sysclk_speed / 2; + apb2_timer_speed = apb2_speed * 2; + /* Wait for main PLL */ while(!(RCC->CR & RCC_CR_PLLRDY)) ; @@ -170,7 +187,7 @@ int main(void) GPIOC->MODER &= ~GPIO_MODER_MODER9_Msk; GPIOC->MODER |= (2<AFR[1] &= ~GPIO_AFRH_AFSEL9_Msk; - GPIOC->OSPEEDR |= (3<OSPEEDR |= (3<CFGR |= (6<AFR[1] &= ~GPIO_AFRH_AFSEL8_Msk; GPIOA->AFR[1] |= 1<MODER |= (1<MODER |= (1<BSRR = 1<<9; + GPIOA->BSRR = 1<<11; adc_fft_buf_ready_idx = !adc_fft_buf_ready_idx; /* DEBUG */ memcpy(adc_fft_buf[!adc_fft_buf_ready_idx], adc_fft_buf[adc_fft_buf_ready_idx] + FMEAS_FFT_LEN/2, sizeof(adc_fft_buf[0][0]) * FMEAS_FFT_LEN/2); - GPIOA->BSRR = 1<<9<<16; - GPIOA->BSRR = 1<<11; + GPIOA->BSRR = 1<<11<<16; + GPIOA->BSRR = 1<<11; float out; if (adc_buf_measure_freq(adc_fft_buf[adc_fft_buf_ready_idx], &out)) { + con_printf("%012d: measurement error\r\n", freq_sample_ts); measurement_errors++; + GPIOA->BSRR = 1<<7; debug_last_freq = NAN; } else { debug_last_freq = out; - - /* + con_printf("%012d: %2d.%03d Hz\r\n", freq_sample_ts, (int)out, (int)(out * 1000) % 1000); + /* frequency ok led */ + if (48 < out && out < 52) + GPIOA->BSRR = 1<<7<<16; + else + GPIOA->BSRR = 1<<7; + + GPIOA->BSRR = 1<<12; dsss_demod_init(&demod_state); dsss_demod_step(&demod_state, out, freq_sample_ts); - */ + GPIOA->BSRR = 1<<12<<16; } GPIOA->BSRR = 1<<11<<16; @@ -226,38 +254,38 @@ int main(void) } void NMI_Handler(void) { - asm volatile ("bkpt"); + asm volatile ("bkpt #1"); } void HardFault_Handler(void) { - asm volatile ("bkpt"); + asm volatile ("bkpt #2"); } void MemManage_Handler(void) { - asm volatile ("bkpt"); + asm volatile ("bkpt #3"); } void BusFault_Handler(void) { - asm volatile ("bkpt"); + asm volatile ("bkpt #4"); } void UsageFault_Handler(void) { - asm volatile ("bkpt"); + asm volatile ("bkpt #5"); } void SVC_Handler(void) { - asm volatile ("bkpt"); + asm volatile ("bkpt #6"); } void DebugMon_Handler(void) { - asm volatile ("bkpt"); + asm volatile ("bkpt #7"); } void PendSV_Handler(void) { - asm volatile ("bkpt"); + asm volatile ("bkpt #8"); } void SysTick_Handler(void) { - asm volatile ("bkpt"); + asm volatile ("bkpt #9"); } diff --git a/controller/fw/src/protocol.c b/controller/fw/src/protocol.c index 17020bd..bbee5ef 100644 --- a/controller/fw/src/protocol.c +++ b/controller/fw/src/protocol.c @@ -1,7 +1,13 @@ #include "sr_global.h" #include "dsss_demod.h" +#include "con_usart.h" -void handle_dsss_received(uint8_t unused_a data[static TRANSMISSION_SYMBOLS]) { - asm volatile ("bkpt"); +void handle_dsss_received(uint8_t data[static TRANSMISSION_SYMBOLS]) { + con_printf("DSSS data received: "); + for (int i=0; i>1) * (data[i]&1 ? 1 : -1); + con_printf("%3d ", x); + } + con_printf("\r\n"); } diff --git a/controller/fw/src/serial.c b/controller/fw/src/serial.c new file mode 100644 index 0000000..71e1868 --- /dev/null +++ b/controller/fw/src/serial.c @@ -0,0 +1,153 @@ +#include +#include +#include + +#include "dma_util.h" +#include "sr_global.h" +#include "serial.h" + +#include + +static void usart_schedule_dma(volatile struct usart_desc *us); +static void usart_dma_reset(volatile struct usart_desc *us); +static void usart_putc_nonblocking_tpf(void *us, char c); + +void usart_dma_reset(volatile struct usart_desc *us) { + us->tx_buf.xfr_start = -1; + us->tx_buf.xfr_end = 0; + us->tx_buf.wr_pos = 0; + us->tx_buf.wr_idx = 0; + us->tx_buf.xfr_next = 0; + us->tx_buf.wraparound = false; + + for (size_t i=0; itx_buf.chunk_end); i++) + us->tx_buf.chunk_end[i] = -1; +} + +void usart_dma_init(volatile struct usart_desc *us, unsigned int baudrate) { + usart_dma_reset(us); + + /* Configure DMA 1 Channel 2 to handle uart transmission */ + us->tx_dmas->PAR = (uint32_t)&(us->le_usart->DR); + us->tx_dmas->CR = + (us->tx_dma_ch<tx_dma_irqn); + NVIC_SetPriority(us->tx_dma_irqn, 30); + + us->le_usart->CR1 = USART_CR1_TE; + /* Set divider for 115.2kBd @48MHz system clock. */ + us->le_usart->BRR = apb2_speed * 16 / baudrate / 16; /* 250kBd */ + us->le_usart->CR3 |= USART_CR3_DMAT; /* TX DMA enable */ + + /* And... go! */ + us->le_usart->CR1 |= USART_CR1_UE; +} + +void usart_schedule_dma(volatile struct usart_desc *us) { + volatile struct dma_tx_buf *buf = &us->tx_buf; + + ssize_t xfr_start, xfr_end, xfr_len; + if (buf->wraparound) { + buf->wraparound = false; + xfr_start = 0; + xfr_len = buf->xfr_end; + xfr_end = buf->xfr_end; + + } else { + if (buf->chunk_end[buf->xfr_next] == -1) + return; /* Nothing to trasnmit */ + + xfr_start = buf->xfr_end; + xfr_end = buf->chunk_end[buf->xfr_next]; + buf->chunk_end[buf->xfr_next] = -1; + buf->xfr_next = (buf->xfr_next + 1) % ARRAY_LENGTH(buf->chunk_end); + + if (xfr_end > xfr_start) { /* no wraparound */ + xfr_len = xfr_end - xfr_start; + + } else { /* wraparound */ + if (xfr_end != 0) + buf->wraparound = true; + xfr_len = sizeof(us->data) - xfr_start; + } + } + + buf->xfr_start = xfr_start; + buf->xfr_end = xfr_end; + + us->comm_led = 100; + + /* initiate transmission of new buffer */ + us->tx_dmas->M0AR = (uint32_t)(us->data + xfr_start); + us->tx_dmas->NDTR = xfr_len; + us->tx_dmas->CR |= DMA_SxCR_EN; +} + +void usart_dma_stream_irq(volatile struct usart_desc *us) { + uint8_t iflags = dma_get_isr_and_clear(us->tx_dma, us->tx_dma_sn); + + if (iflags & DMA_LISR_TCIF0) { /* Transfer complete */ + us->tx_dmas->CR &= ~DMA_SxCR_EN; + //if (us->tx_buf.wraparound) + usart_schedule_dma(us); + } + + if (iflags & DMA_LISR_FEIF0) + us->tx_errors++; +} + +int usart_putc_nonblocking(volatile struct usart_desc *us, char c) { + volatile struct dma_tx_buf *buf = &us->tx_buf; + + if (buf->wr_pos == buf->xfr_start) { + us->tx_byte_overruns++; + return -EBUSY; + } + + buf->data[buf->wr_pos] = c; + buf->wr_pos = (buf->wr_pos + 1) % sizeof(us->data); + return 0; +} + +void usart_putc_nonblocking_tpf(void *us, char c) { + usart_putc_nonblocking((struct usart_desc *)us, c); +} + + +int usart_send_chunk_nonblocking(volatile struct usart_desc *us, const char *chunk, size_t chunk_len) { + for (size_t i=0; itx_buf.chunk_end[us->tx_buf.wr_idx] != -1) { + us->tx_chunk_overruns++; + return -EBUSY; + } + + us->tx_buf.chunk_end[us->tx_buf.wr_idx] = us->tx_buf.wr_pos; + us->tx_buf.wr_idx = (us->tx_buf.wr_idx + 1) % ARRAY_LENGTH(us->tx_buf.chunk_end); + + if (!(us->tx_dmas->CR & DMA_SxCR_EN)) + usart_schedule_dma(us); + return 0; +} + +int usart_printf(volatile struct usart_desc *us, char *fmt, ...) { + va_list va; + va_start(va, fmt); + tfp_format((void *)us, usart_putc_nonblocking_tpf, fmt, va); + return usart_flush(us); +} + diff --git a/controller/fw/src/serial.h b/controller/fw/src/serial.h new file mode 100644 index 0000000..02f6f5d --- /dev/null +++ b/controller/fw/src/serial.h @@ -0,0 +1,82 @@ +/* + * This file is part of the libusbhost library + * hosted at http://github.com/libusbhost/libusbhost + * + * Copyright (C) 2015 Amir Hammad + * + * + * libusbhost is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this library. If not, see . + * + */ + +#ifndef __SERIAL_H__ +#define __SERIAL_H__ + +#include +#include +#include +#include +#include + +#include "sr_global.h" + +struct dma_tx_buf { + /* The following fields are accessed only from DMA ISR */ + ssize_t xfr_start; /* Start index of running DMA transfer */ + ssize_t xfr_end; /* End index of running DMA transfer plus one */ + bool wraparound; + ssize_t xfr_next; + + /* The following fields are written only from non-interrupt code */ + ssize_t wr_pos; /* Next index to be written */ + ssize_t wr_idx; + ssize_t chunk_end[8]; + +/* Make GCC shut up about the zero-size array member. */ +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wpedantic" + /* Written outside ISR by usart_send_chunk_nonblocking, read via DMA */ + uint8_t data[0]; +#pragma GCC diagnostic pop +}; + +struct usart_desc { + struct dma_tx_buf tx_buf; + uint8_t data[512]; + + uint32_t tx_chunk_overruns, tx_byte_overruns; + uint32_t tx_errors; + + volatile uint8_t rx_buf[32]; + + int comm_led; + + USART_TypeDef *le_usart; + int le_usart_irqn; + DMA_Stream_TypeDef *tx_dmas; + int tx_dma_sn; + int tx_dma_ch; + DMA_TypeDef *tx_dma; + int tx_dma_irqn; +}; + +void usart_dma_init(volatile struct usart_desc *us, unsigned int baudrate); +int usart_send_chunk_nonblocking(volatile struct usart_desc *us, const char *chunk, size_t chunk_len); +int usart_putc_nonblocking(volatile struct usart_desc *us, char c); + +void usart_dma_stream_irq(volatile struct usart_desc *us); +int usart_flush(volatile struct usart_desc *us); +int usart_printf(volatile struct usart_desc *us, char *fmt, ...); + +#endif // __SERIAL_H__ diff --git a/controller/fw/src/sr_global.h b/controller/fw/src/sr_global.h index ac77359..451cce0 100644 --- a/controller/fw/src/sr_global.h +++ b/controller/fw/src/sr_global.h @@ -2,12 +2,25 @@ #define __SR_GLOBAL_H__ #include +#include +#include +#include #define UNUSED(x) ((void) x) #define ARRAY_LENGTH(x) (sizeof(x) / sizeof(x[0])) #define unused_a __attribute__((unused)) +extern unsigned int sysclk_speed; +extern unsigned int apb1_speed; +extern unsigned int apb2_speed; +extern unsigned int auxclk_speed; +extern unsigned int apb1_timer_speed; +extern unsigned int apb2_timer_speed; + +extern struct leds { + unsigned int comm_tx; +} leds; static inline uint16_t htole(uint16_t val) { return val; } void __libc_init_array(void); diff --git a/controller/fw/tinyprintf b/controller/fw/tinyprintf new file mode 160000 index 0000000..2ee3012 --- /dev/null +++ b/controller/fw/tinyprintf @@ -0,0 +1 @@ +Subproject commit 2ee30120ec15e321566b43f83c731d060bb437f5 -- cgit