Add some documentation

This commit is contained in:
jaseg 2018-01-05 11:39:20 +01:00
parent a70ad99b36
commit 31fc78d0e0
10 changed files with 178 additions and 136 deletions

20
fw/README.rst Normal file
View file

@ -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

3
fw/mac.c Normal file
View file

@ -0,0 +1,3 @@
#include "mac.h"
uint32_t device_mac = 0xdeadbeef;

22
fw/mac.h Normal file
View 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__ */

157
fw/main.c
View file

@ -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();

View file

@ -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')

View file

@ -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));

View file

@ -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];

Binary file not shown.

View file

@ -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)

22
hw/chibi/chibi_2024/rotator.py Executable file
View file

@ -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)))