diff options
-rw-r--r-- | fw/README.rst | 20 | ||||
-rw-r--r-- | fw/mac.c | 3 | ||||
-rw-r--r-- | fw/mac.h | 22 | ||||
-rw-r--r-- | fw/main.c | 157 | ||||
-rwxr-xr-x | fw/test.py | 27 | ||||
-rw-r--r-- | fw/transpose.c | 47 | ||||
-rw-r--r-- | fw/transpose.h | 2 | ||||
-rw-r--r-- | hw/chibi/chibi_2024/bom-chibi.ods | bin | 28559 -> 32334 bytes | |||
-rw-r--r-- | hw/chibi/chibi_2024/chibi_2024.kicad_pcb | 14 | ||||
-rwxr-xr-x | hw/chibi/chibi_2024/rotator.py | 22 |
10 files changed, 178 insertions, 136 deletions
diff --git a/fw/README.rst b/fw/README.rst new file mode 100644 index 0000000..1e81c46 --- /dev/null +++ b/fw/README.rst @@ -0,0 +1,20 @@ +Note on the LED modulation +========================== + +To control LED brightness, this project uses a modulation technique known as +"Binary Code Modulation" (BCM) or "Bit Angle Modulation" (BAM). The base idea is +to clock out all outputs in parallel bit-by-bit, then modulate this with a +precisely timed output enable signal. In contrast to PWM this allows almost +arbitrarily high channel counts and configurable modulation resolution at low +CPU overhead and high frame rates. In this project that is needed due to the +large number of channels (32) and the medium oversampling rate (1:8). + +A good article explaining BCM can be found on batsocks.co.uk_ and a nice video +explaining has been published by mikeselectricstuff_. A possible optimization +for even smoother brightness fading (probably mostly in unmultiplexed +applications) has been discussed in the forums at picbasic.co.uk_. + +.. _mikeselectricstuff: https://www.youtube.com/watch?v=Sq8SxVDO5wE +.. _`picbasic.co.uk`: http://www.picbasic.co.uk/forum/showthread.php?t=7393 +.. _batsocks.co.uk: http://www.batsocks.co.uk/readme/art_bcm_1.htm + diff --git a/fw/mac.c b/fw/mac.c new file mode 100644 index 0000000..4d05c9a --- /dev/null +++ b/fw/mac.c @@ -0,0 +1,3 @@ +#include "mac.h" + +uint32_t device_mac = 0xdeadbeef; diff --git a/fw/mac.h b/fw/mac.h new file mode 100644 index 0000000..26aaff6 --- /dev/null +++ b/fw/mac.h @@ -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__ */ @@ -1,5 +1,4 @@ - - +/* Workaround for sub-par ST libraries */ #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wstrict-aliasing" #include <stm32f0xx.h> @@ -17,72 +16,9 @@ #include "transpose.h" #include "mac.h" -/* - * Part number: STM32F030F4C6 - */ - -typedef struct -{ - volatile uint32_t CTRL; /*!< Offset: 0x000 (R/W) Control Register */ - volatile uint32_t CYCCNT; /*!< Offset: 0x004 (R/W) Cycle Count Register */ - volatile uint32_t CPICNT; /*!< Offset: 0x008 (R/W) CPI Count Register */ - volatile uint32_t EXCCNT; /*!< Offset: 0x00C (R/W) Exception Overhead Count Register */ - volatile uint32_t SLEEPCNT; /*!< Offset: 0x010 (R/W) Sleep Count Register */ - volatile uint32_t LSUCNT; /*!< Offset: 0x014 (R/W) LSU Count Register */ - volatile uint32_t FOLDCNT; /*!< Offset: 0x018 (R/W) Folded-instruction Count Register */ - volatile uint32_t PCSR; /*!< Offset: 0x01C (R/ ) Program Counter Sample Register */ - volatile uint32_t COMP0; /*!< Offset: 0x020 (R/W) Comparator Register 0 */ - volatile uint32_t MASK0; /*!< Offset: 0x024 (R/W) Mask Register 0 */ - volatile uint32_t FUNCTION0; /*!< Offset: 0x028 (R/W) Function Register 0 */ - uint32_t RESERVED0[1]; - volatile uint32_t COMP1; /*!< Offset: 0x030 (R/W) Comparator Register 1 */ - volatile uint32_t MASK1; /*!< Offset: 0x034 (R/W) Mask Register 1 */ - volatile uint32_t FUNCTION1; /*!< Offset: 0x038 (R/W) Function Register 1 */ - uint32_t RESERVED1[1]; -} DWT_Type; - -#define DWT ((DWT_Type *)0xE0001000) -DWT_Type *dwt = DWT; - -void dwt0_configure(volatile void *addr) { - dwt->COMP0 = (uint32_t)addr; - dwt->MASK0 = 0; -} - -enum DWT_Function { - DWT_R = 5, - DWT_W = 6, - DWT_RW = 7 -}; - -void dwt0_enable(enum DWT_Function function) { - dwt->FUNCTION0 = function; -} - -/* Wait for about 0.2us */ -static void tick(void) { - /* 1 */ /* 2 */ /* 3 */ /* 4 */ /* 5 */ - /* 5 */ __asm__("nop"); __asm__("nop"); __asm__("nop"); __asm__("nop"); __asm__("nop"); - /* 10 */ __asm__("nop"); __asm__("nop"); __asm__("nop"); __asm__("nop"); __asm__("nop"); -} - -void spi_send(int data) { - SPI1->DR = data; - while (SPI1->SR & SPI_SR_BSY); -} - -void strobe_aux(void) { - GPIOA->BSRR = GPIO_BSRR_BS_10; - tick(); - GPIOA->BSRR = GPIO_BSRR_BR_10; -} - -void strobe_leds(void) { - GPIOA->BSRR = GPIO_BSRR_BS_9; - tick(); - GPIOA->BSRR = GPIO_BSRR_BR_9; -} +/* Microcontroller part number: STM32F030F4C6 */ +/* Things used for module status reporting. */ #define FIRMWARE_VERSION 2 #define HARDWARE_VERSION 4 @@ -97,6 +33,18 @@ volatile uint16_t adc_buf[2]; volatile unsigned int sys_time = 0; volatile unsigned int sys_time_seconds = 0; +/* Error counters for debugging */ +static unsigned int uart_overruns = 0; +static unsigned int frame_overruns = 0; +static unsigned int invalid_frames = 0; + +/* Status LED control */ +#define LED_STRETCHING_MS 50 +static volatile int error_led_timeout = 0; +static volatile int comm_led_timeout = 0; +static volatile int id_led_timeout = 0; + +/* Modulation data */ volatile struct framebuf fb[2] = {0}; volatile struct framebuf *read_fb=fb+0, *write_fb=fb+1; volatile int led_state = 0; @@ -108,45 +56,13 @@ volatile union { uint32_t mac_data; } rx_buf; +/* Auxiliary shift register values */ #define LED_COMM 0x0001 #define LED_ERROR 0x0002 #define LED_ID 0x0004 #define SR_ILED_HIGH 0x0080 #define SR_ILED_LOW 0x0040 -unsigned int stk_start(void) { - return SysTick->VAL; -} - -unsigned int stk_end(unsigned int start) { - return (start - SysTick->VAL) & 0xffffff; -} - -unsigned int stk_microseconds(void) { - return sys_time*1000 + (1000 - (SysTick->VAL / (SystemCoreClock/1000000))); -} - -void cfg_spi1() { - /* Configure SPI controller */ - SPI1->I2SCFGR = 0; - SPI1->CR2 &= ~SPI_CR2_DS_Msk; - SPI1->CR2 &= ~SPI_CR2_DS_Msk; - SPI1->CR2 |= LL_SPI_DATAWIDTH_16BIT; - - /* Baud rate PCLK/4 -> 12.5MHz */ - SPI1->CR1 = - SPI_CR1_BIDIMODE - | SPI_CR1_BIDIOE - | SPI_CR1_SSM - | SPI_CR1_SSI - | SPI_CR1_SPE - | (1<<SPI_CR1_BR_Pos) - | SPI_CR1_MSTR - | SPI_CR1_CPOL - | SPI_CR1_CPHA; - /* FIXME maybe try w/o BIDI */ -} - /* This is a lookup table mapping segments to present a standard segment order on the UART interface. This is converted * into an internal representation once on startup in main(). The data type must be at least uint16. */ uint32_t segment_map[8] = {5, 7, 6, 4, 1, 3, 0, 2}; @@ -287,6 +203,7 @@ void cfg_timers_led() { } void TIM1_CC_IRQHandler() { + //static int last_frame_time = 0; /* This handler takes about 1.5us */ GPIOA->BSRR = GPIO_BSRR_BS_0; // Debug @@ -299,11 +216,6 @@ void TIM1_CC_IRQHandler() { if (active_segment == NSEGMENTS) { active_segment = 0; - /* FIXME remove this? - int time = stk_microseconds(); - frame_duration_us = time - last_frame_time; - last_frame_time = time; - */ /* Frame buffer swap */ if (fb_op == FB_UPDATE) { volatile struct framebuf *tmp = read_fb; @@ -319,6 +231,8 @@ void TIM1_CC_IRQHandler() { uint32_t aux_reg = (read_fb->brightness ? SR_ILED_HIGH : SR_ILED_LOW) | (led_state<<1); SPI1->DR = aux_reg | segment_map[active_segment]; + /* TODO: Measure frame rate for status report */ + /* Clear interrupt flag */ TIM1->SR &= ~TIM_SR_CC1IF_Msk; @@ -355,6 +269,27 @@ void TIM3_IRQHandler() { GPIOA->BSRR = GPIO_BSRR_BR_0; // Debug } +void cfg_spi1() { + /* Configure SPI controller */ + SPI1->I2SCFGR = 0; + SPI1->CR2 &= ~SPI_CR2_DS_Msk; + SPI1->CR2 &= ~SPI_CR2_DS_Msk; + SPI1->CR2 |= LL_SPI_DATAWIDTH_16BIT; + + /* Baud rate PCLK/4 -> 12.5MHz */ + SPI1->CR1 = + SPI_CR1_BIDIMODE + | SPI_CR1_BIDIOE + | SPI_CR1_SSM + | SPI_CR1_SSI + | SPI_CR1_SPE + | (1<<SPI_CR1_BR_Pos) + | SPI_CR1_MSTR + | SPI_CR1_CPOL + | SPI_CR1_CPHA; + /* FIXME maybe try w/o BIDI */ +} + void uart_config(void) { USART1->CR1 = /* 8-bit -> M1, M0 clear */ /* RTOIE clear */ @@ -383,11 +318,6 @@ void uart_config(void) { NVIC_SetPriority(USART1_IRQn, 1); } -#define LED_STRETCHING_MS 50 -static volatile int error_led_timeout = 0; -static volatile int comm_led_timeout = 0; -static volatile int id_led_timeout = 0; - void trigger_error_led() { error_led_timeout = LED_STRETCHING_MS; } @@ -400,11 +330,6 @@ void trigger_id_led() { id_led_timeout = LED_STRETCHING_MS; } -/* Error counters for debugging */ -static unsigned int uart_overruns = 0; -static unsigned int frame_overruns = 0; -static unsigned int invalid_frames = 0; - void tx_char(uint8_t c) { while (!(USART1->ISR & USART_ISR_TC)); USART1->TDR = c; @@ -761,7 +686,7 @@ int main(void) { /* Clear frame buffer */ read_fb->brightness = 1; for (int i=0; i<sizeof(read_fb->data)/sizeof(uint32_t); i++) { - read_fb->data[i] = 0xffffffff; /* FIXME DEBUG 0x00000000; */ + read_fb->data[i] = 0xffffffff; /* FIXME this is a debug value. Should be 0x00000000; */ } cfg_timers_led(); @@ -62,6 +62,7 @@ def send_framebuffer(ser, mac, frame): def discover_macs(ser, count=20): found_macs = [] while True: + ser.flushInput() ser.write(b'\0') frame = receive_frame(ser) if len(frame) == 4: @@ -77,6 +78,8 @@ def discover_macs(ser, count=20): def parse_status_frame(frame): print('frame len:', len(frame)) + if not frame: + return None ( firmware_version, hardware_version, digit_rows, @@ -112,24 +115,26 @@ if __name__ == '__main__': frame_len = 4*8*8 black, red = [0]*frame_len, [255]*frame_len frames = \ - [black]*10 +\ - [red]*10 +\ - [[i]*frame_len for i in range(256)] +\ - [[(i + (d//8)*8) % 256*8 for d in range(frame_len)] for i in range(256)] + [black] + #[[0]*i + [255]*(256-i) for i in range(257)] + #[[(i + d)%256 for d in range(frame_len)] for i in range(256)] + #[black]*10 +\ + #[red]*10 +\ + #[[i]*frame_len for i in range(256)] +\ + #[[(i + (d//8)*8) % 256*8 for d in range(frame_len)] for i in range(256)] #frames = [red, black]*5 #frames = [ x for l in [[([0]*i+[255]+[0]*(7-i))*32]*2 for i in range(8)] for x in l ] - found_macs = discover_macs(ser, 1) + found_macs = [0xdeadbeef] #discover_macs(ser, 1) mac, = found_macs import pprint while True: - pprint.pprint(fetch_status(ser, mac)) - time.sleep(0.02) - - while True: + try: + pprint.pprint(fetch_status(ser, mac)) + except e: + print(e) for i, frame in enumerate(frames): send_framebuffer(ser, mac, frame) - print('sending', i, len(frame)) - time.sleep(0.02) + time.sleep(0.1) # to produce framing errors: ser.write(b'\02a\0') diff --git a/fw/transpose.c b/fw/transpose.c index a6c7e71..7af3e8e 100644 --- a/fw/transpose.c +++ b/fw/transpose.c @@ -4,14 +4,52 @@ #include "transpose.h" +/* This file contains conversion routines that pre-format the brightness data + * received from the UART such that the interrupt service routines only need to + * push it out the SPI without further computation, making these ISRs nice and + * tight. + * + * To understand this code note the multiplexing scheme used on the board. The + * circuit contains two MBI5026 shift-register LED drivers of 16 channels each + * cascaded. Effectively this behaves like a 32-channel LED driver fed data + * serially. Each output is connected to a single digit's COM pin. All digit's + * segment anode pins are connected together in a large bus fed by one of the + * two auxiliary shift registers. + * + * The firmware is selecting each segment in turn with a full BCM cycle for each + * segment before the next one is selected. + */ + +/* This array maps the 32 adressable digits on a board to the 32 bits shifted + * out to the LED drivers. */ uint8_t digit_map[33] = { 0, 1, 2, 3, 28,29,30,31, 4, 5, 6, 7, 24,25,26,27, 8, 9,10,11, 20,21,22,23, 12,13,14,15, 16,17,18,19 }; -void transpose_data(volatile uint8_t *rx_buf, volatile struct framebuf *out_fb) { + +/* This function produces a 10-bit output buffer ready for the modulation ISRs + * from 10-bit input data encoded for the UART. For the precise data format, see + * transpose.h. + * + * On the UART side we have digits in the order defined in digit_map, 10 byte + * per digit. The first 8 bytes are the 8 LSBs of each segments brightness value + * in the order [A, B, C, D, E, F, G, DECIMAL_POINT]. The two MSBs to make each + * value 10-bit are bit-packed into the remaining two bytes in big-endian byte + * order starting from DP. + * + * On the display frame buffer side, data is stored in multiplexing order: + * first digits, then time/bits and finally segments. So for each segment you + * have a large buffer containing all the bit periods and digits, and for each + * bit period you have 32 bits for all 32 digits. + */ +void transpose_data(volatile uint8_t *rx_buf, volatile struct framebuf *out_fb) +{ + /* FIXME this can probably be removed. */ memset((uint8_t *)out_fb, 0, sizeof(*out_fb)); + + /* 8 MSB loop */ struct data_format *rxp = (struct data_format *)rx_buf; for (int bit=0; bit<8; bit++) { /* bits */ uint32_t bit_mask = 1U<<bit; @@ -27,6 +65,8 @@ void transpose_data(volatile uint8_t *rx_buf, volatile struct framebuf *out_fb) *outp = acc; } } + + /* 2 packed LSB loop */ for (int bit=0; bit<2; bit++) { /* bits */ volatile uint32_t *frame_data = out_fb->frame[bit].data; for (int seg=0; seg<8; seg++) { /* segments */ @@ -40,10 +80,13 @@ void transpose_data(volatile uint8_t *rx_buf, volatile struct framebuf *out_fb) frame_data[seg] = acc; } } + + /* Global analog brightness value */ out_fb->brightness = ((volatile struct framebuf *)rx_buf)->brightness; } - +/* This function was used for testing transpose_data. It does precisely the + * reverse operation. */ void untranspose_data(struct framebuf *fb, uint8_t *txbuf) { memset(txbuf, 0, sizeof(*fb)); diff --git a/fw/transpose.h b/fw/transpose.h index 389071c..467b12d 100644 --- a/fw/transpose.h +++ b/fw/transpose.h @@ -15,6 +15,7 @@ enum { FRAME_SIZE_WORDS = NROWS*NCOLS*NSEGMENTS/32, }; +/* Framebuffer data format pre-formatted for BCM ISRs */ struct framebuf { /* Multiplexing order: first Digits, then Time/bits, last Segments */ union { @@ -28,6 +29,7 @@ struct framebuf { uint8_t brightness; /* 0 or 1; controls global brighntess control */ }; +/* Efficiently-packed UART data format */ struct data_format { union { uint8_t high[8]; diff --git a/hw/chibi/chibi_2024/bom-chibi.ods b/hw/chibi/chibi_2024/bom-chibi.ods Binary files differindex 5b05ffe..ce6eec8 100644 --- a/hw/chibi/chibi_2024/bom-chibi.ods +++ b/hw/chibi/chibi_2024/bom-chibi.ods diff --git a/hw/chibi/chibi_2024/chibi_2024.kicad_pcb b/hw/chibi/chibi_2024/chibi_2024.kicad_pcb index d96f401..25b3796 100644 --- a/hw/chibi/chibi_2024/chibi_2024.kicad_pcb +++ b/hw/chibi/chibi_2024/chibi_2024.kicad_pcb @@ -14,16 +14,16 @@ (page A4) (layers - (0 F.Cu signal) + (0 F.Cu signal hide) (31 B.Cu signal) (32 B.Adhes user hide) (33 F.Adhes user hide) - (34 B.Paste user hide) - (35 F.Paste user) - (36 B.SilkS user hide) + (34 B.Paste user) + (35 F.Paste user hide) + (36 B.SilkS user) (37 F.SilkS user hide) - (38 B.Mask user hide) - (39 F.Mask user) + (38 B.Mask user) + (39 F.Mask user hide) (40 Dwgs.User user hide) (41 Cmts.User user hide) (42 Eco1.User user hide) @@ -71,7 +71,7 @@ (pad_drill 0.9) (pad_to_mask_clearance 0.2) (aux_axis_origin 0 0) - (visible_elements FFFEFF7F) + (visible_elements FFFEBF7F) (pcbplotparams (layerselection 0x010fc_80000001) (usegerberextensions false) diff --git a/hw/chibi/chibi_2024/rotator.py b/hw/chibi/chibi_2024/rotator.py new file mode 100755 index 0000000..cf45253 --- /dev/null +++ b/hw/chibi/chibi_2024/rotator.py @@ -0,0 +1,22 @@ +#!/usr/bin/env python3 + +import re + +with open('chibi_2024.kicad_pcb') as f: + lines = f.readlines() + +def mangled(lines): + for l in lines: + if 'fp_text' in l and not l.strip().endswith('hide'): + at_re = '\((at\s\S+\s\S+)(\s\S+)?\)' + match = re.search(at_re, l) + if not match: + raise Exception() + rot = int(match.group(2) or '0') + rot = (rot+180)%360 + yield re.sub(at_re, r'(\1 {})'.format(rot), l) + else: + yield l + +with open('out.kicad_pcb', 'w') as f: + f.write(''.join(mangled(lines))) |