[174] Arduino i oscyloskop cz. 4

00.png
11.png
12.png
13.png

Do tej pory traktowaliśmy problem, że tak powiem: zachowawczo. Innymi słowy, skopiowaliśmy ideę wyświetlacza siedmiosegmentowego w świat wektorów, bo tym jest właśnie rysowanie na ekranie oscyloskopu. I takie podejście nie jest złe, ale mało ambitne. Skoro powiedziałem: wektory – stwórzmy prawdziwie wektorowy wyświetlacz. Zacznijmy jednak od kartki papieru.

11.pngPozostaniemy w świecie sześciu węzłów, lecz stworzymy zupełnie inny krój liczb. Zauważmy, że mając ową skromną liczbę punktów nie mamy ograniczeń na ich łączenie. Możemy je łączyć także po skosach czy na skróty – skrajne. Stworzyłem sobie nowy wzór cyfr, prawdziwie retro. Jedynka ma daszek, dwójka bardziej przypomina wersję pisaną, trójka reprezentuje już dawno zapomniany kształt. Tylko zero i ósemka wyglądają po staremu, ale ciężko byłoby tu coś zmienić, mając tylko sześć punktów węzłowych.

Zauważmy jeszcze jedno: zredukowaliśmy nieco liczbę odcinków tworzących cyfry względem poprzedniego projektu. Ósemka ma ich 6, szóstka i dziewiątka po 5, reszta – 4 albo mniej. Zatem rysowanie będzie szybsze. Przejdźmy teraz do programu, który musi się nieco różnić.

const byte inputY = 9;   // Port wejścia Y oscyloskopu (Kanał 2).
const byte inputX = 10;  // Port wejścia X oscyloskopu (Kanał 1).
const byte pot = A1;     // Port wejścia potencjometru.
const byte offset = 48;  // Przesunięcie cyfr.
const byte steps = 65;   // Liczba kroków pośrednich rysowania jednej linii.

#define XL 32                                         // Definicje położenia: Lewy punkt pierwszej cyfry.
#define XR 64                                         // Prawy punkt pierwszej cyfry.
#define YD 96                                         // Dolny punkt siatki odcinków.
#define YM 128                                        // Środkowy punkt siatki odcinków.
#define YU 160                                        // Górny punkt siatki odcinków.
const byte nodeX[7] = { 0, XL, XR, XL, XR, XL, XR };  // Mapowanie węzłów na współrzędne X...
const byte nodeY[7] = { 0, YU, YU, YM, YM, YD, YD };  // oraz Y

#define LU 1  // Definicje indeksów węzłów według klucza: Left-Up itd
#define RU 2
#define LM 3
#define RM 4
#define LD 5
#define RD 6
const byte vectors[10][8] = {
  { RD, LD, LU, RU, RD, 0, 0, 0 },    // Tablica połączeń węzłów dla kolejnych cyfr, 0
  { LM, RU, RD, 0, 0, 0, 0, 0 },      // 1
  { LU, RU, RM, LD, RD, 0, 0, 0 },    // 2
  { LU, RU, LM, RM, LD, 0, 0, 0 },    // 3
  { RU, LM, RM, RD, 0, 0, 0, 0 },     // 4
  { RU, LU, LM, RM, LD, 0, 0, 0 },    // 5
  { RU, LM, LD, RD, RM, LM, 0, 0 },   // 6
  { LU, RU, LD, 0, 0, 0, 0, 0 },      // 7
  { LM, LU, RU, RD, LD, LM, RM, 0 },  // 8
  { RM, LM, LU, RU, RM, LD, 0, 0 }    // 9
};
void setup() {
  TCCR1A = _BV(COM1A1) | _BV(COM1B1) | _BV(WGM10);  // Zmiana częstotliwości pracy PWM na 62,5 kHz
  TCCR1B = _BV(WGM12) | _BV(CS10);
  pinMode(inputY, OUTPUT);  // Inicjuj port wejścia Y oscyloskopu.
  pinMode(inputX, OUTPUT);  // Inicjuj port wejścia X oscyloskopu.
}
void loop() {
  int value = analogRead(pot);          // Zmierz napięcie wyznaczone przez ślizgacz potencjometru.
  drawDigit(value / 1000, 0 * offset);  // Wyciągnij z liczby poszczególne cyfry, przekaż wraz z ich przesunięciem.
  drawDigit((value / 100) % 10, 1 * offset);
  drawDigit((value / 10) % 10, 2 * offset);
  drawDigit(value % 10, 3 * offset);
}
void drawDigit(byte digit, byte xOffset) {    // Odzyskaj cyfrę oraz pozycję rysowania.
  byte step = 0;                              // Numer kolejnego odcinka.
  while (step < 7) {                          // Analizuj po kolei wszystkie odcinki
    byte beginNode = vectors[digit][step];    // Węzeł, od którego należy rysować odcinel.
    byte endNode = vectors[digit][step + 1];  // Węzeł, do którego należy rysować odcinek.
    if (endNode == 0) {                       // Jeśli węzeł docelowy równy jest zero...
      break;                                  // Zakończ rysowanie.
    }
    byte beginX = xOffset + nodeX[beginNode];                       // Początek odcinka w osi X.
    byte beginY = nodeY[beginNode];                                 // Początek odcinka w osi Y.
    byte endX = xOffset + nodeX[endNode];                           // Koniec odcinka w osi X.
    byte endY = nodeY[endNode];                                     // Koniec odcinka w osi Y.
    for (byte nextStep = 0; nextStep <= steps; nextStep++) {        // Rysuj wybrany odcinek krok po kroku.
      byte x = beginX + ((int)(endX - beginX) * nextStep) / steps;  // Oblicz napięcie dla kolejnego punktu osi X
      byte y = beginY + ((int)(endY - beginY) * nextStep) / steps;  // Oblicz napięcie dla kolejnego punktu osi Y
      analogWrite(inputX, x);                                       // Wystaw napięcie dla osi X
      analogWrite(inputY, y);                                       // Wystaw napięcie dla osi Y
    }
    step++;  // Analizuj kolejny odcinek.
  }
}

Przede wszystkim pojawiły się tu definicje – to te wartości, których nie użyjemy w samym programie, a tylko w bloku wstępnym i potrzebne są wyłącznie nam – ludziom, by ponazywać wszystko ludzko, a nie nic nam nie mówiącymi cyframi. Zatem tablice węzłów wypełnimy nie konkretnymi wartościami, a nazwami: X i Y to osie, L, R – strony lewa i prawa, a D, M i U – wysokości. Od skrótów angielskich. Tablica połączeń tych węzłów, czyli przepis na rysowanie cyfr, także została napisany językiem ludzkim. LU to left-up, czyli lewy górny punkt i tak dalej. W tej tablicy mamy sedno nowego podejścia, wektorowego.

{ RD, LD, LU, RU, RD, 0, 0, 0 }

Czytamy ją następująco: przyłóż pisak do punktu – w tym wypadku prawego dolnego, ciągnij w lewy dolny, potem w lewy górny, prawy górny, prawy dolny i – zero, czyli skończ rysowanie. Prawda, że tak jest prościej? Możemy w ten sposób tworzyć dowolne rysunki bazujące na zdefiniowanych wcześniej węzłach, których może być ile bądź. Mało tego, kolejność rysowania należy przyjąć z głową. Po pierwsze – tak, by narysować wszystko jak najprościej, czyli najlepiej bez odrywania hipotetycznego pisaka od papieru. Nawet ósemka, na pierwszy rzut oka wymagająca dopisania kreski, jest rysowana ciągłym ruchem. Proszę sobie prześledzić ścieżkę posunięć. Po drugie, rysowanie najlepiej kończyć w takim miejscu, żeby przeskok do kolejnej cyfry był jak najkrótszy. I tak też rysowanie należy zacząć – jak najbliżej poprzedniej cyfry. Ograniczy nam to przeszkadzające ślady między znakami. Jeśli już muszą się pojawiać, najlepiej, żeby przeszkadzały niżej niż wyżej.

To wszystko wymaga przemyśleń i testów. Tę tablicę chwilę tworzyłem, zanim osiągnęła taką konstrukcję. Dalej nie ma już nic odkrywczego, acz algorytm musiał się zmienić. Przy analizie wszystkich – już teraz nie tyle segmentów, co odcinków – zastawimy pułapkę na bajt zerowy. Jeśli taki znajdziemy – przerwiemy rysowanie cyfry i przejdziemy do kolejnej. Specyfika tablic wymaga, by ta konstrukcja miała tę samą liczbę pozycji w tablicy, zatem dla siódemki czy jedynki jest tam dużo zer, aby dopełnić całość do ośmiu bajtów na znak. Następnie program oblicza adresy początków i końców odcinka do narysowania i rysuje je – jak poprzednio, wystawiając serię napięć.

12.pngCzytelność takiej postaci wyświetlanych cyfr jest wyższa, ale przede wszystkim wygląda to niebywale efektywnie i nie jest możliwe do odtworzenia na klasycznych wyświetlaczach siedmiosegmentowych. Projekt ten można rozbudowywać, tworząc zawiłe mapy wektorowe, a nawet łuki, co będzie wymagać już trygonometrii i bez kombinowania będzie raczej ograniczone do układów trzydziestodwubitowych. Ich ceny jednak spadły, więc to nie jest już problem. Zapewne do oscyloskopu jeszcze powrócę, bo można tam robić wiele innych fajnych rzeczy.

13.png

Płytka edukacyjna TME-EDU-ARD-2Płytka edukacyjna TME-EDU-ARD-2

Inne artykuły z tej kategorii

Nasi partnerzy

TMETech Master EventTME EducationPoweredby
Copyright © 2026 arduino.pl