Журнал LinuxFormat - перейти на главную

LXF160:Arduino взволнован

Материал из Linuxformat
Версия от 04:00, 30 сентября 2018; Olkol (обсуждение | вклад)

(разн.) ← Предыдущая | Текущая версия (разн.) | Следующая → (разн.)
Перейти к: навигация, поиск

Элек­тро­ни­ка. Ап­па­рат­ные про­ек­ты с от­кры­тым ко­дом, рас­ши­ряющие ваш кру­го­зор.

Содержание

Arduino: Го­ним вол­ну таймером

Хит­ро­ум­но ма­ни­пу­ли­руя с тай­ме­ра­ми, Ник Вейч пре­вра­ща­ет Arduino в ге­не­ра­тор ко­ле­ба­ний.

(thumbnail)
Наш эксперт. Ко­гда LXF толь­ко поя­вил­ся, его дер­жа­ли на пла­ву исключительно скрип­ты Bash от Ни­ка Вей­ча. По­том их за­ме­ни­ли «лю­ди», и это, по мне­нию Ни­ка, ста­ло ша­гом на­зад...

В про­шлый раз мы го­во­ри­ли о... вре­мени, а имен­но – в под­роб­но­стях рас­смот­ре­ли ра­бо­ту со счет­чи­ком Timer1 и уз­на­ли, как с по­мо­щью кое-ка­ких низ­ко­уровневых манипу­ля­ций сде­лать то, что нам нуж­но: за­пустить несколь­ко на­деж­ных про­це­дур пре­ры­вания ти­па ре­ле вре­мени. Но, как это ни бы­ло пре­крас­но, воз­мож­но­сти тай­ме­ра го­раз­до ши­ре, и се­го­дня мы рас­смот­рим еще несколь­ко при­ме­ров его ис­поль­зо­вания. Мо­же­те не тре­во­жить свой ящик с де­та­ля­ми, хо­тя ди­на­мик, по­жалуй, не повре­дит.


Впро­чем, пе­ред тем как пе­рей­ти к са­мим тай­ме­рам, вы­ясним, как они ис­поль­зу­ют­ся в са­мом Arduino. Вы когда-нибудь ин­те­ре­со­ва­лись тем, как чис­то циф­ро­вое уст­рой­ст­во фор­ми­ру­ет ана­ло­го­вый вы­ход? Этот спе­ци­аль­ный ре­жим вы­ход­но­го сиг­на­ла мож­но вклю­чить на вы­во­дах 3, 5, 6, 9, 10 и 11 Arduino на ба­зе Atmega168 и Atmega368. При за­пи­си, на­при­мер, зна­чения 128 в один из этих вы­во­дов зна­чение вы­ход­но­го сиг­на­ла ока­зы­ва­ет­ся рав­ным (в дан­ном слу­чае) око­ло 2,5 В.

Про­ис­хо­дит сле­дую­щее: на ко­рот­кий ин­тер­вал вре­мени вы­вод вклю­ча­ет­ся и за­тем сно­ва вы­клю­ча­ет­ся. Он генери­ру­ет пря­мо­уголь­ное ко­ле­бание, ко­то­рое в среднем об­ра­зу­ет сиг­нал на­пря­жением 2,5 В. В ре­аль­но­сти это лишь очень бы­строе пе­ре­клю­чение ме­ж­ду 5 В и 0 В. Зна­чение, ко­то­рое вы за­пи­сы­вае­те, оп­ре­де­ля­ет со­от­но­шение вре­мени в со­стоянии ВКЛ ко вре­мени в со­стоянии ВЫКЛ (ко­эф­фи­ци­ент за­полнения, час­то вы­ра­жае­мый в про­цен­тах). Как и при лов­ко­сти рук, ско­рость здесь – важ­ный фак­тор. Хо­тя среднее на­пря­жение, неза­ви­си­мо от то­го, вклю­чать и вы­клю­чать его два­ж­ды или две­сти раз в се­кун­ду (при том же со­от­но­шении про­дол­жи­тель­но­сти ВКЛ/ВЫКЛ), бу­дет оди­на­ко­во, в пер­вом слу­чае вы по­лу­чи­те ми­гаю­щий свет, а во вто­ром свет ока­жет­ся туск­лым (на са­мом де­ле он про­сто ми­га­ет очень бы­ст­ро). Обыч­но час­то­та пря­мо­уголь­ных ко­ле­баний со­став­ля­ет око­ло 500 Гц при вы­зо­ве функ­ции digitalWrite(), но мож­но из­менить са­ми ча­сы, что­бы по­лу­чить дру­гие зна­чения.

Мо­жет иметь зна­чение основ­ная час­то­та ши­рот­но-им­пульс­ной мо­ду­ля­ции (ШИМ). На­стоя­щие элек­трон­ные ком­понен­ты су­ще­ст­ву­ют в ре­аль­ном ми­ре, и хо­тя вре­мя на­рас­тания сиг­на­ла (тре­буе­мое для из­менения вы­ход­но­го сиг­на­ла с ну­ля [LOW] до единицы [HIGH]) в Arduino из­ме­ря­ет­ся в на­но­се­кун­дах, вре­мя, необ­хо­ди­мое для вклю­чения или вы­клю­чения элек­тро­магнита, лег­ко мо­жет быть в 1000 раз боль­ше. По­это­му иногда основ­ную час­то­ту ШИМ нуж­но из­менить. Для вы­во­дов 11 и 3, ис­поль­зую­щих встро­ен­ный Timer2, это не по­влия­ет на осталь­ную часть ва­шей про­грам­мы. А вот для вы­во­дов 5 и 6 из­менение час­то­ты (т. е. из­менение зна­чения пред­ва­ри­тель­но­го де­ли­те­ля час­то­ты) име­ет по­боч­ный эф­фект. С по­мо­щью им­пуль­сов это­го тай­ме­ра ра­бо­та­ют функ­ции Arduino millis() и delay(), по­это­му умень­шение час­то­ты мо­жет уве­ли­чить за­держ­ки.

Ос­но­вы ШИМ

Тай­мер управ­ля­ет ре­жи­мом ШИМ вполне оче­вид­ным об­ра­зом. Тай­мер под­счи­ты­ва­ет пе­рио­ды сиг­на­ла ВКЛ до сво­его пре­де­ла в 255 (для Timer2) и за­тем пе­ре­пол­ня­ет­ся, сно­ва на­чи­ная от­счет с ну­ля. Мож­но на­стро­ить ре­гистр сравнения на лю­бое из зна­чений от 0 до 255, и когда счет­чик достигнет вы­бран­но­го зна­чения, в за­ви­си­мо­сти от за­дан­но­го ре­жи­ма мо­гут быть вы­полнены раз­лич­ные дей­ст­вия, обыч­но вклю­чаю­щие ав­то­ма­ти­че­скую уста­нов­ку зна­чения оп­ре­де­лен­но­го вы­во­да.

Как всегда на мик­ро­схе­мах Atmega, есть несколь­ко раз­лич­ных ва­ри­ан­тов воз­мож­ных дей­ст­вий. В са­мом про­стом слу­чае ре­гистр сравнения пе­ре­клю­ча­ет вы­ход в «единицу» и сбра­сы­ва­ет­ся сно­ва при пе­ре­за­пуске счет­чи­ка. Тогда вы­со­кое зна­чение ре­ги­ст­ра сравнения оз­на­ча­ет боль­ше вре­мени «вы­клю­че­но» и мень­ше «вклю­че­но». Так­же мож­но ис­поль­зо­вать один из ре­ги­ст­ров сравнения в ка­че­­ст­ве верхнего пре­де­ла счет­чи­ка, та­ким об­ра­зом бо­лее точ­но от­ре­гу­ли­ро­вав его час­то­ту (а так­же вы­чис­ления ко­эф­фи­ци­ен­та за­полнения).

Мы бу­дем поль­зо­вать­ся ШИМ с кор­рек­ци­ей фа­зы, по­ла­гая, что сиг­нал дости­га­ет выс­шей точ­ки ров­но по­сре­ди вре­мен­но­го ин­тер­ва­ла. Это оз­на­ча­ет и то, что сгенери­ро­ван­ный сиг­нал име­ет са­мый низ­кий уро­вень в на­ча­ле и в кон­це это­го вре­мен­но­го ин­тер­ва­ла. Для генера­ции сиг­на­лов пра­виль­ной фор­мы, на­при­мер, для управ­ления дви­га­те­лем или да­же воспро­из­ве­дения зву­ка, это жизнен­но важ­но.

В ре­жи­ме кор­рек­ции фа­зы счет­чик ШИМ ве­дет се­бя ина­че – сна­ча­ла он досчи­ты­ва­ет до 255, а по­том вме­сто сбро­са на­чи­на­ет счи­тать об­рат­но до ну­ля. Ес­ли ре­гистр сравнения на­стро­ен на из­менение вы­ход­но­го сиг­на­ла в обо­их на­прав­лениях, то в ка­ж­дом на­прав­лении на со­стояния ВКЛ и ВЫКЛ при­хо­дят­ся оди­на­ко­вые ин­тер­ва­лы вре­мени, и ко­эф­фи­ци­ент за­полнения со­хра­ня­ет­ся. Яв­ный недоста­ток это­го ре­жи­ма в том, что эф­фек­тив­ная час­то­та умень­ша­ет­ся вдвое. Впро­чем, это не долж­но стать про­бле­мой для нас, так как при час­то­те про­цес­со­ра 16 МГц по­ло­са оста­ет­ся при­ем­ле­мой для ау­дио- и дру­гих цен­ных сиг­на­лов.

Боль­ше шу­ма

Мы мо­жем восполь­зо­вать­ся воз­мож­но­стя­ми ШИМ Timer2 для генера­ции зву­ка. В пре­ды­ду­щих экс­пе­ри­мен­тах со зву­ком мы по­ла­га­лись толь­ко на из­ры­гаю­щую би­ты встро­ен­ную функ­цию tone(), ко­то­рая фор­ми­ру­ет при­ем­ле­мые, раз­ве что зву­ча­щие сер­ди­то, пря­мо­уголь­ные ко­ле­бания на за­дан­ном вы­во­де. Но с воз­мож­но­стя­ми ШИМ и неболь­шим хит­рым ко­дом мы мо­жем соз­да­вать звук лю­бой фор­мы и раз­ме­ра. Конеч­но, хо­тя мы счи­та­ем этот сиг­нал зву­ко­вым, фак­ти­че­­ски мы соз­да­ем сиг­на­лы, при­год­ные в са­мых раз­ных си­туа­ци­ях – как для тес­ти­ро­вания дру­го­го обо­ру­до­вания, так и для непо­сред­ст­вен­но­го управ­ления дви­га­те­ля­ми.

Для генера­ции ко­ле­баний мы мо­жем ими­ти­ро­вать ана­ло­го­вые зна­чения, ис­поль­зуя ШИМ на вы­со­кой час­то­те для вклю­чения и вы­клю­чения вы­во­да. Но нуж­но за­дать это зна­чение на­деж­но. Тут нам по­мо­жет тот же са­мый тай­мер. Как и у Timer1, ко­то­рым мы поль­зо­ва­лись в пре­ды­ду­щей ста­тье, у Timer2 есть век­тор пре­ры­ваний. В ре­жи­ме ШИМ с кор­рек­ци­ей фа­зы его мож­но за­ста­вить сра­ба­ты­вать по за­вер­шению пол­но­го цик­ла счет­чи­ка (т. е. когда счет­чик досчи­тал до 255 и вер­нул­ся об­рат­но к 0). Свя­зав с этим про­це­ду­ру об­ра­бот­ки пре­ры­вания, при необ­хо­ди­мо­сти мож­но ме­нять зна­чение вы­во­да ШИМ на ка­ж­дом цик­ле вы­во­да.

Пол­ный цикл тай­ме­ра со­став­ля­ет 510 так­тов (конеч­ные зна­чения счи­та­ют­ся толь­ко один раз), и это да­ет нам 16 МГц/510 = 31,372 кГц – бо­лее чем доста­точ­но для соз­дания ау­дио­сиг­на­ла. Как и в про­шлый раз, для за­дания кор­рект­ных зна­чений тай­ме­ра необ­хо­ди­мо немно­го по­ра­бо­тать со спе­ци­аль­ны­ми ре­ги­ст­ра­ми мик­ро­схе­мы Atmega. Вот код для уста­нов­ки тай­ме­ра:

TCCR2A = _BV(COM2B1) | _BV(WGM20);

TCCR2B = _BV(CS20);

TIMSK2 = _BV(TOIE2);

pinMode(3,OUTPUT);

Ес­ли вы не чи­та­ли про­шлой ста­тьи, то вряд ли что-то по­ня­ли. Мы не бу­дем воз­вра­щать­ся к по­би­то­вой ариф­ме­ти­ке (то, что де­ла­ет сим­вол |), но немно­го по­яснить про­ис­хо­дя­щее все же сто­ит. TCCR2A и TCCR2B – два 8-би­то­вых ре­ги­ст­ра (т. е. две спе­ци­аль­ные об­лас­ти па­мя­ти), ко­то­рые со­дер­жат зна­чения, управ­ляю­щие ра­бо­той тай­ме­ра. Сре­ди про­че­го они оп­ре­де­ля­ют, ка­кой ре­жим ШИМ ис­поль­зу­ет­ся и как бы­ст­ро он ра­бо­та­ет. Ка­ж­дый бит или на­бор би­тов за­да­ет оп­ре­де­лен­ный ас­пект ра­бо­ты тай­ме­ра. Ус­та­нов­лен­ный бит COM2B1 го­во­рит ком­па­ра­то­ру B, что в ре­жи­ме ШИМ с кор­рек­ци­ей фа­зы нуж­но сбро­сить вы­ход в ноль при дости­жении верхнего пре­де­ла и уста­но­вить его в зна­чение верхнего пре­де­ла при дости­жении ну­ля (об­рат­ный ре­жим). Бит WGM20 пе­ре­во­дит тай­мер в ре­жим ШИМ с кор­рек­ци­ей фа­зы. Три менее важ­ных би­та в TCCR2B уста­нав­ли­ва­ют зна­чение де­ли­те­ля час­то­ты тай­ме­ра (сколь­ко ти­ков сис­тем­ных ча­сов нуж­но ждать для об­нов­ления зна­чения тай­ме­ра). Ес­ли уста­но­вить толь­ко бит CS20, тай­мер бу­дет ра­бо­тать с той же час­то­той, что и сис­тем­ные ча­сы (а сброс всех би­тов в ноль от­клю­ча­ет тай­мер). TIMSK2 – дру­гой ре­гистр, ко­то­рый на сей раз со­дер­жит мас­ку пре­ры­ваний. При возник­но­вении оп­ре­де­лен­но­го со­бы­тия (на­при­мер, дости­жения тай­ме­ром кон­ца цик­ла) про­цес­сор счи­ты­ва­ет зна­чение это­го ре­ги­ст­ра, что­бы по­смот­реть, на­зна­че­но ли пре­ры­вание для это­го со­бы­тия. Ес­ли да, он об­ра­ща­ет­ся к век­то­ру пре­ры­ваний, дру­гой спе­ци­аль­ной об­лас­ти па­мя­ти, в ко­то­рой хранит­ся ад­рес про­це­ду­ры, ко­то­рую нуж­но за­пустить.

На­конец, мы на­зна­ча­ем вы­ход­ной сиг­нал на циф­ро­вой вы­вод 3. По­че­му на него? А он на­пря­мую управ­ля­ет­ся ре­ги­ст­ром сравнения B тай­ме­ра Timer2. Же­лая восполь­зо­вать­ся вы­во­дом 11, мож­но взять вме­сто него ре­гистр сравнения A тай­ме­ра Timer2. Толь­ко два этих вы­во­да управ­ля­ют­ся на­пря­мую с тай­ме­ра.

Пре­ры­вание

Что­бы кор­рект­но за­дать пре­ры­вание, восполь­зу­ем­ся спе­ци­аль­ным мак­ро­сом ISR(). Де­ло в том, что ад­рес пре­ры­вания нуж­но за­гру­жать в век­тор пре­ры­вания, но по­ка мы не ском­пи­ли­ро­ва­ли про­грам­му, этот ад­рес неиз­вес­тен. Этот вол­шеб­ный мак­рос по­за­бо­тит­ся обо всем; нуж­но лишь объ­я­вить сле­дую­щую про­це­ду­ру:

ISR(TIMER2_OVF_vect){

}

Ука­зан­ный бит го­во­рит мак­ро­су, с ка­ким век­то­ром мы хо­тим свя­зать эту про­це­ду­ру. Те­перь нуж­но толь­ко за­полнить ее. Раз уж мы воз­же­ла­ли иметь несколь­ко раз­лич­ных ти­пов ко­ле­баний, есть смысл восполь­зо­вать­ся кон­ст­рук­ци­ей switch ... case. В язы­ке C она по­зво­ля­ет вы­пол­нять раз­лич­ные бло­ки ко­да в за­ви­си­мо­сти от зна­чения од­ной пе­ре­мен­ной. Она ра­бо­та­ет так:

switch(variable) {

case 1:

// что-ни­будь де­ла­ем

break;

case 2:

// что-ни­будь де­ла­ем

break;

default:

// ос­таль­ное не по­до­шло, сде­ла­ем это

}

Ес­ли зна­чение пе­ре­мен­ной рав­но 1, вы­пол­ня­ет­ся пер­вый блок ко­да, и т. д. Ес­ли это зна­чение не со­от­вет­ст­ву­ет ни од­но­му из бло­ков, вы­пол­ня­ет­ся блок default, но он не обя­за­те­лен. Ка­ж­дый блок сле­ду­ет за­вер­шать опе­ра­то­ром break;, по ко­то­ро­му про­ис­хо­дит вы­ход из всей струк­ту­ры, в про­тив­ном слу­чае пе­ре­мен­ная про­дол­жит сравнивать­ся с ка­ж­дым бло­ком (и в ито­ге вы­полнит­ся код по умол­чанию). По­это­му все, что нам нуж­но – пе­ре­мен­ная, ко­то­рая ска­жет нам, ка­кой сиг­нал мы генери­ру­ем. Пе­ре­мен­ная долж­на быть гло­баль­ной и за­да­вать­ся вне этой кон­ст­рук­ции, что­бы мож­но бы­ло уста­но­вить ее в основ­ном ко­де. На­зо­вем ее wave.waveform.

Да, и нуж­но сде­лать кое-что еще. Хо­тя код вы­зо­вет­ся точ­но в нуж­ный мо­мент вре­мени по пре­ры­ванию, на его вы­полнение мо­жет по­тре­бо­вать­ся раз­ное вре­мя, в за­ви­си­мо­сти от то­го, ка­кой блок ко­да бу­дет вы­полнен. Что­бы из­бе­жать дро­жаний вы­ход­но­го сиг­на­ла, мы долж­ны по­дать зна­чение ре­ги­ст­ра сравнения (т. е. на­шу ве­ли­чи­ну от 0 до 255) на вы­ход с са­мо­го на­ча­ла. За­тем мы мо­жем восполь­зо­вать­ся остав­шей­ся ча­стью пре­ры­вания для генера­ции вы­ход­но­го зна­чения для сле­дую­ще­го раза. У ком­па­ра­то­ра есть спе­ци­аль­ный ад­рес, но, к сча­стью, есть и услов­ное зна­чение, в ко­то­рое его мож­но уста­но­вить. Вы­ход­ное зна­чение бу­дет еще од­ной из этих нуд­ных гло­баль­ных пе­ре­мен­ных, по­это­му на­зо­вем его wave.output. На­ча­ло на­шей функ­ции те­перь вы­гля­дит так:

ISR(TIMER2_OVF_vect){

OCR2B = wave.output;

switch(wave.waveform) {

case 1: // пря­мо­уголь­ная вол­на

Да, мы на­чи­на­ем с пря­мо­уголь­ных ко­ле­баний! Ну, да, да, мы мог­ли сде­лать это и рань­ше, но не на тех час­то­тах, ко­то­рые те­перь у нас в рас­по­ря­жении! Пря­мо­уголь­ные ко­ле­бания до­воль­но про­сты: по­лпе­рио­да вол­на про­во­дит ввер­ху, дру­гие по­лпе­рио­да – внизу. Что­бы оп­ре­де­лить, когда ее вклю­чать и вы­клю­чать, нуж­но знать час­то­ту воспро­из­водимой но­ты(wave.frequency) и ко­ли­че­­ст­во ма­лень­ких пор­ций вре­мени, ко­то­рые уже про­шли (м-мм, wave.f_counter?). По­сле это­го генера­ция вол­ны сво­дит­ся к про­вер­ке, в ка­кой вре­мен­ной до­ле мы на­хо­дим­ся, оп­ре­де­лении, не во вто­рой ли мы по­ло­вине пол­но­го цик­ла, и вклю­чении или вы­клю­чении сиг­на­ла. Как мы зна­ем, ка­ж­дая вре­мен­ная до­ля со­став­ля­ет 1/31372,5 се­кун­ды, и де­ло лишь за неболь­шой ариф­ме­ти­кой:

case 1: // пря­мо­уголь­ная вол­на

wave.f_counter++;

if (wave.f_counter<(15686/wave.frequency)){

// пер­вая по­ло­ви­на цик­ла – вы­ход мак­си­ма­лен

wave.output=wave.volume;

}

else{

// вто­рая по­ло­ви­на цик­ла – вы­ход = 0

wave.output=0;

}

if (wave.f_counter>(31372.5/wave.frequency)){

// цикл за­вер­шен, сбо­рос счет­чи­ка

wave.f_counter=0;

}

break;

Да, мы вбро­си­ли ту­да и wave.volume – по­че­му бы нет? Тогда мы смо­жем управ­лять еще и ам­пли­ту­дой пря­мо­уголь­ных коле­баний. Мо­жет быть, сей­час вас бес­по­ко­ит, от­ку­да бу­дут брать­ся эти зна­чения и как мы бу­дем ими поль­зо­вать­ся. Не вол­нуй­тесь, имен­но так и сто­ит пи­сать код. Те­перь у нас есть все необ­хо­ди­мые па­ра­мет­ры, и мож­но соз­дать спе­ци­аль­ную пе­ре­мен­ную, где все они бу­дут хранить­ся. В пре­ды­ду­щих ру­ко­во­д­ствах мы рас­смат­ри­ва­ли струк­ту­ры, и они до­воль­но про­сты. Это лишь спо­соб за­дать со­став­ной тип дан­ных – нечто вро­де клас­са, но без ме­то­дов.

struct generator{

bool active;

uint8_t waveform;

uint16_t frequency;

uint16_t f_counter;

uint16_t duration;

uint16_t table_value;

double table_inc;

uint8_t output;

uint8_t volume;

} wave;

Мож­но по­рез­вить­ся с неко­то­ры­ми пе­ре­мен­ны­ми и ти­па­ми, но в це­лом это очень по­хо­же на го­то­вое управ­ление зву­ком (к таб­лич­ным дан­ным пе­рей­дем че­рез ми­ну­ту). Как ви­ди­те, в конец объ­яв­ления струк­ту­ры мы по­мес­ти­ли имя wave. Это зна­чит, что бу­дет соз­дан один эк­зем­п­ляр струк­ту­ры с именем wave, и для досту­па к ее эле­мен­там мы мо­жем ис­поль­зо­вать кон­ст­рук­ции wave.waveform, wave.frequency... весь­ма удоб­но, по­то­му что мы уже на­пи­са­ли код, ко­то­рый де­ла­ет имен­но это.

Мож­но ра­бо­тать с час­тя­ми этой пе­ре­мен­ной по от­дель­но­сти, но луч­ше соз­дать вспо­мо­га­тель­ную функ­цию для за­дания вы­ход­но­го зна­чения. Она мо­жет принимать один ар­гу­мент (час­то­ту) и уста­нав­ли­вать все необ­хо­ди­мые зна­чения сле­дую­щим об­ра­зом:

void playSquare (uint16_t f){

wave.waveform=1;

wave.frequency=f;

wave.f_counter=0;

wave.active=true;

wave.volume=255;

};

Здесь мы при­сваи­ва­ем бу­лев­ское зна­чение пе­ре­мен­ной wave.active. Ее мож­но ис­поль­зо­вать в ка­че­­ст­ве клю­ча в на­ча­ле про­це­ду­ры пре­ры­вания – ес­ли вол­на вы­клю­че­на, то ника­ких дей­ст­вий пред­принимать не нуж­но. Это по­мо­жет сбе­речь несколь­ко так­тов про­цес­со­ра для дру­гих дей­ст­вий.

Arduino 1.0!

Да, по­сле несколь­ких лет пре­бы­вания в вер­сии «ноль точ­ка», ко­ман­да Arduino, на­конец, со­чла ПО достой­ным при­своения ему но­ме­ра 1.0. Ес­ли вы еще не об­но­ви­лись до этой вер­сии, по­спе­ши­те. Од­на из мел­ких, но важ­ных ре­форм – сме­на рас­ши­рения по умол­чанию для про­ек­тов с ис­ход­ным ко­дом с .pde на .ino, что­бы ис­клю­чить воз­мож­ные кон­флик­ты рас­ши­рений. Фор­мат фай­ла не из­менил­ся, но вы об­на­ру­жи­те, что ста­рые вер­сии про­грамм не рас­по­зна­ют но­вое рас­ши­рение, и что­бы от­крыть файл в но­вой вер­сии про­грам­мы, по­на­до­бит­ся ко­пи­ро­вание и встав­ка. В но­вой вер­сии мож­но сде­лать так, что­бы тип фай­ла ав­то­ма­ти­че­­ски об­нов­лял­ся для бо­лее ста­рых про­ек­тов при их по­втор­ном со­хранении, и это, по­жа­луй, бу­дет удоб­но.

> В но­вой вер­сии Arduino есть не­мно­го кос­ме­ти­че­ских из­ме­не­ний, а так­же не­сколь­ко дол­го­ждан­ных ре­форм в биб­лио­те­ках.

Глянь­те-ка в таб­ли­цу!

До по­яв­ления каль­ку­ля­то­ров у ка­ж­до­го школьника (и, на­вер­ное, школьницы, хо­тя я не хо­дил в та­кую шко­лу) бы­ла книж­ка с таб­ли­цей ло­га­риф­мов. Ка­ж­дый раз, когда вам ну­жен был ло­га­рифм чис­ла, вы за­гля­ды­ва­ли в эту таб­ли­цу с мелки циф­ра­ми, ища мак­си­маль­но близ­кое.

Ныне нам не нуж­ны да­же каль­ку­ля­то­ры – у нас есть ком­пь­ю­те­ры, и боль­шин­ст­во лю­дей и не упом­нят, что та­кое ло­га­рифм. Но по иронии судь­бы по­иск чи­сел в таб­ли­це, неве­ро­ят­но скуч­ный для нас, для ком­пь­ю­те­ра час­то го­раз­до бы­ст­рее (в за­ви­си­мо­сти от тре­буе­мых вы­чис­лений). Рас­чет ло­га­риф­ми­че­­ских и три­го­но­мет­ри­че­­ских зна­чений за­тра­тен, и, что иногда важ­но, для его вы­полнения мо­жет тре­бо­вать­ся раз­ное вре­мя. Взять зна­чение в таб­ли­це на­деж­но, и вре­мя про­цес­со­ра на это всегда тре­бу­ет­ся оди­на­ко­вое.

Соз­дание таб­ли­цы с ре­зуль­та­та­ми и по­иск в ней очень эф­фек­ти­вен, осо­бен­но когда де­ло до­хо­дит до мик­ро­кон­трол­ле­ров, где нет со­про­цес­со­ра для ра­бо­ты с пла­ваю­щей точ­кой. Един­ст­вен­ный недоста­ток – нуж­но сгенери­ро­вать и хранить таб­ли­цу с дан­ны­ми, и ес­ли вам нуж­на очень вы­со­кая точ­ность или у вас нет за­пас­ной опе­ра­тив­ной па­мя­ти, воз­мож­но, сто­ит вы­пол­нять вы­чис­ления на ле­ту. Во всех осталь­ных слу­ча­ев без таб­лиц со­от­вет­ст­вия не обой­тись.

Объ­яснения еще нуж­ны? Хо­ро­шо. Таб­ли­ца, по су­ти, мас­сив чи­сел, а ин­декс – зна­чение, для ко­то­ро­го вы ище­те ре­зуль­тат. Конеч­но, роль иг­ра­ют несколь­ко фак­то­ров – чис­ло зна­чений в мас­си­ве, или, ес­ли угод­но, раз­мер ша­га, за­ви­сит от тре­буе­мой точ­но­сти дан­ных (нечто вро­де бит­рей­та в му­зы­ке). Точ­ность мож­но слег­ка уве­ли­чить, де­лая ин­тер­по­ля­цию ме­ж­ду дву­мя зна­чения­ми из таб­ли­цы, но для это­го нуж­ны до­ба­воч­ные вы­чис­ли­тель­ные ре­сур­сы.

Един­ст­вен­ное ог­раничение для таб­ли­цы со­от­вет­ст­вия – в том, что ин­декс дол­жен быть це­лым чис­лом: ведь нель­зя же по­лу­чить зна­чение 3,82-го эле­мен­та в спи­ске. Но это мо­жет быть и бла­гом! Хо­тя точ­ность при этом снижа­ет­ся, хранение чи­сел без пла­ваю­щей точ­ки де­ла­ет их (обыч­но) мень­ше, и ра­бо­тать с ними ста­но­вит­ся оп­ре­де­лен­но бы­ст­рее. Ес­ли вам хватает двух зна­ков по­сле за­пя­той, за­чем вам че­ты­ре или пять, ко­то­рые мо­гут дать чис­ла с пла­ваю­щей точ­кой? Ум­но­жай­те все на 100 и поль­зуй­тесь це­лы­ми чис­ла­ми! Или, для бо­лее эф­фек­тив­но­го ис­поль­зо­вания про­стран­ст­ва, ис­поль­зуй­те чис­ла до 255, ко­то­рые ак­ку­рат­но уме­ща­ют­ся в один байт.

Си­ну­сит вре­мен

Конеч­но, есть и бо­лее на­ту­раль­ные вол­ны – хо­тя бы тре­уголь­ные и си­ну­сои­даль­ные вол­ны. Си­ну­сои­даль­ная вол­на луч­ше все­го генери­ру­ет­ся с по­мо­щью таб­ли­цы зна­чений (см. врез­ку). Ма­те­ма­ти­че­­ские рас­че­ты для вы­чис­ления зна­чений на ле­ту слиш­ком за­трат­ны по вре­мени. Помните, на­ши вре­мен­ные до­ли рав­ны 1/31372,5 се­кун­ды – а на вы­полнение про­це­ду­ры от­во­дит­ся не бо­лее дан­но­го ин­тер­ва­ла, и нуж­но эко­но­мить! Оп­ре­де­лить по­ло­жение в таб­ли­це, най­ти зна­чение и ин­кре­мен­ти­ро­вать несколь­ко счет­чи­ков – это все­го несколь­ко так­тов про­цес­со­ра (из воз­мож­ных 510, ко­то­рые у нас есть), так что это про­стой под­ход.

Как оп­ре­де­лить на­ше по­ло­жение в таб­ли­це? Нам из­вес­тен раз­мер таб­ли­цы и час­то­та воспро­из­во­ди­мой но­ты – доста­точ­но вы­полнить ум­но­жение, де­ление и по­смот­реть на оста­ток. Нам не нуж­но счи­тать вре­мен­ные до­ли – все слож­ные вы­чис­ления мож­но вы­полнить за­ранее при за­дании па­ра­мет­ров вол­ны; у нас долж­ны быть толь­ко счет­чик ин­дек­са и пе­ре­мен­ная, со­дер­жа­щая ко­ли­че­­ст­во би­тов ин­дек­са, ко­то­рые нуж­но про­пускать с ка­ж­дой до­лей. Мы де­лим зна­чение счет­чи­ка на 256 и ис­поль­зу­ем оста­ток для по­лу­чения сле­дую­ще­го зна­чения. Ес­ли необ­хо­ди­ма боль­шая точ­ность, мож­но сде­лать эти зна­чения дей­ст­ви­тель­ны­ми (с пла­ваю­щей точ­кой) и ра­бо­тать с до­ля­ми зна­чений ин­дек­са (по­лу­чить два и ин­тер­по­ли­ро­вать), но раз­мер таб­ли­цы в лю­бом слу­чае дол­жен быть неве­лик – для хранения боль­шей таб­ли­цы эле­мен­тар­но не хва­тит па­мя­ти. По­это­му те­перь слож­ные рас­че­ты вы­пол­ня­ют­ся во вспо­мо­га­тель­ной функ­ции:

void playSine (uint16_t f){

wave.waveform=3;

wave.frequency=f;

wave.f_counter=0;

wave.table_value=0;

wave.table_inc=(255.0/(31372.54/wave.frequency));

wave.active=true;

}

Но реа­ли­зо­вать ее в пре­ры­ва­нии про­сто:

case 3: // си­ну­сои­даль­ная вол­на

wave.output=sinLUT[wave.table_value%256];

wave.table_value += wave.table_inc;

break;

Ни­че­го не за­бы­ли? Ах да, са­ма таб­ли­ца. Ну, это про­сто стоп­ка чи­сел – ее мож­но най­ти на DVD. Тре­уголь­ная вол­на так­же реа­ли­зо­ва­на в таб­ли­це со­от­вет­ст­вия – лениво, да? Но хо­тя зна­чения для тре­уголь­ной вол­ны доста­точ­но про­сто рас­счи­тать, ра­бо­тать с таб­ли­цей все рав­но бы­ст­рее! Ес­ли вы хо­ти­те по­слу­шать неко­то­рые зву­ко­вые час­то­ты, под­клю­чи­те ди­на­мик ме­ж­ду вы­во­дом 3 и зем­лей, же­ла­тель­но с кон­ден­са­то­ром. |

Персональные инструменты
купить
подписаться
Яндекс.Метрика