Skocz do zawartości

Jak pisać biblioteki na Arduino?


Pomocna odpowiedź

20 minut temu, slon napisał:

Czy taki sposób będzie poprawny

Jeśli led faktycznie będzie obiektem to owszem.

No to teraz proszę bardzo - jako ćwiczenie napisać odpowiednią klasę. Podaję przykładowy interfejs

class BlinkingLed {
  public:
  	BlinkingLed(int pin);
  	BlinkingLed(int pin, int OnTime, int OffTime, int autostart = 0);
  	void setOnTime(int);
  	void setOffTime(int);
  	int getOnTime(void);
  	int getOffTime(void);
  	void start(void);
  	void stop(void);
  	void run(void);
};

  	

Zaproponować i zaimplementować inne metody które mogą być potrzebne.

Zaproponować inny interfejs i krótko opisać dlaczego jest lepszy.

A dla bardziej ambitnych - napisać to samo ale w C a nie w C++ 🙂

Kto chętny?

 

 

@ethanak dziękuje za dalsze podjęcie tematu oraz za podanie przykładowego interfejsu. Natomiast mam pytanie : czy dla podanej klasy BlinkingLed nie będę potrzebował definiować atrybutów? 

class BlinkingLed {
  private:
    int _pin
    int _onTime;
    int _offTime;
    int _autostart;
  public:
  	BlinkingLed(int pin);
  	BlinkingLed(int pin, int OnTime, int OffTime, int autostart = 0);
  	void setOnTime(int);
  	void setOffTime(int);
  	int getOnTime(void);
  	int getOffTime(void);
  	void start(void);
  	void stop(void);
  	void run(void);
};

 

Pewnie że musisz - ale to już kwestia Twojej implementacji, ja zaproponowałem wyłącznie publiczne metody interfejsu.

Takie pytanie: po co Ci atrybut _autostart?

 

Konstruktor przypisuje wartości początkowe do atrybutów

BlinkingLed(int pin, int OnTime, int OffTime, int autostart = 0);

czyli w tym wypadku do atrybutu _autostart domyślnie została by przypisana wartość zero. Tworząc nowy obiekt np:

BlinkingLed led1(13,1000,1000);

podaje wartości początkowe do pozostałych atrybutów.  Metoda start(); sprawdza warunek np:

start()
{
  if (autostart==1) 
  {
   // blink uruchomiony 
  } else 
  // czekamy
}

atrybut _autostart ma wartość 0 więc czekamy. Metoda run(); np:

run()
{
  autostar=1;
}

metoda stop();

stop()
{
 autostart=0; 
}

teraz gdybym chciał utworzyć kolejny obiekt 

BlinkingLed led2(3);

tutaj zastosowałem pierwszy konstruktor

BlinkingLed(int pin);

czyli na tą chwilę miał bym dwie diody na na pinach 3 i 13 gotowe do uruchomienia np:

led1.start(); led2.start(); 
led1.run(); led2.run();

czy to co napisałem jest poprawne zwłaszcza jeśli chodzi o zastosowanie konstruktora?

Hm... nic z tego nie rozumiem. Funkcja start nie startuje migania ledą... funkcja run to tylko sygnalizacja że start ma startować... a miganie odbywa się automagicznie za pomocą telepatii...

Biblioteka to nie tylko zestaw klas, metod i różnych innych przydatnych firdymałków - to również dokumentacja. Nawet taka najkrótsza - przy deklaracji metody jedno zdanie opisu co dana metoda robi i (jeśli nazwy parametrów nie określają jednoznacznie ich znaczenia) co znaczy który parametr. Bez tego użycie nawet najbardziej dopracowanej biblioteki będzie niemożliwe.

Poza tym pisząc bibliotekę musimy mieć na uwadze to, że ktoś ją będzie stosować i ten ktoś najprawdopodobniej nie będzie zaglądał do kodów źródłowych, a przede wszystkim będzie szukał biblioteki spełniającej jego oczekiwania. I znów nawet najpiękniejszy kod nie spowoduje tego, że biblioteka w której zabrakło jakiejś ważnej funkcji stanie się nagle używalna...

Wyobraźmy sobie jakiś nietrywialny program, który będzie korzystać z naszej biblioteki do migania ledami. Niech to będzie np. metronom.

Metronom wyposażony jest w przyciski start-stop, potencjometr regulujący tempo w zakresie 30..250 BPM, ledę sygnalizującą rytm oraz ledę pokazującą stan naładowania akumulatora.

Po wciśnięciu "start" metronom rusza, migając ledą sygnalizacyjną w określonym tempie, przy czym czas zapalenia ledy musi być równy czasowi zgaszenia. Tempo ustalane jest potencjometrem. Po wciśnięciu "stop" metronom się zatrzymuje, a leda gaśnie.

Druga leda pali się cały czas jeśli napięcie odczytane z akumulatora wynosi ponad 3.8V. Jeśli napięcie spada, leda zaczyna migać w stałym rytmie (raz na sekundę mniej więcej), z tym że mignięcia są coraz krótsze w miarę spadku napięcia do 3.2V. Od 3.2V w dół czas zapalenia ledy wynosi 1/10 okresu i już się nie zmniejsza.

Należy spróbować zaprojektować taki program używając funkcji z nieistniejącej jeszcze biblioteki - w ten sposób będziemy mieli całkiem nieźle określony interfejs (a przynajmniej jego fragment) i będziemy mogli wziąć się do tworzenia biblioteki.

Ktoś chętny?

Niekoniecznie do stworzenia biblioteki, ale właśnie programu metronomu i określenia swoich wymagań co do biblioteki migania ledami?

A może ktoś ma inną propozycję programu używającego takiej biblioteki?

 

 

  • Lubię! 1

Moją intencją nie było rozpisywanie pliku BlinkingLed.cpp czy , którejkolwiek z tych metod. Natomiast sam plik nagłówkowy BlinkingLed.h to w zasadzie mógł by wyglądać tak:

#ifndef BlinkingLed_h
#define BlinkingLed_h

#include "Arduino.h"

class BlinkingLed {
  private:
    int _pin;
    int _onTime;
    int _offTime;
    int _autostart;
  public:
  	BlinkingLed(int pin);
  	BlinkingLed(int pin, int OnTime, int OffTime, int autostart = 0);
  	void setOnTime(int);
  	void setOffTime(int);
  	int getOnTime(void);
  	int getOffTime(void);
  	void start(void);
  	void stop(void);
  	void run(void);
};

#endif

oczywiście , żeby to mogło działać to trzeba by było obydwa te pliki (BlinkingLed.cpp oraz BlinkingLed.h) przenieść do folderu BlinkingLed i umieścić w  folderze arduino Libraries (zakładając , że będziemy korzystać z arduino IDE) . Jeśli ktoś jest dalej zainteresowany tym tematem to zostaje do uzupełnienia plik BlinkingLed.cpp i rozpisanie poszczególnych metod.

#include "Arduino.h"
#include "BlinkingLed.h"

BlinkingLed::BlinkingLed(int p)
{

}

 

6 godzin temu, slon napisał:

zostaje do uzupełnienia plik BlinkingLed.cpp i rozpisanie poszczególnych metod.

No i tu jest pierwszy wielki błąd: przed napisaniem kodu należy dokładnie określić, co która metoda ma robić (np. że "start" ma wystartować miganie) i dopiero brać się za realizację założeń. Trudno realizować niesprecyzowane założenia, prawda?

Następny błąd to określanie z góry prywatnych atrybutów. Po co? Przecież użytkowników biblioteki nie interesują prywatne zmienne (no, chyba  że ktoś chce napisać bibliotekę pochodną i będzie klął autora w żywy kamień za użycie "private" zamiast "protected"). Te atrybuty wylezą dopiero w trakcie pisania. Pomyśl: czy pisząc program w C++ najpierw określasz wszystkie zmienne globalne, a potem się tego kurczowo trzymasz?

Jak widać, pisanie bibliotek nie jest taką prosta sprawą nawet jeśli to ma być najprostsza biblioteka do migania ledami 🙂

No ale może ktoś jeszcze spróbuje swoich sił?

 

  • 3 tygodnie później...

Wracając do metronomu to zainteresowały mnie dwa założenia 

Cytat

potencjometr regulujący tempo w zakresie 30..250 BPM

Cytat

 czas zapalenia ledy musi być równy czasowi zgaszenia

wpasowałem to do funkcji, którą zamieściłem na pierwszej stronie

  potencjometr=analogRead(A1);
  potencjometr=map(potencjometr,0,1023,30,250);
  BPM=60000/potencjometr/2; 
  start(7,BPM,BPM);

działa całkiem  fajnie chociaż osobiście chyba bym wolał regulację BPM przyciskami "góra" , "dół".

A dla mnie najwygodniejszy by był enkoder obrotowy i wyświetlacz - przejście przyciskami od najwolniejszego do najszybszego będzie wymagało trochę klikania. Ale w sumie nie o to tu chodziło...

Po prostu sterowanie potencjometrem jest nieprecyzyjne ale najprostsze w realizacji (w sumie jedna linijka w programie), a chodziło o napisanie biblioteki a nie skupianie się nad wprowadzaniem danych do programu 🙂 Mając bibliotekę będziemy mogli napisać sobie metronom tak jak nam pasuje.

 

 

  • 1 rok później...

Witam. 

Ja mam problem z wykorzystaniem obiektu i metod z innej biblioteki w mojej wlasnej. Mianowicie napisalem programik wykorzystujacy biblioteke onewire, napisalem kilka funkcji do tego i w glownym pliku zrobil sie balagan. Chcialem stworzyc biblioteke i przeniesc wszystkie funkcje do tej biblioteki, ale nie wiem, jak zrobic zeby obiekt onewire stworzony w programie glownym dzialal po moja biblioteka. Szukalem w necie i nie moge znalezc. Czy jest ktos kto moglby mi w tym pomoc? 

wstawiam kod biblioteki temp_sensor:

temp_sensor.h:

#ifndef temp_sensor_H
#define temp_sensor_H

#include "Arduino.h"
class OneWire;
class temp_sensor{

#define DEBUG
 
public:

  uint8_t termo[8];
  
  temp_sensor(uint8_t pin);
  void begin();
  bool search_ds();
  uint8_t save_TCS(uint8_t *rom, uint8_t cnt);
  void get_temp(uint8_t Tsensor);
  bool porownaj(uint8_t *rom, uint8_t cnt);
  void start_measT();

private:

  OneWire &DS;
  uint8_t TCS[80], data[12];
  uint8_t TC;
  uint8_t _pin;
  
  bool search_ok;
};



#endif

temp_sensor.cpp:

#include "temp_sensor.h"
#include <OneWire.h>
//#include <DallasTemperature.h>
#include <EEPROM.h>


temp_sensor::temp_sensor(uint8_t pin) {
  _pin = pin;
   begin();
 // DallasTemperature DS(&oneWire);
}

void temp_sensor::begin(){

  DS = OneWire(_pin);
  Serial.begin(9600);
  Serial.print("start");
}

bool temp_sensor::search_ds() {


  uint8_t x = DS.search(termo);
  //  for( i = 0; i < 8; i++) {
  //    Serial.write(' ');
  //    Serial.print(termo[i], HEX);
  //  }
#ifdef DEBUG
  Serial.print("cnt ds:  ");
  Serial.println(x);
#endif

  if (x == 0) {
    //Serial.println("No more addresses.");
    //Serial.println();
    //digitalWrite(LED_BUILTIN, LOW);
    DS.reset_search();
    delay(250);
  }

  if ( x == 1) {
#ifdef DEBUG
    for ( uint8_t i = 0; i < 8; i++) {
      Serial.write(' ');
      Serial.print(termo[i], HEX);
    }
#endif

    for (uint8_t i = 0; i < TC; i++) {

#ifdef DEBUG
      Serial.println();
      Serial.print("TC:   ");
      Serial.println(TC);
      Serial.print("czujnik:   ");
      Serial.println(i);
#endif

      bool flag_ok = porownaj(termo, i);
      if (!flag_ok) {
        //digitalWrite(LED_BUILTIN, HIGH);
        //break;
      }
      else {
        //digitalWrite(LED_BUILTIN, LOW);
        x = 0;
        // break;
      }
    }
    if (x == 0) digitalWrite(LED_BUILTIN, LOW);
    if (x == 1) digitalWrite(LED_BUILTIN, HIGH);

  }
#ifdef DEBUG
  for ( uint8_t i = 0; i < 8; i++) {
    Serial.write(' ');
    Serial.print(termo[i], HEX);
  }
  Serial.println();
#endif
  return x;
}


uint8_t temp_sensor::save_TCS(uint8_t *rom, uint8_t cnt) {

  uint8_t addr = (cnt * 8); //+10;

#ifdef DEBUG
  Serial.println("...........ZAPIS............");
  Serial.print("adres:   ");
  Serial.println(addr);
#endif
  for (uint8_t x = 0; x < 8; x++) {
    TCS[addr] = rom[x];

#ifdef DEBUG
    Serial.write(' ');
    Serial.print(rom[x], HEX);
#endif

    addr++;
  }
#ifdef DEBUG
  Serial.println();
  Serial.println(addr);

  addr = (cnt * 8);
  for (uint8_t x = 0; x < 8; x++) {
    Serial.print(TCS[addr], HEX);
    termo[x] = 0;
    addr++;
  }
#endif

  TC ++;
  search_ok = 0;
  Serial.println();
  //Serial.println("zapis");
  //Serial.print("TC:  ");
  //Serial.println(TC);

  //Serial.println(x);
  digitalWrite(LED_BUILTIN, LOW);
  //delay(1000);
  return TC;
}



void temp_sensor::get_temp(uint8_t Tsensor) {

  Serial.print("temperatura czujnik ");
  Serial.print(Tsensor);
  Serial.print(": ");
  //Serial.println("21,2 C");

  uint8_t addr[8];
  //uint8_t tmp = Tsensor * 8;

  for (uint8_t x = 0; x < 8; x++) {
    addr[x] = TCS[(Tsensor * 8) + x];
    //tmp++;
  }
  DS.reset();
  DS.select(addr);
  DS.write(0xBE);         // Read Scratchpad

  for (uint8_t i = 0; i < 9; i++) {           // we need 9 bytes
    data[i] = DS.read();
  }
  if (DS.crc8(data, 7) != addr[7]) {

    int16_t raw = (data[1] << 8) | data[0];

    float celsius = (float)raw / 16.0;
    Serial.print(celsius);
    Serial.println(" C");
  }
}



bool temp_sensor::porownaj(uint8_t *rom, uint8_t cnt) {

  uint8_t addr = (cnt * 8); //+10;

#ifdef DEBUG
  Serial.println("...........POROWNAJ............");
  Serial.print("adres:   ");
  Serial.println(addr);
  Serial.print("nr TC:   ");
  Serial.println(cnt);
#endif

  bool wynik = 1;
  //bool rowne = 0;


  //  for ( uint8_t i = 0; i < 8; i++) {
  //    Serial.write(' ');
  //    Serial.print(rom[i], HEX);
  //  }
  //  Serial.println();


  for (uint8_t x = 0; x < 8; x++) {

#ifdef DEBUG
    Serial.print(rom[x], HEX);
    Serial.print("  ");
    Serial.print(TCS[addr], HEX);
#endif

    if (!(rom[x] == TCS[addr])) {
#ifdef DEBUG
      Serial.println("   rozne");
#endif

      wynik = 0;
      //break;
    }
#ifdef DEBUG
    else {
      Serial.println("   rowne");
      //rowne = 1;
    }
#endif

    addr++;
  }

#ifdef DEBUG
  if (wynik == 0) Serial.println("znaleziono");
  else Serial.println("nie znaleziono");
#endif

  return wynik;
}


void temp_sensor::start_measT() {

  DS.reset();
  DS.write(0xCC);
  DS.write(0x44, 1);
  
}

to bym chcial miec w bibliotece.

wszystko co wyczytalem i podpatrzylem w innycj bibliotekach zrobilem. niby sie kompiluje ale nie dziala. nawet nie wysyla nic do terminala.

tu kod glowny:

 

#include <EEPROM.h>
#include"temp_sensor.h"

//#define DEBUG

#define INDEX_OF_TSENSORS 9
// Data wire is plugged into port 2 on the Arduino
#define TCcount 9
#define TCarray 10
#define button1 14
#define push    0




#define ONE_WIRE_PIN 2
//#define TEMPERATURE_PRECISION 12

temp_sensor Tsensor(ONE_WIRE_PIN);

// Setup a oneWire instance to communicate with any OneWire devices (not just Maxim/Dallas temperature ICs)
//OneWire oneWire(ONE_WIRE_BUS);

// Pass our oneWire reference to Dallas Temperature.
//DallasTemperature temp(&oneWire);

// arrays to hold device addresses
//uint8_t termo[8], TCS[80], data[12];

// Assign address manually. The addresses below will beed to be changed
// to valid device addresses on your bus. Device address can be retrieved
// by using either oneWire.search(deviceAddress) or individually via
// sensors.getAddress(deviceAddress, index)
// DeviceAddress insideThermometer = { 0x28, 0x1D, 0x39, 0x31, 0x2, 0x0, 0x0, 0xF0 };
// DeviceAddress outsideThermometer   = { 0x28, 0x3F, 0x1C, 0x31, 0x2, 0x0, 0x0, 0x2 };

void setup(void)
{
  // start serial port
  //Serial.begin(9600);

  pinMode(LED_BUILTIN, OUTPUT);
  digitalWrite(LED_BUILTIN, HIGH);

  pinMode(button1, INPUT_PULLUP);


  // Start up the library
  //temp.begin();
  //oneWire.reset_search();
  //delay(1000);

}

/*
   Main function, calls the temperatures in a loop.
*/

uint8_t TC;
uint8_t idx;
bool search_ok;
uint8_t tempcnt;


void loop(void)
{

  //TC = EEPROM.read(TCcount);
  TC = 0;
  idx = 0;
  search_ok = false;
  uint8_t licz = 0;


  Serial.println("start");

  while (1) {

    //Serial.println(licz);

    if (!search_ok) search_ok = Tsensor.search_ds();


    if (search_ok == 1) {
#ifdef DEBUG
      Serial.print("TC:   ");
      Serial.println(TC);
      delay(200);
#endif
      //digitalWrite(LED_BUILTIN, HIGH);
      if (digitalRead(button1) == push) {

        //Serial.println("zapis");

        idx = Tsensor.save_TCS(Tsensor.termo, idx);
        //EEPROM.write(INDEX_OF_TSENSORS, idx);
      }
    }
    //else digitalWrite(LED_BUILTIN, LOW);

    if (idx > 0 && search_ok == 0) {
      for (uint8_t x = 0; x < idx; x++) {
        Tsensor.get_temp(x);
      }
      
      Tsensor.start_measT();
      delay(1500);
    }
    //licz++;
  }
}

 

Tak na szybko.

Najpierw drobiazg:
 

#ifndef temp_sensor_H
#define temp_sensor_H

...

#endif

zamieniasz na
 

#pragma once

podobno wszystkie porządne kompilatory to rozumieją (gcc na pewno).

Dalej:

38 minut temu, jacabe napisał:

temp_sensor::temp_sensor(uint8_t pin) {
	_pin = pin;
	begin(); // to wywal i wywołuj po prostu w setup
}

 

 

40 minut temu, jacabe napisał:

pinMode(LED_BUILTIN, OUTPUT);
digitalWrite(LED_BUILTIN, HIGH); 

 

To masz w setup, a powinno być w bibliotece, natomiast:

41 minut temu, jacabe napisał:

Serial.begin(9600);

 

to masz w bibliotece, a powinno być w setup() (biblioteka w tym przypadku nie ma prawa wiedzieć z jaką prędkością działa serial, a nawet czy w ogóle działa).

I zacznij od czegoś najprostszego - choćby od sprawdzenia, czy po zmianach po wywołaniu begin() pokaże się "start" na terminalu. Jeśli tak - to jesteś na dobrej drodze. Jeśli nie - pomyślimy...

(edytowany)

zrobilm jak radziles.

void setup(void)
{
  // start serial port
  Serial.begin(9600);

  pinMode(button1, INPUT_PULLUP);
  
  Tsensor.begin();
  // Start up the library
  //temp.begin();
  //oneWire.reset_search();
  //delay(1000);

}

/*
   Main function, calls the temperatures in a loop.
*/

uint8_t TC;
uint8_t idx;
bool search_ok;
uint8_t tempcnt;


void loop(void)
{

  //TC = EEPROM.read(TCcount);
  TC = 0;
  idx = 0;
  search_ok = false;
  uint8_t licz = 0;


  Serial.println("start");

  while(1);

w terminalu pojawia sie "st" ze slowa start i nic wiecej...

jakis progres jest.

 

jak zakomentuje w bibliotece jedna linie to pojawia sie caly napis "start"

 

void temp_sensor::begin(){

  //DS = OneWire(_pin);
  ...
}

 

Edytowano przez jacabe

Zaraz zaraz... ale ten Serial.println("start") to miał być w begin a nie loop();

I nie dawaj w takich przypadkach gołego while(1) tylko coś w stylu:

while(1) delay(1);

a w ogóle najlepiej dać jakiegoś większego delaja przed taką kończącą pętlą aby mieć pewnośc, że pokończył wszystko co tam się działo (np. powysyłał wszystko z buforów).

Bądź aktywny - zaloguj się lub utwórz konto!

Tylko zarejestrowani użytkownicy mogą komentować zawartość tej strony

Utwórz konto w ~20 sekund!

Zarejestruj nowe konto, to proste!

Zarejestruj się »

Zaloguj się

Posiadasz własne konto? Użyj go!

Zaloguj się »
×
×
  • Utwórz nowe...