📜 ⬆️ ⬇️

Memo. AVR. Buzic


The essence


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 ...

Lyrical digression


My hobby includes the creation of various devices on microcontrollers, because it does not intersect with my prof. activity (software development), I consider myself an absolute self-taught, and in electronics is not too strong. In fact, I prefer PIC microcontrollers, but it just so happens that I have accumulated a certain amount of Atmel AVR microcontrollers (now Microchip). Immediately make a reservation that I never held the AVR in my hands, i.e. This is my first project at Atmel MCU, namely Atmega48pa. The project itself performs some payload, but here I will describe only its part related to the generation of sound frequencies. I called the test to generate frequencies “buzic”, as a short for buzzer's music. Yes, I almost forgot: on Habré there is a user with the nickname buzic , I wanted to immediately warn that this memo does not apply to him and just in case, I immediately apologize for using the letter combination “Buzic”.

So let's go


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:
')
  1. serves a high level on the foot of the microcontroller
  2. make a delay
  3. 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:
  1. TCCR2A - mode settings and behavior selection
  2. TCCR2B - mode settings and timer frequency divider (still FOC bits - we do not use them)
  3. 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.
  1. each measure, consists of 4 quarters
  2. Each melody has a tempo - i.e. the number of such quarters per minute
  3. Each note can be played as the whole whole beat, and its part 1/2, 1/3, 1/4, etc.
  4. 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. Since for 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.

Long live macros
 #define DIV_MASK (_BV(CS20) | _BV(CS21) | _BV(CS22)) #define DIV_1024 (_BV(CS20) | _BV(CS21) | _BV(CS22)) #define DIV_256 (_BV(CS21) | _BV(CS22)) #define DIV_128 (_BV(CS20) | _BV(CS22)) #define DIV_64 _BV(CS22) #define DIV_32 (_BV(CS20) | _BV(CS21)) #define NOTE_1024( x ) ((F_CPU / (1024 * 2) / x) | (DIV_1024 << 8)) #define NOTE_256( x ) ((F_CPU / (256 * 2) / x) | (DIV_256 << 8)) #define NOTE_128( x ) ((F_CPU / (128 * 2) / x) | (DIV_128 << 8)) #define NOTE_64( x ) ((F_CPU / (64 * 2) / x) | (DIV_64 << 8)) #define NOTE_32( x ) ((F_CPU / (32 * 2) / x) | (DIV_32 << 8)) //  #define DOB NOTE_1024( 65 ) #define DO_B NOTE_1024( 69 ) #define REB NOTE_1024 ( 73 ) #define RE_B NOTE_1024 ( 78 ) #define MIB NOTE_1024 ( 82 ) #define FAB NOTE_1024 ( 87 ) #define FA_B NOTE_1024 ( 93 ) #define SOLB NOTE_1024 ( 98 ) #define SOL_B NOTE_1024 ( 104 ) #define LAB NOTE_1024 ( 110 ) #define LA_B NOTE_1024 ( 116 ) #define SIB NOTE_1024 ( 123 ) //  #define DOS NOTE_256( 131 ) #define DO_S NOTE_256( 138 ) #define RES NOTE_256 ( 146 ) #define RE_S NOTE_256 ( 155 ) #define MIS NOTE_256 ( 164 ) #define FAS NOTE_256 ( 174 ) #define FA_S NOTE_256 ( 185 ) #define SOLS NOTE_256 ( 196 ) #define SOL_S NOTE_256 ( 207 ) #define LAS NOTE_256 ( 219 ) #define LA_S NOTE_256 ( 233 ) #define SIS NOTE_256 ( 246 ) //  #define DO1 NOTE_256( 261 ) #define DO_1 NOTE_256( 277 ) #define RE1 NOTE_256 ( 293 ) #define RE_1 NOTE_256 ( 310 ) #define MI1 NOTE_256 ( 329 ) #define FA1 NOTE_256 ( 348 ) #define FA_1 NOTE_256 ( 369 ) #define SOL1 NOTE_256 ( 391 ) #define SOL_1 NOTE_256 ( 414 ) #define LA1 NOTE_256 ( 439 ) #define LA_1 NOTE_256 ( 465 ) #define SI1 NOTE_256 ( 493 ) //  #define DO2 NOTE_128( 522 ) #define DO_2 NOTE_128( 553 ) #define RE2 NOTE_128 ( 586 ) #define RE_2 NOTE_128 ( 621 ) #define MI2 NOTE_128 ( 658 ) #define FA2 NOTE_128 ( 697 ) #define FA_2 NOTE_128 ( 738 ) #define SOL2 NOTE_128 ( 782 ) #define SOL_2 NOTE_128 ( 829 ) #define LA2 NOTE_128 ( 878 ) #define LA_2 NOTE_128 ( 930 ) #define SI2 NOTE_128 ( 985 ) //  #define DO3 NOTE_64( 1047 ) #define DO_3 NOTE_64( 1109 ) #define RE3 NOTE_64 ( 1175 ) #define RE_3 NOTE_64 ( 1245 ) #define MI3 NOTE_64 ( 1319 ) #define FA3 NOTE_64 ( 1397 ) #define FA_3 NOTE_64 ( 1480 ) #define SOL3 NOTE_64 ( 1568 ) #define SOL_3 NOTE_64 ( 1661 ) #define LA3 NOTE_64 ( 1760 ) #define LA_3 NOTE_64 ( 1865 ) #define SI3 NOTE_64 ( 1976 ) //  #define DO4 NOTE_32( 2093 ) #define DO_4 NOTE_32( 2217 ) #define RE4 NOTE_32 ( 2349 ) #define RE_4 NOTE_32 ( 2489 ) #define MI4 NOTE_32 ( 2637 ) #define FA4 NOTE_32 ( 2794 ) #define FA_4 NOTE_32 ( 2960 ) #define SOL4 NOTE_32 ( 3136 ) #define SOL_4 NOTE_32 ( 3322 ) #define LA4 NOTE_32 ( 3520 ) #define LA_4 NOTE_32 ( 3729 ) #define SI4 NOTE_32 ( 3951 ) 



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:

 uint16_t calc_note_delay(uint16_t precalced_tempo, uint16_t note) { return (precalced_tempo / _BV((note >> 11) & 0b00111)); } 


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:
 uint16_t calc_note_delay(uint16_t precalced_tempo, uint16_t note) { note >>= 11; uint8_t divider = _BV(note & 0b00111); note >>= 3; divider *= ((note & 0b01) ? 3 : 1); divider -= (note >> 1); return (precalced_tempo / divider); } 

Then I “defined” all possible (except for those less than 1/128) duration of the notes.
Here they are
 #define DEL_MINUS_1 0b10000 #define DEL_MUL_3 0b01000 #define DEL_1 0 #define DEL_1N2 1 #define DEL_1N3 (2 | DEL_MINUS_1) #define DEL_1N4 2 #define DEL_1N5 (1 | DEL_MINUS_1 | DEL_MUL_3) #define DEL_1N6 (1 | DEL_MUL_3) #define DEL_1N7 (3 | DEL_MINUS_1) #define DEL_1N8 3 #define DEL_1N11 (2 | DEL_MUL_3 | DEL_MINUS_1) #define DEL_1N12 (2 | DEL_MUL_3) #define DEL_1N15 (4 | DEL_MINUS_1) #define DEL_1N16 4 #define DEL_1N23 (3 | DEL_MUL_3 | DEL_MINUS_1) #define DEL_1N24 (3 | DEL_MUL_3) #define DEL_1N31 (5 | DEL_MINUS_1) #define DEL_1N32 5 #define DEL_1N47 (4 | DEL_MUL_3 | DEL_MINUS_1) #define DEL_1N48 (4 | DEL_MUL_3) #define DEL_1N63 (6 | DEL_MINUS_1) #define DEL_1N64 6 #define DEL_1N95 (5 | DEL_MUL_3 | DEL_MINUS_1) #define DEL_1N96 (5 | DEL_MUL_3) #define DEL_1N127 (7 | DEL_MINUS_1) #define DEL_1N128 7 



Putting it all together


Total, we have the following format of the array element of our ringtone.


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:
 #define EMPTY_NOTE 0 #define NOTE(delay, note) (uint16_t)((delay << 11) | note) ........ ........ ........ void play_music_note(uint16_t note) { if (note) { TCCR2B = (note >> 8) & DIV_MASK; OCR2A = note & 0xff; sbi(DDRB, BUZ_PIN); } else cbi(DDRB, BUZ_PIN); } 

Now I will show you how the ringtone looks, written according to this principle:

 const uint16_t king[] PROGMEM = { NOTE(DEL_1N4, MI3), NOTE(DEL_1N4, FA_3), NOTE(DEL_1N4, SOL3), NOTE(DEL_1N4, LA3), NOTE(DEL_1N4, SI3), NOTE(DEL_1N4, SOL3), NOTE(DEL_1N2, SI3), NOTE(DEL_1N4, LA_3), NOTE(DEL_1N4, FA_3), NOTE(DEL_1N4, LA_3), NOTE(DEL_1N4, EMPTY_NOTE), NOTE(DEL_1N4, LA3), NOTE(DEL_1N4, FA3), NOTE(DEL_1N2, LA3), NOTE(DEL_1N4, MI3), NOTE(DEL_1N4, FA_3), NOTE(DEL_1N4, SOL3), NOTE(DEL_1N4, LA3), NOTE(DEL_1N4, SI3), NOTE(DEL_1N4, SOL3), NOTE(DEL_1N4, SI3), NOTE(DEL_1N4, MI4), NOTE(DEL_1N4, RE4), NOTE(DEL_1N4, SI3), NOTE(DEL_1N4, SOL3), NOTE(DEL_1N4, SI3), NOTE(DEL_1N2, RE4), NOTE(DEL_1N2, EMPTY_NOTE), }; 


Playing a ringtone


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:
 int main(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:

C code file
 #include <avr/io.h> #include <avr/interrupt.h> #include <util/atomic.h> #include "timer1_ticks.h" volatile unsigned long timer1_ticks; //  ISR (TIMER1_COMPA_vect) { timer1_ticks++; } void timer1_ticks_init() { //   // CTC ,     8 TCCR1B |= (1 << WGM12) | (1 << CS11); //     OCR1AH = (uint8_t)(CTC_MATCH_OVERFLOW >> 8); OCR1AL = (uint8_t) CTC_MATCH_OVERFLOW; //    TIMSK1 |= (1 << OCIE1A); } unsigned long ticks() { unsigned long ticks_return; //  ,   ticks_return   //     ATOMIC_BLOCK(ATOMIC_FORCEON) { ticks_return = timer1_ticks; } return ticks_return; } 



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:
 #ifndef TIMER1_TICKS_H_INCLUDED #define TIMER1_TICKS_H_INCLUDED #define MS_DIVIDER 4 #define CTC_MATCH_OVERFLOW ((F_CPU / 1000) / (8 * MS_DIVIDER)) void timer1_ticks_init(); unsigned long ticks(); #endif // TIMER1_TICKS_H_INCLUDED 

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.

 int main(void) { timer1_ticks_init(); //   sei(); timer2_buzzer_init(); //    MS_DIVIDER long time_since = ticks(); //       MS_DIVIDER uint16_t note_delay = 0; //     uint16_t note_pos = 0; //  uint16_t length = sizeof(king) / sizeof(king[0]); //     uint16_t tempo = TEMPO(240); for(;;) { unsigned long time_current = ticks(); if (time_current - time_since > note_delay) { //   uint16_t note = pgm_read_word(&king[note_pos]); //   play_music_note(note); //    note_delay = calc_note_delay(tempo, note); //  if (++note_pos >= length) note_pos = 0; time_since = time_current; } } return 0; } 


Conclusion


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):



Sources

Source: https://habr.com/ru/post/402329/


All Articles