Operácie

Senzor farieb TCS230

Z SensorWiki

Záverečný projekt predmetu MIPS / LS2023 - Tomáš Bečvarov


Zadanie

Senzor farieb - naprogramujte senzor na rozpoznávanie jednotlivých farieb, zobrazenie na LCD displej.

Chceme dosiahnúť toho, aby sme vedeli rozoznávať rôzne farby za pomocy senzora TCS230.


Literatúra:


Analýza a opis riešenia

Senzor pracuje na takom princípe, že keď ho zapojíme do vývojovej dosky, tak bude naspäť posielať obdĺžnikový signál, ktorého frekvencia je priamo úmerná intenzite svetla. Senzor má 64 fotodiód, z čoho 16 má červený filter, 16 má zelený filter, 16 ma modrý filter a 16 má čistích, teda bez filtru. Tieto filtre dokážeme nastavovať pomocou pinov S2 a S3 (S2 -> log.0 a S3 -> log.0 = červený filter, atď). Takže nám stačí nastaviť filter na určitú frabu a potom stačí zmerať frekvenciu zo senzora. Na meranie frekvencii sme použili počítadlo 1 (TIMER1), čo je 16 bitové počítadlo, ktorým sme merali čas od dobežnej hrany po nábežnú a keď sme tým podelili polovicu frekvencie kryštálu (polovicu preto, lebo sme merali iba pól periódu, pretože ten signál ma 50% duty), tak dostaneme výslednú frekvenciu pri danom filtri. Potom to už len zobrazíme na LCD displaj.

TCS230 senzor farieb.

Schéma zapojenia LCD displeja.

Schéma zapojenia LCD displeja.

Schéma zapojenia senzora TCS230.

Schéma zapojenia senzora TCS230.

Algoritmus a program

Algoritmus programu je....


#include <avr/io.h>

#include <avr/io.h>
#include <stdio.h>
#define F_CPU 16000000UL
#include <util/delay.h>
#include "lcd_ch.h"

#define LCD_CLR 0      // DB0: Vyčistenie displeja
#define LCD_CTRL_PORT DDRD
#define LCD_DATA_PORT DDRB

#define LCD_RS_pin 2	// Zadefinovanie pinov LCD displaja
#define LCD_RW_pin 3
#define LCD_EN_pin 4

#define LCD_D4_pin 1
#define LCD_D5_pin 2
#define LCD_D6_pin 3
#define LCD_D7_pin 4

#define S0_PIN  PD5		// Zadefinovanie pinov snímača
#define S1_PIN  PD6
#define S2_PIN  PD7
#define S3_PIN  PB0
#define OUT_PIN  PB5

void setup() 
{
    //LCD:
	DDRD |= (1<<LCD_EN_pin);	// Pin D4 (Enable)  PORTD  výstup  
	DDRD |= (1<<LCD_RW_pin);	// Pin D3 (RW)      PORTD  výstup  
	DDRD |= (1<<LCD_RS_pin);	// Pin D2 (RS)      PORTD  výstup  
	
	LCD_DATA_PORT |= (1<<LCD_D4_pin)|(1<<LCD_D5_pin)|(1<<LCD_D6_pin)|(1<<LCD_D7_pin);	// Piny 1,2,3,4, PORTB ako výstupy (Dáta pre display)
	
	//Snimac:
	DDRD |= (1 << S0_PIN) | (1 << S1_PIN) | (1 << S2_PIN);   // Nastavenie pinov S0, S1 a S2 ako výstupy
	DDRB |= (1 << S3_PIN);		// Nastavenie S3 ako výstup
    DDRB &= ~(1 << OUT_PIN);	// Nastavenie pinu OUT ako vstup

    // Nastavenie frekvenčnej škály na 2%
    PORTD &= ~(1 << S0_PIN);   // S0: 0
    PORTD |= (1 << S1_PIN);    // S1: 1
}

uint16_t TCSMeasure()
{
   if(!(PINB & (1 << OUT_PIN)))		// 
   {
      while(!(PINB & (1 << OUT_PIN)));   //Čakanie na nábežnú hranu  
   }

   while(PINB & (1 << OUT_PIN));   //Čakanie na dobežnú hranu

   TCNT1=0x0000;	//Resetovanie počítadla

   TCCR1B=(1<<CS10);	//Nastavenie predeličky = F_CPU/1 (začiatok počítania)

   while(!(PINB & (1 << OUT_PIN)));   //Čakanie na nábežnú hranu

   TCCR1B=0x00;		//Zastavenie počítadla

   return ((float)8000000UL/TCNT1);     //Vrátenie nameranej frekvenice

}

long map(long x, long in_min, long in_max, long out_min, long out_max)		//Funkcia map na kalibráciu snímača
{
  return (x - in_min) * (out_max - out_min) / (in_max - in_min) + out_min;		//(premenná, najmenšia daná hodnota, najväčšia daná hodnota, najmenšia požadovaná hodnota, najväčšia požadovaná hodnota)
}


int main(void) 
{  
	setup();	// iniciácia nastavenia vstupov
	
	lcd_init();  // iniciácia LCD

	void lcd_puts(const char *s)	// funkcia na použivanie puts
	{
		register unsigned char c;
		while((c = *s++))
			lcd_data(c);
	}	

	char vysledokR[  ]={  };	// nastavenie premenných na zapisovanie hodnôt
	char vysledokG[  ]={  };
	char vysledokB[  ]={  };
		
    while(1)
	{
		lcd_command(1 <<  LCD_CLR);		// vyčistenie displaja
		lcd_command(0b00001100);	//odstranenie kurzora
		lcd_command(0x80);		// nastavenie na prvý riadok
		lcd_puts("Namerana farba:");	//Napiseme do prveho riadku
		
		PORTD &= ~(1 << S2_PIN);	//Nastavenie červeného filtra
		PORTB &= ~(1 << S3_PIN);

		unsigned int hodnotaR = TCSMeasure();	//Uloženie nameranej hodnoty pre červený filter
		hodnotaR = map(hodnotaR,433,2195,0,255);	//upravenie na požadované hodnoty
		
		lcd_command(0xC0);		// nastavenie na druhý riadok
		lcd_puts("R=");
        sprintf(vysledokR,"%d",hodnotaR);	// prepísanie čísla ako charakter
		lcd_puts(vysledokR);		//Vypisanie na LCD displaj
		_delay_ms(10);
		
		PORTD |= (1 << S2_PIN);		//Nastavenie zeleného filtra
		PORTB |= (1 << S3_PIN);
		
		unsigned int hodnotaG = TCSMeasure();	//Uloženie nameranej hodnoty pre zelený filter
		hodnotaG = map(hodnotaG,384,1300,0,255);	//Upravenie na požadované hodnoty
		
		lcd_command(0xC0+5);	// nastavenie na 6tu pozíciu na druhom riadku
		lcd_puts("G=");
		sprintf(vysledokG,"%d",hodnotaG);	// prepísanie čísla ako charakter
		lcd_puts(vysledokG);		//Vypisanie na LCD displaji
		_delay_ms(10);
		
		PORTD &= ~(1 << S2_PIN);	//Nastavenie modrého filtra
		PORTB |= (1 << S3_PIN);
		
		unsigned int hodnotaB = TCSMeasure();	//Uloženie nameranej hodnoty pre modrý filter
		hodnotaB = map(hodnotaB,487,2100,0,255);	//Upravenie na požadované hodtoty
		
		lcd_command(0xC0+10);	// Nastavenie na 11tu poziciu na druhom riadku
		lcd_puts("B=");
		sprintf(vysledokB,"%d",hodnotaB);	// prepísanie čísla ako charakter
		lcd_puts(vysledokB);	//Vypisanie na LCD dispaji
		_delay_ms(200);

	}     
   
}
/*
 * lcd_ch.h
 *
 * Created: 3/10/2021 7:05:39 PM
 *  Author: Admin
 */ 
#ifndef F_CPU
 #define F_CPU 16000000UL	/* Define CPU frequency here 16MHz */
#endif

#ifndef LCD_CH_H_
 #define LCD_CH_H_

#include <avr/io.h>
#include <util/delay.h>

/* Nasledovne define rozhodne ktora cast programu sa prelozi: 
   LCD Shiled (WR ma pripojene na GND) a
   priradenie pinov je dane napr.:
   "zamrzne"
   -    https://www.14core.com/wiring-the-lcd-16x2-keypad-shield-on-arduino/
   -	https://wiki.dfrobot.com/LCD_KeyPad_Shield_For_Arduino_SKU__DFR0009
   resp. 
   LCD, zapojenie vid. stranka MIPS
   "zamrzne"
   - http://senzor.robotika.sk/sensorwiki/index.php/LCD_displej_s_radi%C4%8Dom_HD44780
   , ktore ma pripojene aj pin WR
   t.j. moze sa testovat aj pin BF
*/

 /* !!!!!!! 
    #define _Shield_LCD
    !!!!!!! */
extern unsigned char kon_vyp;

#ifdef _Shield_LCD
	#define LCD_CTRL_DDR DDRB
	#define LCD_CTRL_PORT PORTB

	#define LCD_DATA_DDR DDRD
	#define LCD_DATA_PORT PORTD

	// Riadiaca zbernica display-a
	#define LCD_RS_pin 0
	//#define LCD_RW_pin = 0
	#define LCD_EN_pin 1
	// Datova zbernica
	#define LCD_D4_pin 4
	#define LCD_D5_pin 5
	#define LCD_D6_pin 6
	#define LCD_D7_pin 7

#else
	// LCD  klasik yapojenie vid. MIPS
	#define LCD_CTRL_DDR DDRD
	#define LCD_CTRL_PORT PORTD

	#define LCD_DATA_DDR DDRB
	#define LCD_DATA_PORT PORTB
	#define LCD_DATA_PIN PINB

	#define LCD_RS_pin 2
	#define LCD_RW_pin 3
	#define LCD_EN_pin 4

	#define LCD_D4_pin 1
	#define LCD_D5_pin 2
	#define LCD_D6_pin 3
	#define LCD_D7_pin 4

#endif
 



// Oneskorenie 6 SC
#define NOP() asm("nop")	
#define LCD_DELAY	NOP();NOP();NOP();NOP();NOP();NOP();

#ifdef _Shield_LCD
// formatovanie dat
#define PORT_DATA_WR_H(x) LCD_DATA_PORT &=0b00001111; LCD_DATA_PORT |= (x & 0xF0 )
#define PORT_DATA_WR_L(x) LCD_DATA_PORT &=0b00001111; LCD_DATA_PORT |= (x & 0x0F )<<4
#else

#define PORT_DATA_WR_H(x) LCD_DATA_PORT &=0b11100001; LCD_DATA_PORT |= (x & 0xF0 )>>3
#define PORT_DATA_WR_L(x) LCD_DATA_PORT &=0b11100001; LCD_DATA_PORT |= (x & 0x0F )<<1

#define PORT_DATA_RD_H ((LCD_DATA_PIN & ((1<<LCD_D4_pin)|(1<<LCD_D5_pin)|(1<<LCD_D6_pin)|(1<<LCD_D7_pin)))<<3)
#define PORT_DATA_RD_L ((LCD_DATA_PIN & ((1<<LCD_D4_pin)|(1<<LCD_D5_pin)|(1<<LCD_D6_pin)|(1<<LCD_D7_pin)))>>1)


#endif

/* Public functions for your use */
#ifndef _Shield_LCD
int8_t lcd_read_AC(void);   
void def_spec_znaky_AC(void);
#endif
	
void lcd_init(void);
void lcd_data(unsigned char );
void lcd_command(unsigned char );
// void lcd_puts(const char *s);  /* deklaracia funkcie  */



void ini_ports(void);
void En_imp(void);

void wr_data (unsigned char );
unsigned char busy_flag(void);

void zob_text(char *);
void def_Clear_spec_znaky(void);


void def_znak(unsigned char *,unsigned char );
void def_spec_znaky(void);

#endif /* LCD_CH_H_ */
#include "lcd_ch.h"

unsigned char Znak_OO[8]= {0x00,0x00,0x00,0x00,0x00,0x00,0x15,0};// Prazdny znak	dole su tri bodky

unsigned char Znak_SC[8]= {0x1A,0x1D,0x04,0x04,0x04,0x05,0x02,0};// st.C	
unsigned char Znak_SM[8]= {0x0A,0x04,0x0E,0x10,0x0E,0x01,0x1E,0};//  s + mekcen	
unsigned char Znak_CM[8]= {0x0A,0x04,0x0E,0x10,0x10,0x11,0x0C,0};//  c + mekcen

// Vynulovanie CGRAM
void def_Clear_spec_znaky(void){
	// zapis specialneho znaku do CGRAM  na poziciu 0, a 7
	for(char i = 0; i < 8; i++) def_znak( Znak_OO,i);
	lcd_command(0x80);
} 


 
void def_spec_znaky(void){
// zapis vlastnych znakkov do CGRAM  na poziciu 4, a 0 (8) 


 def_znak( Znak_SC,4);// stupen Celzia
 def_znak( Znak_SM,0);// s + makcen
 // obnovenie obsahu AC. AC nastvime na 1. riadok, 1. znak
 lcd_command(0x80);
}		

#ifndef _Shield_LCD
void def_spec_znaky_AC(void){
// zapis specialneho znaku do CGRAM  na poziciu 7

unsigned char obsah_AC = 0;
 obsah_AC = lcd_read_AC(); //     
 // kon_vyp = obsah_AC;

 // sem dame nove vlastne znaky
 def_znak( Znak_CM,7);// c + makcen
 // obnovenie obsahu AC. AC 

lcd_command(0x80|obsah_AC);

}		
#endif
	 
void ini_ports(void){	 	
	
/* inicializacia portov - vstupy / vystupy 
oba typy displaja */
// nasledovna # riadky su spolocne pre oba typy LCD
LCD_CTRL_DDR |= (1<<LCD_EN_pin);	//  (Enable)    output  
LCD_CTRL_DDR |= (1<<LCD_RS_pin);	//  (RS)        output  	
LCD_DATA_DDR |= (1<<LCD_D4_pin)|(1<<LCD_D5_pin)|(1<<LCD_D6_pin)|(1<<LCD_D7_pin);	// Datove piny ako Output (Data pre display)
#ifndef _Shield_LCD
// klasicky LCD vyuziva aj RW pin
LCD_CTRL_DDR |= (1<<LCD_RW_pin);	//	(RW)        output  
	
#endif
}	


void En_imp(void) {
	LCD_CTRL_PORT |= (1<<LCD_EN_pin);	// -> "log.1"
	LCD_DELAY;							// cca 400 ns
	LCD_CTRL_PORT &= ~(1<<LCD_EN_pin);	// -> "log.0"  spolu cca 500 ns
}

void lcd_init(void)
{   // 4 bitove pripojenie display-a
	ini_ports(); // inicializacia porov
	 _delay_ms(15);
	// 1. -------------------
	LCD_CTRL_PORT &= ~(1 << LCD_RS_pin);		// set RS  = to "log. 0"  Instruction Register (IR)
#ifndef _Shield_LCD
	LCD_CTRL_PORT &= ~(1 << LCD_RW_pin)	;		// set R/W = to "log. 0" - Write
#endif
	// RS R/W   DB7 DB6 DB5 DB4   DB3 DB2 DB1 DB0
	// 0  0     0   0   1   1     x   x   x   x     = 0x30
	PORT_DATA_WR_H(0x30);	// 8-bitove pripojenie
	En_imp();
	// 2. -------------------
    // zopakujem  8-bitove pripojenie
	_delay_ms(5); En_imp();
	// 3. -------------------
	// zopakujem  8-bitove pripojenie
    _delay_ms(1); En_imp(); // -  stacilo by delay 0.1ms, momentalne najkratsie nastavitelne je 1ms
	// 4. -------------------
	// zmenim na 4-bitove pripojenie
	// RS R/W    DB7 DB6 DB5 DB4    DB3 DB2 DB1 DB0
	// 0  0      0   0   1   0      x   x   x   x    = 0x20
	PORT_DATA_WR_H(0x20);	// 4-bitove pripojenie
	_delay_ms(1); En_imp(); // -  stacilo by delay 0.04ms
	// -------------------		
	// LCD function set      mod: DL = 0 - 4-bitove data, N = 1 - 2 riadky, F = 0 - 5x7 dots 
	// RS R/W    DB7 DB6 DB5 DB4    DB3 DB2 DB1 DB0
	// 0  0      0   0   1   DL     N   F   x   x     = 0x28
	// Mozeme nasledujuci prikaz pre LCD vynechat?
	_delay_ms(2);
	lcd_command(0x28);
	// RS R/W    DB7 DB6 DB5 DB4    DB3 DB2 DB1 DB0
	// 0  0      0   0   0   0	    0   0   0   1     = 0x01 , Display clear
	// Mozeme nasledujuci prikaz pre LCD vynechat?
	_delay_ms(2);
	lcd_command(0x01);
	// RS R/W    DB7 DB6 DB5 DB4    DB3 DB2 DB1 DB0
	// 0  0      0   0   0   0	    0   1   I/D S      = 0x06, I/D = 1 - inkrement
	// Mozeme nasledujuci prikaz pre LCD vynechat?
	_delay_ms(2);
	lcd_command(0x02);
	// RS R/W    DB7 DB6 DB5 DB4    DB3 DB2 DB1 DB0
	// 0  0      0   0   0   0	    1   D   C   B      = 0x0E, D = C = 1 - Display a kurzor zapnute
	// Mozeme nasledujuci prikaz pre LCD vynechat?
	_delay_ms(2);
	lcd_command(0x0E);								// B = 0 - blikanie vypnute
}


//zapis data do Data Register
void lcd_data(unsigned char data){	
	while(busy_flag() & 0x80);
	//while(rd_BF()); // test BF sa da realizovat pre klasicky LCD, nie Shield
	LCD_CTRL_PORT |= (1<<LCD_RS_pin);		//    (RS = High)  
#ifndef _Shield_LCD
	LCD_CTRL_PORT &= ~(1<<LCD_RW_pin);		//    (RW = Low, write) 
#endif
	wr_data (data);	
}

// zapis commandu do Instruction Register
void lcd_command(unsigned char command){ 
	while(busy_flag() & 0x80);
	//while(rd_BF()); // test BF sa da realizovat pre klasicky LCD, nie Shield
	LCD_CTRL_PORT &= ~(1<<LCD_RS_pin);		//   (RS = Low)  
#ifndef _Shield_LCD
	LCD_CTRL_PORT &= ~(1<<LCD_RW_pin);		//   (RW = Low, write) 
#endif
	wr_data (command);	
}

void wr_data(unsigned char data) { 
	PORT_DATA_WR_H(data);		// data High nibble
	En_imp();
			
	PORT_DATA_WR_L(data);		// data Low nibble
	En_imp();
}

#ifdef _Shield_LCD
// namiesto testu BF "pockam". 
// LCD typu Shield ma WR pripojene na GND		
unsigned char busy_flag(void){
	_delay_ms(2);	
	return(0);
}	
#else
// namiesto testu BF "pockam". Vacsine prikazov tento cas vyhovuje		
/*int busy_flag(void){
	_delay_ms(2);	
	return(0);
}
*/
// klasicke pripojenie LCD umozni aj test BF
unsigned char busy_flag(void){ //  rd_BF   
	unsigned char pom = 0;
	LCD_CTRL_PORT &= ~(1<<LCD_RS_pin);	//   (RS = Low)  
	LCD_CTRL_PORT |= (1<<LCD_RW_pin);	//    (RW = High, read) 
    // port B, datove bity budu teraz input
	LCD_DATA_DDR &= ~((1<<LCD_D4_pin)|(1<<LCD_D5_pin)|(1<<LCD_D6_pin)|(1<<LCD_D7_pin));	//  Datove piny nastavime  na input, (Data pre disp)
	
	// Spravne by sa malo vycitavaj pred dobeznou hranou EN impulzu. Je tam urcity presah. Mozeme vycitat aj po dobeznej hrane.
	En_imp();
	pom = PORT_DATA_RD_H; // vycitam High nibble AC
	En_imp();
	pom |= PORT_DATA_RD_L; // vycitam Low nibble AC
	// datove bity zase output
	LCD_DATA_DDR |= (1<<LCD_D4_pin)|(1<<LCD_D5_pin)|(1<<LCD_D6_pin)|(1<<LCD_D7_pin);	// Datove piny nastavime na output (Data pre disp) 
	//if(pom & 0x80) return 1;	// display je busy
	//else 
	return pom;					// display je not busy
}

#endif
	

void zob_text(char *s){
	register unsigned char c;
	while((c = *s++))	lcd_data(c); // retazec konci "nulou"
}

void def_znak(unsigned char *ZnakArray,unsigned char kam) {	
	lcd_command(0x40|(kam << 3)); //nastavenie adresy znaku v CGRAM
	for(unsigned char i = 0; i < 8; i++) lcd_data( *(ZnakArray + i));		
}


/* ********************************************************	*/
/* vypis retazca na poziciu, resp. podla nasledovnych 		*/
/* formatovacich prikazov                                       */
/*   : \r - prechod na novy riadok                              */
/*   : \f - prechod na zaciatok displeja                        */
/* ********************************************************     */
 void lcd_puts(  const char *s)   /* definicia funkcie		*/
{
  // sem pride vas vlastny kod...  
}

#ifndef _Shield_LCD
int8_t lcd_read_AC(void){ //  rd_BF   
	char pom_AC ;
	
	while((pom_AC = busy_flag( )) & 0x80);
	// kedze po BF = 0 este cca 4us sa nezmenil obsah AC
	// treba vycitat este raz  
	pom_AC = busy_flag( );
	return pom_AC;					// display not busy
	
}
#endif

Kompletný projekt:

Zdrojový kód: TCS230_kód.zip

Overenie

Na používanie našej aplikácie stačí TCS230 senzor a LCD displej na ktorý budeme vypisovať hodnoty zo senzora. Na konci je tu fotka reálneho zapojenia.

Zapojenie v realite.

Video: