aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--fw/README.rst20
-rw-r--r--fw/mac.c3
-rw-r--r--fw/mac.h22
-rw-r--r--fw/main.c157
-rwxr-xr-xfw/test.py27
-rw-r--r--fw/transpose.c47
-rw-r--r--fw/transpose.h2
-rw-r--r--hw/chibi/chibi_2024/bom-chibi.odsbin28559 -> 32334 bytes
-rw-r--r--hw/chibi/chibi_2024/chibi_2024.kicad_pcb14
-rwxr-xr-xhw/chibi/chibi_2024/rotator.py22
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__ */
diff --git a/fw/main.c b/fw/main.c
index 50c9cfa..7740426 100644
--- a/fw/main.c
+++ b/fw/main.c
@@ -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();
diff --git a/fw/test.py b/fw/test.py
index 695d9d5..63cb324 100755
--- a/fw/test.py
+++ b/fw/test.py
@@ -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
index 5b05ffe..ce6eec8 100644
--- a/hw/chibi/chibi_2024/bom-chibi.ods
+++ b/hw/chibi/chibi_2024/bom-chibi.ods
Binary files differ
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)))