komunikacja SPI (radiówka nRF24L01)

Komunikacja SPI

(Wpis ten jest reaktywacją wpisu z 2017 r. Okazało się, że warto podzielić oryginalny wpis na dwie mniejsze części – jedna dotycząca komunikacji I2C, druga SPI właśnie).

Tutaj https://www.arduino.cc/en/reference/SPI można poczytać czym jest protokół SPI. Nam wystarczy, że jest to szybka komunikacja synchroniczna na krótki dystans, wykorzystująca przynajmniej 3 linie nazwane MISO, MOSI i SCK. Dodatkowo są jeszcze linie CE i SCN. Jeśli więc w jakimś module zobaczymy tak nazwane piny – to znak, że działa w standardzie SPI właśnie. 

Specjalne piny SPI w Arduino to MISO (12 w Arduino), MOSI (11 w Arduino) oraz SCK (13 w Arduin0). Inne piny – CE i CSN są dowolne cyfrowe (występuje różne nazewnictwo – SS to Slave Select i odpowiada CE, który musi działać jako OUTPUT, w odróżnieniu od CSN). W poniższym przykładzie wybrałem 9 i 10 – a dlaczego? okaże się to później (na kolejnych spotkaniach naszego koła). 

nRF24L01+

Bardzo fajny moduł (no i bardzo tani – około 5 zł, co w porównaniu do modułów Bluetooth jest 1/5 ceny) – ale najpierw trzeba wiedzieć, jak podłączać go do Arduino:

UWAGA: zasilanie VCC jest z przedziału 3.3V-3.6V – podłączając do Arduino 5V USZKODZISZ moduł.

Oprogramowanie? Ponownie jest kilka bibliotek, ja użyłem nRF24L01.h by TMRh20. Jak poprzednio – doinstalowujemy do naszego Arduino IDE i tworzymy program odbierający dane:

#include <SPI.h>
#include <nRF24L01.h>
#include <RF24.h>

RF24 radio(9, 10);//CE, CS
const byte rxAddr[6] = "grzyb";

void setup()
{
  Serial.begin(9600);
  Serial.print("nRF24 INIT=");
  bool ok=radio.begin();
  Serial.println(ok);
  radio.openReadingPipe(0, rxAddr);
  
  radio.startListening();
}

char text[32];

void loop()
{
  if (radio.available())
  {
    radio.read(&text, sizeof(text));
    
    Serial.print("t=");
    Serial.print(millis()/1000);
    Serial.print("s, text=");
    Serial.println(text);
  }
}

W linii 6 nadalismy nazwę naszemu strumieniowi – dowolne 5 znaków, niekoniecznie musi być tak jak u mnie… Ale nadajnik też musi nadawać na tym samym „paśmie”:

#include <SPI.h>
#include <nRF24L01.h>
#include <RF24.h>
#include <printf.h>

RF24 radio(9, 10);//CE, CS

uint8_t rxAddr[6] = "grzyb";

void setup()
{
  Serial.begin(9600);
  Serial.print("nRF24 INIT=");
  bool ok=radio.begin();
  Serial.println(ok);
  printf_begin;
  radio.printDetails();
  radio.setRetries(15, 15);
  radio.openWritingPipe(rxAddr);
  
  radio.stopListening();
}

char text[] = "Fiza:         OK!";

void loop()
{
  radio.write(&text, sizeof(text));
  text[7]=48+rand()%10;  
  text[8]=48+rand()%10;
  text[9]=48+rand()%10;
  text[10]=48+rand()%10;
  text[11]=48+rand()%10;
  Serial.println(text);
  delay(3000);
}

Maksymalna długość jednorazowo przesłanego napisu wynosi 32 bajty, a inne ważne rzeczy – w dokumentacji (zachęcam do czytania). 

Problemy? Modyfikacje? 

Np. problem ze zrywaniem połączenia – ludzie piszą o niestabilnym napięciu z pinu 3.3V Arduino oraz o problemie z prądem (pin 3.3V może maksymalnie dać tylko 50mA), dlatego dodają dodatkowy kondensator (mały, np 4.7 μF, 10 μF) do zasilania modułu:

Mogą pojawić się też problemy z anteną, więc albo kupujemy moduł z „wypasioną antenką”:

albo samodzielnie robimy sobie antenkę:

Polecam zajrzeć tutaj aby poczytać o problemach i różnych sposobach ich rozwiązywania, a także o oprogramowywaniu tego moduły.

(c) K.G. 2017, 2019

komunikacja I2C (LCD na hd44780)

(Wpis ten jest reaktywacją wpisu z 2017 r. Okazało się, że warto podzielić oryginalny wpis na dwie mniejsze części – jedna dotycząca komunikacji I2C właśnie, druga SPI).

Komunikacja I2C

To bardzo popularny interface komunikacyjny, obsługujący za pomocą tylko 2 linii aż 127 urządzeń! tymi pinami są SDA (w Arduino pin A4) oraz SDC (w Arduino pin A5). Oznacza to, że gdy podłączamy coś na I2C „tracimy” piny A4 i A5. Trudno – coś za coś. Zresztą, to nic nowego – podobnie jest z komunikacją UART – Serial.begin(xxx) – „zabiera” nam cyfrowe piny #o (TX) i #1 (RX). Przy czym UART to tylko komunikacja z jednym urządzeniem – a tutaj 2 piny i możliwość obsługi do 127 urządzeń!

HD44780 i LCD 16×2

Jako przykład komunikacji I2C użyliśmy wyświetlacza LCD 16×2 z dodatkowym sterownikiem hd44780. Po co ten sterownik? wszak to bardzo popularny wyświetlacz i można go podłączyć w ten sposób:

Przypatrzmy się uważnie co my tu widzimy – zwracam uwagę na liczbę pinów potrzebnych do obsługi wyświetlacza przez Arduino. Uważne oko uczestnika sdanaszych spotkań zauważy, że potrzeba aż 6 cyfrowych pinów. To sporo! Wszystko jest OK, gdy robimy proste programy, poznajemy moduły (w tym właśnie taki LCD) więc tych 6 pinów nie robi problemu. Ale my mamy już nasz pojazd i wykorzystanych 6 pinów do jego sterowania (a w planach kolejne rozbudowy). Jak więc poradzić sobie z problemem (powoli) kończących się pinów w Arduino UNO? Zakupić Megasa? A może użyć właśnie sterownika na I2C – wówczas potrzeba jedynie 2 pinów!

My skorzystaliśmy z wymienimego sterownika PLUS biblioteki LiquidCrystal_I2C autorstwa Franka de Brabander-a. Sporo jest podobnych bibliotek, więc doinstalowywując ją sobie warto zwrócić uwagę na tą użytą przeze mnie, a nie inną. Na szybko poznaliśmy kilka metod nowego obiektu do obsługi wyświetlacza:

include <Wire.h>   // standardowa biblioteka Arduino
#include <LiquidCrystal_I2C.h> // dolaczenie pobranej biblioteki I2C dla LCD

LiquidCrystal_I2C lcd(0x27, 16, 2);
//LiquidCrystal_I2C lcd(0x3f, 16, 2);


void setup(){
  lcd.init();
  lcd.begin(16,2);   // Inicjalizacja LCD 2x16
  
  lcd.backlight(); // zalaczenie podwietlenia 
  lcd.setCursor(0,0); // Ustawienie kursora w pozycji 0,0 (pierwszy wiersz, pierwsza kolumna)
  lcd.print("pomidor!");
  delay(500);
  lcd.setCursor(0,1); //Ustawienie kursora w pozycji 0,0 (drugi wiersz, pierwsza kolumna)
  lcd.print("LCD 16x2 I2C -- hd44780");

}

int nico=7;
void loop() 
{
   lcd.backlight(); // zalaczenie podswietlenia
   lcd.setCursor(10,1);
   lcd.print(millis()/1000);
   delay(1000);
   lcd.noBacklight(); // wylaczenie podswietlenia
   lcd.setCursor(0,0);
   lcd.print(nico);
   nico=nico*2;
   delay(1000);
}

Warto ją poznać dokładniej, czytając dokumentację.

Adresacja użądzeń I2C 

Skoro do tych samych pinów SDA i SDC można podłączyć do 127 urządzeń, to skąd wiadomo, kto co nadaje? Np. jeśli chcemy użyć dwóch LCD-ków, na jedynym wyświetlać pewne informacje a na innym inne to jak kierować napisy na różne urządzenia?

Ważne jest poznanie adresu naszego użądzenia – w tym celu należy wygooglać świetny programik i2c_scanner i wgrać do Arduino z podłączonym wyświetlaczem (zresztą, nie tylko wyświetlaczem – czymkolwiek na I2C).

A jak zmienić adres w omawianym sterowniku hd44780? Bo gdy kupiny sobie dwa takie i chcemy je wykorzystać razem to będzie to niemożliwe – oba będą mieć fabrycznie te same adresy (albo 0x27 – gdy to moduł PCF8574/PCF8574T lub 0x3f – dla modułów PCF8574A). Jest możliwość zmiany adresu zwierając ze sobą (lutując) piny A012 na spodzie modułu:

Kliknij obrazek by dowiedzieć się co i jak.

(c) K.G. 2017, 2019

 

Łączenie rejestrów przesuwnych oraz sterowanie serwomechanizmami.

Na ostatnim spotkaniu przed przerwą świąteczną rozwinęliśmy projekt rozpoczęty na poprzednich zajęciach tj. wyświetlanie cyfr przy pomocy wyświetlacza siedmiosegmentowego oraz rejestru przesuwnego. Główną korzyścią tej metody jest użycie jedynie trzech pinów cyfrowych Arduino. Okazuje się, że dołączenie drugiego wyświetlacza siedmiosegmentowego wymaga podłączenia drugiego rejestru przesuwnego, ale liczba pinów cyfrowych pozostaje taka sama !

Logika stosowania wielu rejestrów przesuwnych może być porównana do tworzenia jednego rejestru, który przedłużamy kolejnymi chipami 74HC595.

Podłączenie drugiego układu scalonego 74HC595 ogranicza się do podłączenia pinów RCLK oraz SRCLK do tych samych pinów arduino co odpowiadające RCLK oraz SRCLK piny pierwszego rejestru. Będzie to oznaczało, że nowy chip będzie on otrzymywał te same sygnały sterujące co pierwszy. Różnicą jest podłączenie pinu SER. Zamiast bezpośrednio do Arduino, podłączamy ten pin do wyjścia 9 naszego pierwszego rejestru przesuwnego. Dzięki temu całość będzie funkcjonowała jak jeden, przedłużony rejestr przesuwny.

 

Nasuwa się pytanie: jak zmodyfikować ostatnio napisany przez nas program tak, by poprawnie działał?
Odpowiedź jest prosta. Przygotowaliśmy się na to już na poprzednim spotkaniu i wystarczy wprowadzić nową wartość dla zmiennej ileScalakow (widocznej od razu w pierwszej linijce kodu), tak by równała się liczbie zastosowanych rejestrów, czyli 2:

Cały kod pozwalający sprawdzić działanie wyświetlaczy

#define ileScalakow 2
#define ilePinow ileScalakow * 8
int SER=8; 
int RCLK=9;
int SRCLK=10; 
int rejestr[ilePinow];

void setup(){
  pinMode(SER, OUTPUT);
  pinMode(RCLK, OUTPUT);
  pinMode(SRCLK, OUTPUT);
  czyscRejestr();
  zapiszRejestr();
}
void czyscRejestr(){
  for(int i=0; i<ilePinow; i++)
    rejestr[i]=LOW;
}
void zapiszRejestr(){
  digitalWrite(RCLK, LOW); 
  for(int i=ilePinow-1; i>=0; i--){
    digitalWrite(SRCLK, LOW);
    digitalWrite(SER, rejestr[i]);
    digitalWrite(SRCLK, HIGH); 
  }
  digitalWrite(RCLK, HIGH); 
}
void ustawPin(int ktory, int wartosc){
  rejestr[ktory]=wartosc;
}

void loop(){
  int i;
  for(i=0;i<ilePinow;i++)
    ustawPin(i, LOW);
    zapiszRejestr();
    delay(500);
   for(i=0;i<ilePinow;i++){
    ustawPin(i, HIGH);
    zapiszRejestr();
    delay(500);
}
}

Po tej prostej zmianie kod działa poprawnie. Jest to przykład jak odpowiednie wprowadzanie zmiennych pozwala na skalowalność i rozwijanie kreowanych programów.

Sterowanie serwomechanizmami

Serwomechanizm, lub potocznie serwo, to nic innego jak silniczek prądu stałego sterowany PWM (modulator szerokości impulsu) z podłączonym odpowiednim układem zębatek. To niewielkie i niedrogie urządzenie pozwala wygenerować niespodziewanie dużą siłę i moment obrotowy, szczególnie zważywszy na jego rozmiary i nieduży pobór mocy (przy mini serwach np. używanych przez nas SG92r produkcji TowerPro rzędu kilku watów).

Przed rozpoczęciem jakichkolwiek doświadczeń z serwami warto pamiętać właśnie o poborze mocy. Rekomendowane jest zasilanie ich z zewnętrznego źródła mocy np. baterii lub zasilacza prądu stałego. Przyczyną jest niebezpieczeństwo, jakie niesie za sobą skok natężenia prądu podczas ruchu serw – podłączenie kilku na raz do Arduino może niechybnie doprowadzić do spalenia sterownika.

Znalezione obrazy dla zapytania arduino servo batteries

Tworzenie programu sterującego serwami

Arduino posiada dedykowaną bibliotekę do sterowania serwami, co niezwykle ułatwia sterowanie i zarządzanie.

#include <Servo.h>

Nasz program pozwala na wpisywanie wartości do monitora szeregowego i dyktowanie ich serwomechanizmom.

Servo serwomechanizm;
int pozycja = 90;

void setup() 
{ 
 serwomechanizm.attach(9);
 Serial.begin(9600);
}

„Servo” jest formą deklaracji, że dany obiekt o nazwie „Serwmechanizm” ma być traktowany przez interpreter kodu jako serwomechanizm. Deklarujemy je w tej samej konwencji, co np. zmienne typu integer. Traktowanie go jak serwomechanizm oznacza, że możemy przypisać mu jakiś pin oraz pozycję (będą to odpowiednie zmienne w obiekcie Servo).

W naszym kodzie w etapie setup() przypisaliśmy pozycję wyjściową 90 stopni (2. linijka), pin 9 (7. linijka) oraz uruchomiliśmy monitor szeregowy dla częstotliwości 9600 baud. Następnie w loop() przy pomocy metody „parseInt” każemy „nasłuchiwać” wartości, które zostaną przypisane zmiennej „pozycja”.

void loop() 
{ 
 if (Serial.available() > 0) {
pozycja = Serial.parseInt();
 }
 if (pozycja > 0 && pozycja < 180) { 
 serwomechanizm.write(pozycja);
 } else { 
 pozycja = pozycja%160+10;
 serwomechanizm.write(pozycja);
 } 
 
}

Tym sposobem możemy sterować serwomechanizmem z klawiatury przypisując mu kąt od 0 do 180 stopni. Warto jednak pamiętać, by unikać skrajnych pozycji takich jak 0 i 180 stopni, gdyż w niektórych przypadkach może to doprowadzić do uszkodzenia serwomechanizmu.

Adnotacja: W bloku „else” umieściłem zabezpieczenie, które nie pozwoli również na przypisanie wartości większych niż 180. Była to nadprogramowa metoda, która pozwala ograniczyć przypisywane na serwo wartości tak, by zamykały się pomiędzy bezpiecznymi kątami 10 – 170 stopni. Sposoby zabezpieczania zależą od nas, jednak nie warto ich zaniedbywać. Szkoda naszego sprzętu i nerwów!

 

Pomiar prądu płynącego przez serwo.

Na bardziej ciekawych czekała również możliwość pomiaru prądu płynącego przez serwo. Warto pamiętać, że pomiar natężenia prądu należy przeprowadzać szeregowo względem badanego układu. Oznacza to, że multimetr w trybie pomiaru prądu musi być „po drodze” prądu między źródłem zasilania, a serwem. Dlatego zrobiliśmy to w następujący sposób:

Źródło napięcia (bez obaw, 5V i prąd zdecydowanie poniżej 1 A można swobodnie „izolować” suchymi dłońmi) dołączyliśmy do czerwonej sondy multimetru, a czarną do wejścia 5V serwa – jest to właśnie połączenie szeregowe.

Przy okazji mogliśmy zbadać wartość prądu pobieraną przez pracujące serwo. Wartości te na ogół wynosiły kilkadziesiąt mA, ale czasami (w skarajnym położeniu a także podczas zmiany położenia) wynosiły 100-200 mA. Oznacza to, że podłączenie kilku serw do Arduino jako źródła napięcia (a nie tylko do zadawania sygnału) skończyłoby się spaleniem płytki. Dlatego budując bardziej złożone układy z pewnością użyjemy zewnętrznych źródeł napięcia.

Do zobaczenia na kolejnym spotkaniu!
Maciej (c)