I have already created a number of different hobby electronic devices, and I have a strange feature: if there is a sound piezoelectric emitter (buzzer) on the board, I, at the end of the main work on the project, start to suffer nonsense and make it play various melodies (as far as possible ). It is especially useful to include a melody, at the end of any lengthy process to attract attention. For example, I used it when I built a self-made exposition camera to expose a photoresist, etc.
But when I began to look for examples of generating frequencies for AVR on the network, for some reason I came across monstrous or insufficiently concise projects that implement the generation of sound frequencies in a purely programmatic way. And then I decided to figure it out myself ...
I got acquainted with a large number of examples from the network - all of them are built either on the simplest loop in the main body of the microprogram, or in interrupting the timer. But they all use the same approach to generate frequency: ')
serves a high level on the foot of the microcontroller
make a delay
serves low on the leg of the microcontroller
By changing the delay and timer settings - adjust the frequency.
I was not satisfied with this approach because I had no desire to write code for manual control of the microcontroller’s foot. I would like for me to generate the sound frequency for the “stone” itself, and I just set the values ​​of certain registers, thereby changing it (frequency).
When studying datasheet (hereinafter LH), I did find the timer mode I needed - and this mode, as you guessed it, is CTC (Clear Timer on Compare Match) mode. Since the function of playing music is, to put it mildly, not the main functionality, I preferred to allocate timer 2 for it (paragraph 22 LH).
Everyone knows that practically any microcontroller has a PWM signal generation mode implemented on timers and it is quite a hardware one. But in this problem, PWM is not suitable. only one frequency will be generated by hardware. Therefore, we need PFM (frequency pulse modulation). Some similarity of PFM is CTC timer mode (clause 22.7.2 LH).
CTC mode
Timer 2 in the Atmega48pa microcontroller is 8-bit, that is, it “ticks” from 0 to 255 and then goes in a circle. By the way, the timer can go in another direction, but not in our case. The next required component is the Compare Unit. Speaking very roughly, this module is the initiator of any events related to the timer. Events can be different - such as interruptions, changes in the level of certain legs of the microcontroller, etc. (Obviously, we are interested in the latter). As you might guess, the comparison module is not just so named - it compares a certain value chosen by the developer of the firmware with the current value of the timer. If the timer value has reached the value specified by us, then an event occurs. Events can also occur when the timer overflows or when it is reset. Ok, we have come to the conclusion that it is convenient for us that at certain moments the timer together with the comparison module independently changes the level on the microcontroller's leg to the opposite - thus generating impulses.
The second task is to set the intervals between these pulses - i.e. generation frequency control. All the uniqueness of the CTC mode is that in this mode the timer does not go to the end (255), but is reset when the set value is reached. Accordingly, by changing this value, we can actually control the frequency. For example, if the value of the comparison module is set to 10, then the level change on the foot of the microcontroller will occur 20 times more often than if we set it (the value of the comparison module) to 200. Now we can control the frequency!
Iron
The pinout of the microcontroller shows that we need to connect our buzzer either to the PB3 foot (OC2A) or to the PD3 foot (OC2B), since OC2A and OC2B means that Timer 2 can generate signals on these legs.
The scheme, which I usually use to connect buzzer'a:
And so we assembled the device.
Registers
In the previous paragraph, we decided on the choice of legs - this is PB3 (OC2A), we will work with it. If you need PD3, then everything will be similar for it, which will be well seen from the narration.
We will adjust our timer 2 by changing 3 registers:
TCCR2A - mode settings and behavior selection
TCCR2B - mode settings and timer frequency divider (still FOC bits - we do not use them)
OCR2A (OCR2B for a case with a PD3 foot) - the value of the comparison module
Consider first the TCCR2A and TCCR2B registers. As you can see, we have 3 groups of bits that are meaningful to us - these are the bits of the COM2xx, WGM2x and CS2x series The first thing we need to change is WGM2x - the main thing is to choose the generation mode - these bits serve to select our CTC mode.
note: obviously in the LH a typo in the "Update of OCR0x at" should be OCR2x
Those. The code will be:
TCCR2A = _BV(WGM21) ;
As you can see, we do not use TCCR2B yet. WGM22 should be zero, but it is already zero.
The next step is to configure the bits COM2xx, more precisely COM2Ax - because we work with PB3 leg (for PD3, COM2Bx is similarly used). It depends on what happens with our leg PB3.
The COM2xx bits depend on the mode we selected with the WGM2x bits, so we will have to find the corresponding section in the LH. Since we have a CTC mode, i.e. not PWM, then we are looking for a label "Compare Output Mode, non-PWM", here it is: Here it is necessary to select “Toggle” - so that the level on the leg changes to the opposite when the timer reaches the set value. The constant level change and implements the generation of the required frequency.
Since bits COM2xx also lie in the register TCCR2A - only it changes:
TCCR2A = _BV(COM2A0) | _BV(WGM21) ;
Naturally, it is also necessary to choose the CS2x frequency divider, and, of course, adjust the PB3 pin to the output ... but we will not do this until we turn on the MK and we don’t get a shrill squeal at an incomprehensible frequency, but when we make all the other settings and include foot on the way out - will be discussed below.
So let's bring our initialization to a complete view:
#include<avr/io.h> //set bit - using bitwise OR operator #define sbi(x,y) x |= _BV(y) //clear bit - using bitwise AND operator #define cbi(x,y) x &= ~(_BV(y)) #define BUZ_PIN PB3 void timer2_buzzer_init() { // PB3 cbi(PORTB, BUZ_PIN); // PB3 , cbi(DDRB, BUZ_PIN); // TCCR2A = _BV(COM2A0) | _BV(WGM21) ; // ( ) OCR2A = 0; }
I used the macros cbi and sbi (peeped somewhere on the network) to set individual bits, and left it that way. Of course, these macros are in my header file, but for clarity, I put them here.
Calculation of the frequency and duration of notes
Now we come to the essence of the issue. Some time ago, familiar musicians tried to drive into my programmer’s brain a certain amount of information about the music staff, my brain almost boiled over, but I still learned a useful bit from these conversations. Immediately I warn you - huge inaccuracies are possible.
each measure, consists of 4 quarters
Each melody has a tempo - i.e. the number of such quarters per minute
Each note can be played as the whole whole beat, and its part 1/2, 1/3, 1/4, etc.
Each note, of course, has a certain frequency.
We have considered the most common case, in fact, everything is more complicated there, at least for me, so I will not exaggerate this topic as part of this narrative.
Well, okay, we will work with what we have. The most important thing for us is to ultimately get the note frequency (in fact, the value of the OCR2A register) and its duration, for example, in milliseconds. Accordingly, it is necessary to make some calculations.
Since we are in the programming language, the easiest way to store ringtones in an array. It is most logical to set each element of the array in the format - note + duration. It is necessary to calculate the size of the element in bytes, because we are writing under the microcontroller and with resources here tight - it means that the size of the element in bytes should be adequate.
Frequency
Let's start with the frequency. Since timer 2 is 8-bit, the OCR2A comparison register is also 8-bit. That is, our element of the melody array will already be at least 2 bytes, because you also need to save the duration. In fact, 2 bytes - this is the limit for this kind of crafts. We don’t get a good sound anyway, to put it mildly, and it’s unwise to spend more bytes. So, we stopped at 2 bytes.
When calculating the frequency, in fact, there is another big problem. If we look at the frequencies of the notes, we will see that they are divided into octaves. For most simple melodies, 3 octaves is enough, but I decided to dodge and implement 6t: large, small, and the following 4e.
Now let's take a break from music and dive back into the world of microcontroller programming. Any timer in the AVR (and the vast majority of other MCs) is tied to the frequency of the MC itself. The frequency of quartz in my circuit is 16Mhz. The same frequency is defined by the “defin” F_CPU equal to 16000000 in my case. In the TCCR2B register, we can choose frequency dividers so that our timer 2 ticks not at a crazy speed of 16000000 times a second, but slightly slower. The frequency divider is selected by the CS2x bits, as mentioned above.
note: obviously in the LH typo instead of "CA2x" should be CS2x
The question arises - how to set up a divider?
To do this, you need to understand how to calculate values ​​for the OCR2A register. And to calculate it is quite simple: OCR2A = F_CPU / (quartz frequency divider * 2) / note frequency For example, take the note to the first octave and the divisor 256 (CS22 = 1, CS21 = 1, CS20 = 0): OCR2A = 16000000 / (256 * 2) / 261 = 119
I’ll explain right away where the multiplication by 2 came from. The fact is that we selected the “Toggle” mode with the COM2Ax registers, which means that the level change on the foot from low to high (or vice versa) and back will occur in 2 passes of the timer: first the timer reaches the OCR2A value and changes the microcontroller's leg, say, from 1 to 0, drops, and only changes the 0 back to 1 on the second lap. Therefore, 2 timer circles go to each complete wave, respectively, the divider needs to be multiplied by 2, otherwise we will get half the frequency of our note.
From here comes the aforementioned trouble ...
If we take the note BEFORE the big octave and leave the divisor 256: OCR2A = 16000000 / (256 * 2) / 65 = 480 !!! 480 - this number is clearly more than 255 and does not physically fit into the 8-bit OCR2A register.
What to do? Obviously changing the divider, but if we set the divider to 1024, then everything will be fine with a large octave. Problems will begin with the upper octaves: The 4th octave - OCR2A = 16000000 / (1024 * 2) / 3520 = 4 LA sharp fourth octave - OCR2A = 16000000 / (1024 * 2) / 3729 = 4 OCR2A values ​​are no longer different, and therefore the sound also ceases to differ.
There is only one way out: for the frequency of the notes, you need to store not only the values ​​of the OCR2A register, but also the bits of the quartz frequency divider.Sincefor different octaves there will be a different value of the quartz frequency divider, which we will have to set in the TCCR2B register!
Now everything falls into place - and I finally explained why we could not immediately fill the value of the divisor in the timer2_buzzer_init () function.
Unfortunately, the frequency divider is 3 more bits. And they will have to be taken in the second byte of the melody array element.
And for the duration of the note, we have only 5 bits left, so let's calculate the duration.
Duration
First you need to translate the tempo value into time units (for example, in milliseconds) - I did it this way: The duration of the musical measure in ms = (60000 ms * 4 quarters) / tempo value.
Accordingly, if we are talking about tact beats, then this value needs to be divided, and at the beginning I thought that the usual left shift for dividers would be enough. Those. the code was:
Those. I used 3 bits (of the remaining 5) and received parts of the musical beat from powers of 2ki to 1/128. But when I gave my friend a request to write me some kind of ringtone for my piece of iron, there were questions why there is no 1/3 or 1 / 6th and I started thinking ...
In the end, I made a tricky system to get such durations. One bit of the remaining 2x - I spent on a sign of multiplication by 3 for the clock divider, which turned out after the shift. And the last bit is to indicate whether to subtract 1. It's hard to describe, it's easier to see the code:
Total, we have the following format of the array element of our ringtone.
1bit: delay divider - 1
1bit: delay divider * 3
3bit: delay divider shift
3bit: cpu clock divider
8bit: OCR2A value
Only 16 bits.
Dear reader, if you wish, you can dream up the format itself, maybe something more capacious will be born than in me.
We forgot to add another empty note, i.e. silence And finally, I explained why at the very beginning, in the timer2_buzzer_init () function, we specifically set the PB3 foot to the input and not to the output. Changing the register of DDRB, we will turn on and off the playback of "silence" or composition as a whole. Since we cannot have notes with a value of 0 - it will be an “empty” note.
Define the missing macros and the sound generation function:
We have one task left - playing the melody. To do this, we need to “run” through the ringtone array, maintaining the appropriate pauses and switching the frequencies of the notes. Obviously, we need another timer, which, by the way, can be used for other common tasks, as I usually do. Moreover, you can switch between elements of an array either by interrupting this timer, or in the main loop, and the timer can be used to calculate the time. In this example, I used the 2nd option.
As you know, the body of any program for MK includes an infinite loop:
intmain(void){ for(;;) { // } return 0; }
In it we will “run” through our array. But we need a function similar to GetTickCount from WinApi, which returns the number of milliseconds in Windows operating systems. But of course in the world of MK there are no such functions out of the box, so we have to write it ourselves.
Timer 1
To count the time intervals (I intentionally do not write milliseconds, later you will understand why) I used timer 1 together with the already known CTC mode. Timer 1 is a 16-bit timer, which means that the value of the comparison module for it is already indicated by 2 8-bit registers OCR1AH ​​and OCR1AL - for the high and low bytes, respectively. I do not want to describe in detail the work with timer 1, since this does not apply to the main topic of this memo. Therefore, I will tell only in 2 words.
We actually need 3 functions:
Timer initialization
Timer interrupt handler
a function that returns the number of time intervals.
Before I show the header file with a specific constant CTC_MATCH_OVERFLOW, we need to go back a little in time to the “Duration” section and determine the most important macro for a melody, which calculates the melody tempo. I waited a long time to determine it, since it is directly connected with the player, and therefore with timer 1. In the first approximation, it looked like this (see the calculations in the Duration section):
#define TEMPO( x ) (60000 * 4 / x)
The value that we get at the output, we must subsequently substitute the first argument in the function calc_note_delay . Now look closely at the calc_note_delay function, namely the line:
return (precalced_tempo / divider);
We see that the value obtained by calculating the TEMPO macro is divided by a certain divisor. Recall that the maximum divisor that we have defined is DEL_1N128 , i.e. divider will be 128.
Now let's take the common tempo value of 240 and do some simple calculations: 60000 * 4/240 = 1000 Oh God! We only got 1000, in view of the fact that this value will still be divided by 128, we risk to slide to 0, at high values ​​of pace. This is the second problem of duration.
How to solve it? Obviously, in order to expand the range of tempo values, we somehow need to increase the number resulting from the computation of the TEMPO macro. This can be done in only one way - to go away from milliseconds and count the time in certain time intervals. Now you understand why all this time I avoided mentioning “milliseconds” in the story. Let's define another macro:
#define MS_DIVIDER 4
Let it be our millisecond divider - let's divide the millisecond, say, by 4 (250 µs). Then you need to change the macro TEMPO:
#define TEMPO( x ) (60000 * MS_DIVIDER * 4 / x)
Now, with a clear conscience, I will give the header file for working with timer 1:
Now we can, changing MS_DIVIDER, adjust the range for our tasks - I have 4 in the code - this was enough for my tasks. Attention: if you have any other tasks tied to timer 1, do not forget to multiply / divide the time reference values ​​for them by MS_DIVIDER.
Record player
Now we write our player. I think everything will be clear from the code and comments.
I hope that to the distinguished reader and myself this memo will be useful in order not to forget all the nuances of playing tunes, in case I again take AVR microcontrollers in my hands.
Well, traditionally, the video and the source code (I developed it in the Code Blocks environment, so do not be intimidated by incomprehensible files):