Greetings Habr, and all its many inhabitants!
Immediately make a reservation that something about what will be discussed here is not very intended for beginners, nevertheless, if there is an interest and craving for learning, on the contrary, I ask you to know.
And this time, it will be about the implementation of hybrid PWM, which has already been born on the net. So I think another one, or two, or three (bonus) will not be superfluous or not superfluous.
So, first of all, why is a hybrid: first of all, because it’s not 100% hardware, but not software-based also 100%.
What this would mean: a hardware counter is used for the implementation, but the output is formed not into one channel of the I / O port, but into three at once, with a configuration mask that masks the port channels in a timer interrupt.
What is the difference from the hardware? Yes, everything is simple - among the channels for the Attiny13a microcontroller.
')
Now more.
Who needs it and why? Well, the main field of application of such tasks is engine control - asynchronous, bipolar and unipolar - stepper, controllers of light and sound effects, ultrasonic devices (ultrasonic baths, engravers, mixers, etc.), etc. Of course, specialized controllers were created for such purposes, but why search, spend money, and dig LH, if there is a small pet at hand.
I settled on the first question, when interest appeared suddenly when reading one of the forums where one developer (let's call it “Regretful”), was saddened by the fact that the Attiny13a microcontroller, not having enough hardware PWM channels, cannot brag required performance in the software implementation of the plan.
"Sad" tried many options, changed constants, optimized C code, even used interrupts, but all was in vain. And the topic he raised on the forum was rotten, in some shaggy year, left unresolved and unanswered.
I was interested in how slow the PWM software mode was for the mentioned microcontroller, which operates at 9.6 MHz. And I, having scribbled a small source, using all aspects painted with “Saddened” whenever possible and having embodied them, slowly received on three paws of the 13th PWM software program, at a frequency of ... about 4 kHz, with software emulation in Proteus.
Little ... Too little! And somehow this is all strange ... but incredibly interesting, because if you do not defeat such a simple task, then it really is one of the first, small disappointments, and a straight road towards more productive and clever chips, which cost accordingly. On the other hand, this is rather a plus, but we will still understand.
And then I thought: “And why, in fact, synchronization is based on timer overflows, when the task is implemented through comparisons?”
And the truth is, because in the overflow mode, the timer is simply obliged to cause an interruption only once, during an overflow, after passing a full cycle. Slightly modifying the source received about 40 kHz. “Already better, but still - there’s something wrong here!”
In general, I will not bore you anymore: all variables were declared in registers, the code was optimized for performance,
and size (almost completely disappeared), and with further games two more modes with relative impulse shifts were obtained.
And then followed the new optimization as a result of which, the slowest of modes (6 - phases) was rewritten to inline assembler. And it was done by chance, when I paid attention to the code received after compilation, which is easy to say gently - I was very surprised! Even when declaring variables in registers, the compiler decided to do all operations through temporary pairs, respectively, the code bloated almost twice, and this, with optimization on by size! A few extra moves and a gain of 40 bytes from the first option, and almost 10 kHz increment in frequency.
The result was three modes:
1. Pulses with phase offset, the maximum available frequency is 75.500 kHz at CLK = 9.6 MHz (at CLK = 20 MHz, the frequency is close to 160 kHz), the PWM duty cycle = 33.3% period - three phases:

2. The pulses are shifted by a third, the maximum frequency is 37.400 kHz (75 kHz at CLK = 20 MHz), duty cycle = 50%, the period is six phases (without using the assembler, it was not possible to overcome the limit of 28 kHz, for details, see the source code):

3. The pulses are shifted by half, the maximum frequency as in the first mode, the duty cycle = 66.6%, the period - three phases:

Under the frequency here refers to the full following of the pulses on the three channels before repeating in the period (or the period of one pulse). Yes, I should note that the change in the duty ratio was not part of this task, although it is very easy to do this using the second timer comparison register (although this is true only for the first mode). In this case, the frequency change is implemented, and performed through a change in the phase width (in more detail in the source code text).
Why so many modes? That's not all, and all of them are due to the characteristics of certain devices.
So, for example, asynchronous motors are controlled by the first mode, and unipolar-shagoviki - the second (however, and the channels they need, or less, or more, but if you have an example, to expand or reduce the number of channels is nothing).
To obtain a lower frequency programmatically, it is necessary to change the register of the timer divider.
If this is too much (for example, for stepper motors), it is necessary to lower the frequency of the microcontroller.
So with a chip frequency of 128 kHz, with a CLK divider set, without a timer divider and a minimum pulse width (maximum frequency), when using the second mode, the pulse frequency is 62 Hz.
If we talk about the control of an asynchronous motor, then using a 12-field motor, we get the maximum pulse repetition rate when using the first mode around 20 kHz (~ 78/4), which is undoubtedly just as much, due to the lack of motors capable of rotating on such revs (per second). : o)
Of course, the calculations above do not reflect the overheads that are required by complete control, such as reading from Hall sensors or back EMF, adjusting the duration of phases and so on. But with such a margin in frequencies, these problems are solved easily, right in the main program loop.
Separately, I apologize to the gurus, who are unnerving conditions in one line, but here it is done consciously for the convenience of quick comment. The second, and more prosaic reason: I hate tolerance - / * comments with asterisks * /, although when moving to inline - there is no other way.
If you intend to use my code in your project, when it starts to swell and overgrow with your handlers, you need to arm all declarations of used variables in registers with the keyword "volatile", as a result of which the compiler will snap, he didn’t want to spit on your requests, and that if he pleases, he will use these registers at his discretion (in case of a shortage of such).
I tried to reflect more detailed information in the comments, trying to be as brief as possible when encoding:

I will be happy to answer your questions, and help with changing signal configurations, if of course this is required.
With all the above-said, I want to indicate: I do not position myself as a super-optimizer, super-programmer and super-electronics engineer.
I wrote all the above in order for the newbies to believe that there are no invincible tasks, and the professionals have paid attention to the chips that can be written off early, and they can find a worthy use in many classes of problems.
PS: For beginners: IMHO, the project is not interesting enough for beginners, due to the use of high frequencies. However, the controller is flashed with firmware with the unclamped line in the main loop for changing the phase width, at 4.8 MHz with the frequency divider (4.8 / 8) turned on (I just don’t remember before optimizing the code or after), and with connected LEDs on PB0, PB1, PB2, looks funny in the role of the table flasher.
Although some, particularly nervous individuals, argue that "this garbage is annoying - to the brain of the elbows." :)
PPS: For NOT newbies: in addition, I would like to say something about optimization, and use (we will do without holivar friends!) Of the following code:
byte A=0, B=1, C=2; if (A==0 || B==1 || C==2) doSomething(); / if (A==0 && B==1 && C==2) doSomething();
And the "wrong" use of the form:
if (A==0 | B==1 | C==2) doSomething(); / if (A==0 & B==1 & C==2) doSomething();
The result will be the same, but the second example will be performed SIGNIFICANTLY longer in time, and will significantly inflate the code.
And why?! And I will not say, you yourself guess! :)
On this note, I propose to stop and get acquainted with the attached materials:
project for Proteus 8.1 and source code created in Atmel Studio 6.2Thank you for your attention, see you soon!
Use in commercial projects, resale of the source code and use for profit is prohibited.
Source codes are distributed free of charge, when used on other sites,
or in other sources, the indication of the author and notice of placement - is mandatory.