SnailScanToo or 2

yep. That is what the old hardware also did use for horizontal frame registration (as I tried to elaborate upon in my previous post). And that is the horizontal frame registration my scanner is using. :sunglasses:

2 Likes

Great insight. When time for Super 8 comes, in addition to the four sides of the sprocket hole detection, I will overscan the film side on the sprocket side and add a fifth detection for the film edge. Currently each side detected provides the image pixel coordinates, and all these can be preserved as a sidecard to the capture.

1 Like

I can’t get into complicated debates, the subtleties of the English language escape me, as you will have noticed by trying to read me.

PM490
Even if the perforations are absolutely fixed, this won’t correct the other various sources of image or frame instability. If the perforations move a little (as little as possible, of course), it’s not a big deal, because after a little post-processing, it won’t be noticeable.
I work for clients who only judge the final result, not the copy that ends up in a museum.

I really like these rules:

  • Think 1 hour, work 5 minutes.
  • No problem can withstand a 16-hour work day.

A small example of the relative stability I achieve with my latest scanner.
Stepper motor drive, no tensioner, and laser perforation detection, scan speed 16i/s.

Stability

The scans I get are of a quality I’m happy with, even if it’s not perfect yet… and I still have to improve it!

Update
I continue peeling the onion of the transport rhythm. While there has been progress, there is still some work to be done… so I will post the full breakdown when I finish breaking the onion, hopefully will help others planning to use steppers in similar transport.

Meanwhile, I sent out to print a 16mm and 8mm guide roller. Love when life imitates CAD.


The roller minimizes the contact to the film, contact is only on the edges.
There are two bearings, top and bottom. It was first tested in the dancing pot hat, the tightest turn, and these worked well.
Waiting for more bearings to arrive in Amazon, and will test replacing the Lego guides (no bearing) and the vinyl cutter rollers (those have 2 bearings also) with new 3D printed.

The new rollers will minimize the film vertical shifting, especially for 16mm. And continue the goal of being able to scan 16, 8 and Super 8 without changing the path.

The material finishing is a bit coarse, these were printed in 3201PA-F Nylon.

1 Like

Update
Half full cup, moving film without sprockets or sprocket detection with an uncertainty of 0.05 mm… Half empty cup, it may be as good as it gets.

It sure feels like everything everywhere all at once time.

  • The sprocket detection algorithm works quite well.
  • The 3D-printed guide rollers do a better job than the lego guides or the vinyl cutter rollers. All guides are the same 3D printed pictured previously.
  • Unexpected project: began controlling the TMC2208 basic micro-stepping setup via UART.
  • Found most of the significant contributors to the frame movement, spoiler alert it will be better, but there will still be some jumps.

Meanwhile, made some progress on a second round of PCB design.


Building upon the progress of the previous round:

MCU - (132 x 86 mm) Pico, ADC conditioning for Tension, and three Analog Devices/Trinamic TMC2208 drivers, now configured via UART. Pictured in front. The MCU receives USB-serial commands from the controlling system (currently the Raspberry Pi) and handles movement (stepper control and tension sensing) and light (DAC control). Board was reformatted to be Pico-W friendly, opening the door for IP-WIFI communications in the future.

DAC - (150 x 60 mm) The eight channel 16bit DAC and its voltage conditioning are separate board, the backplane (pictured) serves to minimize wiring if more than one LED color is needed. It is controlled via I2C, which makes it universal enough to be controlled by boards other than the MCU. It is also possible for a single controller to drive additional instances of the board/DAC, and the board allows jumper configuration of the I2C address (2 bits, up to 4 DACs).

LED Linear Current Driver - Changed form factor to 60mm x 60mm, reducing the foot print of the DAC/Driver assembly. Each driver -up to eight per DAC- is controlled independently by one of the DAC channels. In this design, most components will be surface-mounted, the exception is due to price/availability. Each board is designed to control a separate type of LED, opening the door for narrow-band-multi-spectral scanning, or RGB, or WRGB, or WRB, or WRBG+IR or whatever is needed.

It has been 2 years since I posted the first linear current LED driver design, much has been learned, and much still to be learned on the subject.

This is a significant improvement over the typical LED drivers (femtobuck, etc), which can only control current for the top 80% of the range (20% to 100%). These linear current driver boards control the LED from 0 to 100% range (with the 16 bit resolution of the DAC). The trade-off is heat, and limits the current to about 400mA. Using 30Volts compensates for higher power, in the above pictured system, maximum output is about 8.5W with 12 White LEDs.

From what I see on linkedin, there are a couple of startups working on versions of multi-spectral film scanners… so here is a minimum viable light control system for 1 to 8 bands, and expandable to up to 4 DACs for a total 32 separate bands.

Each of these blocks may be used/improved/configured independently. For example, instead of the raspberry pi/HQ camera, a mac/pc/linux computer can equally control the MCU and use other camera systems.

Lookahead

  • Finalize PCB design, order, and assemble.
  • Incorporate stepper lessons into transport adjustments/improvements (and provide a report to the forum).
  • Setup sprocket detection feedback to Pico, and continue scanning code development.
  • Complete Pico usb-serial command set.

Everything is happening everywhere all at once!
Stay tuned.

3 Likes

@PM490 - what code are you using on the Pico to drive the TMC2208? I am currently driving the film movement stepper with an Arduino Nano, utilizing constant acceleration and de-acceleration. For this to work, TIMER1 (16 bit) is required for appropriate precision.

Specifically, my stepper need to do 400 microsteps exactly to advance one frame - however, the Arduino gets sprocket registration data from the RP4 (via USB-serial) to center the next frame slightly better. So the actual steps taken vary from about 390 to 410. I am using constant acceleration (that is, a linear velocity ramp at the start and end of the movement) to reduce wear on the film stock and to reduce the overall time a single frame advance takes. Specifically, about 100 steps is the initial acceleration part, about 200 steps are performed at maximal velocity, and the final 100 steps are used to ramp down the velocity to a stop. In this way, the maximal velocity you can achieve without the stepper loosing steps is a little bit larger.

At least with bare Micropython on the Pico, I could not reach the precision needed for that scheme. Standard timers in Micropython have a minimum time interval of 1 ms, and I would need at least an order of magnitude better. Of course, you could program the Pico in C/C++, could use one of the PIOs, etc… So I am curious about your software approach…

Simple answer: Bit banging C++ in the Pico.
Started handling the configuration of the TMC with the UART, which provides access to more/less microsteps (1 to 256 per step).

In my case, the capstan moves at a constant number of steps, but the direct driven pickup and supply reels steps are dependent of the spool diameter.

While using a timer certainly makes it more precise, given the requirement to move all three at different rates and at simultaneously made the timer alternative a bit too complex.

Your setup is very similar. I do not plan to have sprocket detector hardware, the detection feedback will also be from the RP4 via USB-serial.

It hasn’t been difficult to work with the toolchain in Pico, but if you are already familiar with arduino, that is also another alternative… arduino IDE with the Pico. I would certainly recommend using the Pico toolchain, the SDK is well documented and other than a bug with USB-Serial, I did not have any issues.

Also using constant acceleration for the capstan, and dividing the steps in 4 blocks.

Very looong answer:
Interlacing the Steppers
In the three stepper implementation, pickup/supply movements are interlaced based on tension and direction of movement, and the result is that these do not move with constant acceleration.

The implementation senses tension, considers what the film is doing (moving forward/backwards or still) and determines what pickup and supply need to do (simultaneously with the capstan movement) for tension.

The options are: Move Clockwise (CW), Move Counter Clockwise (CCW) or not move = a delay equivalent to the step (DLY).

Supply Action
tens_ok cpst_still cpst_rev tens_h Case Direction
0 0 0 0 0 Moving Forward, Tension Low DLY
0 0 0 1 1 Moving Forward, Tension High CW
0 0 1 0 2 Moving Reverse, Tension Low CCW
0 0 1 1 3 Moving Reverse, Tension High DLY
0 1 0 0 4 Not Moving, Tension Low CCW
0 1 0 1 5 Not Moving, Tension High CW
0 1 1 0 6 Not Moving, Tension Low CCW
0 1 1 1 7 Not Moving, Tension High CW
1 X X X Default Tension Ok DLY
Pickup Action
tens_ok cpst_still cpst_rev tens_h Case Direction
0 0 0 0 0 Moving Forward, Tension Low CW
0 0 0 1 1 Moving Forward, Tension High DLY
0 0 1 0 2 Moving Reverse, Tension Low DLY
0 0 1 1 3 Moving Reverse, Tension High CCW
0 1 0 0 4 Not Moving, Tension Low CW
0 1 0 1 5 Not Moving, Tension High CCW
0 1 1 0 6 Not Moving, Tension Low CW
0 1 1 1 7 Not Moving, Tension High CCW
1 X X X Default Tension Ok DLY

The above are implemented in a switch-case. The need for this is due to the uncertainties of the tension sensor, one should avoid the pickup/supply moving contrary to the film movement.

For example, in case 0 of supply if tension is low, to increase the tension, the supply holds position because the film is moving forward, which would increase the tension. If that is not considered, and only tension is looked at, one may think the best alternative is to move the supply CCW, which would increase the tension… but that would actually create jitter/oscillation.

The beauty is keeping the time of the process the same, regardless of what supply/stepper need to do… reason why when these do not move, there is a filler delay.

Also note that the same function can be called just to setup/adjust tension (when cpst_still = true).

Capstan to Supply (or Pickup) ratio
In the context of variable spool ratio, and potentially different stepper gear-ratio and/or micro-stepping between the capstan and the supply or pickup, it was necessary to insure that the ratio of cycles pickup/supply to capstan would be adequate so all three would have sufficient loop-cycles to make the corresponding steps. And follow changes on the micro-stepping configuration.
This is akin to a firmware gear setup. Determining what would be the maximum ratio between the reel and the capstan, determines the gear ratio of the loop… but based on the above tension case, it only provides the opportunity for the reels to move, tension has the final word.

Constant Acceleration times the Capstan to Reel ratio
As a result of the interlacing, the capstan coefficients calculated for constant acceleration are in fact multiplied by the loops of the capstan to supply maximum ratio.

For example. if the diameter of the capstan is 50mm, and the maximum diameter of the supply/pickup is 200mm, and minimum of 32mm, with an equal micro-stepping setting of 3200 micro-steps/turn, the ratio of capstan to reel would be from 0.64 to 4. The firmware gear provides the opportunity to the reels to move more than 4 times the number of steps that the capstan will move.

Summary
The short story is that for every movement the capstan is moving using the constant acceleration coefficients, times the Capstan to Reel factor. The factor does not change through the movement, so the result is constant acceleration for the capstan.

The reels move with the same coefficient for the x cycles of the reel ratio. In the example above, reels go 4 cycles with C0, then 4 cycles with C1, then 4 cycles with C2, etc. But they will move only if the tension case determines. Every 4th cycle, the capstan always moves, which makes the capstan move with a period of 4xC0, then 4xC1, then 4xC2.

The rough code in progress

void ss_Move::MovingAcc (uint CapstanSteps, bool CapstanStill, bool CapstanRev, bool verb ) {
   uint8_t sp_dir_step = 0;
   bool loop_tension = true;
   int capstan_ctr = 0;
   int capstan_gear = 0;
   int tension_ratio_ctr = 0;
   int capstan_ratio = ss_capstan_ratios [Move_Capstan->ss_tmc_ms21_set*4+Move_Pickup->ss_tmc_ms21_set];
   
   // Acceleration Variables
   int32_t acc_steps = 0;
   double usec_x_cn = 0.0;
   double stepper_alpha = 0.0;
   double c0 = 0;
   double c1 = 0;
   double cl = 0;
   double cn = 0;

   acc_steps = CapstanSteps / 4;

   if (acc_steps > 300) {
       acc_steps = 300; // Cap at 300 if larger
   }
   stepper_alpha = 2.0 * M_PI / (double) (Move_Capstan-> ss_tmc_ms21_steps[Move_Capstan->ss_tmc_ms21_set] * Move_Capstan->ss_tmc_gear_ratio);
   c0 = Move_Capstan->ss_tmc_inv_tt * sqrt( (double) 2.0 * stepper_alpha / Move_Capstan->ss_tmc_max_acc [Move_Capstan->ss_tmc_ms21_set]);
   c1 = c0 * (double) 0.676;
   
   // Get the Driver out of automatic standstill current reduction
   Move_Capstan->StepperDisable (); //Disable with Direction
   Move_Supply->StepperDisable (); //Disable with Direction
   Move_Pickup->StepperDisable (); //Disable with Direction
   Move_Supply->StepperEnable (!CapstanRev); //Enable with Direction
   Move_Pickup->StepperEnable (!CapstanRev); //Enable with Direction
   Move_Capstan->StepperEnable (!CapstanRev); //Enable with Direction

   // Tension Loop
   while (loop_tension){
       // Move capstan gear, capstan if not still
       capstan_gear += 1;
       if (!CapstanStill && (capstan_gear == capstan_ratio)) {
           if(!CapstanStill && capstan_ctr < CapstanSteps){
               Move_Capstan->StepperFastStep(0);

               // Ramp Up
               if (capstan_ctr < acc_steps) {
                   switch (int(capstan_ctr))
                       {
                       case 0:
                           cn = c0;
                           break;
                       case 1:
                           cn = c1;
                           break;
                       default:
                           cn = cl - (2*cl / ( 4 * capstan_ctr + 1));
                       }
                       cl = cn;
                       usec_x_cn = cn * (double) 1000000.0 / Move_Capstan->ss_tmc_inv_tt;
                   }

               // For the period between acc_steps and 3*acc_steps it would use the last usex_x_cn as the delay.

               // Ramp Down
               if (capstan_ctr > (CapstanSteps - acc_steps)) {
                   switch (int( CapstanSteps - capstan_ctr))
                       {
                       case 0:
                           cn = c0;
                           break;
                       case 1:
                           cn = c1;
                           break;
                       default:
                           cn = cl / (1 - (2 / (4*( (double) (CapstanSteps - capstan_ctr) + 1) + 1)));
                       }
                   cl = cn;
                   usec_x_cn = cn * (double) 1000000.0 / Move_Capstan->ss_tmc_inv_tt;
                   }
               capstan_ctr += 1;
           }
           else{
               busy_wait_us_32(10);                
           }
           capstan_gear = 0;
       }
       else {
           busy_wait_us_32(10);                
       }

       sp_dir_step =  ss_Move::TensionStepperAction (CapstanStill, CapstanRev);

       switch (sp_dir_step) {
           case 0 :
               // No Movement
               busy_wait_us_32(Move_Supply->ss_tmc_step_set); // Delay For Non-Moving Stepper
               busy_wait_us_32(Move_Pickup->ss_tmc_step_set); // Delay For Non-Moving Stepper
               break;
           case 1 :
               // Pickup Only - CCW
               Move_Pickup->StepperEnableWait (0); //Enable with Direction CCW
               Move_Pickup->StepperFastStep(0);
               busy_wait_us_32(10); // Delay For Non-Moving Stepper
               break;
           case 3 :
               // Pickup Only - CW
               Move_Pickup->StepperEnableWait (1); //Enable with Direction CW
               Move_Pickup->StepperFastStep(0);
               busy_wait_us_32(10); // Delay For Non-Moving Stepper
               break;
           case 4 :
               // Supply Only - CCW
               Move_Supply->StepperEnableWait (0); //Enable with Direction CCW
               Move_Supply->StepperFastStep(0);
               busy_wait_us_32(10); // Delay For Non-Moving Stepper
               break;
           case 5 :
               // Pickup CCW - Supply CCW - 5
               Move_Supply->StepperEnableWait (0); //Enable with Direction CCW
               Move_Pickup->StepperEnableWait (0); //Enable with Direction CCW
               Move_Supply->StepperFastStep(10);
               Move_Pickup->StepperFastStep(10);
               break;
           case 7 :
               // Pickup CW - Supply CCW - 7
               Move_Supply->StepperEnableWait (0); //Enable with Direction CCW
               Move_Pickup->StepperEnableWait (1); //Enable with Direction CW
               Move_Supply->StepperFastStep(10);
               Move_Pickup->StepperFastStep(10);
               break;
           case 12 :
               // Supply Only - CW
               Move_Supply->StepperEnableWait (1); //Enable with Direction CW
               Move_Supply->StepperFastStep(0);
               busy_wait_us_32(Move_Pickup->ss_tmc_step_set); // Delay For Non-Moving Stepper
               break;
           case 13 :
               // Pickup CCW - Supply CW - 13
               Move_Supply->StepperEnableWait (1); //Enable with Direction CW
               Move_Pickup->StepperEnableWait (0); //Enable with Direction CCW
               Move_Supply->StepperFastStep(10);
               Move_Pickup->StepperFastStep(10);
               break;
           case 15 :
               // Pickup CW - Supply CW - 15
               Move_Supply->StepperEnableWait (1); //Enable with Direction CW
               Move_Pickup->StepperEnableWait (1); //Enable with Direction CW
               Move_Supply->StepperFastStep(10);
               Move_Pickup->StepperFastStep(10);
               break;
           }

       if(usec_x_cn == 0.0 ){
           busy_wait_us_32(uint32_t (c0 * (double) 1000000.0 / Move_Capstan->ss_tmc_inv_tt));
       }
       else {
           busy_wait_us_32(uint32_t (usec_x_cn));
//            printf("capstan_ctr = %03d us_x_cn = %f \n", capstan_ctr, usec_x_cn);
       }

       // Check for reaching Capstan ratio
       tension_ratio_ctr += 1;
       if (CapstanStill) {
           if (tension_ratio_ctr == capstan_ratio*4){
               loop_tension = false;
           }            
       }
       else {
           if (tension_ratio_ctr == CapstanSteps * (capstan_ratio)){
               loop_tension = false;
           }            
       }
   }

   if (verb) {
       printf("|cmd:");
   };

}

The code is a bit rough, hope it helps understand how is presently implemented.

1 Like

Interesting approach! My own implementation is using self-made tension sensors (basically a movable arm connected to a standard potentiometer, here are some old images of this construction) on the pickup and supply tables + a movement stepper driving a sprocket gear stolen from an old S-8 projector.

Film advance is triggered by a serial movement command specifying the amount of movement needed to advance to the next frame. This comes from the RP4 which has detected the sprocket hole position in the current frame.

Once that movement is triggered, the Arduino starts accelerating the movement stepper in order to advance the frame (TIMER1 interrupt driven). Due to this movement, the tension on pickup-spool is reduced and correspondingly, the tension on the supply spool is increased. This is of course noticed by the tension sensors - their reading is input into two PID controllers which drive the steppers for the pickup and supply plates. The two PIDs run in an interrupt loop of TIMER2 which drive the pickup and supply steppers as well.

In my setup, it is rather easy to change (independently) the tensions on the pickup and supply side - the RP4 can command a different zero position of the tension sensors. In fact, the supply plate works with less tension than the pickup plate. This is needed because the sprocket gear’s pins are smaller than the sprocket hole of the film. So I need to make sure that the sprocket pins always align on the correct side of the sprocket

While there is no need to monitor the spool ratio in my approach, the tension steppers only react to the movement of the film stepper. So you always introduce a little delay/jitter in the actual tension. Your idea of interlacing can in principle deliver a much smoother ride in term of tension variation - which might be important in order to minimize the risk of slipping on the capstan.

Thanks for sharing the insights into your Pico-programming. I was actually hoping to avoid C/C++ programming when using the Pico ( :innocent:), but it seems not to be possible when tight timing is required. I think I stick for the time being with my Arduino - it works ok with the small standard spools of 15m film length (3 min/3") up to 240 m length (40 min/10") ones. But the tuning of the PID parameters of the controllers was quite a challenge…

1 Like

From what I am learning on researching the uncertainty on my transport (sprocketless), I would speculate that the above is a contributing factor to the source of your movement uncertainty. Another likely contributor is the independence of the pickup tension. My hypothesis would be that the combination provides an opportunity for the film to separate from the pin. I say contributors, because there may be backslash on the driving belt also.

Well, my system I described above just works fine. No issues in scanning S-8 film rolls with over 40 minutes running time. I was just considering to replace my Arduino Nano programmed in C/C++ with a more modern approach based on Micropython and the Pico. I think I programmed in C for more than 20 years now, somewhat less in C++, and I kind of got bored with these languages. But, it seems the time is not yet ripe for using Micropython on a Pico for such tasks…

1 Like

I have not used micropython, but was curious about the limitation.
According to this February 2023 post:

The current version of MicroPython for the Pi Pico does not allow the hardware timers to be used separately . Instead, it is possible to create an almost unlimited number of “software” timers, all of which rely on a single hardware timer.

That appears to be the case currently… for the PICO. In the latest development branch for the WyPi, there are documentation references to hardware timers with a 1 microsec timebase.

You may have to wait a bit for the PICO… but there are ripping signs. At least micropython seems to be taking steps with other hardware to implement hardware timers with microsecond timebase.

Not sure of the particulars of your requirement, but there maybe a workaround using DMA to an output, and hardware it as an input to trigger an input interrupt. DMA allows for changing the output with every cycle (125 MHz) so that is plenty fast (maybe too fast).

1 Like

Well, so it’s wait and see… :sunglasses: :+1:

Very cool project! I’ve got a bit of experience designing LED lighting application for film scanning, so I’m curious about your driver topology. I’m having a bit of trouble following exactly what’s going on. Would you might elaborating? Seems like you’re using a DAC to set the sink current somehow, though I’m not sure I understand how exactly? Is there a reason you chose not to use standard driver with PWM or analog dimming?

LEDs are fast enough to follow the PWM signal. That is, there brightness changes from 100% LED intensity when the PWM is at 100% to something close to 0% intensity when the PWM is at 0%. Usually fast enough (several kHz at least) that your eyes do not notice, but a camera with a sufficient short exposure time will notice this anyway. Of course, you do want to avoid that.

If “Analog dimming” refers to the old dimming unit in AC circuits: they have the same problem, as they are working with chopping frequencies of 50 Hz (most of the world) or 60 Hz (US).

One way out is to have a truely analog current source; one way to implement such a thing is a DAC which drives a op-Amp paired with a beafy FET. An old schematic of such a setup can be found here.

The trick with PWMing a light source designed for photo or video purposes is to set the frequency high enough. Generally 25khz or above should be flicker-free for video and photo applications. The solution I designed used 30khz PWM, and there was no detectable banding on any captures.

The other alternative, analog dimmer, is a feature of constant-current LED drivers that limit the current based on a reference voltage. This would be a DC circuit, and its often as simple as just hooking up a potentiometer to a pin of the driver. I’m a fan of the TI LM3409, which offers both analog dimming and PWM dimming at very high frequencies via the use of a shunt-FET (the PWM signal drives the gate of a FET that shorts the output of the driver to ground in one of the signal phases).

Might be worth checking out since it can drastically cut your BOM size and cost, and reduce the size of the circuit footprint on the PCB.

Thank you @Alexi_Maschas for the kind comment.

Expanding on @cpixip, LEDs are fast enough to follow current variations (IL), and typically buck switching current regulators (like LM3409) produce a sawtooth current (see figure 10 and figure 11 of the Application Note AN1953 for the LM3409).

It is also true that the White LED Phosphor would be a low pass filter to high frequency current variations, but in general it has a fast response time, as demonstrated by this experiment.

Consider also that the clock frequency of the IMX477 may be as high as 27 MHz (Raspberry HQ sensor).

In short, current variations in the tens of nanosec order (very high frequencies saw tooth of the regulator), will be quantified somehow in variations of pixel values. These variations may not be visually perceived (no perceptible pattern in the resulting image), but these will be imprinted in the image (increased “noise”).

A bit purist? definitely.

Topology for Current (above schematic)
EDIT (after incorrectly posting).

This topology is suitable for mid-power LEDs (tens to few hundreds of mA), for higher current LEDs heat and power management may render impractical.

DACX is the output of the DAC, and provides 16 bit resolution in a 0 to 5V range. Similarly to the circuit referenced by @cpixip, the OpAmp + the darlington transistors Q1/Q2 are amplifying the voltage output of the DAC, and level shifting, from the DAC range of 0 to 5V to the range at the emitter of Q2, approximately 0.7 to 8V. The offset (bias current at the 0V of the DAC) is adjusted with RV4, while maximum output (gain) is adjusted with RV3.

That amplified/shifted voltage range is translated to a current range by the four parallel resistors, Q3 Base-Emitter, and R17. The current sensed by Q3 BE, is mirrored at the BEs of Q4 to Q7 and approximately the same at the collectors of Q4 to Q7: the current mirror. The resulting total current at J17 is the total, four times the current sensed at Q3 BE.

The topology has the advantage of lowering the power requirements to the transistor drivers (Q4 to Q7) to 1/4 of the total power.

It is worth clarifying that the White LEDs used are a bit unique, these have a Vf of approx. 9V and maximum IL of 100 mA, unlike the typical LED in a similar power range which would have 1/3 the voltage and 3 times the current.

For current mirror details and other configurations, this is a good overview reference.

2 Likes

Ah OK, somehow I missed that this was a current mirror. That was actually something I tried with my designs as well before moving the TPS92515 and then the LM3409 because of the difficulty of sourcing the former during the pandemic, but I was a little leery of the amount of heat that needs to be managed relative to the current.

I may be wrong about this, but I think you may be making your task harder than it needs to be. The pixel clocks in most sensors do in fact run at several megahertz, but that doesn’t mean that the sensor is actually reading at anything close to that rate. The pixel clock in a sensor is going to determine the bandwidth at which data can be read out of each pixel, but is not directly tied to the read out speed. This differs a bit between CCD and CMOS sensors, but essentially each pixel in a given row of the sensor will read at the same time (assuming a rolling shutter, global shutter sensors will read the entire sensor at once), and then after a short delay the next row will read out. You can see that illustrated here:

w420_2162439_en

Where the pixel clock comes into play is how fast the data can be read out of the pixel. So let’s say you take am exposure at 1/1000th of a second, each pixel in the sensor will integrate for that time (a simplification here, since as I mentioned each row has a staggered start and end), which simply means its counting the photons that hit the detector. If the rate of the photons hitting the pixel varies within that time, the pixel is effectively blind to that. Once it has integrated for the set amount of time, the data is read out from the sensor to whatever is processing the sensor data, and it is at this point that the pixel clock comes into play, determining the bandwidth available for that readout, and thus the speed at which it occurs. There’s a fair bit of complexity here, with CMOS sensor using an ADC per pixel vs the CCD which has to buffer the entire sensor readout and wait on a single ADC, which is why CCD sensors have intrinsically lower frame rates, while still being able to take fast individual exposures.

You get into trouble with LED PWM is when the frequency is synced with the row integration times such that some rows get slightly more or less light over the period of integration. The other failure mode is if the frequency is slow enough relative to the exposure time, it can be either fully on or off during the entire exposure. The trick is to turn of the LED on and off so fast that it goes through multiple cycles during the entire integration time. At 30khz, for instance, while you might see problems if the shutter speed is very high or the duty cycle of the LED PWM is very low, for the most part the different in photon counts over the integration time of the exposure is going to be well below the noise floor of the sensor.

You are also correct that most constant current switching regulators are going to generate current in a sawtooth pattern, though its worth noting that there are several ways to mitigate this. The easiest is probably just putting a capacitor across the output, which in practice seems to smooth out the current spikes from PWMing the output as well. You can also design your application to minimize peak to peak current variation as well, though there are a number of tradeoff to doing this.

Anyway, if you have a working design there’s probably no hugely pressing reason to make changes, but I thought you might find this context useful. Cheers!

Thanks @Alexi_Maschas for taking the time to share this info. Agree that the parameter to consider implications of variations would be the exposure time.

Same here. The design with the current mirror first came about from the difficulty to source the common switching regulators around the pandemic. I was not familiar with these particular ones either.

The ones used in the popular boards however (fentobuck for example AL8805, and some other boards/ICs) had the particular inability to regulate below 20% of max current. A second deal breaker (part availability was first).

It hasn’t been hard, but maybe so. The LM3409 (as other buck regulators) have the similar limitations on deal with the low current segment of the voltage control (see datasheet Figure 12. Amplitude Dimming Using IADJ Pin, between 0 and 0.3), and that will not work well with mid/low current LEDs (100 mA Absolute Max in my case). Essentially the lower portion of the DAC 16 bit, which are required by the exponential nature of light perception, are truncated by the buck.

Undoubtedly the current mirror is -for me- a well known and tested topology. (first design round in this post around the pandemic).

In the context of mid-power LEDs with a rolling shutter stop-motion application, linear is as good as it gets, and is working well.

For other applications (continuous motion, global shutter, large current LEDs), the LM3409 and similar may be simpler, providing the issues (sawtooth and low current regulation) do not affect requirements, are managed, and/or mitigated in the context of the application.

Hah, looks like we took similar paths, since I also did a lot of early experiments with the AL8805 and ran into a similar problem. It’s definitely the case that the analog dimming implementation in many of these drivers has a lower limit, particularly with low and mid-power LEDs. That’s actually one of the reasons I went for high frequency PWM, since it’s very linear down to < 1% brightness. It was a difficult build however, since PWM pulses at that frequency have single ns transition times, and you have to be very careful routing the lines and the ground pours to manage noise. One of the reasons I went with the LM3409 was that TI has published a reference design for a multi-channel, high frequency, high resolution PWM and analog dimmable board using that IC here: TIDA-01415 reference design | TI.com. So if you’re ever tempted to go down that road I’d recommend checking that out.

I think one of the most appealing things to me when I was assessing current mirroring was the fact that my implementation drives a grid of LEDs which are laid in a parallel/series fashion. So each row has 8 LEDs, and each channel has three rows with one driver per channel. As you may know, the risk here is that as the LEDs get hot they can tend to draw more current, and not every emitter responds in a uniform way so one string can end up in self-reinforcing loop where it goes into thermal runaway. I haven’t actually run into this problem, mostly because I think my rows have enough emitters and I’m using an aluminum PCB to spread the heat around, but I definitely considered using a current mirror to mitigate this possibility.

Anyway, it’s a very cool design! Love to see what other solutions people have arrived at for these purposes. Out of curiosity, what are you using the lower brightness levels of the emitters for?

1 Like

Thanks for pointing the reference design, very helpful.

One of the goals is to perform testing of White LED + Colors LEDs. The color LEDs when mixed would need lower brightness down to zero.