cnlohr / cnixxi

My experiment with nixie tubes.
MIT License
124 stars 10 forks source link

Reason of using ADC injection channel for Vref #14

Open monte-monte opened 5 months ago

monte-monte commented 5 months ago

Hi. I'm reading your code, trying to adapt it for my needs. Can you explain why do you read Vref in a separate injection group? What difference does it make compared to measuring both channels in the rule group? Is it to read Vref on separate timer trigger after HVMON channel is converted? Also I am reading the reference manual and there is this note:

Note: 1. When converting a rule group or injection group in intermittent mode, the conversion sequence does
not automatically start from the beginning when it ends. When all subgroups have been converted, the next
trigger event starts the conversion of the first subgroup.
2. You cannot use auto-injection (JAUTO=1) and intermittent mode at the same time.

So does it mean that Vref is being read 2 times in a row? Also this line: https://github.com/cnlohr/cnixxi/blob/d3db1aef0dade1c4579eeff3b5d0570b02ada58f/firmware/nixitest1/nixitest1.c#L397 contradicts the second paragraph of the note above.

Sorry if it's annoying, I am just confused "a little" :)

monte-monte commented 5 months ago

Ok, so sometimes it's better to think a bit longer before asking questions:) I guess you just want to have two registers with two voltages updated per every ADC interrupt, that's why injection mode.

But the second question stands: do ADC_JDISCEN and ADC_JEOCIE work together? And if they do, does injection group get sampled twice?

monte-monte commented 5 months ago

So, I have made a simple boost converter, with an inductor instead of flyback transformer to drive 12-15V leds from 5V. I played with tuning values but couldn't get rid of visible brightness fluctuations that are visible when led is running at low power. I concluded, that it was not ripple from PWM but the PID truing to compensate. First I increased sample time for feedback reading and decreased for VDD. Then I removed ADC_JDISCEN from ADC setup and now I don't have fluctuations even with simple version of your PID (that has only the proportional part).

monte-monte commented 5 months ago

I used ADC DMA example from ch32v003fun to adapt this code to use DMA. Now SetupADC looks like this:

        // 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;

    // ADCCLK = 12 MHz => RCC_ADCPRE divide by 4
    RCC->CFGR0 &= ~RCC_ADCPRE;  // Clear out the bis in case they were set
    RCC->CFGR0 |= RCC_ADCPRE_DIV4;  // set it to 010xx for /4.

    // Set up single conversion on chl 7
    ADC1->RSQR1 = (ADC_NUMCHLS-1) << 20;
    ADC1->RSQR2 = 0;
    ADC1->RSQR3 = (7<<(5*0)) | (8<<(5*1));

    // Sampling time for channels. Careful: This has PID tuning implications.
    // Note that with 3 and 3,the full loop (and injection) runs at 138kHz.
    ADC1->SAMPTR2 = (1<<(3*7)) | (1<<(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
    // 0 = Use TRGO event for Timer 1 to fire ADC rule.
    ADC1->CTLR2 = ADC_ADON | ADC_EXTTRIG | ADC_DMA; 

    // Reset calibration
    ADC1->CTLR2 |= ADC_RSTCAL;
    while(ADC1->CTLR2 & ADC_RSTCAL);

    // Calibrate ADC
    ADC1->CTLR2 |= ADC_CAL;
    while(ADC1->CTLR2 & ADC_CAL);

    // Turn on DMA
    RCC->AHBPCENR |= RCC_AHBPeriph_DMA1;

    //DMA1_Channel1 is for ADC
    DMA1_Channel1->PADDR = (uint32_t)&ADC1->RDATAR;
    DMA1_Channel1->MADDR = (uint32_t)adc_buffer;
    DMA1_Channel1->CNTR  = ADC_NUMCHLS;
    DMA1_Channel1->CFGR  =
        DMA_M2M_Disable |        
        DMA_Priority_VeryHigh |
        DMA_MemoryDataSize_HalfWord |
        DMA_PeripheralDataSize_HalfWord |
        DMA_MemoryInc_Enable |
        DMA_Mode_Circular |
        DMA_DIR_PeripheralSRC;

    // Turn on DMA channel 1
    DMA1_Channel1->CFGR |= DMA_CFGR1_EN;

    // enable the ADC Conversion Complete IRQ
    NVIC_EnableIRQ( ADC_IRQn );

    // ADC_JEOCIE: Enable the End-of-conversion interrupt.
    // ADC_SCAN: Allow scanning.
    ADC1->CTLR1 = ADC_EOCIE | ADC_SCAN;

And in ADC1_IRQHandler I read values from adc_buffer[0] and adc_buffer[1] respectively. Now everything works even with low sampling time, but with my hardware setup I still get better result from using just proportional part of PID. If I try to use full PID I get brightness fluctuations that are not reflected in voltage monitor in testnix app. I guess I'd need to dive deeper into PID tuning for it to work perfectly for me, but for now I don't see much reason. But I'd like to better understand what causes this difference in behavior. When using full PID it seems like timing of ADC reads plays a big role, such if I change sampling times I see different behavior, with only proportional I don't see any visible change. The next step will be to try implement constant current mode with use of another ADC channel and built-in OPAMP.

monte-monte commented 5 months ago

Continuing tinkering I've soldered 3.3V LDO to supply power from the same source as the boost part of the circuit (before MCU was powered via WCH-link). Now I see a slight brightness ripple at a low power. I've concluded that noisy VDD readings are a culprit here. If I set the VDD hardcoded to 3.3V value the brightness ripple is gone. BUT! The strange part is even with live VDD readings the ripple is only present when testnix app is running. If I close the app, even without detaching a probe, the ripple is gone. So does DMDATA0 reads and writes introduce some timing lag, or what? I haven't got an oscilloscope, so I can't accurately test such fast signals.

EDIT: I've tried to increase sampling time for the VDD channel and also to decrease IIR rate, but no visible difference. Also I am thinking to try to take VDD readings at a different frequency, maybe it could help with the noise. So I guess I've found the working setup for my case, using external LDO I can trust that voltage is accurate enough, considering I don't use 10x transformer. I can use just a constant number for the VDD, or even update it once in a while for some unforeseen fluctuations.

cnlohr commented 4 months ago

I am so sorry, I have been so heads down on porting the ch32x035 stuff, I have been neglecting my github... But to summarize things for anyone else coming along,

  1. I measure VCC with the injection, because VCC is the voltage reference for all other measurements, you need to know your VCC if you want to know the voltage of your other measured voltages.
  2. I regret that I don't recall the specifics surrounding how the injection stuff works, I barely understood it when I wrote the code to begin with :(
  3. Looking at your code, I am still unclear what it's measuring with the rule and what it's measuring with the injection.
  4. ADC TIMING MATTERS MASSIVELY You MUST align your ADC sampling with your PWM switch. For some applications it must be on the "on" and others it must be on the "off" but the ADC should be aligned to whatever is unaffected by pulse width. Failure to do this will easily increase your measured noise by an order of magnitude!!! Please note also - your ideal sample time might be inverted to mine.
  5. I have never seen issues writing/reading from DMDATA0 - but now you have me worried. It is entirely possible that there could be some unintuitive impact on the system by reading/writing from DMDATA0.

Thinking forward - I wonder if there is some other scheme that would let you align all of your voltage readings. I.e. move everything to the injection, and chain it all off the rule? Or maybe manually reconfigure the ADC every interrupt to switch between the regular ADC reading and the VDD reading.

Also, also, if you know you have a fixed LDO, then there should be no need to monitor VDD at all, and you can simplify the system immensely.

SIDE NOTE: If you would like a much better place for discussion is the #ch32v003 channel in my discord... I can give you access if you want, my user name is cnlohr

monte-monte commented 4 months ago

@cnlohr no worries, I glad I mostly figured out myself, that's better for understanding :)

3. Looking at your code, I am still unclear what it's measuring with the rule and what it's measuring with the injection

In my code I don't use injection at all, that's what I was getting at. I read both channels in rule group one after another and use DMA to store the values in an array. I've found that without injection I can decrease sampling time and it somehow decreases noise on both ADC readings. Injection itself introduces delay when it is called automatically after rule reading. I guess it messes timing, because getting rid of it improved things in my case. I wonder if it will work with your hardware better/worse.

4. You MUST align your ADC sampling with your PWM switch

Yes, I get this, I've read your comments in code several dozens times while trying understand it :) In current setup ADC read starts on timer rollover, so is there any other way to adjust read timing apart from changing sampling time and/or introducing other channel reads before the one we measuring?

5. I have never seen issues writing/reading from DMDATA0 - but now you have me worried. It is entirely possible that there could be some unintuitive impact on the system by reading/writing from DMDATA0.

I don't have a clue, I just report what I see, can it somehow skew ADC read timings?

Also, also, if you know you have a fixed LDO, then there should be no need to monitor VDD at all, and you can simplify the system immensely.

That's what I figured out in the end :)

If you would like a much better place for discussion is the #ch32v003 channel in my discord

Should I add you in discord, or just give you my username here (montemonte)? I'm not very familiar with it specifics.

cnlohr commented 4 months ago

Re: No Injection: @monte-monte this is extremely interesting - how do you make sure to maintain alignment of the samples? Re: Rollover: You can also sync the timer off of a channel compare. You aren't limited to TRGO. Re: I don't have any idea how reading/write DMDATA0 could affect things. You may need to peel things back. I just did a bunch of testing last night and could not repro. My data seemed to be just fine? Re: Just add me on Discord. I will send you an invite.

monte-monte commented 4 months ago

how do you make sure to maintain alignment of the samples?

Alignment to what? As I understand, rollover happens -> TRGO triggers ADC conversions -> ADC converts 2 channels one after another in one go adding values to array via DMA -> interrupt is triggered. So basically all I did was: activated DMA and added second channel to rule group and removed code for injection. Now you make me think I am doing something wrong :) but hey, everything works (somehow).

You may need to peel things back. I just did a bunch of testing last night and could not repro

It may have something to do with hardware part that is different in my setup. So basically I see some shimmering of the LED only on the lowest values that keeps it lit, so it has to be a very low current at that point. Maybe it connected to inductor saturation, or how mosfet opens/closes at different conditions, idk my knowledge and experience is not enough to accurately evaluate this behavior. All I know it that when testnix monitor is running I have this shimmer and when I close it shimmer is gone. And it's only when using measured VCC as the baseline, if I hardcode the voltage value I see no issues. So I guess not really an issue after all, but an interesting thing to understand what causes it.

cnlohr commented 4 months ago

Alignment: I just meant if you wanted you could trigger off of a timer compare match on an unused timer compare channel instead of TRGO.

I really don't know what could be going on with your shimmer issue.