Czujnik Kolorów TCS3200

Tu poruszamy tematy związane z pisaniem programów w języku C++ dla AVR.
Awatar użytkownika
WoodPaker
User
User
Posty: 136
Rejestracja: czwartek 17 wrz 2015, 19:23
Lokalizacja: USA
Kontaktowanie:

Czujnik Kolorów TCS3200

Postautor: WoodPaker » poniedziałek 22 maja 2017, 21:41

Dzień Dobry wszystkim czytającym. Z braku czasu dawno nic nie robiłem na żadnym układzie. Czasem wszedłem na czata pogadać. Ot i tyle. Ale trafił w moje ręce czujnik TCS3200. Jest to o czujnik, który "zamienia" kolor jaki "widzi" na częstotliwość, którą możemy odczytać, a następnie przekształcić na RGB. Jako, że zostało mi postawione pewne zadania, a w ramach powolnego przerzucania się z C na C++ postanowiłem napisać program do obsługi tegoż urządzenia w oparciu o klasy.
Na początku garść informacji o samym czujniku. Czujnik posiada diody światłoczułe, macierz 4x4 z filtrem R, 4x4 z filtrem G, 4x4 z filtrem B oraz 4x4 całkowicie bez filtra. Poprzez piny możemy wybrać, z których diod odczytujemy wartość. Czujnik ma 8 wyprowadzeń, 2 od zasilania (GND, VCC) oraz 8 I/O (S0, S1,S2,S3,OE,OUT), które służą do sterowania czujnikiem.

TCS3200.jpg

czujnik TCS3200

Wejścia S0 do S3 służą do ustawienia filtra oraz częstotliwości, OE (Output Enabled - 0=aktywne) służy do włączania, wyłączania pomiaru koloru. Na OUT zaś możemy odczytać częstotliwość jaką zwraca nam czujnik po "odczytaniu" koloru.
W tabeli poniżej pokazane jest jak należy sterować urządzeniem.

TCS3200-table.png

Tabela ustawień czunika TCS3200

Aby odczytać częstotliwość na pinie OUT możemy wykorzystać kilka podejść. Najprostszym dla mnie było użycie przerwania sprzętowego INT i zliczanie wystąpień przerwania w ustalonym czasie, dla każdego filtra z osobna. W tym celu przygotowałem sobie klasę, która obsługuje sprzętowy INT oraz wykorzystałem gotową klasę Antystatycznego do obsługi UART. Poniżej przedstawiam pliki deklaracji klasy oraz główny program do odczytu kolorów. Oczywiście jest to tylko podstawa, którą można rozbudować wedle własnego uznania. Natomiast po klasę do obsługi USARTa proszę zgłosić się do Antystatycznego.
int.hpp

Kod: Zaznacz cały

//int.hpp - autor - WoodPaker
#include <avr/interrupt.h>
#pragma once
 
enum _EICRA {_LOW,_ANY,_FALING,_RASING};
 
 
class class_INT0
{
    public:
        /** Default constructor */
        class_INT0();
        class_INT0(uint8_t _EICRA, void(*callback_int)(void));
        /** Default destructor */
 
         void on(void);
         void off(void);
         ~class_INT0();
 
 
    protected:
 
    private:
};


int.cpp

Kod: Zaznacz cały

//int.cpp - autor - WoodPaker
#include "_INT.h"
 
 
void (*callback_i)();
 
class_INT0::class_INT0()
{
    EICRA=0x03;
    EIMSK |= (1 << INT0);;
    callback_i=nullptr;
}
 
class_INT0::class_INT0(uint8_t _EICRA, void(*callback_int)(void))
{
    EICRA=_EICRA;
    callback_i = callback_int;
 
}
 
void class_INT0::on(void)
{
    EIMSK |= (1 << INT0);
}
 
 
void class_INT0::off(void)
{
    EIMSK &= ~(1 << INT0);
}
 
class_INT0::~class_INT0()
{
    //dtor
}
 
ISR (INT0_vect)
{
    (*callback_i)();
}


main.cpp

Kod: Zaznacz cały

#include <stdint.h>
#include <avr/io.h>
#include <avr/interrupt.h>
#include <util/delay.h>
#include "usart.hpp"
#include <stdlib.h>
#include "_INT.h"
 
#define S0 PC5
#define S1 PC4
#define S2 PC3
#define S3 PC2
#define OUT PD2
#define OE PD5
 
#define S0_high PORTC |= (1<<S0);
#define S0_low PORTC &= ~(1<<S0);
 
#define S1_high PORTC |= (1<<S1);
#define S1_low PORTC &= ~(1<<S1);
 
#define S2_high PORTC |= (1<<S2);
#define S2_low PORTC &= ~(1<<S2);
 
#define S3_high PORTC |= (1<<S3);
#define S3_low PORTC &= ~(1<<S3);
 
 
volatile uint32_t time;
volatile uint32_t R;
volatile uint32_t G;
volatile uint32_t B;
volatile uint32_t C;
#define R_max 14478
#define G_max 14478
#define B_max 14478
#define C_max 14480
#define R_min 172
#define G_min 176
#define B_min 236
#define C_min 835
CircularBuffer rxBuffer, txBuffer;
 
void sendStr(char *buf);
 
 
Usart myUsart(0, 115200, &rxBuffer, &txBuffer);
char textR[6];
char textG[6];
char textB[6];
char textC[6];
enum Colours {RED,GREEN,BLUE,CLEAR}; //Unumerate Colours RED,GREEN,BLUE and ClEAR
uint32_t ReadColour(uint8_t Colour)
{
  switch(Colour)
  {
    case RED:
        S2_low;
        S3_low;
    break;
 
    case GREEN:
        S2_high;
        S3_high;
      break;
 
    case BLUE:
        S2_low;
        S3_high;
      break;
 
    case CLEAR:
        S2_high;
        S3_low;
      break;
  }
  _delay_ms(10);
  return time;
}
 
void INT_0()
{
    time++;
}
int main()
{
    class_INT0 Przerwanie(_RASING,INT_0);
    DDRC |= (1<<S0) | (1<<S1) | (1<<S2) | (1<<S3); //Ustawjako wyjœcia
    DDRD |= (1<<OE);
    DDRD &= ~(1<<OUT);//Ustaw jako wejœcie
    sei();
    myUsart << "\n\rSTART - freq 100 procent\n\r";
    S0_high;
    S1_high;
    PORTD |= (1<<OE);
    while(1)
    {
//RED
    Przerwanie.on();
    time=0;
    R=ReadColour(RED);
    Przerwanie.off();
 
//GREEN
    Przerwanie.on();
    time=0;
    G=ReadColour(GREEN);
    Przerwanie.off();
//BLUE
    Przerwanie.on();
    time=0;
    B=ReadColour(BLUE);
    Przerwanie.off();
//CLEAR
    Przerwanie.on();
    time=0;
    C=ReadColour(CLEAR);
    Przerwanie.off();
    itoa(R,textR,10);
    itoa(G,textG,10);
    itoa(B,textB,10);
    itoa(C,textC,10);
    myUsart << "R="<<textR<<"   G="<<textG<<"   B="<<textB<<"   C="<<textC;
    myUsart << "\n\rKOLEJNY POMIAR\n\r";
    _delay_ms(2000);
    }
}


Jak działa program?
Otóż na początku trzeba stworzyć obiekty obsługujące przerwanie zewnętrzne INT0 oraz obiekt obsługujący UART. Zrobione jest to poprzez te dwie definicje:
Usart myUsart(0, 115200, &rxBuffer, &txBuffer);
class_INT0 Przerwanie(_RASING,INT_0);


W konstruktorze obiektu przerwania podajemy funkcję (a raczej jej adres) do obsługi przerwania, w tym przypadku jest to zwiększenie licznika, oraz przy jakiej zmianie na PINie przerwanie ma się pojawiać. Ja wybrałem sygnał wzrastający. Funkcja uint32_t ReadColour(uint8_t Colour) służy do odczytu ilości wystąpień przerwania w czasie 10ms. Następnie wartość ta składowana jest w zmiennej globalnej. Wiem, wiem, program główny jest lekko przerośnięty ale to dlatego, że robiłem testy, chcę jeszcze wprowadzić do niego funkcje przeliczającą odczytaną częstotliwość na RGB i stąd kompletny brak optymalizacji.
Jak odczytać kolor w postaci RGB?
Po pierwsze trzeba skalibrować czujnik. Czyli odczytujemy wartości częstotliwości dla kanałów RGB dla białego koloru i składujemy je jako zmienne (lub stałe) Rb,Gb,Bb. To samo robimy dla koloru czarnego i składujemy je jako Rc, Gc, Bc. A następnie trzeba przeskalować otrzymaną wartość dla danego koloru według następującego wzoru( (X-Xc)/(Xb-Xc))*255. Gdzie X to odczytana wartość danego kanału dla wybranego koloru, (RGB), Xc to wartość dla wybranego kanału dla koloru czarnego, a Xb to wartości dla koloru białego. Jednym matematycznym zdaniem X={R,G,B}.

I to tyle w temacie. Zapraszam do korzystania. I nie czepiać sie programu głównego bo służy on tylko do testów i jest całkowicie niezoptymalizowany. Można się czepiać wyłącznie procedur i ewentualnie klas :P
Nie masz wymaganych uprawnień, aby zobaczyć pliki załączone do tego posta.
Life is to short to eject USB safely

Awatar użytkownika
tasza
Geek
Geek
Posty: 1082
Rejestracja: czwartek 12 sty 2017, 10:24
Kontaktowanie:

Re: Czujnik Kolorów TCS3200

Postautor: tasza » poniedziałek 22 maja 2017, 21:51

no super! a ja teraz w głowę zachodzę, czy układ co wspominasz nie siedzi czasem w tym bricku do Lego EV3 ( 45506 )
http://www.sklepmanami.pl/pl/p/LEGO-455 ... ensor-/531
bo kilka wzmianek na forach by to potwierdzało właśnie ... no nic, jak się zbierzemy na odwagę to kostkę otworzymy w celach badawczych ;)
______________________________________________ ____ ___ __ _ _ _ _
Kończysz tworzyć dopiero, gdy umierasz. (Marina Abramović)

Awatar użytkownika
WoodPaker
User
User
Posty: 136
Rejestracja: czwartek 17 wrz 2015, 19:23
Lokalizacja: USA
Kontaktowanie:

Re: Czujnik Kolorów TCS3200

Postautor: WoodPaker » poniedziałek 22 maja 2017, 22:12

Wcale bym się nie zdziwił gdyby tak było. Może być też układ podobny TS230, który ma troszke inny kształt. W sumie to mam tego bricka ale jakoś nie mam odwagi tego otworzyć choć akurat nie kosztowała mnie ta zabawka nic :). Jedną rzecz jaką zauważyłem w czasie odczytu, że odczytana wartość RGB (już po przekształceniu) jest kilka tonów za ciemna. Teoretycznie można by się pokusić o wykorzystanie kanału CLEAR żeby odpowiednio wysterować przekształcenia ale jakoś nie mam sumienia bawić się w matematykę. Mam jej na co dzień dość sporo w pracy. Najlepiej użyć algorytmu, który umie odczytać wartość RGB i powiedzieć nam czy jest to w zakresie koloru czerwonego, białego, żółtego itd. Takie algorytmy są gotowe i proste do znalezienia w necie. Są tez gotowe aplikacje pod arduino z zastosowaniem tych przekształceń i wykorzystaniem tego czujnika. Także można sobie przerobić albo używać arduino. Ja osobiście wole sam zrobić od zera niż bawić się w arduino....Ale co kto lubi :)
Life is to short to eject USB safely

Awatar użytkownika
Antystatyczny
Geek
Geek
Posty: 1168
Rejestracja: czwartek 03 wrz 2015, 22:02

Re: Czujnik Kolorów TCS3200

Postautor: Antystatyczny » wtorek 23 maja 2017, 00:17

Wood, całkiem fajna robótka. Aha, nic nie stoi na przeszkodzie, byś udostępnił ten mój kawałek softu w C++. To tylko wersja próbna, ale może kogoś zainspirować ;)
"The true sign of intelligence is not knowledge but imagination" Albert Einstein.

Awatar użytkownika
WoodPaker
User
User
Posty: 136
Rejestracja: czwartek 17 wrz 2015, 19:23
Lokalizacja: USA
Kontaktowanie:

Re: Czujnik Kolorów TCS3200

Postautor: WoodPaker » wtorek 23 maja 2017, 21:16

Za radą Antystatycznego wrzucam kod USARTa oraz jego bufora

buffer.hpp

Kod: Zaznacz cały

/*
 * buffer.hpp
 *
 *  Created on: 28 wrz 2014
 *      Author: Arkadiusz Pytlik
 */

#ifndef BUFFER_HPP_
#define BUFFER_HPP_

#include <stdint.h>

// Wielkosc bufora powinna byc potega… 2-ki.
#ifndef BUFFER_SIZE
#   define BUFFER_SIZE   8
#endif

class CircularBuffer
{
public:
   CircularBuffer();
   void write(uint8_t data);
   uint8_t read(void);
   void reset(void);
   bool isEmpty(void) const;
   bool isFull(void) const;
private:
   uint8_t buffer[BUFFER_SIZE];
   volatile uint8_t head;
   volatile uint8_t tail;
};
#endif /* BUFFER_HPP_ */



buffer.cpp

Kod: Zaznacz cały

/*
 * buffer.cpp
 *
 *  Created on: 28 wrz 2014
 *      Author: Arkadiusz Pytlik
 */

#include"buffer.hpp"

#define BUFFER_SIZE_MASK ( BUFFER_SIZE - 1)

#if ( BUFFER_SIZE & BUFFER_SIZE_MASK)
# error "Buffer size must be power of 2"
#endif

CircularBuffer::CircularBuffer() :
      head(0), tail(0)//inicjalizacja zmiennych
{

}

// XXX: Przed odczytem zawsze sprawdz czy bufor nie jest pusty (isEmpty()).
uint8_t CircularBuffer::read(void)
{
   uint8_t temp = buffer[tail];

   tail = (tail + 1) & BUFFER_SIZE_MASK;

   return temp;
}

// XXX: Przed zapisem zawsze sprawdz czy bufor nie jest pelny (isFull()).
void CircularBuffer::write(uint8_t data)
{
   uint8_t newHead;//dlaczego nie register?

   newHead = (head + 1) & BUFFER_SIZE_MASK;

   buffer[head] = data;

   head = newHead;
}

// Wyzerowanie bufora.
void CircularBuffer::reset(void)
{
   head = tail = 0;
}

bool CircularBuffer::isEmpty(void) const
{
   return (head == tail);
}

bool CircularBuffer::isFull(void) const
{
   return (((BUFFER_SIZE + head - tail) & BUFFER_SIZE_MASK) == BUFFER_SIZE_MASK);//to jest w pelni jasne
}



usart.hpp

Kod: Zaznacz cały

 
/*
 * usart.hpp
 *
 *  Created on: 25 wrz 2014
 *      Author: Arkadiusz
 */

#ifndef USART_HPP_
#define USART_HPP_

#include <stdio.h>
#include <stdint.h>
#include "buffer.hpp"

#define USART_COUNT   2

class Usart
{
   // Klasa nie jest kopiowalna.
   // Ma zablokowany konstruktor domyslny i operator przypisania
   Usart();
   Usart& operator=(const Usart&);//nie do konca to kumam <----

public:
   Usart(uint8_t usartNr, uint32_t baud = 9600, CircularBuffer * const rxBuf = 0, CircularBuffer * const txBuf = 0);

   void putByte(const uint8_t byte) const;
   uint8_t getByte(void) const;
   void flush(void) const;

   // A tak dla jaj, zrobię operator << ze stringiem i się nada :-)
   Usart& operator<<(char const * str);

   void readUsart(void) const;
   void writeUsart(void) const;
private:

   uint8_t usartNumber;
   CircularBuffer *rxBuffer;
   CircularBuffer *txBuffer;
};

#endif /* USART_HPP_ */



usart.cpp

Kod: Zaznacz cały

/*
 * usart.cpp
 *
 *  Created on: 25 wrz 2014
 *      Author: Arkadiusz
 */
#include<avr/interrupt.h>
#include"usart.hpp"

#define USART0_IDX   0
#define USART1_IDX   1

//typ wskaznikowy na metody w klasie Usart
typedef void (Usart::*usartCallbackType)(void) const;

static usartCallbackType rxCallback = &Usart::readUsart;
static usartCallbackType txCallback = &Usart::writeUsart;

static Usart *usartTable[USART_COUNT];

Usart::Usart(uint8_t usartNr, uint32_t baud, CircularBuffer * const rxBuf,
      CircularBuffer * const txBuf) :
      usartNumber(usartNr)
{
   uint16_t ubrr;

   ubrr = ((F_CPU + baud * 8UL) / (16UL * baud) - 1);
   UBRR0H = ubrr >> 8;
   UBRR0L = ubrr;

   // Sekcja krytyczna
   // -----------------
   uint8_t sreg = SREG;         //zapis statusu procesora
   cli();

   // Adresy są 2 bajtowe, wymagana atomowość.

   // Przypisanie adresów buforów i uruchomienie przerwań
   if (nullptr != (rxBuffer = rxBuf))
   {
      UCSR0B |= (1 << RXCIE0);
   }
   if (nullptr != (txBuffer = txBuf))
   {
      UCSR0B |= (1 << UDRIE0);
   }

   usartTable[usartNumber] = this;   //this = 2 bajtowy adres bieżącej instancji

   SREG = sreg;               //odtwarzam  status procesora

   // ------------------------
   // Koniec sekcji krytycznej

   // Uruchomienie nadajnika i odbiornika oraz zerowanie odbiornika
   UCSR0B |= (1 << RXEN0) | (1 << TXEN0) | (1 << RXC0);
}

void Usart::putByte(const uint8_t byte) const
{
   // Jeśli został przydzielony bufor, nieblokujące (o ile się da) wysłanie za jego pośrednictwem
   if (txBuffer)
   {
      // Oczekiwanie aż będzie wolne miejsce w buforze
      while (txBuffer->isFull());
      // Wysłanie danych do bufora
      txBuffer->write(byte);
      // Wymuszenie wysłania przez zgłoszenie przerwania ,,pusty bufor nadawczy USART''
      UCSR0B |= (1 << UDRIE0);
   }
   else
   {
      // Nie ma przydzielonego bufora, sprawdzenie czy port gotowy do nadania (wyszły poprzednie dane)
      while (!(UCSR0A & (1 << UDRE0)));
      // Nadanie
      UDR0 = byte;
   }
}

uint8_t Usart::getByte(void) const
{
   // Jeśli przydzielony bufor, to nieblokujące (o ile się da) pobranie z bufora
   if (rxBuffer)
   {
      // Oczekiwanie aż będą dane w do odebrania z bufora
      while ( !( rxBuffer->isEmpty() ) );
      // Odebranie danych z bufora
      return rxBuffer->read();
   }
   // Nie ma przydzielonego bufora, sprawdzenie czy jest bajt gotowy do odebrania
   while (!(UCSR0A & (1 << RXC0)));
   // Zwrócenie bajtu z portu
   return UDR0;
}

// Oczekiwanie aż wszystkie dane opuszczą Usart
void Usart::flush(void) const
{
   // Nie ma przydzielonego bufora
   if (!txBuffer)
   {
      while (!(UCSR0A & (1 << UDRE0)));
      return;
   }
   // Oczekiwanie aż bufor będzie pusty
   while (!(txBuffer->isEmpty()));
}

Usart& Usart::operator<<(char const * str) {
   uint8_t value;
   while((value = *str++) != '\0') {
      putByte(value);
   }
   return *this;
}

// *********************   callbacki ***********************************

void Usart::readUsart(void) const
{
   // Bufor przepełniony, natychmiastowy powrót, dane zgubione.
   if (rxBuffer->isFull())
   {
      // Zignorowanie odebranego bajtu
      UCSR0A &= ~(1 << RXC0);//a gdzie odczyt UDR???
      return;
   }
   rxBuffer->write(UDR0);
}

void Usart::writeUsart(void) const
{
   // Bufor pusty, nie ma co zapisywać.
   if (txBuffer->isEmpty())
   {
      // Kasowanie flagi przerwania. Nie ma nic do wysłania.
      UCSR0B &= ~(1 << UDRIE0);
      return;
   }

   // Wysyłanie danych z bufora
   UDR0 = txBuffer->read();
}

// ************************** ISR **********************************

// Przerwanie wywołane jeśli rejestr odbiorczy zawiera dane
ISR(USART_RXC_vect)
{
   if (usartTable[USART0_IDX])
   {
      (usartTable[USART0_IDX]->*rxCallback)();
   }
}

// Przerwanie wywołane jeśli rejestr nadawczy będzie pusty
ISR(USART_UDRE_vect)
{
   if (usartTable[USART0_IDX])
   {
      (usartTable[USART0_IDX]->*txCallback)();//zostanie wywolany, gdy oprozni sie rejestr
   }
}



To jest prawdziwy majstersztyk C++. :)
Life is to short to eject USB safely


Wróć do „Programowanie AVR w C++”

Kto jest online

Użytkownicy przeglądający to forum: Obecnie na forum nie ma żadnego zarejestrowanego użytkownika i 2 gości