Move to new serial protocol.
This code is tested.
This commit is contained in:
parent
545abc6b6f
commit
ddbb807e54
9 changed files with 478 additions and 117 deletions
|
|
@ -3,12 +3,14 @@ CMSIS_PATH ?= STM32Cube/Drivers/CMSIS
|
|||
CMSIS_DEV_PATH ?= $(CMSIS_PATH)/Device/ST/STM32F0xx
|
||||
HAL_PATH ?= STM32Cube/Drivers/STM32F0xx_HAL_Driver
|
||||
|
||||
MAC_ADDR ?= 0xDEBE10BB
|
||||
|
||||
CC := arm-none-eabi-gcc
|
||||
OBJCOPY := arm-none-eabi-objcopy
|
||||
OBJDUMP := arm-none-eabi-objdump
|
||||
SIZE := arm-none-eabi-size
|
||||
|
||||
CFLAGS = -Wall -g -std=gnu11 -Os
|
||||
CFLAGS = -Wall -Wpedantic -Wstrict-aliasing -g -std=gnu11 -Os
|
||||
CFLAGS += -mlittle-endian -mcpu=cortex-m0 -march=armv6-m -mthumb
|
||||
#CFLAGS += -ffunction-sections -fdata-sections -Wl,--gc-sections
|
||||
CFLAGS += -Wl,-Map=main.map
|
||||
|
|
@ -16,7 +18,7 @@ CFLAGS += -Wl,-Map=main.map
|
|||
# Technically we're using an STM32F030F4, but apart from the TSSOP20 package that one is largely identical to the
|
||||
# STM32F030*6 and there is no separate device header provided for it, so we're faking a *6 device here. This is
|
||||
# even documented in stm32f0xx.h. Thanks ST!
|
||||
CFLAGS += -DSTM32F030x6 -DHSE_VALUE=25000000
|
||||
CFLAGS += -DSTM32F030x6 -DHSE_VALUE=25000000 -DMAC_ADDR=$(MAC_ADDR)
|
||||
|
||||
CFLAGS += -Tstm32_flash.ld
|
||||
CFLAGS += -I$(CMSIS_DEV_PATH)/Include -I$(CMSIS_PATH)/Include -I$(HAL_PATH)/Inc -Iconfig
|
||||
|
|
@ -31,7 +33,7 @@ all: main.elf
|
|||
cmsis_exports.c: $(CMSIS_DEV_PATH)/Include/stm32f030x6.h $(CMSIS_PATH)/Include/core_cm0.h
|
||||
python3 gen_cmsis_exports.py $^ > $@
|
||||
|
||||
sources.tar.xz: main.c Makefile
|
||||
sources.tar.xz: main.c serial.h global.h serial.c adc.h adc.c Makefile
|
||||
tar -cf $@ $^
|
||||
|
||||
# don't ask...
|
||||
|
|
@ -41,7 +43,7 @@ sources.tar.xz.zip: sources.tar.xz
|
|||
sources.c: sources.tar.xz.zip
|
||||
xxd -i $< | head -n -1 | sed 's/=/__attribute__((section(".source_tarball"))) =/' > $@
|
||||
|
||||
main.elf: main.c startup_stm32f030x6.s system_stm32f0xx.c $(HAL_PATH)/Src/stm32f0xx_ll_utils.c cmsis_exports.c sources.o
|
||||
main.elf: main.c mac.c adc.c serial.c startup_stm32f030x6.s system_stm32f0xx.c $(HAL_PATH)/Src/stm32f0xx_ll_utils.c cmsis_exports.c
|
||||
$(CC) $(CFLAGS) -o $@ $^
|
||||
$(OBJCOPY) -O ihex $@ $(@:.elf=.hex)
|
||||
$(OBJCOPY) -O binary $@ $(@:.elf=.bin)
|
||||
|
|
|
|||
101
firmware/adc.c
Normal file
101
firmware/adc.c
Normal file
|
|
@ -0,0 +1,101 @@
|
|||
/* Megumin LED display firmware
|
||||
* Copyright (C) 2018 Sebastian Götte <code@jaseg.net>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program 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 General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <stm32f0xx.h>
|
||||
#include <stdint.h>
|
||||
#include <system_stm32f0xx.h>
|
||||
#include <stm32f0xx_ll_utils.h>
|
||||
#include <math.h>
|
||||
|
||||
#include "adc.h"
|
||||
|
||||
volatile int16_t adc_vcc_mv = 0;
|
||||
volatile int16_t adc_temp_celsius = 0;
|
||||
|
||||
static volatile uint16_t adc_buf[2];
|
||||
|
||||
void adc_init(void) {
|
||||
/* The ADC is used for temperature measurement. To compute the temperature from an ADC reading of the internal
|
||||
* temperature sensor, the supply voltage must also be measured. Thus we are using two channels.
|
||||
*
|
||||
* The ADC is triggered by compare channel 4 of timer 1. The trigger is set to falling edge to trigger on compare
|
||||
* match, not overflow.
|
||||
*/
|
||||
ADC1->CFGR1 = ADC_CFGR1_DMAEN | ADC_CFGR1_DMACFG | (2<<ADC_CFGR1_EXTEN_Pos) | (1<<ADC_CFGR1_EXTSEL_Pos);
|
||||
/* Clock from PCLK/4 instead of the internal exclusive high-speed RC oscillator. */
|
||||
ADC1->CFGR2 = (2<<ADC_CFGR2_CKMODE_Pos);
|
||||
/* Use the slowest available sample rate */
|
||||
ADC1->SMPR = (7<<ADC_SMPR_SMP_Pos);
|
||||
/* Internal VCC and temperature sensor channels */
|
||||
ADC1->CHSELR = ADC_CHSELR_CHSEL16 | ADC_CHSELR_CHSEL17;
|
||||
/* Enable internal voltage reference and temperature sensor */
|
||||
ADC->CCR = ADC_CCR_TSEN | ADC_CCR_VREFEN;
|
||||
/* Perform ADC calibration */
|
||||
ADC1->CR |= ADC_CR_ADCAL;
|
||||
while (ADC1->CR & ADC_CR_ADCAL)
|
||||
;
|
||||
/* Enable ADC */
|
||||
ADC1->CR |= ADC_CR_ADEN;
|
||||
ADC1->CR |= ADC_CR_ADSTART;
|
||||
|
||||
/* Configure DMA 1 Channel 1 to get rid of all the data */
|
||||
DMA1_Channel1->CPAR = (unsigned int)&ADC1->DR;
|
||||
DMA1_Channel1->CMAR = (unsigned int)&adc_buf;
|
||||
DMA1_Channel1->CNDTR = sizeof(adc_buf)/sizeof(adc_buf[0]);
|
||||
DMA1_Channel1->CCR = (0<<DMA_CCR_PL_Pos);
|
||||
DMA1_Channel1->CCR |=
|
||||
DMA_CCR_CIRC /* circular mode so we can leave it running indefinitely */
|
||||
| (1<<DMA_CCR_MSIZE_Pos) /* 16 bit */
|
||||
| (1<<DMA_CCR_PSIZE_Pos) /* 16 bit */
|
||||
| DMA_CCR_MINC
|
||||
| DMA_CCR_TCIE; /* Enable transfer complete interrupt. */
|
||||
DMA1_Channel1->CCR |= DMA_CCR_EN; /* Enable channel */
|
||||
|
||||
/* triggered on transfer completion. We use this to process the ADC data */
|
||||
NVIC_EnableIRQ(DMA1_Channel1_IRQn);
|
||||
NVIC_SetPriority(DMA1_Channel1_IRQn, 3);
|
||||
}
|
||||
|
||||
void DMA1_Channel1_IRQHandler(void) {
|
||||
/* This interrupt takes either 1.2us or 13us. It can be pre-empted by the more timing-critical UART and LED timer
|
||||
* interrupts. */
|
||||
static int count = 0; /* oversampling accumulator sample count */
|
||||
static uint32_t adc_aggregate[2] = {0, 0}; /* oversampling accumulator */
|
||||
|
||||
/* Clear the interrupt flag */
|
||||
DMA1->IFCR |= DMA_IFCR_CGIF1;
|
||||
|
||||
adc_aggregate[0] += adc_buf[0];
|
||||
adc_aggregate[1] += adc_buf[1];
|
||||
|
||||
if (++count == (1<<ADC_OVERSAMPLING)) {
|
||||
/* This has been copied from the code examples to section 12.9 ADC>"Temperature sensor and internal reference
|
||||
* voltage" in the reference manual with the extension that we actually measure the supply voltage instead of
|
||||
* hardcoding it. This is not strictly necessary since we're running off a bored little LDO but it's free and
|
||||
* the current supply voltage is a nice health value.
|
||||
*/
|
||||
adc_vcc_mv = (3300 * VREFINT_CAL)/(adc_aggregate[0]>>ADC_OVERSAMPLING);
|
||||
int32_t temperature = (((uint32_t)TS_CAL1) - ((adc_aggregate[1]>>ADC_OVERSAMPLING) * adc_vcc_mv / 3300)) * 1000;
|
||||
temperature = (temperature/5336) + 30;
|
||||
adc_temp_celsius = temperature;
|
||||
|
||||
count = 0;
|
||||
adc_aggregate[0] = 0;
|
||||
adc_aggregate[1] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
30
firmware/adc.h
Normal file
30
firmware/adc.h
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
/* Megumin LED display firmware
|
||||
* Copyright (C) 2018 Sebastian Götte <code@jaseg.net>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program 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 General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef __ADC_H__
|
||||
#define __ADC_H__
|
||||
|
||||
#include "global.h"
|
||||
|
||||
#define ADC_OVERSAMPLING 8
|
||||
|
||||
extern volatile int16_t adc_vcc_mv;
|
||||
extern volatile int16_t adc_temp_celsius;
|
||||
|
||||
void adc_init(void);
|
||||
|
||||
#endif/*__ADC_H__*/
|
||||
13
firmware/global.h
Normal file
13
firmware/global.h
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
#ifndef __GLOBAL_H__
|
||||
#define __GLOBAL_H__
|
||||
|
||||
#define FIRMWARE_VERSION 2
|
||||
#define HARDWARE_VERSION 2
|
||||
|
||||
#define TS_CAL1 (*(uint16_t *)0x1FFFF7B8)
|
||||
#define VREFINT_CAL (*(uint16_t *)0x1FFFF7BA)
|
||||
|
||||
extern uint32_t sys_time;
|
||||
extern uint32_t sys_time_seconds;
|
||||
|
||||
#endif/*__GLOBAL_H__*/
|
||||
3
firmware/mac.c
Normal file
3
firmware/mac.c
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
#include "mac.h"
|
||||
|
||||
uint32_t device_mac = MAC_ADDR;
|
||||
22
firmware/mac.h
Normal file
22
firmware/mac.h
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
#ifndef __MAC_H__
|
||||
#define __MAC_H__
|
||||
|
||||
#include <unistd.h>
|
||||
|
||||
/* Device MAC address.
|
||||
*
|
||||
* 32 bits might seem a little short for a device MAC, but at 20 bus nodes the probablility of a collision is about 1 in
|
||||
* 10 million. Check for yourself using the python code below.
|
||||
*
|
||||
* #!/usr/bin/env python3
|
||||
* from operator import mul
|
||||
* from functools import reduce
|
||||
* m = 32
|
||||
* n = 20
|
||||
* print(reduce(mul, [2**m-i for i in range(n)]) / ((2**m)**n))
|
||||
* # -> 0.9999999557621786
|
||||
*/
|
||||
|
||||
extern uint32_t device_mac;
|
||||
|
||||
#endif /* __MAC_H__ */
|
||||
135
firmware/main.c
135
firmware/main.c
|
|
@ -34,6 +34,10 @@
|
|||
#include <stm32f0xx_ll_utils.h>
|
||||
#include <math.h>
|
||||
|
||||
#include "global.h"
|
||||
#include "serial.h"
|
||||
#include "adc.h"
|
||||
|
||||
/* Bit count of this device. Note that to change this you will also have to adapt the per-bit timer period lookup table
|
||||
* below.
|
||||
*/
|
||||
|
|
@ -45,18 +49,6 @@
|
|||
|
||||
void do_transpose(void);
|
||||
|
||||
/* Right-aligned integer raw channel brightness values like so:
|
||||
*
|
||||
* bit index 31 ... 16 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
|
||||
* | (MSB) serial data put *here* (LSB) |
|
||||
* |<-utterly ignored->| |<-----------------MAX_BITS------------------>|
|
||||
* |<----------------NBITS---------------->| |<>|--ignored
|
||||
* | (MSB) brightness data (LSB) | |<>|--ignored
|
||||
*/
|
||||
uint32_t brightness[32] = {
|
||||
0x23
|
||||
};
|
||||
|
||||
/* Bit-golfed modulation data generated from the above values by the main loop, ready to be sent out to the shift
|
||||
* registers.
|
||||
*/
|
||||
|
|
@ -67,15 +59,12 @@ uint32_t sys_time = 0;
|
|||
uint32_t sys_time_seconds = 0;
|
||||
|
||||
int main(void) {
|
||||
/* Get all the good clocks and PLLs on this thing up and running. We're
|
||||
* running from an external 25MHz crystal, which we're first dividing
|
||||
* down by 5 to get 5 MHz, then PLL'ing up by 6 to get 30 MHz as our
|
||||
* main system clock.
|
||||
/* Get all the good clocks and PLLs on this thing up and running. We're running from an external 25MHz crystal,
|
||||
* which we're first dividing down by 5 to get 5 MHz, then PLL'ing up by 6 to get 30 MHz as our main system clock.
|
||||
*
|
||||
* The busses are all run directly from these 30 MHz because why not.
|
||||
*
|
||||
* Be careful in mucking around with this code since you can kind of
|
||||
* semi-brick the chip if you do it wrong.
|
||||
* Be careful in mucking around with this code since you can kind of semi-brick the chip if you do it wrong.
|
||||
*/
|
||||
RCC->CR |= RCC_CR_HSEON;
|
||||
while (!(RCC->CR&RCC_CR_HSERDY));
|
||||
|
|
@ -160,8 +149,7 @@ int main(void) {
|
|||
TIM1->ARR = 1;
|
||||
TIM1->CR1 |= TIM_CR1_CEN;
|
||||
|
||||
/* Configure Timer 1 update (overrun) interrupt on NVIC.
|
||||
* Used only for update (overrun) for strobe timing. */
|
||||
/* Configure Timer 1 update (overrun) interrupt on NVIC. Used only for update (overrun) for strobe timing. */
|
||||
NVIC_EnableIRQ(TIM1_BRK_UP_TRG_COM_IRQn);
|
||||
NVIC_SetPriority(TIM1_BRK_UP_TRG_COM_IRQn, 1);
|
||||
|
||||
|
|
@ -174,8 +162,7 @@ int main(void) {
|
|||
TIM3->PSC = 30;
|
||||
TIM3->ARR = 1000;
|
||||
|
||||
/* Configure Timer 3 update (overrun) interrupt on NVIC.
|
||||
* Used only for update (overrun) for USART timeout handling. */
|
||||
/* Configure Timer 3 update (overrun) interrupt on NVIC. Used only for update (overrun) for USART timeout handling. */
|
||||
NVIC_EnableIRQ(TIM3_IRQn);
|
||||
NVIC_SetPriority(TIM3_IRQn, 2);
|
||||
|
||||
|
|
@ -204,16 +191,20 @@ int main(void) {
|
|||
NVIC_EnableIRQ(USART1_IRQn);
|
||||
NVIC_SetPriority(USART1_IRQn, 2);
|
||||
|
||||
adc_init();
|
||||
|
||||
/* Idly loop around, occassionally disfiguring some integers. */
|
||||
while (42) {
|
||||
/* Debug output on LED. */
|
||||
GPIOA->ODR ^= GPIO_ODR_6;
|
||||
|
||||
/* Bit-mangle the integer brightness data to produce raw modulation data */
|
||||
do_transpose();
|
||||
/* Wait a moment */
|
||||
for (int k=0; k<10000; k++)
|
||||
asm volatile("nop");
|
||||
if (framebuf_out_of_sync != 0) {
|
||||
/* This logic is very slightly racy, but that should not matter since we're updating the frame buffer often
|
||||
* enough so you don't notice one miss every billion frames. */
|
||||
framebuf_out_of_sync = 0;
|
||||
/* Bit-mangle the integer framebuf data to produce raw modulation data */
|
||||
do_transpose();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -224,7 +215,7 @@ void do_transpose(void) {
|
|||
uint32_t mask = 1<<i<<(MAX_BITS-NBITS); /* Bit mask for this bit value. */
|
||||
uint32_t bv = 0; /* accumulator thing */
|
||||
for (uint32_t j=0; j<32; j++) {
|
||||
if (brightness[j] & mask)
|
||||
if (rx_buf.set_fb_rq.framebuf[j] & mask)
|
||||
bv |= 1<<j;
|
||||
}
|
||||
brightness_by_bit[i] = bv;
|
||||
|
|
@ -299,9 +290,9 @@ void TIM1_BRK_UP_TRG_COM_IRQHandler(void) {
|
|||
uint32_t val = brightness_by_bit[idx];
|
||||
|
||||
/* Shift out the current period's data. The shift register clear and strobe lines are handled by the timers
|
||||
* capture/compare channel 3 complementary outputs. The dead-time generator is used to sequence the clear and strobe
|
||||
* edges one after another. Since there may be small variations in IRQ service latency it is critical to allow for
|
||||
* some leeway between the end of this data transmission and strobe and clear. */
|
||||
* capture/compare channel 3 complementary outputs. The dead-time generator is used to sequence the clear and
|
||||
* strobe edges one after another. Since there may be small variations in IRQ service latency it is critical to
|
||||
* allow for some leeway between the end of this data transmission and strobe and clear. */
|
||||
SPI1->DR = (val&0xffff);
|
||||
while (SPI1->SR & SPI_SR_BSY);
|
||||
SPI1->DR = (val>>16);
|
||||
|
|
@ -362,88 +353,6 @@ void TIM3_IRQHandler(void) {
|
|||
rxpos = 0;
|
||||
}
|
||||
|
||||
/* This macro defines the lowest channel number of this board on the serial command bus. On a shared bus with several
|
||||
* boards, you would generally assign increasing USART_CHANNEL_OFFX values to each one (0, 8, 16, 24, ...).
|
||||
*
|
||||
* Example: Let USART_CHANNEL_OFFX be 8.
|
||||
*
|
||||
* /--Command channel number received in command packet (packet.set_step.step)
|
||||
* | /--USART_OFFX
|
||||
* | | /--4 raw channels per logical channel (step): R, G, B, W
|
||||
* | | | /--Raw channel offset for R, G, B, W
|
||||
* | | | | /--Resulting raw channels for R, G, B, W data received in command packet
|
||||
* | | | | |
|
||||
* v v v v v
|
||||
*
|
||||
* (8 - 8) * 4 + {0, 1, 2, 3} = {0, 1, 2, 3}
|
||||
* (9 - 8) * 4 + {0, 1, 2, 3}
|
||||
* (10 - 8) * 4 + {0, 1, 2, 3}
|
||||
* (11 - 8) * 4 + {0, 1, 2, 3}
|
||||
* (12 - 8) * 4 + {0, 1, 2, 3}
|
||||
* (13 - 8) * 4 + {0, 1, 2, 3}
|
||||
* (14 - 8) * 4 + {0, 1, 2, 3}
|
||||
* (15 - 8) * 4 + {0, 1, 2, 3}
|
||||
*/
|
||||
#ifndef USART_CHANNEL_OFFX
|
||||
#define USART_CHANNEL_OFFX 0
|
||||
#endif//USART_CHANNEL_OFFX
|
||||
|
||||
#define NCHANNELS (sizeof(brightness)/sizeof(brightness[0]))
|
||||
void USART1_IRQHandler() {
|
||||
static union packet rxbuf;
|
||||
|
||||
int isr = USART1->ISR;
|
||||
USART1->RQR |= USART_RQR_RXFRQ;
|
||||
/* Overrun detected? */
|
||||
if (isr & USART_ISR_ORE) {
|
||||
USART1->ICR = USART_ICR_ORECF; /* Acknowledge overrun */
|
||||
//asm("bkpt"); /* uncomment for debug */
|
||||
return;
|
||||
}
|
||||
|
||||
if (!(isr & USART_ISR_RXNE)) {
|
||||
//asm("bkpt"); /* uncomment for debug */
|
||||
return;
|
||||
}
|
||||
|
||||
/* Store received data */
|
||||
uint8_t data = USART1->RDR;
|
||||
rxbuf.data[rxpos] = data;
|
||||
rxpos++;
|
||||
|
||||
/* If we finished receiving a packet, deal with it. */
|
||||
if (rxpos == sizeof(union packet)) {
|
||||
/* Check packet header */
|
||||
if (rxbuf.set_step.cmd == 0x23 &&
|
||||
/* bounds-check received channel number. This allows several driver boards to share one common serial bus */
|
||||
rxbuf.set_step.step >= USART_CHANNEL_OFFX &&
|
||||
rxbuf.set_step.step < USART_CHANNEL_OFFX+NCHANNELS) {
|
||||
|
||||
/* Calculate raw channel brightness value base address for logical channel */
|
||||
uint32_t *out = &brightness[(rxbuf.set_step.step - USART_CHANNEL_OFFX)*4];
|
||||
|
||||
/* Correct RGBW raw channel ordering per logical channel according to SUB-D pinout used.
|
||||
*
|
||||
* (matti) (treppe)
|
||||
* weiß blau
|
||||
* rot weiß
|
||||
* grün rot
|
||||
* blau grün
|
||||
*/
|
||||
out[1] = rxbuf.set_step.rgbw[0];
|
||||
out[2] = rxbuf.set_step.rgbw[1];
|
||||
out[3] = rxbuf.set_step.rgbw[2];
|
||||
out[0] = rxbuf.set_step.rgbw[3];
|
||||
}
|
||||
/* Reset receive data counter */
|
||||
rxpos = 0;
|
||||
}
|
||||
|
||||
/* Reset usart timeout handler */
|
||||
TIM3->CNT = 0;
|
||||
TIM3->CR1 |= TIM_CR1_CEN;
|
||||
}
|
||||
|
||||
/* Misc IRQ handlers */
|
||||
void NMI_Handler(void) {
|
||||
}
|
||||
|
|
|
|||
214
firmware/serial.c
Normal file
214
firmware/serial.c
Normal file
|
|
@ -0,0 +1,214 @@
|
|||
/* Megumin LED display firmware
|
||||
* Copyright (C) 2018 Sebastian Götte <code@jaseg.net>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program 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 General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <stm32f0xx.h>
|
||||
#include <stdint.h>
|
||||
#include <system_stm32f0xx.h>
|
||||
#include <stm32f0xx_ll_utils.h>
|
||||
#include <math.h>
|
||||
|
||||
#include "serial.h"
|
||||
#include "adc.h"
|
||||
#include "mac.h"
|
||||
|
||||
unsigned int uart_overruns = 0;
|
||||
unsigned int frame_overruns = 0;
|
||||
unsigned int invalid_frames = 0;
|
||||
|
||||
static union tx_buf_union tx_buf;
|
||||
volatile union rx_buf_union rx_buf;
|
||||
volatile uint8_t framebuf_out_of_sync;
|
||||
|
||||
void tx_char(uint8_t c) {
|
||||
while (!(USART1->ISR & USART_ISR_TC));
|
||||
USART1->TDR = c;
|
||||
}
|
||||
|
||||
void send_frame_formatted(uint8_t *buf, int len) {
|
||||
uint8_t *p=buf, *q=buf, *end=buf+len;
|
||||
do {
|
||||
while (*q && q!=end)
|
||||
q++;
|
||||
tx_char(q-p+1);
|
||||
while (*p && p!=end)
|
||||
tx_char(*p++);
|
||||
p++, q++;
|
||||
} while (p < end);
|
||||
tx_char('\0');
|
||||
}
|
||||
|
||||
void send_status_reply(void) {
|
||||
tx_buf.desc_reply.firmware_version = FIRMWARE_VERSION;
|
||||
tx_buf.desc_reply.hardware_version = HARDWARE_VERSION;
|
||||
tx_buf.desc_reply.uptime_s = sys_time_seconds;
|
||||
tx_buf.desc_reply.vcc_mv = adc_vcc_mv;
|
||||
tx_buf.desc_reply.temp_celsius = adc_temp_celsius;
|
||||
tx_buf.desc_reply.uart_overruns = uart_overruns;
|
||||
tx_buf.desc_reply.frame_overruns = frame_overruns;
|
||||
tx_buf.desc_reply.invalid_frames = invalid_frames;
|
||||
send_frame_formatted(tx_buf.byte_data, sizeof(tx_buf.desc_reply));
|
||||
}
|
||||
|
||||
/* This is the higher-level protocol handler for the serial protocol. It gets passed the number of data bytes in this
|
||||
* frame (which may be zero) and returns a pointer to the buffer where the next frame should be stored.
|
||||
*/
|
||||
volatile uint8_t *packet_received(int len) {
|
||||
static enum {
|
||||
PROT_ADDRESSED = 0,
|
||||
PROT_EXPECT_FRAME_SECOND_HALF = 1,
|
||||
PROT_IGNORE = 2,
|
||||
} protocol_state = PROT_IGNORE;
|
||||
/* Use mac frames as delimiters to synchronize this protocol layer */
|
||||
if (len == 0) { /* Discovery packet */
|
||||
if (sys_time < 100 && sys_time_seconds == 0) { /* Only respond during the first 100ms after boot */
|
||||
send_frame_formatted((uint8_t*)&device_mac, sizeof(device_mac));
|
||||
}
|
||||
|
||||
} else if (len == 1) { /* Command packet */
|
||||
if (protocol_state == PROT_ADDRESSED) {
|
||||
switch (rx_buf.byte_data[0]) {
|
||||
case 0x01:
|
||||
send_status_reply();
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
invalid_frames++;
|
||||
}
|
||||
protocol_state = PROT_IGNORE;
|
||||
|
||||
} else if (len == 4) { /* Address packet */
|
||||
if (rx_buf.mac_data == device_mac) { /* we are addressed */
|
||||
protocol_state = PROT_ADDRESSED; /* start listening for frame buffer data */
|
||||
} else { /* we are not addressed */
|
||||
protocol_state = PROT_IGNORE; /* ignore packet */
|
||||
}
|
||||
|
||||
} else if (len == sizeof(rx_buf.set_fb_rq.framebuf)) {
|
||||
if (protocol_state == PROT_ADDRESSED) { /* First of two half-framebuffer data frames */
|
||||
/* Kick off buffer transfer. This triggers the main loop to copy data out of the receive buffer and paste it
|
||||
* properly formatted into the frame buffer. */
|
||||
if (framebuf_out_of_sync == 0) {
|
||||
framebuf_out_of_sync = 1;
|
||||
} else {
|
||||
/* FIXME An overrun happend. What should we do? */
|
||||
frame_overruns++;
|
||||
}
|
||||
|
||||
/* Go to "hang mode" until next zero-length packet. */
|
||||
protocol_state = PROT_IGNORE;
|
||||
}
|
||||
|
||||
} else {
|
||||
/* FIXME An invalid packet has been received. What should we do? */
|
||||
invalid_frames++;
|
||||
protocol_state = PROT_IGNORE; /* go into "hang mode" until next zero-length packet */
|
||||
}
|
||||
|
||||
/* By default, return rx_buf.byte_data . This means if an invalid protocol state is reached ("hang mode"), the next
|
||||
* frame is still written to rx_buf. This is not a problem since whatever garbage is written at that point will be
|
||||
* overwritten before the next buffer transfer. */
|
||||
return rx_buf.byte_data;
|
||||
}
|
||||
|
||||
void USART1_IRQHandler(void) {
|
||||
/* Since a large amount of data will be shoved down this UART interface we need a more reliable and more efficient
|
||||
* way of framing than just waiting between transmissions.
|
||||
*
|
||||
* This code uses "Consistent Overhead Byte Stuffing" (COBS). For details, see its Wikipedia page[0] or the proper
|
||||
* scientific paper[1] published on it. Roughly, it works like this:
|
||||
*
|
||||
* * A frame is at most 254 bytes in length.
|
||||
* * The null byte 0x00 acts as a frame delimiter. There is no null bytes inside frames.
|
||||
* * Every frame starts with an "overhead" byte indicating the number of non-null payload bytes until the next null
|
||||
* byte in the payload, **plus one**. This means this byte can never be zero.
|
||||
* * Every null byte in the payload is replaced by *its* distance to *its* next null byte as above.
|
||||
*
|
||||
* This means, at any point the receiver can efficiently be synchronized on the next frame boundary by simply
|
||||
* waiting for a null byte. After that, only a simple state machine is necessary to strip the overhead byte and a
|
||||
* counter to then count skip intervals.
|
||||
*
|
||||
* Here is Wikipedia's table of example values:
|
||||
*
|
||||
* Unencoded data Encoded with COBS
|
||||
* 00 01 01 00
|
||||
* 00 00 01 01 01 00
|
||||
* 11 22 00 33 03 11 22 02 33 00
|
||||
* 11 22 33 44 05 11 22 33 44 00
|
||||
* 11 00 00 00 02 11 01 01 01 00
|
||||
* 01 02 ...FE FF 01 02 ...FE 00
|
||||
*
|
||||
* [0] https://en.wikipedia.org/wiki/Consistent_Overhead_Byte_Stuffing
|
||||
* [1] Cheshire, Stuart; Baker, Mary (1999). "Consistent Overhead Byte Stuffing"
|
||||
* IEEE/ACM Transactions on Networking. doi:10.1109/90.769765
|
||||
* http://www.stuartcheshire.org/papers/COBSforToN.pdf
|
||||
*/
|
||||
|
||||
/* This pointer stores where we write data. The higher-level protocol logic decides on a frame-by-frame-basis where
|
||||
* the next frame's data will be stored. */
|
||||
static volatile uint8_t *writep = rx_buf.byte_data;
|
||||
/* Index inside the current frame payload */
|
||||
static int rxpos = 0;
|
||||
/* COBS state machine. This implementation might be a little too complicated, but it works well enough and I find it
|
||||
* reasonably easy to understand. */
|
||||
static enum {
|
||||
COBS_WAIT_SYNC = 0, /* Synchronize with frame */
|
||||
COBS_WAIT_START = 1, /* Await overhead byte */
|
||||
COBS_RUNNING = 2 /* Process payload */
|
||||
} cobs_state = 0;
|
||||
/* COBS skip counter. During payload processing this contains the remaining non-null payload bytes */
|
||||
static int cobs_count = 0;
|
||||
|
||||
if (USART1->ISR & USART_ISR_ORE) { /* Overrun handling */
|
||||
uart_overruns++;
|
||||
/* Reset and re-synchronize. Retry next frame. */
|
||||
rxpos = 0;
|
||||
cobs_state = COBS_WAIT_SYNC;
|
||||
/* Clear interrupt flag */
|
||||
USART1->ICR = USART_ICR_ORECF;
|
||||
|
||||
} else { /* Data received */
|
||||
uint8_t data = USART1->RDR; /* This automatically acknowledges the IRQ */
|
||||
|
||||
if (data == 0x00) { /* End-of-packet */
|
||||
/* Process higher protocol layers on this packet. */
|
||||
writep = packet_received(rxpos);
|
||||
|
||||
/* Reset for next packet. */
|
||||
cobs_state = COBS_WAIT_START;
|
||||
rxpos = 0;
|
||||
|
||||
} else { /* non-null byte */
|
||||
if (cobs_state == COBS_WAIT_SYNC) { /* Wait for null byte */
|
||||
/* ignore data */
|
||||
|
||||
} else if (cobs_state == COBS_WAIT_START) { /* Overhead byte */
|
||||
cobs_count = data;
|
||||
cobs_state = COBS_RUNNING;
|
||||
|
||||
} else { /* Payload byte */
|
||||
if (--cobs_count == 0) { /* Skip byte */
|
||||
cobs_count = data;
|
||||
data = 0;
|
||||
}
|
||||
|
||||
/* Write processed payload byte to current receive buffer */
|
||||
writep[rxpos++] = data;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
67
firmware/serial.h
Normal file
67
firmware/serial.h
Normal file
|
|
@ -0,0 +1,67 @@
|
|||
/* Megumin LED display firmware
|
||||
* Copyright (C) 2018 Sebastian Götte <code@jaseg.net>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program 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 General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef __SERIAL_H__
|
||||
#define __SERIAL_H__
|
||||
|
||||
#include "global.h"
|
||||
|
||||
/* High-level stuff */
|
||||
void serial_init(void);
|
||||
void send_status_reply(void);
|
||||
|
||||
/* Internal low-level stuff */
|
||||
void tx_char(uint8_t c);
|
||||
void send_frame_formatted(uint8_t *buf, int len);
|
||||
volatile uint8_t *packet_received(int len);
|
||||
|
||||
/* Error counters for debugging */
|
||||
extern unsigned int uart_overruns;
|
||||
extern unsigned int frame_overruns;
|
||||
extern unsigned int invalid_frames;
|
||||
|
||||
union tx_buf_union {
|
||||
struct __attribute__((packed)) {
|
||||
uint8_t firmware_version,
|
||||
hardware_version;
|
||||
uint32_t uptime_s,
|
||||
uart_overruns,
|
||||
frame_overruns,
|
||||
invalid_frames;
|
||||
int16_t vcc_mv,
|
||||
temp_celsius;
|
||||
} desc_reply;
|
||||
uint8_t byte_data[0];
|
||||
};
|
||||
|
||||
union rx_buf_union {
|
||||
/* Right-aligned integer raw channel brightness values like so:
|
||||
*
|
||||
* bit index 31 ... 16 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
|
||||
* | (MSB) serial data put *here* (LSB) |
|
||||
* |<-utterly ignored->| |<-----------------MAX_BITS------------------>|
|
||||
* |<----------------NBITS---------------->| |<>|--ignored
|
||||
* | (MSB) brightness data (LSB) | |<>|--ignored
|
||||
*/
|
||||
struct __attribute__((packed)) { uint32_t framebuf[32]; uint8_t end[0]; } set_fb_rq;
|
||||
uint8_t byte_data[0];
|
||||
uint32_t mac_data;
|
||||
};
|
||||
extern volatile union rx_buf_union rx_buf;
|
||||
extern volatile uint8_t framebuf_out_of_sync;
|
||||
|
||||
#endif/*__SERIAL_H__*/
|
||||
Loading…
Add table
Add a link
Reference in a new issue