DAC w Arduino czyli Covox na ratunek

Wiele miejsca w różnych tutorialach poświęca się tematowi konwersji sygnałów analogowych na cyfrowe. Mówiąc inaczej  – o mierzeniu napięcia. Tym razem pomówimy o procesie odwrotnym, czyli zamianie wartości cyfrowej na analogową (napięcie).

Jak mierzeniem napięcia zajmują się przetworniki analogowo-cyfrowe (ADC – Analog Digital Converter), to w drugą stronę taki przetwornik nazywamy cyfrowo-analogowym (DAC – Digital Analog Converter). ADC znajdziesz w każdym praktycznie mikrokontrolerze. DAC już jest czymś rzadszym (ale np Teensy 3.2 ma taki jeden 12-sto bitowy).

Po co może być potrzebny DAC? Najczęściej gdy mowa jest o dźwięku. Jeśli obracamy się w kontekście Arduino, to ma ono przecież funkcje tone()  i można znaleźć wiele przykładów jak zagrać melodyjkę na buzzerze. Tak, tyle że tone generuje sygnał 0/1 z odpowiednią częstotliwością i buzzer  tak pobudzony wydaje dźwięki. Ale jeżeli zapragniemy sygnału o większych subtelnościach niż 0/5V to musimy zapomnieć o tej funkcji z Arduino.

Od razu powiem, że nie próbujemy tutaj stworzyć audiofilskiego urządzenia. Z wielu powodów sygnał będzie daleki od ideału, ale i tak dużo lepiej niż z jednym pinem i tone().

Zgodnie z tym co przed chwilą napisałem, do konwersji będziemy potrzebować większej ilości pinów. Ile? Tu się musisz zastanowić jakiej rozdzielczości potrzebujesz. Czyli jak duże (lub małe) kroki w zadawaniu napięcia potrzebujesz. Tak jak konwertujesz napięcie w ADC do wartości liczbowej z pewną dokładnością, tak i w drugą stronę – nie uzyskasz dokładnie każdej wartości napięcia tylko określoną ich liczbę.

Wyjście cyfrowe jest skrajnym przypadkiem – mamy dwie wartości napięcia 0V i 5V (mówimy o zwykłym Arduino). To jest jeden bit. Jeśli weźmiemy dwa bity to powinniśmy mieć możliwość ustawienia 4 wartości. Mniej więcej 0, 1.66V, 3.32V i 5V. I tak dokładając kolejne bity zwiększa się liczba dostępnych napięć (milcząco zakładamy że nasz przetwornik jest liniowy, tzn każdy krok zwiększa napięcie o taką samą wartość).

Podchodząc do projektu założyłem, że 5 bitów rozdzielczości powinno być wystarczające. Daje to 32 wartości, czyli krok napięcia około 0.16V. Przynajmniej w teorii, bo duży wpływ na to czy krok tyle wyniesie, będzie miała precyzja elementów których użyjemy.

Jak zamienić bity na napięcie? Są oczywiście dedykowane układy które się tym zajmują. My jednak zgodnie z duchem tego bloga będziemy szukać rozwiązania typu DIY :) ma być tanio i prosto. Jednym z najtańszych elementów są rezystory. I korzystając z rezystorów zbudujemy dzielnik napięcia, nieco bardziej rozbudowany.

I teraz powinno być sporo teorii. Rezystancja zastępcza, przekształcanie sieci rezystorów tak by uzyskać jedną wartość. Wszyscy studenci kierunków elektronika/elektrotechnika itp na pewno się z tym spotkali i mają złe wspomnienia z kolokwiów ;) Więc będzie bez szczegółów, musicie uwierzyć na słowo.

Szukałem dobrego wytłumaczenia w sieci, ale po polsku nie znalazłem, jeśli znasz angielski to polecam przeczytać o DAC R/2nR na AllAboutCircuits oraz o DAC R/2R. Jeśli chodzi R/2R to trochę teorii o której pisałem w poprzednim akapicie znajdziecie wytłumaczonej na blogu Tektronika.

Czyli, budujemy przetwornik DAC typu R/2R. Takim urządzeniem (tyle, że 8-mio bitowym) była jedna z pierwszych kart dźwiękowych do PCta. Nazywała się COVOX i często można w sieci jest taka nazwa używana jako synonim konwertera DAC.

Całość wygląda tak:

Drabinka została podłączona do portów A0-A4. Jeśli jest to dla Ciebie zaskoczenie, to wiedz, że porty analogowe w Arduino mogą być używane jak zwykłe porty cyfrowe.

Źródłem dźwięku jest buzzer lub głośniczek. Pomiędzy drabinką a buzzerem pojawia się jeszcze jeden element. To wzmacniacz operacyjny. Po co on jest? Z punktu widzenia rezystorów wzmacniacz operacyjny stanowi „izolację” od rezystancji buzzera/głośnika. Rezystancja buzzera wpływałaby na podział napięcia na drabince rezystorowej. W takiej konfiguracji wzmacniacz (zwarte negatywne wejście z wyjściem) pracuje w trybie śledzącym napięcie. Czyli napięcie na wyjściu wzmacniacza jest równe temu na wejściu. I wzmacniacz operacyjny zasila głośnik a napięcie jest takie jak wygenerowaliśmy z Arduino.

Dobra, to teraz kod by przetestować czy nasz DAC działa:

#define DELAY 10

void setup() {
  DDRC = 0b00111111; 
}

void loop() {
  for (byte i = 0; i < 32; i++) {
    PORTC = i;
    delayMicroseconds(DELAY);
  }
  PORTC = 0;
  delayMicroseconds(100);
}

Kod ten jest trochę inny niż zwykle tutaj pokazywane. Nie korzystamy z funkcji do manipulacji portami oferowanymi przez Arduino. Bezpośrednie operacje na portach są dużo szybsze niż digitalWrite a ponadto oferują operacje na 8 portach jednocześnie.

DDRx to rejestr kontrolujący tryb pracy pinów. Piny są pogrupowane w porty A, B, C. Wejścia analogowe to port C, więc DDRC kontroluje ich tryb pracy. Ustawienie bitów  na 1 w porcie DDRC na 1 ustawia dany pin w tryb pracy OUTPUT. A0 to bit 0 w porcie DDRC czyli 'ostatni’.

Zapis 0b000111 to liczba binarna, taki zapis ułatwia nam czytanie kodu.

O ile DDRx kontroluje tryb pracy portu x to PORTx ustawia IO w porcie x na zadane poziomy. I tak, np PORTC = 0b00000011 ustawi A0 i A1 na stan wysoki a resztę na niski.

OK, jaki jest rezultat pracy tego kodu? Powinno napięcie na wyjściu przetwornika rosnąć od zera do maksimum. Jak to wygląda? Ano tak:

Jak widać, 32 kroki to całkiem sporo, nasz sygnał o kształcie piły nie jest bardzo ząbkowany :) Teraz pozostaje nauczyć się generować sygnał o kształcie sinusoidy. Jak to zrobić w sposób efektywny – o tym w kolejnym poście.

Dla kompletu jeszcze schemat naszego układu:

Mamy dwa wzmacniacze, bo LM358 jest układem podwójnym, ale korzystamy tylko z jednego.

Lista użytych części: