From cb4a682e557b6e983cade29817ac9cb9e14cf49b Mon Sep 17 00:00:00 2001 From: jaseg Date: Tue, 23 Apr 2019 22:46:38 +0900 Subject: driver: Add ac bc framing and a bunch of comments --- driver/driver.sch | 2 +- driver_fw/main.c | 100 +++++++++++++++++++++++++++++++++++------------------- 2 files changed, 67 insertions(+), 35 deletions(-) diff --git a/driver/driver.sch b/driver/driver.sch index 1d3261b..2aeb0b8 100644 --- a/driver/driver.sch +++ b/driver/driver.sch @@ -2745,7 +2745,7 @@ F 3 "~" H 17400 2950 50 0001 C CNN 1 0 0 -1 $EndComp Text Notes 7600 10050 0 50 ~ 0 -TODO:\n* RS485 drv fp is wide, should be narrow SOIC-8\n* Heatsink holes are plated-through, shouln't be\n* Add thermal reliefs in upper logic ground plane\n* GND/VCC input labels are swapped\n* White label field for MAC\n* Remove optoisolators\n* Add series resistor to RS485 GND\n* Add decoupling cap next to mosfet temp sensor\n* Add INA226 filter network (see datasheet p.14)\n* Add voltage test points\n* Add pulldowns plus filter caps for CTRL_A, CTRL_B +TODO:\n* RS485 drv fp is wide, should be narrow SOIC-8\n* Heatsink holes are plated-through, shouln't be\n* Add thermal reliefs in upper logic ground plane\n* GND/VCC input labels are swapped\n* White label field for MAC\n* Remove optoisolators\n* Add series resistor to RS485 GND\n* Add decoupling cap next to mosfet temp sensor\n* Add INA226 filter network (see datasheet p.14)\n* Add voltage test points\n* Add pulldowns plus filter caps for CTRL_A, CTRL_B\n* Label J4/J5 something like "in" and "out" NoConn ~ 14600 2800 Text Notes 14100 9650 0 50 ~ 0 I2C addr=010'0111 diff --git a/driver_fw/main.c b/driver_fw/main.c index d64bca4..ef01bfb 100644 --- a/driver_fw/main.c +++ b/driver_fw/main.c @@ -75,8 +75,6 @@ enum STATUS_LEDS { static void set_status_leds(uint8_t val) { /* Reset strobe. Will be set in systick handler */ GPIOA->BRR = 1<<4; - //for (int i=0; i<100; i++) - // asm volatile ("nop"); /* Workaround for *nasty* hardware behavior: If SPI data width is configured as 8 bit but DR is written as 16 * bit, SPI actually sends 16 clock cycles. Thus, we have to make sure the compiler emits a 8-bit write here. * Thanks, TI! */ @@ -102,7 +100,12 @@ int main(void) { NVIC_EnableIRQ(SysTick_IRQn); NVIC_SetPriority(SysTick_IRQn, 3<<5); - /* GPIO setup */ + /* GPIO setup + * + * Note: since we have quite a bunch of pin constraints we can't actually use complementary outputs for the + * complementary MOSFET driver control signals (CTRL_A & CTRL_B). Instead, we use two totally separate output + * channels (1 & 4) and emulate the dead-time generator in software. + */ GPIOA->MODER |= (3<MODER |= (2<CR1 = 0; /* Disable ARR preload (double-buffering) */ TIM3->PSC = 48-1; /* Prescaler 48 -> f=1MHz/T=1us */ TIM3->DIER = TIM_DIER_UIE; /* Enable update (overflow) interrupt */ + + /* Set both CCRs to 0xffff to ensure both bridge halves are turned off after we enable the timer. If we don't do + * this, we will cause a very low-ohm short circuit that at best will trigger our power supply's short-circuit or + * over-current protection right after power-on but at worst will detonate the mosfets. */ TIM3->CCR1 = 0xffff; TIM3->CCR4 = 0xffff; - TIM3->CCMR1 = 6<CCMR2 = 6<CCER = TIM_CCER_CC1E | TIM_CCER_CC1P | TIM_CCER_CC4E | TIM_CCER_CC4P; /* Confiugre CH1 to complementary outputs */ - TIM3->BDTR = TIM_BDTR_MOE; /* Enable MOE on next update event, i.e. on initial timer load. */ + /* Configure output compare unit 1 to PWM mode 1, enable CCR1 preload */ + TIM3->CCMR1 = 6<CCMR2 = 6<CCER = TIM_CCER_CC1E | TIM_CCER_CC1P | TIM_CCER_CC4E | TIM_CCER_CC4P; + /* Enable MOE on next update event, i.e. on initial timer load. */ + TIM3->BDTR = TIM_BDTR_MOE; + /* Enable timer */ TIM3->CR1 |= TIM_CR1_CEN; - TIM3->ARR = 800-1; /* Set f=2.5kHz/T=0.4ms */ + /* Set f=2.5kHz/T=0.4ms */ + TIM3->ARR = 800-1; + /* Initialize AC protocol state machine in TIM3 ISR with the AC protocol comma */ xfr_8b10b_encode_reset(&txstate.st); txstate.current_symbol = xfr_8b10b_encode(&txstate.st, K28_1) | 1<<10; + /* The timer is still stopped. Start it by manually triggering an update event. */ TIM3->EGR |= TIM_EGR_UG; - lcd_write_str(0, 0, "8seg driver"); - lcd_write_str(0, 1, "initialized \xbc"); - NVIC_EnableIRQ(TIM3_IRQn); NVIC_SetPriority(TIM3_IRQn, 2<<4); + lcd_write_str(0, 0, "8seg driver"); + lcd_write_str(0, 1, "initialized \xbc"); while (42) { - if (sys_flag_1Hz) { + if (sys_flag_1Hz) { /* Update display every second */ sys_flag_1Hz = 0; char buf[17]; - int temp = mcp9801_read_mdegC(); int deg = temp/1000; int frac = (temp%1000)/100; mini_snprintf(buf, sizeof(buf), "Temp: %d.%01d\xdf""C" LCD_FILL, deg, frac); lcd_write_str(0, 0, buf); - mini_snprintf(buf, sizeof(buf), "I=%dmA U=%dmV" LCD_FILL, ina226_read_i()*INA226_I_LSB_uA/1000, ina226_read_v()*INA226_VB_LSB_uV/1000); lcd_write_str(0, 1, buf); - } } } @@ -228,33 +234,58 @@ static int flipbits10(int in) { } +#define BACKCHANNEL_INTERVAL 10 + void TIM3_IRQHandler() { static int txpos = -1; static unsigned int tx_start_tick = 0; - static uint8_t txbuf[3] = {0x01, 0x05, 0x01}; + static uint8_t txbuf[2] = {0x04, 0x05}; + static int backchannel_counter = 0; TIM3->SR &= ~TIM_SR_UIF; int sym = txstate.current_symbol; int bit = sym&1; sym >>= 1; if (sym == 1) { /* last bit shifted out */ - if (txpos == -1) - sym = xfr_8b10b_encode(&txstate.st, -K28_1); - else - sym = xfr_8b10b_encode(&txstate.st, txbuf[txpos]); - - txpos++; - if (txpos >= sizeof(txbuf)/sizeof(txbuf[0])) { - frame_duration_us = (sys_time_tick - tx_start_tick) * 10 * 1000; - tx_start_tick = sys_time_tick; - txpos = -1; + + /* Insert the backchannel sync control symbol K.28.2 once every BACKCHANNEL_INTERVAL symbols independent from AC + * forward channel protocol framing. The backchannel sync control symbol is different from the AC protocol comma + * K.28.1. The backchannel sync control symbol is not a comma, so the 8b10b receiver cannot lock on it. The only + * practical implication of this is that after powerup or other loss of sync, the receiver will only lock on the + * backchannel sync once the first AC forward-channel protocol frame has been begun. Since all backchannel comm + * is triggered by the driver anyway this should not be noticeable in practice. + */ + backchannel_counter++; + if (backchannel_counter == BACKCHANNEL_INTERVAL) { + backchannel_counter = 0; + sym = xfr_8b10b_encode(&txstate.st, -K28_2); /* TODO factor out backchannel comma into constant */ + + } else { + + if (txpos == -1) + sym = xfr_8b10b_encode(&txstate.st, -K28_1); /* TODO factor out comma into constant */ + else + sym = xfr_8b10b_encode(&txstate.st, txbuf[txpos]); + + txpos++; + if (txpos >= sizeof(txbuf)/sizeof(txbuf[0])) { + frame_duration_us = (sys_time_tick - tx_start_tick) * 10 * 1000; + tx_start_tick = sys_time_tick; + txpos = -1; + } } + /* Append one '1' bit as an end-of-symbol marker for this state machine. This bit is not actually transmitted. */ sym = flipbits10(sym) | 1<<10; } txstate.current_symbol = sym; + /* FIXME factor out into header, or even make configurable */ #define DEAD_TIME 100 + /* Set both CCRs to values for opposing polarities. The dead time is always inserted at the beginning of the timer + * cycle due to the way the capture/compare unit PWM machinery works. By setting the CCR to 0xffff we make sure the + * output is never turned on, since 0xffff is larger than the ARR/counter top value. + */ TIM3->CCR1 = bit ? 0xffff : DEAD_TIME; TIM3->CCR4 = bit ? DEAD_TIME : 0xffff; } @@ -285,7 +316,7 @@ void SysTick_Handler(void) { /* This is a hack. We could use the SPI interrupt here if that didn't fire at the start instead of end of transmission.... -.- */ if (sys_time_tick&1) { - uint8_t val = (sys_time_ms >= 500) ? STATUS_LED_OPERATION : 0; + uint8_t val = (sys_time_ms >= 300) ? STATUS_LED_OPERATION : 0; if (comm_led_ctr) { comm_led_ctr--; @@ -299,6 +330,7 @@ void SysTick_Handler(void) { set_status_leds(val); } else { + /* Reset strobe for the status LED shift register. Reset in set_status_leds. */ GPIOA->BSRR = 1<<4; } } -- cgit