Files
nixie-driver/firmware/nixitest1/nixitest1.c
CNLohr cc6365c1f4 * Revert pcb so it matches production files.
* Move original thoughts into attic.
* Update comments on firmware
2023-04-22 23:29:10 -04:00

334 lines
9.0 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// Could be defined here, or in the processor defines.
#define SYSTEM_CORE_CLOCK 48000000
#include "ch32v003fun.h"
#include <stdio.h>
#define APB_CLOCK SYSTEM_CORE_CLOCK
uint32_t count;
//#define ENABLE_TUNING
// VDD Adjusted.
#define ABSOLUTE_MAX_ADC_SET 208 // Actually around 188V (0 to 208 maps to 0 to 190V)
// Do not mess with PWM_ values unless you know what you are willing to go down a very deep rabbit hole.
#ifndef ENABLE_TUNING
#define PWM_PERIOD 140
#else
int PWM_PERIOD = 140;
#endif
int PWM_MAXIMUM_DUTY = 48; //This actually gets overwrittenin the first few milliseconds onces a system VDD is read.
#define ERROR_P_TERM 1 // Actually a shift. Normally we would do the opposite to smooth out, but we can realy bang this around! It's OK if we rattle like crazy.
int update_targ_based_on_vdd = 0;
int target_feedback = 0;
int target_feedback_vdd_adjusted = 0;
int lastadc = 0;
int lastvdd = 0;
int fade_enable = 0;
int fade_time0;
int fade_time1;
int fade_disp0;
int fade_disp1;
int fade_place = 0;
static void ApplyOnMask( uint16_t onmask )
{
GPIOD->OUTDR = onmask >> 8;
GPIOC->OUTDR = onmask & 0xff;
}
void ADC1_IRQHandler(void) __attribute__((interrupt));
void ADC1_IRQHandler(void)
{
// This interrupt should happen ~3.5uS on current settings.
int adc = lastadc = ADC1->RDATAR;
int err = target_feedback_vdd_adjusted - adc;
ADC1->STATR &= ~ADC_EOC;
if( err < 0 )
TIM1->CH2CVR = 0;
else
{
err = err << ERROR_P_TERM;
if( err > PWM_MAXIMUM_DUTY ) err = PWM_MAXIMUM_DUTY;
TIM1->CH2CVR = err;
}
int fadepos = (++count) & 0xff;
if( fadepos == 0 )
{
ADC1->CTLR2 |= ADC_JSWSTART;
}
else if( fadepos == 2 )
{
// Use injection channel data to read vref.
// Ballparks:
// 0xF0 / 240 for 5V input << lastvdd
// 0x175 / 373 for 3.3v input << lastvdd
// Tunings (experimentally found)
// Duty/Period
// 100/160 will literally cook the LEDs but can get up to 180V @ 3.3V under load.
// 48/120 is more efficient than 48/96 at 3.3v. (139V)
// 60/112 is pretty efficient, too. (150V)
// 84/140 = 176V (reported, 180 actual) with 8 at 3.3 <<< This is a really nice thing to run at on 3.3V
// The transformer DOES get very warm though.
// Backto 5V.
// 54/140 --> Is what it is is for 5V if period is set to 140.
//
// therefore I want to map, for maximum duty cycle, the following:
// 373 -> 84 // Ratio is 4.440
// 240 -> 56 // Ratio is 4.444
// Wow! That's nice!
lastvdd = ADC1->IDATAR1;
#ifndef ENABLE_TUNING
// IF we aren't enabling tuning, we can update max-on-time with this value.
// There's a neat hack where you can divide by weird decimal divisors by adding and subtracing terms.
// I apply that weird trick here.
// 1÷(1÷41÷641÷1281÷1024) is roughly equal to dividing by 4.43290
// We actually can simplify it for our purposes as: 1÷(1÷41÷641÷128)
//
PWM_MAXIMUM_DUTY = (lastvdd>>2) - (lastvdd>>6) - (lastvdd>>7); // lastvdd / 4.44. For ~5V, this works out to 45, for ~3.3V it works out to ~70.
#endif
update_targ_based_on_vdd = 1;
}
if( fade_enable )
{
if( fadepos < fade_time0 )
ApplyOnMask( fade_disp0 );
else if( fadepos == fade_time0 )
ApplyOnMask( 0 );
else if( fadepos < fade_time1 )
ApplyOnMask( fade_disp1 );
else
ApplyOnMask( 0 );
}
}
static void SetupTimer()
{
// Main inductor is ~5uH.
// Our peak current is ~200mA
// Our target cycle duty is ~1/6
// Our nominal voltage is ~4V
// 4V / .000005H = 800000A/s / 0.2 = 0.00000025 = 250nS, but we are only on for 1/6 of the time., or 1.5uS. Let's set our period to be 64/48 = 652nS.
// GPIO A1 Push-Pull, Auto Function, 50 MHz Drive Current
GPIOA->CFGLR &= ~(0xf<<(4*1));
GPIOA->CFGLR |= (GPIO_Speed_50MHz | GPIO_CNF_OUT_PP_AF)<<(4*1);
// Enable Timer 1
RCC->APB2PRSTR |= RCC_APB2Periph_TIM1;
RCC->APB2PRSTR &= ~RCC_APB2Periph_TIM1;
TIM1->PSC = 0x0000; // Prescalar to 0x0000 (so, 24MHz base clock)
TIM1->ATRLR = PWM_PERIOD;
TIM1->SWEVGR = TIM_UG;
TIM1->CCER = TIM_CC2E | TIM_CC2NP; // CH2 is control for FET.
TIM1->CHCTLR1 = TIM_OC2M_2 | TIM_OC2M_1;
TIM1->CH2CVR = 0; // Actual duty cycle.
// Setup TRGO for ADC. TODO: this should be on update (TIM_MMS_1)
TIM1->CTLR2 = TIM_MMS_1;
// Enable TIM1 outputs
TIM1->BDTR = TIM_MOE;
TIM1->CTLR1 = TIM_CEN;
}
static void SetupADC()
{
// Configure ADC.
// PD4 is analog input chl 7
GPIOD->CFGLR &= ~(0xf<<(4*4)); // CNF = 00: Analog, MODE = 00: Input
// Reset the ADC to init all regs
RCC->APB2PRSTR |= RCC_APB2Periph_ADC1;
RCC->APB2PRSTR &= ~RCC_APB2Periph_ADC1;
// Set up single conversion on chl 7
ADC1->RSQR1 = 0;
ADC1->RSQR2 = 0;
ADC1->RSQR3 = 7; // 0-9 for 8 ext inputs and two internals
ADC1->ISQR = 8 | (3<<20); //Injection group is 8. NOTE: See note in 9.3.12 (ADC_ISQR) of TRM.
// set sampling time for chl 7
ADC1->SAMPTR2 = (4<<(3*7)) | (4<<(3*8)); // 0:7 => 3/9/15/30/43/57/73/241 cycles
// (4 == 43 cycles), (6 = 73 cycles) Note these are alrady /2, so
// setting this to 73 cycles actually makes it wait 256 total cycles
// @ 48MHz.
// turn on ADC and set rule group to sw trig
ADC1->CTLR2 = ADC_ADON | ADC_JEXTTRIG | ADC_JEXTSEL | ADC_EXTTRIG; // 0 = Use TRGO event for Timer 1 to fire ADC rule.
// Reset calibration
ADC1->CTLR2 |= ADC_RSTCAL;
while(ADC1->CTLR2 & ADC_RSTCAL);
// Calibrate
ADC1->CTLR2 |= ADC_CAL;
while(ADC1->CTLR2 & ADC_CAL);
// enable the ADC Conversion Complete IRQ
NVIC_EnableIRQ( ADC_IRQn );
// Enable the End-of-conversion interrupt.
ADC1->CTLR1 = ADC_EOCIE | ADC_SCAN | ADC_JDISCEN;
}
uint16_t GenOnMask( int segmenton )
{
if( segmenton > 0 )
{
segmenton--;
if( segmenton < 8 )
return 1<<segmenton;
else if( segmenton == 8 )
return (1<<2)<<8;
else if( segmenton == 9 )
return (1<<3)<<8;
else if( segmenton == 10 )
return (1<<0)<<8;
else if( segmenton == 11 )
return (1<<7)<<8;
}
return 0;
}
static void HandleCommand( uint32_t dmdword )
{
// ./minichlink -s 0x04 0x01110040
// ./minichlink -g 0x04
// It is a valid status word back from the PC.
int command = dmdword & 0x0f;
switch( command )
{
case 1:
{
int feedback = dmdword>>16;
if( feedback > ABSOLUTE_MAX_ADC_SET ) feedback = ABSOLUTE_MAX_ADC_SET;
target_feedback = feedback;
break;
}
case 2:
{
int segmenton = (dmdword>>16)&0x0f;
// Disable all fading.
fade_enable = 0;
ApplyOnMask( GenOnMask( segmenton ) );
break;
}
case 3:
{
// Configure a fade.
int disp0 = ( dmdword >> 8 ) & 0xf;
int disp1 = ( dmdword >> 12 ) & 0xf;
int time0 = ( dmdword >> 16 ) & 0xff;
int time1 = ( dmdword >> 24 ) & 0xff;
fade_time0 = time0;
fade_time1 = time1;
fade_disp0 = GenOnMask( disp0 );
fade_disp1 = GenOnMask( disp1 );
fade_enable = 1;
break;
}
case 4:
{
#ifdef ENABLE_TUNING
// this is only for tuning.
if( ( dmdword & 0xff00 ) == 0xaa00 )
{
int period = (dmdword>>16)&0xff;
if( period < 20 ) period = 20;
PWM_PERIOD = period;
TIM1->ATRLR = PWM_PERIOD;
int max_duty = (dmdword>>24)&0xff;
if( max_duty > period - 14 ) max_duty = period - 14;
PWM_MAXIMUM_DUTY = max_duty;
}
#endif
break;
}
}
*DMDATA0 = (lastadc << 12) | (lastvdd << 22);
}
int main()
{
SystemInit48HSI();
SetupDebugPrintf();
Delay_Ms( 10 );
// Enable Peripherals
RCC->APB2PCENR |= RCC_APB2Periph_GPIOD | RCC_APB2Periph_GPIOC | RCC_APB2Periph_GPIOA
| RCC_APB2Periph_TIM1 | RCC_APB2Periph_ADC1;
GPIOD->CFGLR =
(GPIO_Speed_10MHz | GPIO_CNF_OUT_PP)<<(4*6) | // GPIO D6 Push-Pull (for debug)
(GPIO_Speed_50MHz | GPIO_CNF_OUT_PP)<<(4*7) | // DIG_AUX
(GPIO_Speed_50MHz | GPIO_CNF_OUT_PP)<<(4*3) | // DIG_9
(GPIO_Speed_50MHz | GPIO_CNF_OUT_PP)<<(4*2) | // DIG_8
(GPIO_Speed_10MHz | GPIO_CNF_IN_FLOATING)<<(4*1) | // Leave PGM pin floating, dont make it an ADC.
(GPIO_Speed_50MHz | GPIO_CNF_OUT_PP)<<(4*0); // DIG_DOT
GPIOC->CFGLR =
(GPIO_Speed_50MHz | GPIO_CNF_OUT_PP)<<(4*0) | // DIG_0
(GPIO_Speed_50MHz | GPIO_CNF_OUT_PP)<<(4*1) | // DIG_1
(GPIO_Speed_50MHz | GPIO_CNF_OUT_PP)<<(4*2) | // DIG_2
(GPIO_Speed_50MHz | GPIO_CNF_OUT_PP)<<(4*3) | // DIG_3
(GPIO_Speed_50MHz | GPIO_CNF_OUT_PP)<<(4*4) | // DIG_4
(GPIO_Speed_50MHz | GPIO_CNF_OUT_PP)<<(4*5) | // DIG_5
(GPIO_Speed_50MHz | GPIO_CNF_OUT_PP)<<(4*6) | // DIG_6
(GPIO_Speed_50MHz | GPIO_CNF_OUT_PP)<<(4*7); // DIG_7
GPIOC->BSHR = 1<<4;
SetupADC();
SetupTimer();
*DMDATA0 = 0;
target_feedback = 0;
while(1)
{
GPIOD->BSHR = 1<<6;
uint32_t dmdword = *DMDATA0;
if( (dmdword & 0xf0) == 0x40 )
{
HandleCommand( dmdword );
}
GPIOD->BSHR = (1<<(16+6));
if( update_targ_based_on_vdd )
{
// target_feedback is in volts. 0..200 maps to the physical device voltage.
// lastvdd = 0xF0 for 5V input.
// lastvdd = 0x175 for 3.3v input.
//
// target_feedback_vdd_adjusted = 408 for ~192V @ 5
// target_feedback_vdd_adjusted = 680 for ~192V @ 3.3
//
// 408 = 192 * 240 / x = (192*240)/408 = 112.941176471
// 680 = 192 * 373 / x = (373*680)/192 = 105.317647059
// Close enough to 128.
//
target_feedback_vdd_adjusted = (target_feedback * lastvdd) >> 7;
update_targ_based_on_vdd = 0;
}
}
}