Mega 2560: >20kHz PWM across 11 pins

Hi all. Obligatory: Long-time lurker, this is my first post here! I'm excited to join this community!

Jump down to the bolded section to get right to the technical challenge at hand. I've given some extensive background on the project that's not particularly necessary to the specific technical challenge.

Anyway, I'm converting a reed organ (pump organ) to be MIDI-controlled with 88 solenoids, one under every key.

Solenoids are special because they need a lot of power to pull in: in my case, my solenoids draw 1.3A @ 12V each. However, once they are down, they don't need nearly as much power to stay in. They stay down with just 4V (therefore just over 400mA of current). Continuously feeding them 12V also results in a large amount of heating; after just a minute of being held in they get intolerably hot.

My solution? PWM them. Of course, it would be fairly impractical to generate and control 88 PWM channels, so I'm going to PWM them in groups. I've designed and ordered 15x (!!!)(I can't make any changes now) of a 16-solenoid driver PCB, and each half of this driver board (groups of 8 solenoid driver circuits) has another MOSFET controlling the overall power, like so:

Of course, this schematic is only showing a 4-channel setup. These are actually in groups of 8.

88 keys/8 per group = 11 groups of PWM control = 5 and a half driver boards.

How does this setup work? I'm using 3 Arduino Megas to control the individual notes (using only 2 would work in terms of pins, but I'm using 3 for wiring purposes).They are all reading serial data off the same data line and triggering notes accordingly - currently all the PWM MOSFET gates are just pulled high, effectively bypassing them. This is working beautifully, ignoring the absurd power consumption and solenoid heating.

Now that I have my explanation of my setup complete, here's where stuff gets more difficult. My plan is to PWM the "PWM control MOSFETS" at around 40% duty cycle when no solenoids are being triggered. When a solenoid needs to go down, the duty cycle goes to 100% for a split second before going back to 40% to hold it in. I've tried this with an individual solenoid, and it works very well - sort of. At the default Mega 2560 PWM frequency of 490Hz, the solenoid is very audible. Raising the frequency to 31kHz by setting the timer prescaler to 1 works very well for eliminating this hum.

If I'm messing with timers, then I've decided I'm probably best off using another standalone Mega for generating the PWM, especially as my MIDI-decoding code works perfectly. The key-triggering Megas would simply have 11 output lines between them running into my PWM-generating Mega. Whenever a MIDI note came in, the key-triggering Arduino would make the line high that corresponded to the driver board that MIDI note's solenoid was attached to. The PWM-generating Arduino would be constantly generating a 40% duty cycle 31kHz PWM signal across all the power control MOSFET gates, and when one of its inputs was high, it would change to 100% duty cycle for a split-second on the line connected to the correct solenoid group. This sounds complicated in my explanation but it really isn't.

So after this long-winded explanation, this is the situation I am left with: I need to generate a >20kHz PWM signal @ ~40% duty cycle across 11 Mega pins. The Mega needs to listen to 11 pins, and whenever one goes high, it needs to change to 100% duty cycle on the corresponding output pin.

Am I better off bit-banging this instead of using analogWrite() and non-default timer frequencies? Can I achieve >20kHz PWM if I bit-bang it? Will analogWrite() take too much time for my application? I don't have a scope, so I really can't test these myself.

Or do I have completely the wrong approach? What if I used 11 separate attiny 45 or similar chips? That would certainly make the coding easier...

Although probably not really necessary, phase-shifting the PWM across the 11 pins would be much nicer on my power supplies... this would make bit-banging it more complicated.

Any help/ideas appreciated. Apologies for the large amount of words.

You're obviously gong to have to design a custom PCB anyway, so why not simply put the current control in hardware instead? Your proposed approach is excessively complex, with no discernible benefit. The increased pull-down current could be easily implemented with a simple R-C timer and FET to switch a resistor in series with the relay coil.

Regards,
Ray L.

Hi Ray.

I've already designed and ordered a batch of custom PCBs with the circuit shown and don't want to order another batch.

I am aware that I can use the capacitor timing solution that you've mentioned to get a large pull-down current and then smaller hold-down current, and I considered that solution at the start of this project. There are two issues with it for my application. The first is the significantly increased component count and cost - capacitors alone that are large enough for this cost ~ $1 a piece in bulk which adds nearly $100 in total cost. I'm going to be hand-soldering everything, and currently I'm at ~1000 through-hole solder joints for this project - I don't want even more. That approach, in my opinion, adds much more complexity than using PWM like I've outlined.

The other idea I had would be to use a 555 timer on every channel to generate the PWM.

Am I better off bit-banging this instead of using analogWrite() and non-default timer frequencies? Can I achieve >20kHz PWM if I bit-bang it? Will analogWrite() take too much time for my application? I don't have a scope, so I really can't test these myself.

If 31KHz works, then I don't see any advantage of going to a different frequency with a more complex timer set up than simply changing the prescaler to 1.

For the 11 channels, I would recommend that you avoid Timer 0 (because it controls the millis() timing) and use use the 11 pins available from the 16 bit timers.
Timer1: 11,12
Timer3: 2,3,5
Timer4: 6,7,8
Timer5: 44,45,46

analogWrite() should be fast enough. It defaults to digitalWrite() for the 100% and for the 40% it sets a timer register(like you would do if you managed the timers. The only penalties to these high level functions are are in the overhead of the "digital pin to timer or PORT" used instead of the direct access.

If your experience with this project says that digitalRead() and digitalWrite() are fast enough, then you shouldn't have problem with reading your inputs, and shifting the outputs between analogWrite(pin, 102), and analogWrite(pin, 255).

Thanks for the reply.

Just did the math on the frequency my code should run at just using digitalRead() and analogWrite().

According to here: How exactly slow is digitalRead()? - Microcontrollers - Arduino Forum

Peter_n:
A single I/O instruction is 0.125us. The ATmega chip is optimized for single I/O bit instructions.
A digitalRead() is about 3.6us.

And according to here: How long does analogWrite() take? - Programming Questions - Arduino Forum

rchard2scout:
Ok, I've been monitoring how long it takes, i created a loop with 10 analogWrites in it. It ran 168887 times, and the average time was 77.1 uS. Most took 76 uS, but I've also seen 80 and 84 uS.

Therefore:

113.6uS + 117.7uS = 124.3uS for 11x digitalRead() and 11x analogWrite().

I'm going to add 20% for if/then and other decision making, therefore the total loop time is ~150uS.

This means my loop frequency would be around 6.6kHz.

Does this look about right?

This speed is almost certainly fast enough for my application. I'm not going to be playing notes even close to that fast!

Thanks,

Will

Does this look about right?

Yes. Managing the timing for the "split second" at 100% will clearly take some processor time, and there is a few microsecond overhead from loop() but that's taken care of by your +20%. At this point, there is probably nothing fundamental standing in the way of your taking the next step.

I think your analysis(11 reads, 11 writes) is worst case, and you should be able to speed things up by only calling the analogWrite() for the 40% or 100% duty cycle only at the change points and not every pass of loop.

Put another way, you will need to read all the inputs every pass of loop(), but you do not need to write all the outputs every time if they are unchanging. The last value written is persistent.

DRV110 6- to 48-V DC Current Controller for Solenoids, Relays, and Valves

The DRV110 device is a PWM current controller for solenoids. The device is designed to regulate the current with a well-controlled waveform to reduce power dissipation. The solenoid current is ramped up fast to ensure opening of the valve or relay. After initial ramping, the solenoid current is kept at a peak value to ensure correct operation, after which the current is reduced to a lower hold level to avoid thermal problems and reduce power dissipation.

The peak current duration is set with an external capacitor. The peak and hold levels of the current ramp, as well as the PWM frequency, can independently be set with external resistors. External setting resistors can also be omitted if the default values for the corresponding parameters are suitable for the application.

Just one example of a premade device that already does exactly what you want.