tag:blogger.com,1999:blog-55138330726088374102024-03-19T18:39:12.103-04:00Fabiano ArndtConfira aqui postagens sobre Arduino, Eletrônica e outros assuntos!
<br><br>
Youtube:
<a href="https://www.youtube.com/user/fabianoallex?sub_confirmation=1">youtube.com/fabianoallex</a>
<br>
Facebook:
<a href="https://www.facebook.com/dicasarduino">
facebook.com/dicasarduino</a>Fabiano Arndthttp://www.blogger.com/profile/15268923442674744187noreply@blogger.comBlogger103125tag:blogger.com,1999:blog-5513833072608837410.post-43225426390792013272019-04-05T10:51:00.000-04:002019-04-05T11:29:15.821-04:00Arduino - CharlieplexingNesse vídeo demonstro o funcionamento da técnica de multiplexação de leds chamada Charlieplex.<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<iframe allowfullscreen="" class="YOUTUBE-iframe-video" data-thumbnail-src="https://i9.ytimg.com/vi/H-aekB33svI/default.jpg?sqp=CMDRneUF&rs=AOn4CLCRew21HGFtquRlHdpkoFPo0UsHeQ" frameborder="0" height="415" src="https://www.youtube.com/embed/H-aekB33svI?feature=player_embedded" width="660"></iframe></div>
<br />
<br />
<b>Código-Fonte:</b><br />
<br />
<pre class="brush: html" style="background-color: #eeeeee; border: 1px dashed rgb(153, 153, 153); overflow: auto; padding: 5px; width: 100%;">/*************************************************************************************************************
*******************************BIT ARRAY *********************************************************************
**************************************************************************************************************
mais informações aqui: http://fabianoallex.blogspot.com.br/2015/09/arduino-array-de-bits.html
**************************************************************************************************************/
class BitArray{
private:
int _num_bits; //quantidade de bits a serem gerenciados
int _num_bytes; //quantidade de bytes utilizados para armazenar os bits a serem gerenciados
byte * _bytes; //array de bytes onde estaram armazenados os bits
public:
BitArray(int num_bits){
_num_bits = num_bits;
_num_bytes = _num_bits/8 + (_num_bits%8 ? 1 : 0) + 1;
_bytes = (byte *)(malloc( _num_bytes * sizeof(byte) ) );
}
void write(int index, byte value) {
byte b = _bytes[ index/8 + (index%8 ? 1 : 0) ];
unsigned int bit = index%8;
if (value) { b |= (1 << bit); } else { b &= ~(1 << bit); }
_bytes[ index/8 + (index%8 ? 1 : 0) ] = b;
}
void write(byte value) {
for(int j=0; j<_num_bytes;j++) { _bytes[j] = value ? B11111111 : B00000000; }
}
int read(int index) {
byte b = _bytes[ index/8 + (index%8 ? 1 : 0) ];
unsigned int bit = index%8;
return (b & (1 << bit)) != 0;
}
~BitArray(){ free( _bytes ); }
};
/*************************************************************************************************************
*******************************FIM BIT ARRAY *****************************************************************
**************************************************************************************************************/
class Charlieplexing {
private:
byte _numPins; //numero de pinos utilizados
int * _pins; //ponteiro para array com os pinos utilizados
byte _ledsOnPerGroup;//quantidade de leds de um mesmo grupo que pode ser ligada ao mesmo tempo
BitArray * _states; //armazenas o estados do leds
public:
Charlieplexing(int * pins, byte numPins, byte _ledsOnPerGroup=2);
int numLeds() { return _numPins * _numPins - _numPins; }
void update(int delay_=10, int repeat=1);
void turnOn(int i); //liga led i
void turnOff(int i); //desliga led i
void clear();
byte getState(int i); //retorna status led i
};
Charlieplexing::Charlieplexing(int * pins, byte numPins, byte ledsOnPerGroup){
_ledsOnPerGroup = ledsOnPerGroup;
_numPins = numPins;
_pins = pins;
_states = new BitArray(numLeds());
}
void Charlieplexing::turnOn(int i){
if (i < numLeds()){
_states->write(i, HIGH);
}
}
void Charlieplexing::turnOff(int i){
if (i < numLeds()){
_states->write(i, LOW);
}
}
byte Charlieplexing::getState(int i){
if (i < numLeds()){
return _states->read(i);
}
}
void Charlieplexing::clear(){
_states->write(LOW);
}
void Charlieplexing::update(int delay_, int repeat){
if (repeat <=0) { repeat = 1; }
for (int q=0; q<repeat; q++) {
for (int i=0; i<_numPins; i++) {
digitalWrite(_pins[i], LOW);
pinMode(_pins[i], OUTPUT);
byte cont = 0; //conta se há algum led a ser ligado
for (int j=0;j<_numPins;j++){
if (i != j) {
pinMode(_pins[j], INPUT);
digitalWrite(_pins[j], LOW);
}
}
for (int j=0; j<_numPins; j++) {
if (i != j) {
int posLed = 0;
if (i>j){
posLed = i * i - i + 2 * j + 1; //baseado no calculo do numero triangular
} else {
posLed = j * j - j + 2 * i; //baseado no calculo do numero triangular
}
if (_states->read(posLed) == HIGH){
pinMode(_pins[j], OUTPUT);
digitalWrite(_pins[j], LOW);
cont++;
}
}
if (cont == _ledsOnPerGroup && cont > 0 ) {
cont = 0;
digitalWrite(_pins[i], HIGH);
delay(delay_);
digitalWrite(_pins[i], LOW);
for (int jj=0;jj<=j;jj++){
if (i != jj) {
pinMode(_pins[jj], INPUT);
}
}
}
}
if (cont > 0){
cont = 0;
digitalWrite(_pins[i], HIGH);
delay(delay_);
digitalWrite(_pins[i], LOW);
}
}
}
}
int pins[] = {12, 11, 10, 9, 8};
int qtPins = 5;
int qtLedsOn = 2;
Charlieplexing cp(pins, qtPins, qtLedsOn); //pinos, qtpinos, qtledsOn
void setup(){
cp.turnOn(0);
cp.turnOn(2);
cp.turnOn(4);
cp.turnOn(8);
cp.turnOn(14);
cp.turnOn(7);
cp.turnOn(18);
cp.turnOn(13);
cp.turnOn(15);
cp.turnOn(17);
cp.turnOn(19);
}
void loop() {
cp.update(5, 20); //delay, repeat
}
</pre>
<br />Fabiano Arndthttp://www.blogger.com/profile/15268923442674744187noreply@blogger.com3tag:blogger.com,1999:blog-5513833072608837410.post-20248203885226708872019-03-07T17:09:00.004-04:002019-03-07T17:11:43.389-04:00Arduino - Várias lâmpadas controladas por um mesmo interruptorNesse vídeo mostro como controlar várias lâmpadas através de um único interruptor através do Arduino.<br />
<br />
<b>Vídeo</b><br />
<div class="separator" style="clear: both; text-align: center;">
<iframe allowfullscreen="" class="YOUTUBE-iframe-video" data-thumbnail-src="https://i9.ytimg.com/vi/bmSBw-K6Cto/default.jpg?sqp=CKiRhuQF&rs=AOn4CLAlTYmXAXZSJRjTmxRjgy1pnLAI4w" frameborder="0" height="415" src="https://www.youtube.com/embed/bmSBw-K6Cto?feature=player_embedded" width="660"></iframe></div>
<br />
<br />
<b>Código-fonte</b><br />
<pre class="brush: html" style="background-color: #eeeeee; border: 1px dashed rgb(153, 153, 153); overflow: auto; padding: 5px; width: 100%;">byte pinL1 = 3;
byte pinL2 = 4;
byte pinL3 = 5;
byte pinL4 = 6;
byte pinBt = 2;
byte lamp1 = 1;
byte lamp2 = 1;
byte lamp3 = 1;
byte lamp4 = 1;
void setup()
{
pinMode(pinBt, INPUT_PULLUP); //internal pullup resistor is used to simplify the circuit
pinMode(pinL1, OUTPUT);
pinMode(pinL2, OUTPUT);
pinMode(pinL3, OUTPUT);
pinMode(pinL4,OUTPUT);
}
byte digitalReadOnce(byte val){
static byte lastVal = LOW;
static unsigned long m = 0;
if (lastVal != val && millis() > (m+100) ) { //M+100 -->DEBOUNCING 100ms
lastVal = val;
m = millis();
return lastVal;
}
return LOW;
}
int getCommand(){
static unsigned long m1 = 0; //millis no momento inicia de pressionar o botão
static unsigned long m2 = 0; //millis após soltar o botão
static byte count = 0;
byte r = digitalRead(pinBt);
if (digitalReadOnce(r) == HIGH){
m1 = millis();
count++;
}
if (r == HIGH){
m2 = millis();
} else {
if (! (m2>0 && m2-m1 < 1000) ){ //o botão deve ser pressionado por menos de 1 segundo, senão cancela o comando
count = 0;
m1 = 0;
m2 = 0;
}
if (m2>0 && millis()-m2 > 1500){ //após a ultima vez pressionado o botao, aguarda 1,5 segundos para finalizar e retornar o comando.
byte c = count;
count = 0;
m1 = 0;
m2 = 0;
return c;
}
}
return 0;
}
void loop() {
if( digitalRead(pinBt) == HIGH ){
digitalWrite(pinL1, LOW);
digitalWrite(pinL2,LOW);
digitalWrite(pinL3,LOW);
digitalWrite(pinL4,LOW);
} else {
digitalWrite(pinL1, lamp1);
digitalWrite(pinL2,lamp2);
digitalWrite(pinL3,lamp3);
digitalWrite(pinL4, lamp4);
}
int command = getCommand();
if (command == 1){
lamp1 = !lamp1;
}
if (command == 2){
lamp2 = !lamp2;
}
if (command == 3){
lamp3 = !lamp3;
}
if (command == 4){
lamp4 = !lamp4;
}
delay(10); //apenas utilizado no simulador
}
</pre>
Fabiano Arndthttp://www.blogger.com/profile/15268923442674744187noreply@blogger.com3tag:blogger.com,1999:blog-5513833072608837410.post-34139348220853944662017-11-14T11:58:00.000-03:002017-11-16T11:43:48.001-03:00Arduino - Botão com múltiplas funçõesNesse vídeo mostro como executar em um único botão diversas funções distintas.<br />
<br />
<b>Vídeo</b><br />
<iframe allowfullscreen="" class="YOUTUBE-iframe-video" data-thumbnail-src="https://i9.ytimg.com/vi/diDgm_2gy6U/default.jpg?sqp=CMSHrNAF&rs=AOn4CLAhk4JpsflRPH_JrMyh0PI7XxVdgA" frameborder="0" height="415" src="https://www.youtube.com/embed/diDgm_2gy6U?feature=player_embedded" width="660"></iframe><br />
<br />
<b>Código-fonte</b><br />
<pre class="brush: html" style="background-color: #eeeeee; border: 1px dashed rgb(153, 153, 153); overflow: auto; padding: 5px; width: 100%;">byte inputPin = 10;
byte ledPin = 13;
byte redPin = 6;
byte greenPin = 4;
byte bluePin = 3;
byte digitalReadOnce(byte val){
static byte lastVal = HIGH;
static unsigned long m = 0;
if (lastVal != val && millis() > (m+100) ) { //M+100 -->DEBOUNCING 100ms
lastVal = val;
m = millis();
return lastVal;
}
return HIGH;
}
int getCommand(){
static unsigned long m1 = 0; //millis no momento inicia de pressionar o botão
static unsigned long m2 = 0; //millis após soltar o botão
static byte count = 0;
byte r = digitalRead(inputPin);
if (digitalReadOnce(r) == LOW){
m1 = millis();
count++;
}
if (r == LOW){
m2 = millis();
} else {
if (! (m2>0 && m2-m1 < 1000) ){ //o botão deve ser pressionado por menos de 1 segundo, senão cancela o comando
count = 0;
m1 = 0;
m2 = 0;
}
if (m2>0 && millis()-m2 > 1500){ //após a ultima vez pressionado o botao, aguarda 1,5 segundos para finalizar e retornar o comando.
byte c = count;
count = 0;
m1 = 0;
m2 = 0;
return c;
}
}
return 0;
}
void setup() {
Serial.begin(9600);
pinMode(inputPin, INPUT_PULLUP); //internal pullup resistor is used to simplify the circuit
pinMode(bluePin, OUTPUT);
pinMode(greenPin, OUTPUT);
pinMode(redPin, OUTPUT);
pinMode(ledPin,OUTPUT);
}
void loop() {
int command = getCommand();
if (command == 1){
digitalWrite(redPin, !digitalRead(redPin));
}
if (command == 2){
digitalWrite(greenPin, !digitalRead(greenPin));
}
if (command == 3){
digitalWrite(bluePin, !digitalRead(bluePin));
}
if (command == 4){
digitalWrite(redPin, HIGH);
digitalWrite(greenPin, HIGH);
digitalWrite(bluePin, HIGH);
}
if (command == 5){
digitalWrite(redPin, LOW);
digitalWrite(greenPin, LOW);
digitalWrite(bluePin, LOW);
}
digitalWrite(ledPin, !digitalRead(inputPin));
delay(10); //apenas utilizado no simulador
}
</pre>
<b><br /></b>
<b><br /></b><br />
<b>Comandos via LDR</b><br />
<b><br /></b>
Baseado no exemplo anterior, foi substituido o botão por um LDR, onde é possível enviar comandos pro Arduino através de luz.<br />
<br />
<b>Vídeo:</b><br />
<div class="separator" style="clear: both; text-align: center;">
<iframe allowfullscreen="" class="YOUTUBE-iframe-video" data-thumbnail-src="https://i9.ytimg.com/vi/VvpxjnrPRmM/default.jpg?sqp=CMDGttAF&rs=AOn4CLAZqIoC1pElsCdiKbl0Z5a2oVT19w" frameborder="0" height="415" src="https://www.youtube.com/embed/VvpxjnrPRmM?feature=player_embedded" width="660"></iframe></div>
<br />
<br />
<b>Código-Fonte:</b><br />
<pre class="brush: html" style="background-color: #eeeeee; border: 1px dashed rgb(153, 153, 153); overflow: auto; padding: 5px; width: 100%;">byte ledPin = 13;
byte redPin = 6;
byte greenPin = 4;
byte bluePin = 3;
boolean hasLightOnce(byte val){
static boolean lastVal = false;
static unsigned long m = 0;
if (lastVal != val && millis() > (m+100) ) { //M+100 -->DEBOUNCING 100ms
lastVal = val;
m = millis();
return lastVal;
}
return false;
}
boolean hasLight(){
return (analogRead(A0) < 100);
}
int getCommand(){
static unsigned long m1 = 0; //millis no momento inicia de pressionar o botão
static unsigned long m2 = 0; //millis após soltar o botão
static byte count = 0;
boolean light = hasLight();
if (hasLightOnce(light)){
m1 = millis();
count++;
}
if (light){
m2 = millis();
} else {
if (! (m2>0 && m2-m1 < 1000) ){ //o botão deve ser pressionado por menos de 1 segundo, senão cancela o comando
count = 0;
m1 = 0;
m2 = 0;
}
if (m2>0 && millis()-m2 > 1500){ //após a ultima vez pressionado o botao, aguarda 1,5 segundos para finalizar e retornar o comando.
byte c = count;
count = 0;
m1 = 0;
m2 = 0;
return c;
}
}
return 0;
}
void setup() {
Serial.begin(9600);
pinMode(bluePin, OUTPUT);
pinMode(greenPin, OUTPUT);
pinMode(redPin, OUTPUT);
pinMode(ledPin,OUTPUT);
}
void loop() {
int command = getCommand();
if (command == 1){
digitalWrite(redPin, !digitalRead(redPin));
}
if (command == 2){
digitalWrite(greenPin, !digitalRead(greenPin));
}
if (command == 3){
digitalWrite(bluePin, !digitalRead(bluePin));
}
if (command == 4){
digitalWrite(redPin, HIGH);
digitalWrite(greenPin, HIGH);
digitalWrite(bluePin, HIGH);
}
if (command == 5){
digitalWrite(redPin, LOW);
digitalWrite(greenPin, LOW);
digitalWrite(bluePin, LOW);
}
digitalWrite(ledPin, hasLight() );
delay(10); //apenas utilizado no simulador
}
</pre>
<b><br /></b>
<b><br /></b>Fabiano Arndthttp://www.blogger.com/profile/15268923442674744187noreply@blogger.com2tag:blogger.com,1999:blog-5513833072608837410.post-3989712087818132252017-11-03T14:49:00.001-03:002017-11-03T14:50:02.179-03:00Arduino - Charlieplex com botõesDemonstração de como utilizar vários botões com poucos pinos através da multiplexação Charlieplex.<br />
<br />
<b>Vídeo:</b><br />
<div class="separator" style="clear: both; text-align: center;">
<br /></div>
<div style="text-align: center;">
<iframe allowfullscreen="" class="YOUTUBE-iframe-video" data-thumbnail-src="https://i9.ytimg.com/vi/tyB8ZdUqcEI/default.jpg?sqp=CJzW8s8F&rs=AOn4CLBA9zbOAgSVpBE5ydEdctT-AkDQwA" frameborder="0" height="415" src="https://www.youtube.com/embed/tyB8ZdUqcEI?feature=player_embedded" width="660"></iframe></div>
<br />
<br />
<b>Código-fonte:</b><br />
<pre class="brush: html" style="background-color: #eeeeee; border: 1px dashed rgb(153, 153, 153); overflow: auto; padding: 5px; width: 100%;">#define NUM_PINS 4
const int pins[] = {2,3,4,5};
void init_pins(){
for (int i = 0; i< NUM_PINS; i++) {
pinMode(pins[i], INPUT_PULLUP);
digitalWrite(pins[i], HIGH);
}
}
byte readCharlieplexButons(int pin1, int pin2){
init_pins();
pinMode(pin1, OUTPUT);
digitalWrite(pin1, LOW);
return !digitalRead(pin2);
}
int getButton(){
byte index_button = 0;
for (int i=0; i<NUM_PINS; i++) {
for (int j=i+1; j<NUM_PINS; j++) {
if (i!=j) {
if (readCharlieplexButons(pins[j], pins[i])) {
return index_button;
}
index_button++;
if (readCharlieplexButons(pins[i], pins[j])) {
return index_button;
}
index_button++;
}
}
}
return -1; //-1 --> nenhum botão pressionado
}
int getButtonOnce(){
static int lastVal = -1;
static unsigned long m = 0;
int val = getButton();
if (lastVal != val && millis() > (m+100) ) { //M+100 -->DEBOUNCING 100ms
lastVal = val;
m = millis();
return lastVal;
}
return -1;
}
void setup() {
init_pins();
Serial.begin(9600);
}
void loop() {
int button = getButtonOnce();
if ( button >=0){
Serial.println(button);
}
delay(10); //necessário apenas no simulador.
}
</pre>
Fabiano Arndthttp://www.blogger.com/profile/15268923442674744187noreply@blogger.com3tag:blogger.com,1999:blog-5513833072608837410.post-45488253552665920662017-10-31T16:10:00.001-03:002017-11-01T09:51:53.123-03:00Arduino - Teclado 4x4 (keypad)Como ligar um teclado 4x4 no Arduino sem o uso de biblioteca<br />
<br />
<b>Vídeo:</b><br />
<div class="separator" style="clear: both; text-align: center;">
<iframe allowfullscreen="" class="YOUTUBE-iframe-video" data-thumbnail-src="https://i9.ytimg.com/vi/Kcl7n_Ve8Fg/default.jpg?sqp=CNyS488F&rs=AOn4CLBwv6t5UI2AsODrOt6QqxlXBta_Wg" frameborder="0" height="415" src="https://www.youtube.com/embed/Kcl7n_Ve8Fg?feature=player_embedded" width="660"></iframe></div>
<br />
<br />
<b>Código-fonte:</b><br />
<pre class="brush: html" style="background-color: #eeeeee; border: 1px dashed rgb(153, 153, 153); overflow: auto; padding: 5px; width: 100%;">byte rows[] = {11,10,9,8};
byte columns[] = {7,6,5,4};
char getKey(){
char keys[4][4] = {
{'1','2','3','A'},
{'4','5','6','B'},
{'7','8','9','C'},
{'*','0','#','D'}
};
char k = '\0';
for (int r=0; r<4; r++){
digitalWrite(rows[r], LOW);
for (int c=0; c<4; c++){
if (digitalRead(columns[c]) == LOW){
k = keys[r][c];
break;
}
}
digitalWrite(rows[r], HIGH);
if (k) {
break;
}
}
return k;
}
char getKeyOnce(){
static char lastVal = '\0';
static unsigned long m = 0;
char val = getKey();
if (lastVal != val && millis() > (m+100) ) { //M+100 -->DEBOUNCING 100ms
lastVal = val;
m = millis();
return lastVal;
}
return '\0';
}
void setup() {
for (int i=0; i<4; i++){
pinMode(rows[i], OUTPUT);
pinMode(columns[i], INPUT_PULLUP);
digitalWrite(rows[i], HIGH);
}
Serial.begin(9600);
}
void loop(){
char k = getKeyOnce();
if (k) {
Serial.println(k);
}
delay(10); //apenas para o simulador.
}
</pre>
<br />
<br />
<b>Exemplo de uma Calculadora utilizando keypad 4x4:</b><br />
<div class="separator" style="clear: both; text-align: center;">
<iframe allowfullscreen="" class="YOUTUBE-iframe-video" data-thumbnail-src="https://i.ytimg.com/vi/arUV6y2JiH0/0.jpg" frameborder="0" height="415" src="https://www.youtube.com/embed/arUV6y2JiH0?feature=player_embedded" width="660"></iframe></div>
<b><br /></b>
<b><br /></b>
<b>Código-fonte:</b>
<br />
<pre class="brush: html" style="background-color: #eeeeee; border: 1px dashed rgb(153, 153, 153); overflow: auto; padding: 5px; width: 100%;">#include <LiquidCrystal.h>
LiquidCrystal lcd(13, 12, 11, 10, 9, 8);
byte rows[] = {5,4,3,2};
byte columns[] = {A5,A4,A3,A2};
String valor1 = "";
String valor2 = "";
String resultado = "";
char operacao = '\0';
char getKey(){
char keys[4][4] = {
{'1','2','3','A'},
{'4','5','6','B'},
{'7','8','9','C'},
{'*','0','#','D'}
};
char k = '\0';
for (int r=0; r<4; r++){
digitalWrite(rows[r], LOW);
for (int c=0; c<4; c++){
if (digitalRead(columns[c]) == LOW){
k = keys[r][c];
break;
}
}
digitalWrite(rows[r], HIGH);
if (k) { break; }
}
return k;
}
char getKeyOnce(){
static char lastVal = '\0';
static unsigned long m = 0;
char val = getKey();
if (lastVal != val && millis() > (m+100) ) { //M+100 -->DEBOUNCING 100ms
lastVal = val;
m = millis();
return lastVal;
}
return '\0';
}
void setup() {
lcd.begin(16, 2);
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("youtube.com/");
lcd.setCursor(0, 1);
lcd.print("fabianoallex");
for (int i=0; i<4; i++){
pinMode(rows[i], OUTPUT);
pinMode(columns[i], INPUT_PULLUP);
digitalWrite(rows[i], HIGH);
}
delay(3000);
lcd.clear();
lcd.setCursor(0, 0);
}
void loop() {
execKeypad();
delay(10); //apenas para o simulador.
}
void execKeypad(){
char operacoes[] = {'\0', '*', '/', '+', '-'};
static byte indexOperacao = 0;
char k = getKeyOnce();
if (k) {
if (k >= 48 && k<= 57) { /*digitos numericos*/
resultado = "";
if (operacao){
valor2 = valor2 + k;
} else {
valor1 = valor1 + k;
}
}
if (k == '*') {
if (resultado != "" && valor1 == ""){
valor1 = resultado;
resultado = "";
}
indexOperacao++;
if (indexOperacao > 4){ indexOperacao = 0; }
operacao = operacoes[indexOperacao];
}
if (k == '#' && valor1 != "" && valor2 != "") {
double v1, v2, r;
char vchar[10];
valor1.toCharArray(vchar, 10);
v1 = atof(vchar);
valor2.toCharArray(vchar, 10);
v2 = atof(vchar);
if (operacao == '*') { r = v1 * v2; }
if (operacao == '+') { r = v1 + v2; }
if (operacao == '-') { r = v1 - v2; }
if (operacao == '/') { r = (v2 == 0) ? 0 : v1 / v2; }
sprintf(vchar, "%d.%02d", (int)r, (long)(abs(r)*100)%100);
resultado = (operacao == '/' && r == 0) ? "Div 0" : String(vchar);
valor2 = "";
valor1 = "";
indexOperacao = 0;
operacao = '\0';
}
lcd.clear();
lcd.setCursor(0, 0);
lcd.print(resultado != "" ? resultado : valor1);
lcd.setCursor(0, 1);
lcd.print(valor2);
lcd.setCursor(15, 0);
lcd.print(operacao ? operacao : ' ');
}
}
</pre>
Fabiano Arndthttp://www.blogger.com/profile/15268923442674744187noreply@blogger.com1tag:blogger.com,1999:blog-5513833072608837410.post-32901651228502242392016-08-24T11:09:00.001-04:002016-08-24T11:30:17.872-04:00Arduino - Utilizando módulo JoystickDemonstração do uso de um módulo joystick com Arduino.<br />
<br />
O módulo possui 5 pinos: gnd, vcc, pino do botão e mais dois pinos analógicos dos eixos x e y.<br />
<br />
Os pinos analógicos nada mais são do que dois potenciômetros ligados nas portas analógicas do Arduino. Caso não saiba como é feita a leitura de um potenciômetro, é interessante aprender antes.<br />
<br />
<b>Vídeo:</b><br />
<b><br /></b>
<br />
<div class="separator" style="clear: both; text-align: center;">
<iframe allowfullscreen="" class="YOUTUBE-iframe-video" data-thumbnail-src="https://i9.ytimg.com/vi/Yr_veydzXeA/default.jpg?sqp=CPDs9r0F&rs=AOn4CLDgvkSvJWgtXKwX-MfUt2rZ6A4jiQ" frameborder="0" height="415" src="https://www.youtube.com/embed/Yr_veydzXeA?feature=player_embedded" width="660"></iframe></div>
<br />
<br />
<b>Código-fonte:</b><br />
<br />
<pre class="brush: html" style="background-color: #eeeeee; border: 1px dashed rgb(153, 153, 153); overflow: auto; padding: 5px; width: 100%;">#include <LiquidCrystal_I2C.h>
/*************************************************************************************************************
************************************CLASSE JOYSTICK***********************************************************
*************************************************************************************************************/
class Joystick {
private:
boolean _isTop : 1;
boolean _isBottom : 1;
boolean _isLeft : 1;
boolean _isRight : 1;
unsigned int _x : 10;
unsigned int _y : 10;
byte _pinX;
byte _pinY;
byte _pinZ;
float _angle;
public:
Joystick(int pinX, int pinY, int pinZ){
_pinX = pinX;
_pinY = pinY;
_pinZ = pinZ;
pinMode(pinX, INPUT);
pinMode(pinY, INPUT);
pinMode(pinZ, INPUT_PULLUP);
}
update() {
_x = analogRead(_pinX);
_y = analogRead(_pinY);
_isTop = (_y < 200);
_isBottom = (_y > 800);
_isRight = (_x < 200);
_isLeft = (_x > 800);
float distX = 0;
float distY = 0;
if (_x < 480) { distX = 480 - _x; }
if (_x > 525) { distX = 525 - _x; }
if (_y < 480) { distY = 480 - _y; }
if (_y > 525) { distY = 525 - _y; }
if (!(distX == 0 && distY == 0)){
_angle = degrees(atan2(distY, distX)); //degrees --> converte de radianos (retornado por atan2) para graus
} else {
_angle = 0;
}
if (_angle < 0) { _angle = _angle + 360; }
}
boolean isTop() { return _isTop; }
boolean isBottom() { return _isBottom; }
boolean isLeft() { return _isLeft; }
boolean isRight() { return _isRight; }
unsigned int readX() { return _x; }
unsigned int readY() { return _y; }
float readAngle() { return _angle; }
byte buttonRead() { return digitalRead(_pinZ); }
byte readTop() { if (_y < 480) { return map(_y, 480, 0, 0, 255); } return 0; }
byte readBottom() { if (_y > 525) { return map(_y, 525, 1023, 0, 255); } return 0; }
byte readLeft() { if (_x > 525) { return map(_x, 525, 1023, 0, 255); } return 0; }
byte readRight() {if (_x < 480) { return map(_x, 480, 0, 0, 255); } return 0;}
};
/*************************************************************************************************************
************************************FIM CLASSE CLASSE JOYSTICK************************************************
*************************************************************************************************************/
Joystick joystick(A1, A2, A0); //pino analogico eixo x, pino analógico eixo y, pino digita botão
LiquidCrystal_I2C lcd(0x3F,20,4);
void setup() {
Serial.begin(9600);
lcd.init();
lcd.backlight();
byte c0[8] = {B00100, B01110, B10101, B00100, B00100, B00100, B00100, B00000 }; //seta cima
byte c1[8] = {B00100, B00100, B00100, B00100, B10101, B01110, B00100, B00000 }; //seta baixo
lcd.createChar(0, c0);
lcd.createChar(1, c1);
}
void loop() {
static unsigned long mref = 0;
if (millis() - mref > 500) {
mref = millis();
joystick.update(); //antes de fazer a leitura dos valores, é necessário chamar o update.
lcd.clear();
lcd.setCursor(0,0);
lcd.print((int)joystick.readAngle());
lcd.write(223);
lcd.setCursor(8,0);
if (joystick.isTop()) { lcd.write(0); }
if (joystick.isBottom()) { lcd.write(1); }
if (joystick.isLeft()) { lcd.write(127); }
if (joystick.isRight()) { lcd.write(126); }
lcd.setCursor(0,1);
lcd.print("x: ");
lcd.print(joystick.readX());
lcd.setCursor(8,1);
if (joystick.readLeft() > 0) {
lcd.print("left: ");
lcd.print(joystick.readLeft()); //mostra a leitura analógica para a esquerda de 0 a 255
}
if (joystick.readRight() > 0) {
lcd.print("right: ");
lcd.print(joystick.readRight()); //mostra a leitura analógica para a direita de 0 a 255
}
lcd.setCursor(0,2);
lcd.print("y: ");
lcd.print(joystick.readY());
lcd.setCursor(8,2);
if (joystick.readTop() > 0) {
lcd.print("top: ");
lcd.print(joystick.readTop()); //mostra a leitura analógica para cima de 0 a 255
}
if (joystick.readBottom() > 0) {
lcd.print("bottom: ");
lcd.print(joystick.readBottom()); //mostra a leitura analógica para baixo de 0 a 255
}
lcd.setCursor(0,3);
lcd.print("z: ");
lcd.print(joystick.buttonRead()); //mostra se o botão (eixo z) foi pressionado
}
}
</pre>
<br />Fabiano Arndthttp://www.blogger.com/profile/15268923442674744187noreply@blogger.com1tag:blogger.com,1999:blog-5513833072608837410.post-39286198542190208692016-08-19T09:33:00.000-04:002016-08-19T14:29:01.872-04:00Arduino - Tetris com display LCD 20x4 e JoystickEm junho postei duas implementações do jogo Tetris (<a href="http://fabianoallex.blogspot.com.br/2016/06/arduino-tetris-game-com-rotary-encoder.html" target="_blank">veja aqui</a>), uma com display de leds e outra com display LCD 16x2. Em nenhuma o tamanho original do jogo foi utilizado (20 linhas e 10 colunas), por limitações dos componentes. Mas a ideia não era reproduzir o jogo em seu formato 100% original e sim, criar algo mais por diversão e pra servir de exemplo pra quem gosta de programar.<br />
<br />
Mas essa semana chegou um display LCD 20x4 e um módulo I2C para o display que comprei há algum tempo e pensei que poderia criar uma versão do tetris pra ele também. Basicamente o que era preciso, era apenas substituir a biblioteca LCD pela biblioteca LCD I2C.<br />
<br />
Através da própria IDE do Arduino (versão 1.6.10) procurei e instalei a biblioteca para display LCD I2C, depois fiz uma cópia da sketch que tinha usado no jogo com o display 16x2 e fiz algumas pequenas alterações no código aumentando a quantidade de linhas e colunas do jogo. O Resultado foi que ficou extremamente lenta a atualização dos dados no display LCD. Fiquei um pouco frustrado, achei que não iria dar certo utilizando o display 20x4. Mas resolvi dar uma olhada na biblioteca I2C, pra ver como era a comunicação entre o Arduino e o display.<br />
<br />
A biblioteca do display I2C utiliza a biblioteca Wire para o envio dos dados para o display e analisando melhor o código da biblioteca, de fato havia algumas coisas que poderiam ser melhoradas para que ela ficasse mais rápida. Pois a biblioteca, para cada caractere enviado para o display, inicia uma transmissão, envia o dado e finaliza a transmissão, isso significa que se 20 caracteres forem enviados para o display, serão iniciadas e finalizadas 20 transmissões, algo que não seria necessário, pois a biblioteca Wire conta com um buffer que pode armazenar vários dados, e depois de finalizado, os dados podem ser enviados todos dentro de uma mesma transmissão, economizando assim, chamadas desnecessárias. Para mais de 90% das aplicações que utilizam um display LCD com certeza isso não é nenhum problema, mas no caso do jogo, as atualizações da tela são executadas a uma taxa bem alta, faz muita diferença.<br />
<br />
Então eu tinha duas opções, criar uma nova biblioteca, baseada na biblioteca I2C ou, na própria sketch criar uma classe que herdasse da classe original, e nela fizesse as alterações necessárias. Fiquei com a segunda opção, pois não queria criar e manter uma biblioteca a parte. Porém, foi necessário fazer algumas alterações na biblioteca original para que eu pudesse sobrescrever o método que fazia o envio de dados (tornar o método virtual) e também precisei alterar a visibilidade de alguns atributos da classe, para que as mesmas ao invés de serem private passassem a ser protected. Essas alterações foram necessárias apenas no Header (arquivo .h) da biblioteca. Com isso, na própria sketch criei uma nova classe herdada da classe original, e fiz os devidos tratamentos.<br />
<br />
Nessa nova classe foram implementados dois métodos, startWrite() e sendAll(). Com isso, agora, antes de enviar os dados para o display eu chamo startWrite e começo a enviar os dados para o display, que na verdade são mantidos no buffer, e somente após eu chamar sendAll, os dados são de fatos enviados para o display. (Na verdade, se durante o envio o buffer ficar cheio, a classe envia os dados para o display).<br />
<br />
Depois de tudo pronto, fiz os testes. Melhorou bastante, mas ainda não estava satisfatório o resultado, então resolvi analisar o que eu poderia alterar no código do próprio jogo, e vi que eu fiz, para que a cada caractere enviado para o display estava sendo posicionado onde deveria ser escrito (através de setCursor(...) ). Porém se os dados são enviados dentro de uma mesma linha, não há a necessidade de chamar setCursor para cada escrita, pois o próprio display, ao receber o comando de escrita de um caractere, já posiciona o cursor na próxima posição. Com isso eliminei, várias chamadas desnecessárias.<br />
<br />
Testei novamente e o resultado agora foi bem mais satisfatório, sendo os dados atualizados numa velocidade aceitável.<br />
<br />
Outra alteração, foi que agora implementei o jogo com um joystick, onde antes eu utilizava um rotary encoder.<br />
<br />
<b>Vídeo</b><br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<iframe allowfullscreen="" class="YOUTUBE-iframe-video" data-thumbnail-src="https://i9.ytimg.com/vi/BoB6Tel9EL0/default.jpg?sqp=CICP3L0F&rs=AOn4CLCzJOVWTaSKiL-mgEl3mF3rCDJgDw" frameborder="0" height="415" src="https://www.youtube.com/embed/BoB6Tel9EL0?feature=player_embedded" width="660"></iframe></div>
<br />
<br />
<b>Código-fonte</b><br />
<br />
<pre class="brush: html" style="background-color: #eeeeee; border: 1px dashed rgb(153, 153, 153); overflow: auto; padding: 5px; width: 100%;">/*
Fabiano A. Arndt - 2016
www.youtube.com/user/fabianoallex
www.facebook.com/dicasarduino
fabianoallex@gmail.com
*/
#include <LiquidCrystal_I2C.h>
/*************************************************************************************************************
*******************************CLASSE DERIVADA DE LiquidCrystal_I2C*******************************************
**************************************************************************************************************
*essa classe foi criada para ser mais rápida na hora de enviar os dados para o display através da biblioteca
*wire, utilizando o buffer, requerendo assim, menos chamadas a biblioteca wire. a biblioteca original, a cada
*chamada para write estava iniciando uma transmissão, enviando o dado e em seguida finalizando a transmissão,
*mesmo que várias informações estivessem sendo enviadas sequencialmente. sendo desnecessario o envio de
*cada informação dentro uma transmissao separada. as alterações foram justamente pra utilizar o buffer
*que a biblioteca wire disponibiliza.
*
*foram incluídos os métodos startWrite e sendAll. responsáveis pelo inicio e fim da transmissao. se durante o
*envio dos dados o buffer ficar totalmente cheio, o fim da transmissao é automaticamente chamado e em seguida
*reiniciada outra transmissao.
*
*caso esses métodos não forem chamados durante o uso da classe, os dados são enviados
*a cada escrita dentro de uma transmissao pra cada escrita, sem utilizar o buffer, como é exatamente o
*comportamento da biblioteca original.
*
*foi necessário fazer algumas alterações na biblioteca original, no arquivo header (LiquidCrystal_I2C.h).
*os métodos e membros abaixo, passaram a ser protected ao invés de private:
*protected:
uint8_t _Addr; //alterado fabiano. passou a ser protected para ser visto pelas classes derivadas
uint8_t _backlightval; //alterado fabiano. passou a ser protected para ser visto pelas classes derivadas
void pulseEnable(uint8_t); //alterado fabiano. passou a ser protected para ser visto pelas classes derivadas
a função abaixo passou a ser virtual para que pudesse ser reescrita
virtual void expanderWrite(uint8_t); //alterado fabiano. definido como método virtual, para poder ser redefinido
**************************************************************************************************************/
class LiquidCrystal_I2C_ : public LiquidCrystal_I2C {
private:
uint8_t _startWrite;
uint8_t _countByte;
void write4bits(uint8_t value) { pulseEnable(value); }
void expanderWrite(uint8_t _data){
if (_startWrite == 0) { Wire.beginTransmission(_Addr); } else {
_countByte += sizeof(int);
if (_countByte > 64 ) { //32 é o tamanho do buffer definido em BUFFER_LENGTH ............ fiz o teste com 64 bytes e funcionou, com valor maior ocorreu erros, caso der algum erro de envio, voltar para o valor BUFFER_LENGHT no lugar do 64
sendAll();
startWrite();
_countByte += sizeof(int);
}
}
Wire.write((int)(_data) | _backlightval);
if (_startWrite == 0) { Wire.endTransmission(); }
}
public:
LiquidCrystal_I2C_(uint8_t lcd_Addr,uint8_t lcd_cols,uint8_t lcd_rows) : LiquidCrystal_I2C(lcd_Addr, lcd_cols, lcd_rows){
_startWrite = 0;
_countByte = 0;
}
inline size_t write(uint8_t value) {
uint8_t highnib = value&0xf0;
uint8_t lownib = (value<<4)&0xf0;
write4bits((highnib)|Rs);
write4bits((lownib)|Rs);
return 1;
}
void startWrite() {
_countByte = 0;
_startWrite = 1;
Wire.beginTransmission(_Addr);
}
void sendAll(){
_startWrite = 0;
Wire.endTransmission();
}
};
/*************************************************************************************************************
*******************************FIM CLASSE DERIVADA DE LiquidCrystal_I2C***************************************
**************************************************************************************************************/
/*************************************************************************************************************
************************************CLASSE JOYSTICK***********************************************************
*************************************************************************************************************/
class Joystick {
private:
boolean _isTop : 1;
boolean _isBottom : 1;
boolean _isLeft : 1;
boolean _isRight : 1;
unsigned int _x : 10;
unsigned int _y : 10;
byte _pinX : 8;
byte _pinY : 8;
byte _pinZ : 8;
public:
Joystick(int pinX, int pinY, int pinZ){
_pinX = pinX;
_pinY = pinY;
_pinZ = pinZ;
pinMode(pinX, INPUT);
pinMode(pinY, INPUT);
pinMode(pinZ, INPUT_PULLUP);
}
update() {
_x = analogRead(_pinX);
_y = analogRead(_pinY);
_isTop = (_x < 200);
_isBottom = (_x > 800);
_isRight = (_y < 200);
_isLeft = (_y > 800);
}
boolean isTop() { return _isTop; }
boolean isBottom() { return _isBottom; }
boolean isLeft() { return _isLeft; }
boolean isRight() { return _isRight; }
unsigned int readX() { return _x; }
unsigned int readY() { return _y; }
byte buttonRead() { return digitalRead(_pinZ); }
};
/*************************************************************************************************************
************************************FIM CLASSE CLASSE JOYSTICK************************************************
*************************************************************************************************************/
/*************************************************************************************************************
*******************************BIT ARRAY *********************************************************************
**************************************************************************************************************
mais informações aqui: http://fabianoallex.blogspot.com.br/2015/09/arduino-array-de-bits.html
**************************************************************************************************************/
class BitArray2D {
private:
unsigned int _rows;
unsigned int _columns;
unsigned int _cols_array; //pra cada 8 colunas, 1 byte é usado
byte** _bits;
public:
BitArray2D(unsigned int rows, unsigned int columns){
_rows = rows;
_columns = columns;
_cols_array = columns/8 + (_columns%8 ? 1 : 0) + 1; //divide por 8 o número de colunas
_bits = (byte **)malloc(_rows * sizeof(byte *));
for(int i=0;i<_rows;i++){ _bits[i] = (byte *)malloc( _cols_array * sizeof(byte)); } //cria varios arrays
clear();
}
unsigned int rows(){ return _rows; }
unsigned int columns(){ return _columns; }
void clear() {
for(int i=0;i<_rows;i++){
for(int j=0; j<_cols_array;j++) { _bits[i][j] = B00000000; }
}
}
void write(unsigned int row, unsigned int column, int value){
byte b = _bits[row][ column/8 + (column%8 ? 1 : 0) ];
unsigned int bit = column%8;
if (value) { b |= (1 << bit); } else { b &= ~(1 << bit); }
_bits[row][ column/8 + (column%8 ? 1 : 0) ] = b;
}
void write(byte value){
for(int i=0;i<_rows;i++){
for(int j=0; j<_cols_array;j++) {
_bits[i][j] = value ? B11111111 : B00000000;
}
}
}
int read(unsigned int row, unsigned int column){
byte b = _bits[row][ column/8 + (column%8 ? 1 : 0) ];
unsigned int bit = column%8;
return (b & (1 << bit)) != 0;
}
void toggle(unsigned int row, unsigned int column){ write(row, column, !read(row, column)); }
void toggle(){ for(int i=0;i<_rows;i++){ for(int j=0; j<_columns;j++) { toggle(i,j); } } }
};
/*************************************************************************************************************
*******************************FIM BIT ARRAY *****************************************************************
**************************************************************************************************************/
/*************************************************************************************************************
************************************CLASSE UNIQUE RANDOM******************************************************
**************************************************************************************************************
mais informações aqui: http://fabianoallex.blogspot.com.br/2015/09/arduino-numeros-aleatorio-repetidos-e.html
*************************************************************************************************************/
class UniqueRandom{
private:
int _index;
int _min;
int _max;
int _size;
int* _list;
void _init(int min, int max) {
_list = 0;
if (min < max) { _min = min; _max = max; } else { _min = max; _max = min; }
_size = _max - _min;
_index = 0;
}
public:
UniqueRandom(int max) { _init(0, max); randomize(); } //construtor com 1 parametro
UniqueRandom(int min, int max) { _init(min, max); randomize(); } //construtor com 2 parametros
void randomize() {
_index = 0;
if (_list == 0) { _list = (int*) malloc(size() * sizeof(int)); }
for (int i=0; i<size(); i++) { _list[i] = _min+i; } //preenche a lista do menor ao maior valor
//embaralha a lista
for (int i=0; i<size(); i++) {
int r = random(0, size()); //sorteia uma posição qualquer
int aux = _list[i];
_list[i] = _list[r];
_list[r] = aux;
}
}
int next() { //retorna o proximo numero da lista
int n = _list[_index++];
if (_index >= size() ) { _index = 0;} //após recuper o ultimo numero, recomeça na posicao 0
return n;
}
void first() { _index = 0; }
boolean eof() { return size() == _index+1; }
int size() { return _size; }
int getIndex() { return _index; }
~UniqueRandom(){ free ( _list ); } //destrutor
};
/*************************************************************************************************************
************************************FIM CLASSE UNIQUE RANDOM**************************************************
*************************************************************************************************************/
/*************************************************************************************************************
*******************************CLASSES TETRIS GAME************************************************************
**************************************************************************************************************/
const int TETRIS_TIME_INIT = 700; //tempo entre deslocamento das peças
const int TETRIS_TIME_INC = 20; //incremento da velocidade
enum Direction { DIR_STOP, DIR_TOP, DIR_LEFT, DIR_BOTTOM, DIR_RIGHT};
enum TetrisGameStatus { TETRIS_GAME_ON, TETRIS_GAME_OVER };
enum TetrisBlockTypes { TETRIS_I, TETRIS_J, TETRIS_L, TETRIS_O, TETRIS_S, TETRIS_Z, TETRIS_T, TETRIS_COUNT_BTYPES};
struct Position { int row, column; };
struct Block { byte row0:4; byte row1:4; byte row2:4; byte row3:4; }; //4 bit cada linha
//blocos e suas rotacoes
Block block_i[] = { {B1111, B0000, B0000, B0000}, {B0001, B0001, B0001, B0001} };
Block block_j[] = { {B0000, B0010, B0010, B0011}, {B0000, B0000, B0001, B0111}, {B0000, B0110, B0010, B0010}, {B0000, B0000, B0111, B0100} };
Block block_l[] = { {B0000, B0010, B0010, B0110}, {B0000, B0000, B0100, B0111}, {B0000, B0011, B0010, B0010}, {B0000, B0000, B0111, B0001} };
Block block_o[] = { {B0000, B0000, B0110, B0110} };
Block block_s[] = { {B0000, B0001, B0011, B0010}, {B0000, B0000, B0110, B0011} };
Block block_z[] = { {B0000, B0000, B0110, B1100}, {B0000, B1000, B1100, B0100} };
Block block_t[] = { {B0000, B0000, B0010, B0111}, {B0000, B0010, B0011, B0010}, {B0000, B0000, B0111, B0010}, {B0000, B0001, B0011, B0001} };
class TetrisBlock{
private:
Block _block; //bloco que vai descendo. player
Position _position; //posição do bloco na tela. pode assumir posição negativa
TetrisBlockTypes _blockType;
byte _blockTypeRotation;
public:
TetrisBlock(){
_blockType = (TetrisBlockTypes) random(0, TETRIS_COUNT_BTYPES);
}
Position getPosition() { return _position; }
byte getRow(int row) {
if (row == 0 ) return _block.row0;
if (row == 1 ) return _block.row1;
if (row == 2 ) return _block.row2;
if (row == 3 ) return _block.row3;
return 0;
}
byte getColumn(int col) {
byte b = 0;
for (int i=0;i<4;i++){ bitWrite(b, 3-i, bitRead(getRow(i), 3-col) ); }
return b;
}
int read(int row, int column){
byte b = getRow(row);
unsigned int bit = column%8;
return (b & (1 << bit)) != 0;
}
void left() { _position.column--; }
void right() { _position.column++; }
void bottom() { _position.row++; }
void top() { _position.row--; }
TetrisBlockTypes getBlockType() { return _blockType; }
void generateBlock(byte col, TetrisBlockTypes blockType = TETRIS_COUNT_BTYPES) {
_position.row = 0;
_position.column = col;
_blockType = (blockType == TETRIS_COUNT_BTYPES) ? (TetrisBlockTypes) random(0, TETRIS_COUNT_BTYPES) : blockType;
_blockTypeRotation = 0;
if ( _blockType == TETRIS_I ){ _block = block_i[_blockTypeRotation]; }
if ( _blockType == TETRIS_J ){ _block = block_j[_blockTypeRotation]; }
if ( _blockType == TETRIS_L ){ _block = block_l[_blockTypeRotation]; }
if ( _blockType == TETRIS_O ){ _block = block_o[_blockTypeRotation]; }
if ( _blockType == TETRIS_S ){ _block = block_s[_blockTypeRotation]; }
if ( _blockType == TETRIS_Z ){ _block = block_z[_blockTypeRotation]; }
if ( _blockType == TETRIS_T ){ _block = block_t[_blockTypeRotation]; }
if (!getRow(0)) { _position.row--;
if (!getRow(1)) { _position.row--;
if (!getRow(2)) { _position.row--; } } }
}
void rotate(int direction = 1){
int maxPos = 0;
if ( _blockType == TETRIS_I ){ maxPos = sizeof(block_i)/sizeof(Block); }
if ( _blockType == TETRIS_J ){ maxPos = sizeof(block_j)/sizeof(Block); }
if ( _blockType == TETRIS_L ){ maxPos = sizeof(block_l)/sizeof(Block); }
if ( _blockType == TETRIS_O ){ maxPos = sizeof(block_o)/sizeof(Block); }
if ( _blockType == TETRIS_S ){ maxPos = sizeof(block_s)/sizeof(Block); }
if ( _blockType == TETRIS_Z ){ maxPos = sizeof(block_z)/sizeof(Block); }
if ( _blockType == TETRIS_T ){ maxPos = sizeof(block_t)/sizeof(Block); }
(direction==1) ? _blockTypeRotation++ : _blockTypeRotation--;
if (_blockTypeRotation == 255) { _blockTypeRotation = maxPos-1; }
if (_blockTypeRotation >= maxPos) { _blockTypeRotation = 0; }
if ( _blockType == TETRIS_I ){ _block = block_i[_blockTypeRotation]; }
if ( _blockType == TETRIS_J ){ _block = block_j[_blockTypeRotation]; }
if ( _blockType == TETRIS_L ){ _block = block_l[_blockTypeRotation]; }
if ( _blockType == TETRIS_O ){ _block = block_o[_blockTypeRotation]; }
if ( _blockType == TETRIS_S ){ _block = block_s[_blockTypeRotation]; }
if ( _blockType == TETRIS_Z ){ _block = block_z[_blockTypeRotation]; }
if ( _blockType == TETRIS_T ){ _block = block_t[_blockTypeRotation]; }
}
};
class TetrisGame {
private:
BitArray2D * _display; //display com todos os objetos da tela. recebido como parâmetro
BitArray2D * _recipient; //recepiente onde os blocos vão se acumulando. criado internamente pela classe
TetrisBlock * _block; //bloco controlado
TetrisBlock * _blockNext; //bloco que fica na parte superior, indicando o próximo bloco a ser controlado
Direction _direction;
TetrisGameStatus _tetrisGameStatus;
unsigned long _last_millis;
unsigned long _last_millis_player;
int _time;
int _score;
byte _fast : 1; //acelerar a descida
UniqueRandom * _ur; //utilizado no game over
void _gameOver(){
_tetrisGameStatus = TETRIS_GAME_OVER;
_direction = DIR_STOP;
_time = 20;
}
void _inc_speed(){
_score++;
_time -= TETRIS_TIME_INC;
}
void _start(){
_recipient->clear();
_last_millis = 0;
_score = 0;
_time = TETRIS_TIME_INIT;
_fast = 0;
_direction = DIR_STOP;
_tetrisGameStatus = TETRIS_GAME_ON;
_blockNext->generateBlock( _display->columns() / 2 - 1);
_newBlock();
}
void _newBlock() {
_block->generateBlock( _display->columns() / 2 - 1, _blockNext->getBlockType() );
_blockNext->generateBlock( _display->columns() / 2 - 1 );
if (_testCollision()){ _gameOver(); }
}
void _updateDisplay() {
if (_tetrisGameStatus == TETRIS_GAME_ON) { _display->clear(); }
boolean indPrintNext = false;
for (int r=0; r<4; r++) {
for (int c=0; c<4; c++) {
if ( _block->read(r, c) ) {
_display->write(_block->getPosition().row+r, _block->getPosition().column+c, HIGH );
if (_block->getPosition().row > 2 ) { indPrintNext = true; }
}
}
}
if (indPrintNext){ //só imprime o próximo bloco, quando o bloco controlado já tiver descido um pouco
for (int r=0; r<4; r++) {
for (int c=0; c<4; c++) {
if ( _blockNext->read(r, c) ) {
_display->write(_blockNext->getPosition().row+r, _blockNext->getPosition().column+c, HIGH );
}
}
}
}
for (int row=0; row<_display->rows(); row++) {
for (int col=0; col<_display->columns(); col++) {
boolean val = _recipient->read(row, col);
if (val) { _display->write(row, col, val ); }
}
}
}
boolean _testCollision(){
int col = _block->getPosition().column; //coluna do bloco em relacao ao recepiente. pode ser negativa
int row = _block->getPosition().row; //linha do bloco em relacao ao recepiente. pode ser negativa
for (byte c=0; c<4; c++){
for (byte r=0; r<4; r++){
if (! _block->read(r, c) ) { continue; }
if ( row+r < 0 ) { return true;}
if ( row+r >= _display->rows() ) { return true;}
if ( col+c < 0 ) { return true;}
if ( col+c >= _display->columns() ) { return true;}
if ( _recipient->read(row+r, col+c) ) { return true;}
}
}
return false;
}
boolean _canLeft(){
_block->left();
boolean r = ! _testCollision();
_block->right();
return r;
}
boolean _canRight(){
_block->right();
boolean r = ! _testCollision();
_block->left();
return r;
}
boolean _canBottom(){
_block->bottom();
boolean r = ! _testCollision();
_block->top();
return r;
}
boolean _canRotate(){
_block->rotate();
boolean r = ! _testCollision();
_block->rotate(-1);
return r;
}
void _blockToRecipient(){
for (byte c=0; c<4; c++){
for (byte r=0; r<4; r++){
if ( _block->read(r, c) ){ _recipient->write(_block->getPosition().row+r, _block->getPosition().column+c, HIGH); }
}
}
_verifyCompleteRow();
_newBlock();
}
void _verifyCompleteRow(){
for (int row=0; row<_recipient->rows(); row++) {
boolean complete = true;
for (int col=0; col<_recipient->columns(); col++) {
if (! _recipient->read(row, col) ) { complete = false; break; }
}
if (complete){
_incScore();
//move as linhas pra baixo
for (int row2 = row; row2>=0; row2--) {
for (int col2=0; col2<_recipient->columns(); col2++){
_recipient->write(row2, col2, (row2>0) ? _recipient->read(row2-1, col2) : 0 );
}
}
}
}
}
void _incScore(){
_score++;
_time -= TETRIS_TIME_INC;
}
void _runGameOver(){
int r = _ur->next();
int row = (r / _display->columns());
int col = (r % _display->columns());
_display->write(row, col, HIGH );
if ( _ur->getIndex() == (_ur->size()-1) ) {
_ur->randomize();
_ur->first();
_start();
}
}
void _runPlayer() {
if (_direction == DIR_LEFT && _canLeft() ) { _block->left(); }
if (_direction == DIR_RIGHT && _canRight() ) { _block->right(); }
_direction = DIR_STOP;
}
void _runBlock() {
_canBottom() ? _block->bottom() : _blockToRecipient() ;
_direction = DIR_STOP;
}
public:
TetrisGame(BitArray2D * display){
_display = display;
_recipient = new BitArray2D( _display->rows(), _display->columns() );
_ur = new UniqueRandom( _display->rows() * _display->columns() );
_block = new TetrisBlock();
_blockNext = new TetrisBlock();
_start();
}
void fast() { _fast = 1; } //acelerar o deslocamento
void noFast() { _fast = 0; } //descida do bloco na velocidade normal
void left() { _direction = DIR_LEFT; }
void right() { _direction = DIR_RIGHT; }
void bottom() { _direction = DIR_BOTTOM; }
void rotate() { if (_canRotate()) { _block->rotate(); } }
int getScore(){ return _score; }
Position getPosition() { return _block->getPosition(); }
int update(){
int r = false;
if (millis() - _last_millis > ( (_fast==1) ? 50 : _time ) ) {
r = true;
_last_millis = millis();
if (_tetrisGameStatus == TETRIS_GAME_ON) { _runBlock(); }
if (_tetrisGameStatus == TETRIS_GAME_OVER) { _runGameOver(); }
}
if (millis() - _last_millis_player > 70 ) { //atualiza os movimentos do player a cada 70ms
r = true;
_last_millis_player = millis();
if (_tetrisGameStatus == TETRIS_GAME_ON) { _runPlayer(); }
}
if (r) { _updateDisplay(); }
return r; //r-->indica se houve mudança no display
}
};
/*************************************************************************************************************
*******************************FIM CLASSES TETRIS GAME********************************************************
**************************************************************************************************************/
/*************************************************************************************************************
*******************************DISPLAY************************************************************************
**************************************************************************************************************/
/*
rotation indica qual parte do display estará para cima
TOP
L . . . . . . . . R
E . . . . . . . . I
F . . . . . . . . G
T . . . . . . . . H
. . . . . . . . T
. . . . . . . .
. . . . . . . .
. . . . . . . .
BOTTOM
*/
enum Rotation {TOP, LEFT, BOTTOM, RIGHT};
struct Display {
int index;
Position position;
Rotation rotation;
};
/*************************************************************************************************************
*******************************FIM DISPLAY********************************************************************
**************************************************************************************************************/
const int LINHAS = 20;
const int COLUNAS = 10;
/*************************************************************************************************************
*******************************CLASSE GAME LCD****************************************************************
*classe auxiliar para imprimir o jogo no display lcd e criar os caracteres customizáveis
**************************************************************************************************************/
/*
0 1 2 3 4 5 6
*** *** *** ***
*** *** *** ***
*** *** *** ***
*** *** *** ***
*** *** *** ***
*** *** *** ***
*/
byte c0[8] = {B11111, B11111, B00000, B11111, B11111, B00000, B11111, B11111 };
byte c1[8] = {B00000, B00000, B00000, B11111, B11111, B00000, B11111, B11111 };
byte c2[8] = {B00000, B00000, B00000, B00000, B00000, B00000, B11111, B11111 };
byte c3[8] = {B11111, B11111, B00000, B11111, B11111, B00000, B00000, B00000 };
byte c4[8] = {B11111, B11111, B00000, B00000, B00000, B00000, B11111, B11111 };
byte c5[8] = {B00000, B00000, B00000, B11111, B11111, B00000, B00000, B00000 };
byte c6[8] = {B11111, B11111, B00000, B00000, B00000, B00000, B00000, B00000 };
class GameLCD {
private:
byte _col;
byte _row;
LiquidCrystal_I2C_ * _lcd; //ponteiro para um objeto lcd
public:
void createChars() {
_lcd->createChar(0, c0);
_lcd->createChar(1, c1);
_lcd->createChar(2, c2);
_lcd->createChar(3, c3);
_lcd->createChar(4, c4);
_lcd->createChar(5, c5);
_lcd->createChar(6, c6);
}
GameLCD(LiquidCrystal_I2C_ * lcd) { _lcd = lcd; }
void write(byte col, byte row, byte val){
if ( ! (_row == row && _col == (col-1) && col < (LINHAS-1) ) ) { _lcd->setCursor(col, row); } //só seta o cursor, caso a posicao não seja a que está na sequencia
if (val == B000) { _lcd->print(" "); }
if (val == B111) { _lcd->write((byte)0); }
if (val == B011) { _lcd->write((byte)1); }
if (val == B001) { _lcd->write((byte)2); }
if (val == B110) { _lcd->write((byte)3); }
if (val == B101) { _lcd->write((byte)4); }
if (val == B010) { _lcd->write((byte)5); }
if (val == B100) { _lcd->write((byte)6); }
_col = col;
_row = row;
}
void startWrite(){ _lcd->startWrite(); }
void sendAll() { _lcd->sendAll(); }
};
/*************************************************************************************************************
*******************************FIM CLASSE GAME LCD***********************************************************
**************************************************************************************************************/
/*************************************************************************************************************
*******************************DECLARACAO DOS OBJETOS*********************************************************
**************************************************************************************************************/
LiquidCrystal_I2C_ lcd(0x3F,20,4); // set the LCD address to 0x3F for a 20 chars and 4 line display
GameLCD gameLcd(&lcd);
BitArray2D ba(LINHAS, COLUNAS); //8 linhas e 16 colunas, usada para armazenar o estado do display
TetrisGame tetris(&ba);
Joystick joystick(A0, A1, 4);
/*************************************************************************************************************
*******************************FIM DECLARACAO DOS OBJETOS*****************************************************
**************************************************************************************************************/
void update_display() {
gameLcd.startWrite();
int cols_lcd = (ba.columns() / 3 + (ba.columns()%3 ? 1 : 0));
for(int col_lcd=0; col_lcd<cols_lcd; col_lcd++ ) {
for (int lin=ba.rows()-1; lin>=0; lin--) {
byte val = 0;
for(byte i=0;i<3;i++) {
byte leitura = (col_lcd*3+i >= ba.columns()) ? HIGH : ba.read(lin, col_lcd*3+i); //se for a coluna maior que o tamnho do display, retorna como "parede"
if (i == 0){ val = 0; }
val = val << 1;
val = val | leitura;
if (i == 2) { gameLcd.write(LINHAS-lin-1, col_lcd, val) ; }
}
}
}
gameLcd.sendAll();
}
void setup() {
lcd.init();
lcd.backlight();
gameLcd.createChars();
randomSeed(analogRead(A2));
// Serial.begin(9600);
}
void loop() {
joystick.update();
static byte lastLeft = 0;
static unsigned long m_left = 0;
byte left = joystick.isLeft();
if( left && !lastLeft ) { //borda de descida
tetris.left();
delay(70);
m_left = millis();
} else if (left) {
if (millis() - m_left > 100) {
tetris.left();
m_left = millis();
}
}
lastLeft = left;
static byte lastRight = 0;
static unsigned long m_right = 0;
byte right = joystick.isRight();
if( right && !lastRight ) { //borda de descida
tetris.right();
delay(70);
m_left = millis();
} else if (right) {
if (millis() - m_right > 100) {
tetris.right();
m_right = millis();
}
}
lastRight = right;
if ( tetris.update() ) { update_display(); }
static byte lastb = HIGH; //leitura anterior do botão
static unsigned long m_pressed = 0; //millis na borda de subida do botão
int b = joystick.buttonRead();
if( !b && lastb ) { //borda de subida
m_pressed = millis();
delay(70);
}
if( b && !lastb ) { //borda de descida
if (millis() - m_pressed < 350) {
tetris.rotate();
m_pressed = millis();
}
}
if( joystick.isBottom() ){
tetris.fast();
} else {
tetris.noFast();
}
lastb = b;
}
</pre>
<br />
<br />
<br />
<b>LiquidCristal_I2C.h - Alterações da biblioteca original</b><br />
<br />
<br />
<pre class="brush: html" style="background-color: #eeeeee; border: 1px dashed rgb(153, 153, 153); overflow: auto; padding: 5px; width: 100%;">//YWROBOT
#ifndef LiquidCrystal_I2C_h
#define LiquidCrystal_I2C_h
#include <inttypes.h>
#include "Print.h"
#include <Wire.h>
// commands
#define LCD_CLEARDISPLAY 0x01
#define LCD_RETURNHOME 0x02
#define LCD_ENTRYMODESET 0x04
#define LCD_DISPLAYCONTROL 0x08
#define LCD_CURSORSHIFT 0x10
#define LCD_FUNCTIONSET 0x20
#define LCD_SETCGRAMADDR 0x40
#define LCD_SETDDRAMADDR 0x80
// flags for display entry mode
#define LCD_ENTRYRIGHT 0x00
#define LCD_ENTRYLEFT 0x02
#define LCD_ENTRYSHIFTINCREMENT 0x01
#define LCD_ENTRYSHIFTDECREMENT 0x00
// flags for display on/off control
#define LCD_DISPLAYON 0x04
#define LCD_DISPLAYOFF 0x00
#define LCD_CURSORON 0x02
#define LCD_CURSOROFF 0x00
#define LCD_BLINKON 0x01
#define LCD_BLINKOFF 0x00
// flags for display/cursor shift
#define LCD_DISPLAYMOVE 0x08
#define LCD_CURSORMOVE 0x00
#define LCD_MOVERIGHT 0x04
#define LCD_MOVELEFT 0x00
// flags for function set
#define LCD_8BITMODE 0x10
#define LCD_4BITMODE 0x00
#define LCD_2LINE 0x08
#define LCD_1LINE 0x00
#define LCD_5x10DOTS 0x04
#define LCD_5x8DOTS 0x00
// flags for backlight control
#define LCD_BACKLIGHT 0x08
#define LCD_NOBACKLIGHT 0x00
#define En B00000100 // Enable bit
#define Rw B00000010 // Read/Write bit
#define Rs B00000001 // Register select bit
class LiquidCrystal_I2C : public Print {
public:
LiquidCrystal_I2C(uint8_t lcd_Addr,uint8_t lcd_cols,uint8_t lcd_rows);
void begin(uint8_t cols, uint8_t rows, uint8_t charsize = LCD_5x8DOTS );
void clear();
void home();
void noDisplay();
void display();
void noBlink();
void blink();
void noCursor();
void cursor();
void scrollDisplayLeft();
void scrollDisplayRight();
void printLeft();
void printRight();
void leftToRight();
void rightToLeft();
void shiftIncrement();
void shiftDecrement();
void noBacklight();
void backlight();
void autoscroll();
void noAutoscroll();
void createChar(uint8_t, uint8_t[]);
void setCursor(uint8_t, uint8_t);
#if defined(ARDUINO) && ARDUINO >= 100
virtual size_t write(uint8_t);
#else
virtual void write(uint8_t);
#endif
void command(uint8_t);
void init();
////compatibility API function aliases
void blink_on(); // alias for blink()
void blink_off(); // alias for noBlink()
void cursor_on(); // alias for cursor()
void cursor_off(); // alias for noCursor()
void setBacklight(uint8_t new_val); // alias for backlight() and nobacklight()
void load_custom_character(uint8_t char_num, uint8_t *rows); // alias for createChar()
void printstr(const char[]);
////Unsupported API functions (not implemented in this library)
uint8_t status();
void setContrast(uint8_t new_val);
uint8_t keypad();
void setDelay(int,int);
void on();
void off();
uint8_t init_bargraph(uint8_t graphtype);
void draw_horizontal_graph(uint8_t row, uint8_t column, uint8_t len, uint8_t pixel_col_end);
void draw_vertical_graph(uint8_t row, uint8_t column, uint8_t len, uint8_t pixel_col_end);
private:
void init_priv();
void send(uint8_t, uint8_t);
void write4bits(uint8_t);
virtual void expanderWrite(uint8_t); //alterado fabiano. definido como método virtual, para poder ser redefinido
uint8_t _displayfunction;
uint8_t _displaycontrol;
uint8_t _displaymode;
uint8_t _numlines;
uint8_t _cols;
uint8_t _rows;
protected:
uint8_t _Addr; //alterado fabiano. passou a ser protected para ser visto pelas classes derivadas
uint8_t _backlightval; //alterado fabiano. passou a ser protected para ser visto pelas classes derivadas
void pulseEnable(uint8_t); //alterado fabiano. passou a ser protected para ser visto pelas classes derivadas
};
#endif
</pre>
<br />
<br />
<br />
<br />Fabiano Arndthttp://www.blogger.com/profile/15268923442674744187noreply@blogger.com1tag:blogger.com,1999:blog-5513833072608837410.post-67718679452435910442016-08-16T14:29:00.003-04:002018-06-07T11:48:24.813-04:00Arduino - Data e horaNativamente, o Arduino não possui nenhum recurso que permita nos dar a data e hora atuais, apesar de possuir recursos para a contagem de tempo, como a função millis e a função micros, onde a primeira retorna a quantidade de milissegundos que se passaram desde que a placa foi ligada ou resetada, enquanto a segunda a quantidade de microssegundos. Mas essa informação por si só não é capaz de nos dizer se hoje é terça ou quinta, se é abril ou setembro ou se estamos em 1970 ou em 2016.<br />
<br />
Para obtermos data e horas atuais no Arduino precisamos contar com algum recurso externo, como por exemplo, um módulo RTC ou através de um servidor de data e hora na internet.<br />
<br />
Existem diversas formas de conseguir obter a data e hora atual, a forma mais viável vai depender do projeto em si. Nesse artigo, vamos ver sobre algumas das estratégias que podemos utilizar.<br />
<br />
<br />
<ul>
<li>Módulo RTC</li>
<li>Servidor de Data e Hora</li>
<li>Entrada através da Serial</li>
<li>Entrada Manual</li>
<li>Data e Hora de compilação</li>
</ul>
<br />
<br />
<b>RTC</b><br />
<br />
Contar com o um módulo RTC é uma boa saída para obtermos data e hora em nossas aplicações, há alguns modelos disponíveis no mercado, os quais contam com uma bateria de 3V, de modo que, mesmo sem energia na placa, o próprio módulo consegue se manter.<br />
<br />
<b>Servidor de Data e Hora</b><br />
<br />
Caso a aplicação conte com algum meio de acesso a internet, ou mesmo acesso a uma rede interna, uma boa saída é acessar um servidor NTP. Nesse artigo falo um pouco como funciona esse recurso:<br />
<a href="http://fabianoallex.blogspot.com.br/2015/08/arduino-ntp-acessando-servidor-de-hora.html">http://fabianoallex.blogspot.com.br/2015/08/arduino-ntp-acessando-servidor-de-hora.html</a><br />
<br />
É preciso ficar atento ao fato de que o servidor de data e hora pode não retornar a hora no mesmo fuso-horário de onde a aplicação estiver rodando, ficando a cargo do programador fazer os ajustes necessários, além disso, alguns servidores também não retornam a data no horário de verão.<br />
<br />
<b>Serial</b><br />
<br />
Um meio simples e eficiente de sincronizarmos a data e hora do Arduino é através da comunicação serial, caso o arduino esteja conectado a outros arduinos ou computadores que possam fornecer a data e hora. Esse tipo de solução pode ser utilizado em aplicações distribuídas, onde um Arduino tem acesso a data e hora, e consegue repassar essa informação através de uma comunicação serial. Por exemplo, se existe um Arduino com um módulo RTC o qual se conecta com outros Arduinos, não tem porque cada um dos Arduinos possuirem um módulo. Basta que o Arduino com o módulo RTC repasse a data e hora atual para os demais.<br />
<br />
<b>Manual</b><br />
<br />
Caso não seja possível para uma aplicação contar com nenhum outro meio de obter a data e hora, ainda existe a possibilidade do próprio usuário da aplicação informar a data e hora atual. A partir do momento que o Arduino sabe qual é a data e hora atual, ele é capaz de se manter atualizado enquanto estiver energizado ou não for resetado. Obviamente não é o meio mais confiável de se obter data e hora, mas pode ser a única solução dependendo da aplicação. O único inconveniente, é que será necessário ter uma interface onde o usuário possa dar a entrada manual. Se durante a execução, existir o risco da aplicação ser reiniciada, uma saída paliativa para conseguir recuperar a data e hora, seria gravar a cada tantos segundos a data atual na eeprom (ou outro meio disponível), e caso reiniciar a aplicação, a mesma iniciaria com a última data gravada. O inconveniente deste método, é que cada vez que a aplicação reiniciar, a precisão da hora será perdida. E caso o meio utilizado pra gravar a informação for a eeprom, é preciso ficar atento ao limite de vezes que uma eeprom pode ser gravada, antes de acabar sua vida útil.<br />
<br />
<b>Data e Hora de compilação</b><br />
<br />
Outra opção, que é mais aconselhável para ser usada durante a programação, é utilizar a data e hora de compilação do arquivo executável. Ela facilita o programador ter uma forma de testar seu código, sem ter necessariamente que contar com algum recurso externo. Essa forma de obtenção de data e hora só não é viável para se utilizar em produção, pois sempre que a aplicação for resetada, a data inicial será a mesma, então se uma aplicação foi compilada ha dois meses, ao reiniciar a aplicação, a data será a data de dois meses atrás.<br />
<br />
<b>Outros</b><br />
<br />
Ainda há outros modos de se obter a data e hora, como por exemplo, através de um módulo de GPS ou GSM, caso a aplicação utilize-os.<br />
<br />
<br />
No exemplo abaixo mostro um exemplo que sincroniza a data e hora com um módulo RTC, com um servidor NTP, com a entrada serial e com a data e hora de compilação do executável.<br />
<br />
<b>Código-fonte:</b><br />
<b><br /></b>
Inicialmente foi criado uma struct chamada Datetime, para que possamos ter os campos da data separados, o que facilita nosso entendimento. E também foi definido o tipo time_t, que na verdade é um long, de 4 bytes. Esse tipo na verdade é o chamado Unix Time, que nada mais é que a quantidade de segundos que se passaram desde o dia 01/01/1970. Ter a hora armazenada em um tipo de dado único facilita diversas operações com datas, como soma e subtração, por isso, a definição e utilização do tipo de dados time_t.<br />
<br />
Como há dois tipos de dados, Datetime (que é uma struct) e time_t (que é um long), sendo que ambos representam a mesma coisa, foram criadas duas funções que converte a data de um tipo para o outro, sendo elas:<br />
<br />
unixTimeToDatetime e datetimeToUnixTime<br />
<br />
Com essas duas funções, fica fácil alternar entre um e outro tipo de dados. As Demais classes e funções utilizadas para tratar as datas se baseiam nesses tipos de dados definidos e nessas duas funções de conversões.<br />
<br />
Para controlar a data atual do Arduino, foi criada uma classe chamada ArduinoDatetime. Essa classe é responsável por nos informar a data e hora atual do Arduino. Mas para isso, antes precisamos informar a data inicial. Isso é feito através do método Sync. Depois disso podemos obter a data e hora atuais através das funções getDatetime(), que retorna a data atual no formato Datetime, e através na função now() que retorna a data e hora atual no formato time_t.<br />
<br />
Essa classe armazena duas informações: o millis no momento da sincronização e a data e hora daquele momento. Para a classe retornar a data atual, basta que classe calcule quantos segundos se passaram desde a última sincronização e somar esse valor à data e hora do momento de sincronização.<br />
<br />
A princípio, é necessário apenas uma única sincronização para que o Arduino possa nos dar a data e hora atual, mas é aconselhável, dentro de um certo período de tempo, refazer a sincronização. Isso porque o clock do Arduino pode ter algum atraso durante longos períodos de tempo.<br />
<br />
Veja que a classe em si não é baseada em nenhum meio específico de obtenção da data e hora, ela apenas depende de uma prévia sincronização de data e hora, sem se preocupar com a origem da data. Mais abaixo no código, temos as diferentes funções que nos dão data e hora com diferentes origens: vindo do módulo DS3231, via NTP, via Serial e ainda da data e hora da compilação. Esses são apenas alguns exemplos. É perfeitamente possível que outras funções sejam incluídas.<br />
<br />
No loop, o código fica alternando a sincronização da data com as diversas funções que criamos que podem nos retornar a data inicial. Obviamente que em uma aplicação não será necessário ter mais de uma. O que foi feito aqui é apenas para demonstrar as possibilidades.<br />
<br />
<br />
<br />
<pre class="brush: html" style="background-color: #eeeeee; border: 1px dashed rgb(153, 153, 153); overflow: auto; padding: 5px; width: 100%;">#include "Wire.h"
#include <LiquidCrystal_I2C.h>
#include <Ethernet.h>
#include <EthernetUdp.h>
#include "Dns.h"
LiquidCrystal_I2C lcd(0x3F,20,4); // set the LCD address to 0x27 for a 16 chars and 2 line display
struct Datetime { //sizeof(Datetime) ==> 5 bytes --- sem bitfields teria 8 bytes
byte second : 6; //0..63
byte minute : 6; //0..63
byte hour : 5; //0..31
byte dayOfWeek : 3; //0..7
byte dayOfMonth : 5; //0..31
byte month : 4; //0..15
unsigned int year : 11; //0..2047
};
typedef unsigned long time_t; //variavel de 32 bits. para maior precisão (datas acima do ano de 2038. utilizar a linha abaixo.)
//typedef unsigned long long time_t; //variavel de 64 bits
/****************************************************************************************
************************************ETHERNET CONFIG/FUNCTIONS****************************
*****************************************************************************************
se for rodar numa rede com firewall, verifique se os ip utilizado está liberado.
Servidores da NTP.br
a.st1.ntp.br 200.160.7.186 e 2001:12ff:0:7::186
b.st1.ntp.br 201.49.148.135
c.st1.ntp.br 200.186.125.195
d.st1.ntp.br 200.20.186.76
a.ntp.br 200.160.0.8 e 2001:12ff::8
b.ntp.br 200.189.40.8
c.ntp.br 200.192.232.8
****************************************************************************************/
byte mac[] = { 0x00, 0x00, 0xAA, 0xBB, 0xCC, 0xDD};
unsigned int localPort = 8888; // local port to listen for UDP packets
IPAddress timeServer(200, 192, 232, 8); // time.nist.gov NTP server (fallback) - segunda tentativa caso a primeira de erro
const int NTP_PACKET_SIZE = 48; // NTP time stamp is in the first 48 bytes of the message
byte packetBuffer[ NTP_PACKET_SIZE]; // buffer to hold incoming and outgoing packets
//host para a primeira tentativa
const char* host = "a.st1.ntp.br"; // servidor da NTP.br - ver lista acima para todos os servidores da NTP.br
//const char* host = "192.168.200.254"; // servidor interno 01 - caso tenha um servidor de hora interno, pode ser configurado o nome ou ip na variavel host
EthernetUDP Udp;
DNSClient Dns;
IPAddress rem_add;
unsigned long sendNTPpacket(IPAddress& address) { // send an NTP request to the time server at the given address
memset(packetBuffer, 0, NTP_PACKET_SIZE); // set all bytes in the buffer to 0
packetBuffer[0] = 0b11100011; // LI, Version, Mode // Initialize values needed to form NTP request (see URL above for details on the packets)
packetBuffer[1] = 0; // Stratum, or type of clock
packetBuffer[2] = 6; // Polling Interval
packetBuffer[3] = 0xEC; // Peer Clock Precision
packetBuffer[12] = 49; // 8 bytes of zero for Root Delay & Root Dispersion
packetBuffer[13] = 0x4E;
packetBuffer[14] = 49;
packetBuffer[15] = 52;
Udp.beginPacket(address, 123); //NTP requests are to port 123 // all NTP fields have been given values, now you can send a packet requesting a timestamp:
Udp.write(packetBuffer,NTP_PACKET_SIZE);
Udp.endPacket();
}
/****************************************************************************************
************************************FIM ETHERNET CONFIG/FUNCTIONS************************
****************************************************************************************/
/****************************************************************************************
****************************DATA/HORA*****************************************************
****************************************************************************************/
#define LEAP_YEAR(_year) ((_year%4)==0)
static byte monthDays[] = {31, 28, 31, 30 , 31, 30, 31, 31, 30, 31, 30, 31};
#define DS3231_I2C_ADDRESS 0x68
enum WeekDay {WD_SUNDAY=0, WD_MONDAY, WD_TUESDAY, WD_WEDNESDAYM, WD_THURSDAY, WD_FRIDEY, WD_SATURDAY};
Datetime unixTimeToDatetime(time_t timep) {
Datetime dt = {0,0,0,0,0,0,0};
unsigned long long epoch = timep;
byte year, month, monthLength;
unsigned long days;
dt.second = epoch % 60;
epoch /= 60; // now it is minutes
dt.minute = epoch % 60;
epoch /= 60; // now it is hours
dt.hour = epoch % 24;
epoch /= 24; // now it is days
dt.dayOfWeek = (epoch+4) % 7;
year = 70;
days = 0;
while((unsigned)(days += (LEAP_YEAR(year) ? 366 : 365)) <= epoch) { year++; }
dt.year = year; // is returned as years from 1900
days -= LEAP_YEAR(year) ? 366 : 365;
epoch -= days; // now it is days in this year, starting at 0
for (month=0; month<12; month++) {
monthLength = ( (month==1) && LEAP_YEAR(year) ) ? 29 : monthDays[month]; // month==1 -> february
if (epoch >= monthLength) { epoch -= monthLength; } else { break; }
}
dt.month = month+1; // jan is month 0
dt.dayOfMonth = epoch+1; // day of month
dt.year += 1900;
return dt;
}
time_t datetimeToUnixTime(Datetime dt) { // note year argument is full four digit year (or digits since 2000), i.e.1975, (year 8 is 2008)
time_t seconds;
if(dt.year < 69) { dt.year += 2000; } // seconds from 1970 till 1 jan 00:00:00 this year
seconds = (dt.year-1970)*(60*60*24L*365);
for (int i=1970; i<dt.year; i++) { if (LEAP_YEAR(i)) { seconds += 60*60*24L; } } // add extra days for leap years
for (int i=0; i<dt.month-1; i++) { seconds += (i==1 && LEAP_YEAR(dt.year)) ? 60*60*24L*29 : 60*60*24L*monthDays[i]; } // add days for this year
seconds += (dt.dayOfMonth-1)*3600*24L + dt.hour*3600L + dt.minute*60L + dt.second;
return seconds;
}
class DS3231{
private:
static byte decToBcd(byte val) { return( (val/10*16) + (val%10) ); } // Convert normal decimal numbers to binary coded decimal
static byte bcdToDec(byte val) { return( (val/16*10) + (val%16) ); } // Convert binary coded decimal to normal decimal numbers
public:
static void sync(time_t t) { sync(unixTimeToDatetime(t)); }
static void sync(Datetime dt) {
Wire.beginTransmission(DS3231_I2C_ADDRESS);
Wire.write(0); // set next input to start at the seconds register
Wire.write(decToBcd(dt.second)); // set seconds
Wire.write(decToBcd(dt.minute)); // set minutes
Wire.write(decToBcd(dt.hour)); // set hours
Wire.write(decToBcd(dt.dayOfWeek+1));// set day of week (1=Sunday, 7=Saturday)
Wire.write(decToBcd(dt.dayOfMonth)); // set date (1 to 31)
Wire.write(decToBcd(dt.month)); // set month
Wire.write(decToBcd( (byte)(dt.year-2000) )); // set year (0 to 99)
//Wire.write(2000-decToBcd(dt.year)); // set year (0 to 99) --> substituido pela linha acima corrigido 07/06/201
Wire.endTransmission();
}
static Datetime getDatetime() {
Datetime dt;
Wire.beginTransmission(DS3231_I2C_ADDRESS);
Wire.write(0); // set DS3231 register pointer to 00h
Wire.endTransmission();
Wire.requestFrom(DS3231_I2C_ADDRESS, 7);
dt.second = bcdToDec(Wire.read() & 0x7f); // request seven bytes of data from DS3231 starting from register 00h
dt.minute = bcdToDec(Wire.read());
dt.hour = bcdToDec(Wire.read() & 0x3f);
dt.dayOfWeek = bcdToDec(Wire.read())-1;
dt.dayOfMonth = bcdToDec(Wire.read());
dt.month = bcdToDec(Wire.read());
dt.year = bcdToDec(Wire.read())+2000;
return dt;
}
static time_t now(){ return datetimeToUnixTime(getDatetime()); }
};
class ArduinoDatetime {
private:
static unsigned long _mRef; //millis de referencia na hora do sincronismo
static time_t _time; //data e hora no momento do último sincronismo
public:
static void sync(Datetime dt) { sync(datetimeToUnixTime(dt)); }
static void sync(time_t t) { _mRef = millis(); _time = t; }
static time_t now() { return _time + (millis() - _mRef)/1000; }
static Datetime getDatetime() { return unixTimeToDatetime(now()) ;}
};
unsigned long ArduinoDatetime::_mRef = 0;
time_t ArduinoDatetime::_time = 0;
//retorna a data e hora que o programa foi compilado
Datetime compiledDatetime() {
Datetime dt;
char s_month[5];
int day, hour, minute, second, year;
static const char month_names[] = "JanFebMarAprMayJunJulAugSepOctNovDec";
sscanf(__DATE__, "%s %d %d", s_month, &day, &year);
sscanf(__TIME__, "%2d %*c %2d %*c %2d", &hour, &minute, &second);
dt.second = second;
dt.minute = minute;
dt.hour = hour;
dt.dayOfMonth = day;
dt.month = (strstr(month_names, s_month) - month_names) / 3 + 1;
dt.year = year;
return dt;
}
//retorna a data e hora de um servidor ntp (sem fuso-horário)
Datetime NTPDatetime(){
if(Dns.getHostByName(host, rem_add) == 1 ){
Serial.println("DNS resolve...");
Serial.print(host);
Serial.print(" = ");
Serial.println(rem_add);
sendNTPpacket(rem_add);
} else {
Serial.println("DNS fail...");
Serial.print("time.nist.gov = ");
Serial.println(timeServer); // caso a primeira tentativa não retorne um host válido
sendNTPpacket(timeServer); // send an NTP packet to a time server
}
delay(1000); //aguarda um segundo, para receber os dados enviados.
if ( Udp.parsePacket() ) {
Udp.read(packetBuffer, NTP_PACKET_SIZE); // We've received a packet, read the data from it. read the packet into the buffer
unsigned long highWord = word(packetBuffer[40], packetBuffer[41]); // the timestamp starts at byte 40 of the received packet and is four bytes or two words, long. First, esxtract the two words:
unsigned long lowWord = word(packetBuffer[42], packetBuffer[43]);
unsigned long secsSince1900 = highWord << 16 | lowWord; // combine the four bytes (two words) into a long integer this is NTP time (seconds since Jan 1 1900):
Serial.print("Segundos desde 1 de Jan. de 1900 = " );
Serial.println(secsSince1900);
const unsigned long seventyYears = 2208988800UL; // Unix time starts on Jan 1 1970. In seconds, that's 2208988800:
unsigned long epoch = secsSince1900 - seventyYears; //desconta 70 anos
return unixTimeToDatetime(epoch);
}
return unixTimeToDatetime(0); //01/01/1970 00:00:00
}
//---------------------funcoes para sincronia da data e hora da Classe ArduinoDatetime
void syncCompiledDatetime(){
lcd.setCursor(0, 0);
lcd.clear();
lcd.print("Sinc pela compilacao");
ArduinoDatetime::sync(compiledDatetime());
}
void syncDS3231(){
lcd.setCursor(0, 0);
lcd.clear();
lcd.print("Sinc. pelo rtc");
ArduinoDatetime::sync(DS3231::getDatetime());
}
void syncNTP(){
lcd.setCursor(0, 0);
lcd.clear();
lcd.print("Sinc via NTP");
ArduinoDatetime::sync(NTPDatetime());
}
void syncSerial() {
//formato |DATETIME|DIA|MES|ANO|HORA|MINUTO|SEGUNDO|
while (Serial.available() ) {
String str = Serial.readStringUntil('|');
if (str == F("DATETIME")) {
lcd.setCursor(0, 0);
lcd.clear();
lcd.print("Sinc. pela serial");
Datetime dt;
dt.dayOfMonth = Serial.parseInt();
Serial.readStringUntil('|');
dt.month = Serial.parseInt();
Serial.readStringUntil('|');
dt.year = Serial.parseInt();
Serial.readStringUntil('|');
dt.hour = Serial.parseInt();
Serial.readStringUntil('|');
dt.minute = Serial.parseInt();
Serial.readStringUntil('|');
dt.second = Serial.parseInt();
Serial.readStringUntil('|');
ArduinoDatetime::sync(dt);
}
}
}
String zero(int a){ if(a>=10) {return (String)a+"";} else { return "0"+(String)a;} }
/****************************************************************************************
****************************FIM DATA/HORA************************************************
****************************************************************************************/
void setup() {
Serial.begin(9600);
Wire.begin();
lcd.init(); // initialize the lcd
lcd.backlight();
pinMode(4, OUTPUT); //ANTES DE INICIAR O ETHERNET, DESABILITA O SDCARD
digitalWrite(4, HIGH);
if (Ethernet.begin(mac) == 0) { Serial.println("Failed to configure Ethernet using DHCP"); }
Udp.begin(localPort);
Dns.begin(Ethernet.dnsServerIP() );
syncNTP();
}
void loop() {
syncSerial(); //aguarda a entrada de dados via serial pra configurar a data.
Datetime dt = ArduinoDatetime::getDatetime();
lcd.setCursor(0, 1);
lcd.print(zero(dt.dayOfMonth));
lcd.print("/");
lcd.print(zero(dt.month));
lcd.print("/");
lcd.print(dt.year);
lcd.print(" ");
lcd.print(zero(dt.hour));
lcd.print(":");
lcd.print(zero(dt.minute));
lcd.print(":");
lcd.print(zero(dt.second));
delay(999);
static unsigned long m = 0;
static int cont = 0;
if (millis() - m > 10000){
m = millis();
if (cont == 0){ syncDS3231(); }
if (cont == 1){ syncCompiledDatetime(); }
if (cont == 2){ syncNTP(); }
cont++;
if (cont == 3){ cont = 0; }
}
}
</pre>
<br />
<b>Vídeo</b><br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<iframe allowfullscreen="" class="YOUTUBE-iframe-video" data-thumbnail-src="https://i9.ytimg.com/vi/RKKgS7mdkxA/default.jpg?sqp=CPy0zb0F&rs=AOn4CLADqbEopM3_J1ocJhYAp6RRzKOR3w" frameborder="0" height="415" src="https://www.youtube.com/embed/RKKgS7mdkxA?feature=player_embedded" width="660"></iframe></div>
<br />
<br />Fabiano Arndthttp://www.blogger.com/profile/15268923442674744187noreply@blogger.com4tag:blogger.com,1999:blog-5513833072608837410.post-69626247829502784962016-06-16T08:22:00.000-04:002016-06-20T09:08:28.085-04:00Arduino - Tetris game - com rotary encoderMais um jogo pra Arduino, dessa vez o famoso Tetris!!<br />
<br />
Essa é uma implementação para fins didáticos, voltada para programadores iniciantes (ou não).<br />
<br />
Veja os demais vídeos de jogos para Arduino no canal:<br />
<br />
Wall Defender - Versão display 8x8: <a href="https://youtu.be/LBVgnoU8u38">https://youtu.be/LBVgnoU8u38</a><br />
Wall Defender - Versão display LCD: <a href="https://youtu.be/7VWK050VXa4">https://youtu.be/7VWK050VXa4</a><br />
Vídeo do jogo Snake: <a href="https://youtu.be/In1RD3-msJ8">https://youtu.be/In1RD3-msJ8</a><br />
Vídeo do jogo Race: <a href="https://youtu.be/lVmGilUGi8A">https://youtu.be/lVmGilUGi8A</a><br />
<br />
Inscreva-se no Canal: <a href="https://www.youtube.com/user/fabianoallex?sub_confirmation=1">https://www.youtube.com/user/fabianoallex?sub_confirmation=1</a><br />
Curta a página do facebook: <a href="https://www.facebook.com/dicasarduino">https://www.facebook.com/dicasarduino</a><br />
<br />
<b>Vídeo:</b><br />
<b><br /></b>
<br />
<div class="separator" style="clear: both; text-align: center;">
<iframe allowfullscreen="" class="YOUTUBE-iframe-video" data-thumbnail-src="https://i9.ytimg.com/vi/c4CTDo7_OUk/default.jpg?sqp=CMSwirsF&rs=AOn4CLAQ0eJP-j6KBpdvypt9ljEGLROu8A" frameborder="0" height="415" src="https://www.youtube.com/embed/c4CTDo7_OUk?feature=player_embedded" width="660"></iframe></div>
<br />
<br />
<b>Código-fonte:</b>
<br />
<b><br /></b>
<br />
<pre class="brush: html" style="background-color: #eeeeee; border: 1px dashed rgb(153, 153, 153); overflow: auto; padding: 5px; width: 100%;">/*
Fabiano A. Arndt - 2016
www.youtube.com/user/fabianoallex
www.facebook.com/dicasarduino
fabianoallex@gmail.com
*/
/*************************************************************************************************************
*******************************BIT ARRAY *********************************************************************
**************************************************************************************************************
mais informações aqui: http://fabianoallex.blogspot.com.br/2015/09/arduino-array-de-bits.html
**************************************************************************************************************/
class BitArray2D {
private:
unsigned int _rows;
unsigned int _columns;
unsigned int _cols_array; //pra cada 8 colunas, 1 byte é usado
byte** _bits;
public:
BitArray2D(unsigned int rows, unsigned int columns){
_rows = rows;
_columns = columns;
_cols_array = columns/8 + (_columns%8 ? 1 : 0) + 1; //divide por 8 o número de colunas
_bits = (byte **)malloc(_rows * sizeof(byte *));
for(int i=0;i<_rows;i++){ _bits[i] = (byte *)malloc( _cols_array * sizeof(byte)); } //cria varios arrays
clear();
}
unsigned int rows(){ return _rows; }
unsigned int columns(){ return _columns; }
void clear() {
for(int i=0;i<_rows;i++){
for(int j=0; j<_cols_array;j++) { _bits[i][j] = B00000000; }
}
}
void write(unsigned int row, unsigned int column, int value){
byte b = _bits[row][ column/8 + (column%8 ? 1 : 0) ];
unsigned int bit = column%8;
if (value) { b |= (1 << bit); } else { b &= ~(1 << bit); }
_bits[row][ column/8 + (column%8 ? 1 : 0) ] = b;
}
void write(byte value){
for(int i=0;i<_rows;i++){
for(int j=0; j<_cols_array;j++) {
_bits[i][j] = value ? B11111111 : B00000000;
}
}
}
int read(unsigned int row, unsigned int column){
byte b = _bits[row][ column/8 + (column%8 ? 1 : 0) ];
unsigned int bit = column%8;
return (b & (1 << bit)) != 0;
}
void toggle(unsigned int row, unsigned int column){ write(row, column, !read(row, column)); }
void toggle(){ for(int i=0;i<_rows;i++){ for(int j=0; j<_columns;j++) { toggle(i,j); } } }
};
/*************************************************************************************************************
*******************************FIM BIT ARRAY *****************************************************************
**************************************************************************************************************/
/*************************************************************************************************************
************************************CLASSE UNIQUE RANDOM******************************************************
**************************************************************************************************************
mais informações aqui: http://fabianoallex.blogspot.com.br/2015/09/arduino-numeros-aleatorio-repetidos-e.html
*************************************************************************************************************/
class UniqueRandom{
private:
int _index;
int _min;
int _max;
int _size;
int* _list;
void _init(int min, int max) {
_list = 0;
if (min < max) { _min = min; _max = max; } else { _min = max; _max = min; }
_size = _max - _min;
_index = 0;
}
public:
UniqueRandom(int max) { _init(0, max); randomize(); } //construtor com 1 parametro
UniqueRandom(int min, int max) { _init(min, max); randomize(); } //construtor com 2 parametros
void randomize() {
_index = 0;
if (_list == 0) { _list = (int*) malloc(size() * sizeof(int)); }
for (int i=0; i<size(); i++) { _list[i] = _min+i; } //preenche a lista do menor ao maior valor
//embaralha a lista
for (int i=0; i<size(); i++) {
int r = random(0, size()); //sorteia uma posição qualquer
int aux = _list[i];
_list[i] = _list[r];
_list[r] = aux;
}
}
int next() { //retorna o proximo numero da lista
int n = _list[_index++];
if (_index >= size() ) { _index = 0;} //após recuper o ultimo numero, recomeça na posicao 0
return n;
}
void first() { _index = 0; }
boolean eof() { return size() == _index+1; }
int size() { return _size; }
int getIndex() { return _index; }
~UniqueRandom(){ free ( _list ); } //destrutor
};
/*************************************************************************************************************
************************************FIM CLASSE UNIQUE RANDOM**************************************************
*************************************************************************************************************/
/*************************************************************************************************************
*******************************LEDCONTROL ALTERADA************************************************************
mais informações: http://fabianoallex.blogspot.com.br/2015/09/arduino-alteracoes-na-biblioteca.html
**************************************************************************************************************/
//the opcodes for the MAX7221 and MAX7219
#define OP_DECODEMODE 9
#define OP_INTENSITY 10
#define OP_SCANLIMIT 11
#define OP_SHUTDOWN 12
#define OP_DISPLAYTEST 15
class LedControl {
private :
byte spidata[16];
byte * status;
int SPI_MOSI;
int SPI_CLK;
int SPI_CS;
int maxDevices;
int _auto_send;
void spiTransfer(int addr, volatile byte opcode, volatile byte data) {
int offset = addr*2;
int maxbytes = maxDevices*2;
for(int i=0;i<maxbytes;i++) { spidata[i]=(byte)0; }
spidata[offset+1] = opcode;
spidata[offset] = data;
digitalWrite(SPI_CS,LOW);
for(int i=maxbytes;i>0;i--) { shiftOut(SPI_MOSI,SPI_CLK,MSBFIRST,spidata[i-1]); }
digitalWrite(SPI_CS,HIGH);
}
public:
LedControl(int dataPin, int clkPin, int csPin, int numDevices) {
_auto_send = true;
SPI_MOSI = dataPin;
SPI_CLK = clkPin;
SPI_CS = csPin;
maxDevices = numDevices;
pinMode(SPI_MOSI, OUTPUT);
pinMode(SPI_CLK, OUTPUT);
pinMode(SPI_CS, OUTPUT);
digitalWrite(SPI_CS, HIGH);
status = new byte[maxDevices * 8]; //instancia o array de acordo com a quantia de displays usados
for(int i=0;i<maxDevices * 8 ;i++) { status[i]=0x00; }
for(int i=0;i<maxDevices;i++) {
spiTransfer(i, OP_DISPLAYTEST,0);
setScanLimit(i, 7); //scanlimit is set to max on startup
spiTransfer(i, OP_DECODEMODE,0); //decode is done in source
clearDisplay(i);
shutdown(i,true); //we go into shutdown-mode on startup
}
}
void startWrite() { _auto_send = false; };
void send() {
for (int j=0; j<maxDevices; j++) {
int offset = j*8;
for(int i=0;i<8;i++) { spiTransfer(j, i+1, status[offset+i]); }
}
_auto_send = true;
}
int getDeviceCount(){ return maxDevices; }
void shutdown(int addr, bool b){
if(addr<0 || addr>=maxDevices) return;
spiTransfer(addr, OP_SHUTDOWN, b ? 0 : 1);
}
void setScanLimit(int addr, int limit){
if(addr<0 || addr>=maxDevices) return;
if(limit>=0 && limit<8) spiTransfer(addr, OP_SCANLIMIT,limit);
}
void setIntensity(int addr, int intensity) {
if(addr<0 || addr>=maxDevices) { return; }
if(intensity>=0 && intensity<16) { spiTransfer(addr, OP_INTENSITY, intensity); }
}
void clearDisplay(int addr){
if(addr<0 || addr>=maxDevices) return;
int offset = addr*8;
for(int i=0;i<8;i++) {
status[offset+i] = 0;
if (_auto_send) { spiTransfer(addr, i+1, status[offset+i]); }
}
}
void setLed(int addr, int row, int column, boolean state) {
if(addr<0 || addr>=maxDevices) { return; }
if(row<0 || row>7 || column<0 || column>7) { return; }
int offset = addr*8;
byte val = B10000000 >> column;
if(state) { status[offset+row] = status[offset+row] | val; } else { val=~val; status[offset+row] = status[offset+row]&val; }
if (_auto_send) { spiTransfer(addr, row+1, status[offset+row]); }
}
void setRow(int addr, int row, byte value) {
if(addr<0 || addr>=maxDevices) return;
if(row<0 || row>7) return;
int offset = addr*8;
status[offset+row] = value;
if (_auto_send) { spiTransfer(addr, row+1, status[offset+row]); }
}
void setColumn(int addr, int col, byte value) {
if(addr<0 || addr>=maxDevices) return;
if(col<0 || col>7) return;
byte val;
for(int row=0; row<8; row++) {
val=value >> (7-row);
val=val & 0x01;
setLed(addr,row,col,val);
}
}
};
/*************************************************************************************************************
*******************************FIM LEDCONTROL ALTERADA********************************************************
**************************************************************************************************************/
/*************************************************************************************************************
************************************CLASSE ROTARY ENCODER*****************************************************
mais informações: http://fabianoallex.blogspot.com.br/2016/05/arduino-rotary-encoder.html
*************************************************************************************************************/
#define ROTARY_NO_BUTTON 255
struct RotaryEncoderLimits{
int min;
int max;
};
class RotaryEncoder {
private:
byte _pin_clk;
byte _pin_dt;
byte _pin_sw;
volatile byte _num_results;
volatile int _result;
volatile int * _results;
byte _index_result;
RotaryEncoderLimits * _limits;
byte _a : 1;
byte _b : 1;
public:
RotaryEncoder(byte pin_clk, byte pin_dt, byte pin_sw = ROTARY_NO_BUTTON, byte num_results=1, RotaryEncoderLimits * limits=0){ //parametro do botao opcional
_pin_clk = pin_clk;
_pin_dt = pin_dt;
_pin_sw = pin_sw;
pinMode(_pin_clk, INPUT);
pinMode(_pin_dt, INPUT);
if (_pin_sw != ROTARY_NO_BUTTON){
pinMode(_pin_sw, INPUT);
digitalWrite(_pin_sw, HIGH);
}
if (num_results == 0) { num_results = 1; }
_num_results = num_results;
_results = new int[_num_results];
for (int i; i<_num_results; i++){ _results[i] = (limits) ? limits[i].min : 0; }
_index_result = 0;
_limits = limits;
_a = false;
_b = false;
}
byte getIndex() { return _index_result; }
void next() { _index_result++; if (_index_result >= _num_results) { _index_result = 0; } }
void update_a() {
_result = 0;
delay (1);
if( digitalRead(_pin_clk) != _a ) {
_a = !_a;
if ( _a && !_b ) { _result = -1; }
}
if (_results[_index_result]+_result >= _limits[_index_result].min &&
_results[_index_result]+_result <= _limits[_index_result].max ) {
_results[_index_result] += _result;
}
}
void update_b() {
_result = 0;
delay (1);
if( digitalRead(_pin_dt) != _b ) {
_b = !_b;
if ( _b && !_a ) { _result = +1; }
}
if (_results[_index_result]+_result >= _limits[_index_result].min &&
_results[_index_result]+_result <= _limits[_index_result].max ) {
_results[_index_result] += _result;
}
}
int read(){ return _result; } //retorn -1, 0 ou 1.
int getValue(int index=-1) { //retorna o valor da variável corrente ou a passada como parametro
if (index < 0 ){ return _results[_index_result]; }
return _results[index];
}
void setValue(int value, int index=-1){ _results[ (index==-1) ? _index_result : index] = value; } //caso a variável inicializa em determinado valor diferente de zero, utilizar esse método.
int buttonRead(){ return (_pin_sw == ROTARY_NO_BUTTON) ? LOW : digitalRead(_pin_sw); }
};
/*************************************************************************************************************
************************************FIM CLASSE ROTARY ENCODER*************************************************
*************************************************************************************************************/
/*************************************************************************************************************
*******************************CLASSES TETRIS GAME************************************************************
**************************************************************************************************************/
const int TETRIS_TIME_INIT = 700; //tempo entre deslocamento dos inimigos
const int TETRIS_TIME_INC = 20; //incremento da velocidade
enum Direction { DIR_STOP, DIR_TOP, DIR_LEFT, DIR_BOTTOM, DIR_RIGHT};
enum TetrisGameStatus { TETRIS_GAME_ON, TETRIS_GAME_OVER };
enum TetrisBlockTypes { TETRIS_I, TETRIS_J, TETRIS_L, TETRIS_O, TETRIS_S, TETRIS_Z, TETRIS_T, TETRIS_COUNT_BTYPES};
struct Position { int row, column; };
struct Block { byte row0:4; byte row1:4; byte row2:4; byte row3:4; }; //4 bit cada linha
//blocos e suas rotacoes
Block block_i[] = { {B1111, B0000, B0000, B0000}, {B0001, B0001, B0001, B0001} };
Block block_j[] = { {B0000, B0010, B0010, B0011}, {B0000, B0000, B0001, B0111}, {B0000, B0110, B0010, B0010}, {B0000, B0000, B0111, B0100} };
Block block_l[] = { {B0000, B0010, B0010, B0110}, {B0000, B0000, B0100, B0111}, {B0000, B0011, B0010, B0010}, {B0000, B0000, B0111, B0001} };
Block block_o[] = { {B0000, B0000, B0110, B0110} };
Block block_s[] = { {B0000, B0001, B0011, B0010}, {B0000, B0000, B0110, B0011} };
Block block_z[] = { {B0000, B0000, B0110, B1100}, {B0000, B1000, B1100, B0100} };
Block block_t[] = { {B0000, B0000, B0010, B0111}, {B0000, B0010, B0011, B0010}, {B0000, B0000, B0111, B0010}, {B0000, B0001, B0011, B0001} };
class TetrisBlock{
private:
Block _block; //bloco que vai descendo. player
Position _position; //posição do bloco na tela. pode assumir posição negativa
TetrisBlockTypes _blockType;
byte _blockTypeRotation;
public:
Position getPosition() { return _position; }
byte getRow(int row) {
if (row == 0 ) return _block.row0;
if (row == 1 ) return _block.row1;
if (row == 2 ) return _block.row2;
if (row == 3 ) return _block.row3;
return 0;
}
byte getColumn(int col) {
byte b = 0;
for (int i=0;i<4;i++){ bitWrite(b, 3-i, bitRead(getRow(i), 3-col) ); }
return b;
}
int read(int row, int column){
byte b = getRow(row);
unsigned int bit = column%8;
return (b & (1 << bit)) != 0;
}
void left() { _position.column--; }
void right() { _position.column++; }
void bottom() { _position.row++; }
void top() { _position.row--; }
void generateBlock(byte col) {
_position.row = 0;
_position.column = col;
_blockType = (TetrisBlockTypes) random(0, TETRIS_COUNT_BTYPES);
_blockTypeRotation = 0;
if ( _blockType == TETRIS_I ){ _block = block_i[_blockTypeRotation]; }
if ( _blockType == TETRIS_J ){ _block = block_j[_blockTypeRotation]; }
if ( _blockType == TETRIS_L ){ _block = block_l[_blockTypeRotation]; }
if ( _blockType == TETRIS_O ){ _block = block_o[_blockTypeRotation]; }
if ( _blockType == TETRIS_S ){ _block = block_s[_blockTypeRotation]; }
if ( _blockType == TETRIS_Z ){ _block = block_z[_blockTypeRotation]; }
if ( _blockType == TETRIS_T ){ _block = block_t[_blockTypeRotation]; }
if (!getRow(0)) { _position.row--;
if (!getRow(1)) { _position.row--;
if (!getRow(2)) { _position.row--; } } }
}
void rotate(int direction = 1){
int maxPos = 0;
if ( _blockType == TETRIS_I ){ maxPos = sizeof(block_i)/sizeof(Block); }
if ( _blockType == TETRIS_J ){ maxPos = sizeof(block_j)/sizeof(Block); }
if ( _blockType == TETRIS_L ){ maxPos = sizeof(block_l)/sizeof(Block); }
if ( _blockType == TETRIS_O ){ maxPos = sizeof(block_o)/sizeof(Block); }
if ( _blockType == TETRIS_S ){ maxPos = sizeof(block_s)/sizeof(Block); }
if ( _blockType == TETRIS_Z ){ maxPos = sizeof(block_z)/sizeof(Block); }
if ( _blockType == TETRIS_T ){ maxPos = sizeof(block_t)/sizeof(Block); }
(direction==1) ? _blockTypeRotation++ : _blockTypeRotation--;
if (_blockTypeRotation == 255) { _blockTypeRotation = maxPos-1; }
if (_blockTypeRotation >= maxPos) { _blockTypeRotation = 0; }
if ( _blockType == TETRIS_I ){ _block = block_i[_blockTypeRotation]; }
if ( _blockType == TETRIS_J ){ _block = block_j[_blockTypeRotation]; }
if ( _blockType == TETRIS_L ){ _block = block_l[_blockTypeRotation]; }
if ( _blockType == TETRIS_O ){ _block = block_o[_blockTypeRotation]; }
if ( _blockType == TETRIS_S ){ _block = block_s[_blockTypeRotation]; }
if ( _blockType == TETRIS_Z ){ _block = block_z[_blockTypeRotation]; }
if ( _blockType == TETRIS_T ){ _block = block_t[_blockTypeRotation]; }
}
};
class TetrisGame {
private:
BitArray2D * _display; //display com todos os objetos da tela. recebido como parâmetro
BitArray2D * _recipient; //recepiente onde os blocos vão se acumulando. criado internamente pela classe
TetrisBlock * _block;
Direction _direction;
TetrisGameStatus _tetrisGameStatus;
unsigned long _last_millis;
unsigned long _last_millis_player;
int _time;
int _score;
byte _fast : 1; //acelerar a descida
UniqueRandom * _ur; //utilizado no game over
void _gameOver(){
_tetrisGameStatus = TETRIS_GAME_OVER;
_direction = DIR_STOP;
_time = 20;
}
void _inc_speed(){
_score++;
_time -= TETRIS_TIME_INC;
}
void _start(){
_recipient->clear();
_last_millis = 0;
_score = 0;
_time = TETRIS_TIME_INIT;
_fast = 0;
_direction = DIR_STOP;
_tetrisGameStatus = TETRIS_GAME_ON;
_newBlock();
}
void _newBlock() {
_block->generateBlock( _display->columns() / 2 - 1 );
if (_testCollision()){ _gameOver(); }
}
void _updateDisplay() {
if (_tetrisGameStatus == TETRIS_GAME_ON) { _display->clear(); }
for (int r=0; r<4; r++) {
for (int c=0; c<4; c++) {
if ( _block->read(r, c) ) {
_display->write(_block->getPosition().row+r, _block->getPosition().column+c, HIGH );
}
}
}
for (int row=0; row<_display->rows(); row++) {
for (int col=0; col<_display->columns(); col++) {
boolean val = _recipient->read(row, col);
if (val) { _display->write(row, col, val ); }
}
}
}
boolean _testCollision(){
int col = _block->getPosition().column; //coluna do bloco em relacao ao recepiente. pode ser negativa
int row = _block->getPosition().row; //linha do bloco em relacao ao recepiente. pode ser negativa
for (byte c=0; c<4; c++){
for (byte r=0; r<4; r++){
if (! _block->read(r, c) ) { continue; }
if ( row+r < 0 ) {return true;}
if ( row+r >= _display->rows() ) {return true;}
if ( col+c < 0 ) {return true;}
if ( col+c >= _display->columns() ) {return true;}
if ( _recipient->read(row+r, col+c) ) {return true;}
}
}
return false;
}
boolean _canLeft(){
_block->left();
boolean r = ! _testCollision();
_block->right();
return r;
}
boolean _canRight(){
_block->right();
boolean r = ! _testCollision();
_block->left();
return r;
}
boolean _canBottom(){
_block->bottom();
boolean r = ! _testCollision();
_block->top();
return r;
}
boolean _canRotate(){
_block->rotate();
boolean r = ! _testCollision();
_block->rotate(-1);
return r;
}
void _blockToRecipient(){
for (byte c=0; c<4; c++){
for (byte r=0; r<4; r++){
if ( _block->read(r, c) ){ _recipient->write(_block->getPosition().row+r, _block->getPosition().column+c, HIGH); }
}
}
_verifyCompleteRow();
_newBlock();
}
void _verifyCompleteRow(){
for (int row=0; row<_recipient->rows(); row++) {
boolean complete = true;
for (int col=0; col<_recipient->columns(); col++) {
if (! _recipient->read(row, col) ) { complete = false; break; }
}
if (complete){
_incScore();
//move as linhas pra baixo
for (int row2 = row; row2>=0; row2--) {
for (int col2=0; col2<_recipient->columns(); col2++){
_recipient->write(row2, col2, (row2>0) ? _recipient->read(row2-1, col2) : 0 );
}
}
}
}
}
void _incScore(){
_score++;
_time -= TETRIS_TIME_INC;
}
void _runGameOver(){
int r = _ur->next();
int row = (r / _display->columns());
int col = (r % _display->columns());
_display->write(row, col, HIGH );
if ( _ur->getIndex() == (_ur->size()-1) ) {
_ur->randomize();
_ur->first();
_start();
}
}
void _runPlayer() {
if (_direction == DIR_LEFT && _canLeft() ) { _block->left(); }
if (_direction == DIR_RIGHT && _canRight() ) { _block->right(); }
_direction = DIR_STOP;
}
void _runBlock() {
_canBottom() ? _block->bottom() : _blockToRecipient() ;
_direction = DIR_STOP;
}
public:
TetrisGame(BitArray2D * display){
_display = display;
_recipient = new BitArray2D( _display->rows(), _display->columns() );
_ur = new UniqueRandom( _display->rows() * _display->columns() );
_block = new TetrisBlock();
_start();
}
void fast() { _fast = 1; } //acelerar o deslocamento
void noFast() { _fast = 0; } //descida do bloco na velocidade normal
void left() { _direction = DIR_LEFT; }
void right() { _direction = DIR_RIGHT; }
void bottom() { _direction = DIR_BOTTOM; }
void rotate() { if (_canRotate()) { _block->rotate(); } }
int getScore(){ return _score; }
Position getPosition() { return _block->getPosition(); }
int update(){
int r = false;
if (millis() - _last_millis > ( (_fast==1) ? 50 : _time ) ) {
r = true;
_last_millis = millis();
if (_tetrisGameStatus == TETRIS_GAME_ON) { _runBlock(); }
if (_tetrisGameStatus == TETRIS_GAME_OVER) { _runGameOver(); }
}
if (millis() - _last_millis_player > 70 ) { //atualiza os movimentos do player a cada 70ms
r = true;
_last_millis_player = millis();
if (_tetrisGameStatus == TETRIS_GAME_ON) { _runPlayer(); }
}
if (r) { _updateDisplay(); }
return r; //r-->indica se houve mudança no display
}
};
/*************************************************************************************************************
*******************************FIM CLASSES TETRIS GAME********************************************************
**************************************************************************************************************/
/*************************************************************************************************************
*******************************DISPLAY************************************************************************
**************************************************************************************************************/
/*
rotation indica qual parte do display estará para cima
TOP
L . . . . . . . . R
E . . . . . . . . I
F . . . . . . . . G
T . . . . . . . . H
. . . . . . . . T
. . . . . . . .
. . . . . . . .
. . . . . . . .
BOTTOM
*/
enum Rotation {TOP, LEFT, BOTTOM, RIGHT};
struct Display {
int index;
Position position;
Rotation rotation;
};
/*************************************************************************************************************
*******************************FIM DISPLAY********************************************************************
**************************************************************************************************************/
/*************************************************************************************************************
*******************************DECLARACAO DOS OBJETOS*********************************************************
**************************************************************************************************************/
/*
tamanho display real
* * * * * * * * * * * * * * * *
* * * * * * * * * * * * * * * *
* * * * * * * * * * * * * * * *
* * * * * * * * * * * * * * * *
* * * * * * * * * * * * * * * *
* * * * * * * * * * * * * * * *
* * * * * * * * * * * * * * * *
* * * * * * * * * * * * * * * *
*/
const int LINHAS = 16;
const int COLUNAS = 8;
Display displays_8x8[] = {
{1, {0,0}, BOTTOM},
{0, {8,0}, TOP}
};
BitArray2D ba(LINHAS, COLUNAS); //8 linhas e 16 colunas, usada para armazenar o estado do display
TetrisGame tetris(&ba);
RotaryEncoderLimits lim[] = { {-1000,1000} }; //limites máximos e mínimos que as variaveis podem atingir
RotaryEncoder re(A0, A1, 4, 1, lim); //pino clk, pino dt, pino sw, qtd variaveis, limites
LedControl lc=LedControl(10,12,11,2); //pin 10: DataIn ; pin 12: CLK ; pin 11: LOAD --- 2 display 8x8
/*************************************************************************************************************
*******************************FIM DECLARACAO DOS OBJETOS*****************************************************
**************************************************************************************************************/
/*************************************************************************************************************
*******************************TRATAMENTO DAS INTERRUPÇÕES****************************************************
**************************************************************************************************************/
//interrupções dos pinos A0 e A1 via Pin Change Interrupt
ISR(PCINT1_vect) {
volatile static byte lastVal_a0 = LOW;
volatile static byte lastVal_a1 = LOW;
byte val_a0 = digitalRead(A0);
byte val_a1 = digitalRead(A1);
if (lastVal_a0 != val_a0){ re.update_a(); lastVal_a0 = val_a0; }
if (lastVal_a1 != val_a1){ re.update_b(); lastVal_a1 = val_a1; }
}
void setup_interrupts(){
//-----PCI - Pin Change Interrupt ----
pinMode(A0,INPUT); // set Pin as Input (default)
digitalWrite(A0,HIGH); // enable pullup resistor
pinMode(A1,INPUT); // set Pin as Input (default)
digitalWrite(A1,HIGH); // enable pullup resistor
cli();
PCICR |= 0b00000010; // habilita a porta C - Pin Change Interrupts
PCMSK1 |= 0b00000011; // habilita interrupção da porta c nos pinos: PCINT8 (A0) e PCINT9(A1)
sei();
}
/*************************************************************************************************************
*******************************FIM TRATAMENTO DAS INTERRUPÇÕES****************************************************
**************************************************************************************************************/
void update_display() {
lc.startWrite();
for (int lin=0; lin<ba.rows(); lin++) {
for (int col=0; col<ba.columns(); col++) {
for (int i=0; i<sizeof(displays_8x8)/sizeof(Display); i++) {
int l = lin - displays_8x8[i].position.row;
int c = col - displays_8x8[i].position.column;
if (l>=0 && l<=7 && c>=0 && c<=7) {
if (displays_8x8[i].rotation == BOTTOM) { c=7-c; l=7-l; }
if (displays_8x8[i].rotation == LEFT) { int aux=c; c=l; l=7-aux; }
if (displays_8x8[i].rotation == RIGHT) { int aux=l; l=c; c=7-aux; }
lc.setLed(displays_8x8[i].index, l, c, ba.read(lin, col) );
}
}
}
}
lc.send();
}
void setup() {
lc.shutdown(0,false);
lc.setIntensity(0,1);
lc.clearDisplay(0);
lc.shutdown(1,false);
lc.setIntensity(1,1);
lc.clearDisplay(1);
setup_interrupts();
randomSeed(analogRead(A2));
re.setValue(0, 0); //inicializa o rotary em 0
}
void loop() {
static int val_encoder = 0;
static boolean change = false;
if (re.getValue(0) < val_encoder && !change){ tetris.left(); change = true; } //sentido anti-horario move para esquerda
if (re.getValue(0) > val_encoder && !change){ tetris.right(); change = true; } //sentido horario move para direita
val_encoder = re.getValue(0);
if ( tetris.update() ) {
change = false;
update_display();
}
//controla o click do botao do enconder
//clique curto --> rotate
//clique longo --> fast
static byte lastb = HIGH; //leitura anterior do botão
static unsigned long m_pressed = 0; //millis na borda de subida do botão
int b = re.buttonRead();
if( !b && lastb ) { //borda de subida
m_pressed = millis();
delay(70);
}
if( b && !lastb ) { //borda de descida
if (millis() - m_pressed < 350) {
tetris.rotate();
m_pressed = millis();
}
}
if( !b && millis()-m_pressed > 350){
tetris.fast();
} else {
tetris.noFast();
}
lastb = b;
}
</pre>
<br />
<br />
<b>Atualização 20/06/2016 - Versão em display LCD 16x2</b><br />
<br />
Como já implementei alguns outros jogos em display lcd 16x2, não poderia deixar de fazer uma versão do tetris também.<br />
<br />
<b>Vídeo:</b><br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<iframe allowfullscreen="" class="YOUTUBE-iframe-video" data-thumbnail-src="https://i9.ytimg.com/vi/wUHnpcJ3xOc/default.jpg?sqp=CPTFn7sF&rs=AOn4CLCVdtUoKp0IrrR37hh8zCLhzVwyTw" frameborder="0" height="415" src="https://www.youtube.com/embed/wUHnpcJ3xOc?feature=player_embedded" width="660"></iframe></div>
<br />
<br />
<b>Código-fonte:</b><br />
<br />
<pre class="brush: html" style="background-color: #eeeeee; border: 1px dashed rgb(153, 153, 153); overflow: auto; padding: 5px; width: 100%;">/*
Fabiano A. Arndt - 2016
www.youtube.com/user/fabianoallex
www.facebook.com/dicasarduino
fabianoallex@gmail.com
*/
#include <LiquidCrystal.h>
/*************************************************************************************************************
*******************************BIT ARRAY *********************************************************************
**************************************************************************************************************
mais informações aqui: http://fabianoallex.blogspot.com.br/2015/09/arduino-array-de-bits.html
**************************************************************************************************************/
class BitArray2D {
private:
unsigned int _rows;
unsigned int _columns;
unsigned int _cols_array; //pra cada 8 colunas, 1 byte é usado
byte** _bits;
public:
BitArray2D(unsigned int rows, unsigned int columns){
_rows = rows;
_columns = columns;
_cols_array = columns/8 + (_columns%8 ? 1 : 0) + 1; //divide por 8 o número de colunas
_bits = (byte **)malloc(_rows * sizeof(byte *));
for(int i=0;i<_rows;i++){ _bits[i] = (byte *)malloc( _cols_array * sizeof(byte)); } //cria varios arrays
clear();
}
unsigned int rows(){ return _rows; }
unsigned int columns(){ return _columns; }
void clear() {
for(int i=0;i<_rows;i++){
for(int j=0; j<_cols_array;j++) { _bits[i][j] = B00000000; }
}
}
void write(unsigned int row, unsigned int column, int value){
byte b = _bits[row][ column/8 + (column%8 ? 1 : 0) ];
unsigned int bit = column%8;
if (value) { b |= (1 << bit); } else { b &= ~(1 << bit); }
_bits[row][ column/8 + (column%8 ? 1 : 0) ] = b;
}
void write(byte value){
for(int i=0;i<_rows;i++){
for(int j=0; j<_cols_array;j++) {
_bits[i][j] = value ? B11111111 : B00000000;
}
}
}
int read(unsigned int row, unsigned int column){
byte b = _bits[row][ column/8 + (column%8 ? 1 : 0) ];
unsigned int bit = column%8;
return (b & (1 << bit)) != 0;
}
void toggle(unsigned int row, unsigned int column){ write(row, column, !read(row, column)); }
void toggle(){ for(int i=0;i<_rows;i++){ for(int j=0; j<_columns;j++) { toggle(i,j); } } }
};
/*************************************************************************************************************
*******************************FIM BIT ARRAY *****************************************************************
**************************************************************************************************************/
/*************************************************************************************************************
************************************CLASSE UNIQUE RANDOM******************************************************
**************************************************************************************************************
mais informações aqui: http://fabianoallex.blogspot.com.br/2015/09/arduino-numeros-aleatorio-repetidos-e.html
*************************************************************************************************************/
class UniqueRandom{
private:
int _index;
int _min;
int _max;
int _size;
int* _list;
void _init(int min, int max) {
_list = 0;
if (min < max) { _min = min; _max = max; } else { _min = max; _max = min; }
_size = _max - _min;
_index = 0;
}
public:
UniqueRandom(int max) { _init(0, max); randomize(); } //construtor com 1 parametro
UniqueRandom(int min, int max) { _init(min, max); randomize(); } //construtor com 2 parametros
void randomize() {
_index = 0;
if (_list == 0) { _list = (int*) malloc(size() * sizeof(int)); }
for (int i=0; i<size(); i++) { _list[i] = _min+i; } //preenche a lista do menor ao maior valor
//embaralha a lista
for (int i=0; i<size(); i++) {
int r = random(0, size()); //sorteia uma posição qualquer
int aux = _list[i];
_list[i] = _list[r];
_list[r] = aux;
}
}
int next() { //retorna o proximo numero da lista
int n = _list[_index++];
if (_index >= size() ) { _index = 0;} //após recuper o ultimo numero, recomeça na posicao 0
return n;
}
void first() { _index = 0; }
boolean eof() { return size() == _index+1; }
int size() { return _size; }
int getIndex() { return _index; }
~UniqueRandom(){ free ( _list ); } //destrutor
};
/*************************************************************************************************************
************************************FIM CLASSE UNIQUE RANDOM**************************************************
*************************************************************************************************************/
/*************************************************************************************************************
************************************CLASSE ROTARY ENCODER*****************************************************
mais informações: http://fabianoallex.blogspot.com.br/2016/05/arduino-rotary-encoder.html
*************************************************************************************************************/
#define ROTARY_NO_BUTTON 255
struct RotaryEncoderLimits{
int min;
int max;
};
class RotaryEncoder {
private:
byte _pin_clk;
byte _pin_dt;
byte _pin_sw;
volatile byte _num_results;
volatile int _result;
volatile int * _results;
byte _index_result;
RotaryEncoderLimits * _limits;
byte _a : 1;
byte _b : 1;
public:
RotaryEncoder(byte pin_clk, byte pin_dt, byte pin_sw = ROTARY_NO_BUTTON, byte num_results=1, RotaryEncoderLimits * limits=0){ //parametro do botao opcional
_pin_clk = pin_clk;
_pin_dt = pin_dt;
_pin_sw = pin_sw;
pinMode(_pin_clk, INPUT);
pinMode(_pin_dt, INPUT);
if (_pin_sw != ROTARY_NO_BUTTON){
pinMode(_pin_sw, INPUT);
digitalWrite(_pin_sw, HIGH);
}
if (num_results == 0) { num_results = 1; }
_num_results = num_results;
_results = new int[_num_results];
for (int i; i<_num_results; i++){ _results[i] = (limits) ? limits[i].min : 0; }
_index_result = 0;
_limits = limits;
_a = false;
_b = false;
}
byte getIndex() { return _index_result; }
void next() { _index_result++; if (_index_result >= _num_results) { _index_result = 0; } }
void update_a() {
_result = 0;
delay (1);
if( digitalRead(_pin_clk) != _a ) {
_a = !_a;
if ( _a && !_b ) { _result = -1; }
}
if (_results[_index_result]+_result >= _limits[_index_result].min &&
_results[_index_result]+_result <= _limits[_index_result].max ) {
_results[_index_result] += _result;
}
}
void update_b() {
_result = 0;
delay (1);
if( digitalRead(_pin_dt) != _b ) {
_b = !_b;
if ( _b && !_a ) { _result = +1; }
}
if (_results[_index_result]+_result >= _limits[_index_result].min &&
_results[_index_result]+_result <= _limits[_index_result].max ) {
_results[_index_result] += _result;
}
}
int read(){ return _result; } //retorn -1, 0 ou 1.
int getValue(int index=-1) { //retorna o valor da variável corrente ou a passada como parametro
if (index < 0 ){ return _results[_index_result]; }
return _results[index];
}
void setValue(int value, int index=-1){ _results[ (index==-1) ? _index_result : index] = value; } //caso a variável inicializa em determinado valor diferente de zero, utilizar esse método.
int buttonRead(){ return (_pin_sw == ROTARY_NO_BUTTON) ? LOW : digitalRead(_pin_sw); }
};
/*************************************************************************************************************
************************************FIM CLASSE ROTARY ENCODER*************************************************
*************************************************************************************************************/
/*************************************************************************************************************
*******************************CLASSES TETRIS GAME************************************************************
**************************************************************************************************************/
const int TETRIS_TIME_INIT = 700; //tempo entre deslocamento dos inimigos
const int TETRIS_TIME_INC = 20; //incremento da velocidade
enum Direction { DIR_STOP, DIR_TOP, DIR_LEFT, DIR_BOTTOM, DIR_RIGHT};
enum TetrisGameStatus { TETRIS_GAME_ON, TETRIS_GAME_OVER };
enum TetrisBlockTypes { TETRIS_I, TETRIS_J, TETRIS_L, TETRIS_O, TETRIS_S, TETRIS_Z, TETRIS_T, TETRIS_COUNT_BTYPES};
struct Position { int row, column; };
struct Block { byte row0:4; byte row1:4; byte row2:4; byte row3:4; }; //4 bit cada linha
//blocos e suas rotacoes
Block block_i[] = { {B1111, B0000, B0000, B0000}, {B0001, B0001, B0001, B0001} };
Block block_j[] = { {B0000, B0010, B0010, B0011}, {B0000, B0000, B0001, B0111}, {B0000, B0110, B0010, B0010}, {B0000, B0000, B0111, B0100} };
Block block_l[] = { {B0000, B0010, B0010, B0110}, {B0000, B0000, B0100, B0111}, {B0000, B0011, B0010, B0010}, {B0000, B0000, B0111, B0001} };
Block block_o[] = { {B0000, B0000, B0110, B0110} };
Block block_s[] = { {B0000, B0001, B0011, B0010}, {B0000, B0000, B0110, B0011} };
Block block_z[] = { {B0000, B0000, B0110, B1100}, {B0000, B1000, B1100, B0100} };
Block block_t[] = { {B0000, B0000, B0010, B0111}, {B0000, B0010, B0011, B0010}, {B0000, B0000, B0111, B0010}, {B0000, B0001, B0011, B0001} };
class TetrisBlock{
private:
Block _block; //bloco que vai descendo. player
Position _position; //posição do bloco na tela. pode assumir posição negativa
TetrisBlockTypes _blockType;
byte _blockTypeRotation;
public:
Position getPosition() { return _position; }
byte getRow(int row) {
if (row == 0 ) return _block.row0;
if (row == 1 ) return _block.row1;
if (row == 2 ) return _block.row2;
if (row == 3 ) return _block.row3;
return 0;
}
byte getColumn(int col) {
byte b = 0;
for (int i=0;i<4;i++){ bitWrite(b, 3-i, bitRead(getRow(i), 3-col) ); }
return b;
}
int read(int row, int column){
byte b = getRow(row);
unsigned int bit = column%8;
return (b & (1 << bit)) != 0;
}
void left() { _position.column--; }
void right() { _position.column++; }
void bottom() { _position.row++; }
void top() { _position.row--; }
void generateBlock(byte col) {
_position.row = 0;
_position.column = col;
_blockType = (TetrisBlockTypes) random(0, TETRIS_COUNT_BTYPES);
_blockTypeRotation = 0;
if ( _blockType == TETRIS_I ){ _block = block_i[_blockTypeRotation]; }
if ( _blockType == TETRIS_J ){ _block = block_j[_blockTypeRotation]; }
if ( _blockType == TETRIS_L ){ _block = block_l[_blockTypeRotation]; }
if ( _blockType == TETRIS_O ){ _block = block_o[_blockTypeRotation]; }
if ( _blockType == TETRIS_S ){ _block = block_s[_blockTypeRotation]; }
if ( _blockType == TETRIS_Z ){ _block = block_z[_blockTypeRotation]; }
if ( _blockType == TETRIS_T ){ _block = block_t[_blockTypeRotation]; }
if (!getRow(0)) { _position.row--;
if (!getRow(1)) { _position.row--;
if (!getRow(2)) { _position.row--; } } }
}
void rotate(int direction = 1){
int maxPos = 0;
if ( _blockType == TETRIS_I ){ maxPos = sizeof(block_i)/sizeof(Block); }
if ( _blockType == TETRIS_J ){ maxPos = sizeof(block_j)/sizeof(Block); }
if ( _blockType == TETRIS_L ){ maxPos = sizeof(block_l)/sizeof(Block); }
if ( _blockType == TETRIS_O ){ maxPos = sizeof(block_o)/sizeof(Block); }
if ( _blockType == TETRIS_S ){ maxPos = sizeof(block_s)/sizeof(Block); }
if ( _blockType == TETRIS_Z ){ maxPos = sizeof(block_z)/sizeof(Block); }
if ( _blockType == TETRIS_T ){ maxPos = sizeof(block_t)/sizeof(Block); }
(direction==1) ? _blockTypeRotation++ : _blockTypeRotation--;
if (_blockTypeRotation == 255) { _blockTypeRotation = maxPos-1; }
if (_blockTypeRotation >= maxPos) { _blockTypeRotation = 0; }
if ( _blockType == TETRIS_I ){ _block = block_i[_blockTypeRotation]; }
if ( _blockType == TETRIS_J ){ _block = block_j[_blockTypeRotation]; }
if ( _blockType == TETRIS_L ){ _block = block_l[_blockTypeRotation]; }
if ( _blockType == TETRIS_O ){ _block = block_o[_blockTypeRotation]; }
if ( _blockType == TETRIS_S ){ _block = block_s[_blockTypeRotation]; }
if ( _blockType == TETRIS_Z ){ _block = block_z[_blockTypeRotation]; }
if ( _blockType == TETRIS_T ){ _block = block_t[_blockTypeRotation]; }
}
};
class TetrisGame {
private:
BitArray2D * _display; //display com todos os objetos da tela. recebido como parâmetro
BitArray2D * _recipient; //recepiente onde os blocos vão se acumulando. criado internamente pela classe
TetrisBlock * _block;
Direction _direction;
TetrisGameStatus _tetrisGameStatus;
unsigned long _last_millis;
unsigned long _last_millis_player;
int _time;
int _score;
byte _fast : 1; //acelerar a descida
UniqueRandom * _ur; //utilizado no game over
void _gameOver(){
_tetrisGameStatus = TETRIS_GAME_OVER;
_direction = DIR_STOP;
_time = 20;
}
void _inc_speed(){
_score++;
_time -= TETRIS_TIME_INC;
}
void _start(){
_recipient->clear();
_last_millis = 0;
_score = 0;
_time = TETRIS_TIME_INIT;
_fast = 0;
_direction = DIR_STOP;
_tetrisGameStatus = TETRIS_GAME_ON;
_newBlock();
}
void _newBlock() {
_block->generateBlock( _display->columns() / 2 - 1 );
if (_testCollision()){ _gameOver(); }
}
void _updateDisplay() {
if (_tetrisGameStatus == TETRIS_GAME_ON) { _display->clear(); }
for (int r=0; r<4; r++) {
for (int c=0; c<4; c++) {
if ( _block->read(r, c) ) {
_display->write(_block->getPosition().row+r, _block->getPosition().column+c, HIGH );
}
}
}
for (int row=0; row<_display->rows(); row++) {
for (int col=0; col<_display->columns(); col++) {
boolean val = _recipient->read(row, col);
if (val) { _display->write(row, col, val ); }
}
}
}
boolean _testCollision(){
int col = _block->getPosition().column; //coluna do bloco em relacao ao recepiente. pode ser negativa
int row = _block->getPosition().row; //linha do bloco em relacao ao recepiente. pode ser negativa
for (byte c=0; c<4; c++){
for (byte r=0; r<4; r++){
if (! _block->read(r, c) ) { continue; }
if ( row+r < 0 ) {return true;}
if ( row+r >= _display->rows() ) {return true;}
if ( col+c < 0 ) {return true;}
if ( col+c >= _display->columns() ) {return true;}
if ( _recipient->read(row+r, col+c) ) {return true;}
}
}
return false;
}
boolean _canLeft(){
_block->left();
boolean r = ! _testCollision();
_block->right();
return r;
}
boolean _canRight(){
_block->right();
boolean r = ! _testCollision();
_block->left();
return r;
}
boolean _canBottom(){
_block->bottom();
boolean r = ! _testCollision();
_block->top();
return r;
}
boolean _canRotate(){
_block->rotate();
boolean r = ! _testCollision();
_block->rotate(-1);
return r;
}
void _blockToRecipient(){
for (byte c=0; c<4; c++){
for (byte r=0; r<4; r++){
if ( _block->read(r, c) ){ _recipient->write(_block->getPosition().row+r, _block->getPosition().column+c, HIGH); }
}
}
_verifyCompleteRow();
_newBlock();
}
void _verifyCompleteRow(){
for (int row=0; row<_recipient->rows(); row++) {
boolean complete = true;
for (int col=0; col<_recipient->columns(); col++) {
if (! _recipient->read(row, col) ) { complete = false; break; }
}
if (complete){
_incScore();
//move as linhas pra baixo
for (int row2 = row; row2>=0; row2--) {
for (int col2=0; col2<_recipient->columns(); col2++){
_recipient->write(row2, col2, (row2>0) ? _recipient->read(row2-1, col2) : 0 );
}
}
}
}
}
void _incScore(){
_score++;
_time -= TETRIS_TIME_INC;
}
void _runGameOver(){
int r = _ur->next();
int row = (r / _display->columns());
int col = (r % _display->columns());
_display->write(row, col, HIGH );
if ( _ur->getIndex() == (_ur->size()-1) ) {
_ur->randomize();
_ur->first();
_start();
}
}
void _runPlayer() {
if (_direction == DIR_LEFT && _canLeft() ) { _block->left(); }
if (_direction == DIR_RIGHT && _canRight() ) { _block->right(); }
_direction = DIR_STOP;
}
void _runBlock() {
_canBottom() ? _block->bottom() : _blockToRecipient() ;
_direction = DIR_STOP;
}
public:
TetrisGame(BitArray2D * display){
_display = display;
_recipient = new BitArray2D( _display->rows(), _display->columns() );
_ur = new UniqueRandom( _display->rows() * _display->columns() );
_block = new TetrisBlock();
_start();
}
void fast() { _fast = 1; } //acelerar o deslocamento
void noFast() { _fast = 0; } //descida do bloco na velocidade normal
void left() { _direction = DIR_LEFT; }
void right() { _direction = DIR_RIGHT; }
void bottom() { _direction = DIR_BOTTOM; }
void rotate() { if (_canRotate()) { _block->rotate(); } }
int getScore(){ return _score; }
Position getPosition() { return _block->getPosition(); }
int update(){
int r = false;
if (millis() - _last_millis > ( (_fast==1) ? 50 : _time ) ) {
r = true;
_last_millis = millis();
if (_tetrisGameStatus == TETRIS_GAME_ON) { _runBlock(); }
if (_tetrisGameStatus == TETRIS_GAME_OVER) { _runGameOver(); }
}
if (millis() - _last_millis_player > 70 ) { //atualiza os movimentos do player a cada 70ms
r = true;
_last_millis_player = millis();
if (_tetrisGameStatus == TETRIS_GAME_ON) { _runPlayer(); }
}
if (r) { _updateDisplay(); }
return r; //r-->indica se houve mudança no display
}
};
/*************************************************************************************************************
*******************************FIM CLASSES TETRIS GAME********************************************************
**************************************************************************************************************/
/*************************************************************************************************************
*******************************DISPLAY************************************************************************
**************************************************************************************************************/
/*
rotation indica qual parte do display estará para cima
TOP
L . . . . . . . . R
E . . . . . . . . I
F . . . . . . . . G
T . . . . . . . . H
. . . . . . . . T
. . . . . . . .
. . . . . . . .
. . . . . . . .
BOTTOM
*/
enum Rotation {TOP, LEFT, BOTTOM, RIGHT};
struct Display {
int index;
Position position;
Rotation rotation;
};
/*************************************************************************************************************
*******************************FIM DISPLAY********************************************************************
**************************************************************************************************************/
/*************************************************************************************************************
*******************************CLASSE GAME LCD****************************************************************
**************************************************************************************************************/
/*
0 1 2 3 4 5 6
*** *** *** ***
*** *** *** ***
*** *** *** ***
*** *** *** ***
*** *** *** ***
*** *** *** ***
*/
byte c0[8] = {B11111, B11111, B00000, B11111, B11111, B00000, B11111, B11111 };
byte c1[8] = {B00000, B00000, B00000, B11111, B11111, B00000, B11111, B11111 };
byte c2[8] = {B00000, B00000, B00000, B00000, B00000, B00000, B11111, B11111 };
byte c3[8] = {B11111, B11111, B00000, B11111, B11111, B00000, B00000, B00000 };
byte c4[8] = {B11111, B11111, B00000, B00000, B00000, B00000, B11111, B11111 };
byte c5[8] = {B00000, B00000, B00000, B11111, B11111, B00000, B00000, B00000 };
byte c6[8] = {B11111, B11111, B00000, B00000, B00000, B00000, B00000, B00000 };
class GameLCD {
private:
LiquidCrystal * _lcd; //ponteiro para um objeto lcd
public:
void createChars() {
_lcd->createChar(0, c0);
_lcd->createChar(1, c1);
_lcd->createChar(2, c2);
_lcd->createChar(3, c3);
_lcd->createChar(4, c4);
_lcd->createChar(5, c5);
_lcd->createChar(6, c6);
}
GameLCD(LiquidCrystal * lcd) { _lcd = lcd; }
void write(byte col, byte row, byte val){
_lcd->setCursor(col, row);
if (val == B000) { _lcd->print(" "); }
if (val == B111) { _lcd->write((byte)0); }
if (val == B011) { _lcd->write((byte)1); }
if (val == B001) { _lcd->write((byte)2); }
if (val == B110) { _lcd->write((byte)3); }
if (val == B101) { _lcd->write((byte)4); }
if (val == B010) { _lcd->write((byte)5); }
if (val == B100) { _lcd->write((byte)6); }
}
};
/*************************************************************************************************************
*******************************FIM CLASSE GAME LCD***********************************************************
**************************************************************************************************************/
/*************************************************************************************************************
*******************************DECLARACAO DOS OBJETOS*********************************************************
**************************************************************************************************************/
/*
tamanho display real
* * * * * * * * * * * * * * * *
* * * * * * * * * * * * * * * *
* * * * * * * * * * * * * * * *
* * * * * * * * * * * * * * * *
* * * * * * * * * * * * * * * *
* * * * * * * * * * * * * * * *
* * * * * * * * * * * * * * * *
* * * * * * * * * * * * * * * *
*/
const int LINHAS = 16;
const int COLUNAS = 6;
LiquidCrystal lcd(12, 11, 10, 9, 8, 7);
GameLCD gameLcd(&lcd);
BitArray2D ba(LINHAS, COLUNAS); //8 linhas e 16 colunas, usada para armazenar o estado do display
TetrisGame tetris(&ba);
RotaryEncoderLimits lim[] = { {-1000,1000} }; //limites máximos e mínimos que as variaveis podem atingir
RotaryEncoder re(A0, A1, 4, 1, lim); //pino clk, pino dt, pino sw, qtd variaveis, limites
/*************************************************************************************************************
*******************************FIM DECLARACAO DOS OBJETOS*****************************************************
**************************************************************************************************************/
/*************************************************************************************************************
*******************************TRATAMENTO DAS INTERRUPÇÕES****************************************************
**************************************************************************************************************/
//interrupções dos pinos A0 e A1 via Pin Change Interrupt
ISR(PCINT1_vect) {
volatile static byte lastVal_a0 = LOW;
volatile static byte lastVal_a1 = LOW;
byte val_a0 = digitalRead(A0);
byte val_a1 = digitalRead(A1);
if (lastVal_a0 != val_a0){ re.update_a(); lastVal_a0 = val_a0; }
if (lastVal_a1 != val_a1){ re.update_b(); lastVal_a1 = val_a1; }
}
void setup_interrupts(){
//-----PCI - Pin Change Interrupt ----
pinMode(A0,INPUT); // set Pin as Input (default)
digitalWrite(A0,HIGH); // enable pullup resistor
pinMode(A1,INPUT); // set Pin as Input (default)
digitalWrite(A1,HIGH); // enable pullup resistor
cli();
PCICR |= 0b00000010; // habilita a porta C - Pin Change Interrupts
PCMSK1 |= 0b00000011; // habilita interrupção da porta c nos pinos: PCINT8 (A0) e PCINT9(A1)
sei();
}
/*************************************************************************************************************
*******************************FIM TRATAMENTO DAS INTERRUPÇÕES************************************************
**************************************************************************************************************/
void update_display() {
for (int lin=ba.rows()-1; lin>=0; lin--) {
byte lin_lcd = 0;
byte cont = 0;
byte val = 0;
for (int col=0; col<ba.columns(); col++) {
if (cont == 0){ val = 0; }
val = val << 1;
val = val | ba.read(lin, col);
cont++;
if (cont == 3) {
gameLcd.write(LINHAS-lin-1, lin_lcd, val) ;
cont = 0;
lin_lcd++;
}
}
}
}
void setup() {
setup_interrupts();
lcd.begin(16, 2);
gameLcd.createChars();
randomSeed(analogRead(A2));
re.setValue(0, 0); //inicializa o rotary em 0
}
void loop() {
static int val_encoder = 0;
static boolean change = false;
if (re.getValue(0) < val_encoder && !change){ tetris.left(); change = true; } //sentido anti-horario move para esquerda
if (re.getValue(0) > val_encoder && !change){ tetris.right(); change = true; } //sentido horario move para direita
val_encoder = re.getValue(0);
if ( tetris.update() ) {
change = false;
update_display();
}
//controla o click do botao do enconder
//clique curto --> rotate
//clique longo --> fast
static byte lastb = HIGH; //leitura anterior do botão
static unsigned long m_pressed = 0; //millis na borda de subida do botão
int b = re.buttonRead();
if( !b && lastb ) { //borda de subida
m_pressed = millis();
delay(70);
}
if( b && !lastb ) { //borda de descida
if (millis() - m_pressed < 350) {
tetris.rotate();
m_pressed = millis();
}
}
if( !b && millis()-m_pressed > 350){
tetris.fast();
} else {
tetris.noFast();
}
lastb = b;
}
</pre>
<br />
<br />Fabiano Arndthttp://www.blogger.com/profile/15268923442674744187noreply@blogger.com3tag:blogger.com,1999:blog-5513833072608837410.post-32052700768670304702016-06-10T08:22:00.000-04:002016-06-16T10:13:50.250-04:00Arduino - Wall Defender Game com Display LCD (e com matriz de led) e Rotary EncoderMais um jogo no Arduino em display LCD. Agora foi uma versão do Jogo Wall Defender do Atari 2600. Lembra dele?<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhV7oQagjSii8ft4r4S7ahh5fNsM4-zE3iW-jtfEcERVmd8o8Ss5PNIvDtezBsXTA_TR1nu9RnW4eyje6HF7GcSwZHQK43QKtW9eemCBZB60xbnEU8IF9W8sDuWzcmKMJShkKuLBGW9pZc/s1600/wall.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="240" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhV7oQagjSii8ft4r4S7ahh5fNsM4-zE3iW-jtfEcERVmd8o8Ss5PNIvDtezBsXTA_TR1nu9RnW4eyje6HF7GcSwZHQK43QKtW9eemCBZB60xbnEU8IF9W8sDuWzcmKMJShkKuLBGW9pZc/s320/wall.jpg" width="320" /></a></div>
<br />
<br />
Claro que é uma versão bem mais simples. Mas a ideia é ser simples mesmo.<br />
<br />
<br />
Veja outros jogos que já implementei:<br />
<br />
Snake Game: <a href="http://fabianoallex.blogspot.com.br/2016/05/arduino-snake-game-com-display-lcd-e.html">http://fabianoallex.blogspot.com.br/2016/05/arduino-snake-game-com-display-lcd-e.html</a><br />
Race Game: <a href="http://fabianoallex.blogspot.com.br/2016/06/arduino-race-game-com-display-lcd.html">http://fabianoallex.blogspot.com.br/2016/06/arduino-race-game-com-display-lcd.html</a><br />
<br />
<b>Vídeo</b><br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<iframe allowfullscreen="" class="YOUTUBE-iframe-video" data-thumbnail-src="https://i9.ytimg.com/vi/7VWK050VXa4/default.jpg?sqp=CJjc6roF&rs=AOn4CLCdPFcRlgAi0rJ58On4u8FAdpeUAA" frameborder="0" height="415" src="https://www.youtube.com/embed/7VWK050VXa4?feature=player_embedded" width="660"></iframe></div>
<br />
<br />
<b>Código-Fonte:</b><br />
<br />
<pre class="brush: html" style="background-color: #eeeeee; border: 1px dashed rgb(153, 153, 153); overflow: auto; padding: 5px; width: 100%;">/*
Fabiano A. Arndt - 2016
www.youtube.com/user/fabianoallex
www.facebook.com/dicasarduino
fabianoallex@gmail.com
*/
#include <LiquidCrystal.h>
/*************************************************************************************************************
*******************************BIT ARRAY *********************************************************************
**************************************************************************************************************
mais informações aqui: http://fabianoallex.blogspot.com.br/2015/09/arduino-array-de-bits.html
**************************************************************************************************************/
class BitArray2D {
private:
unsigned int _rows;
unsigned int _columns;
unsigned int _cols_array; //pra cada 8 colunas, 1 byte é usado
byte** _bits;
public:
BitArray2D(unsigned int rows, unsigned int columns){
_rows = rows;
_columns = columns;
_cols_array = columns/8 + (_columns%8 ? 1 : 0) + 1; //divide por 8 o número de colunas
_bits = (byte **)malloc(_rows * sizeof(byte *));
for(int i=0;i<_rows;i++){ _bits[i] = (byte *)malloc( _cols_array * sizeof(byte)); } //cria varios arrays
clear();
}
unsigned int rows(){ return _rows; }
unsigned int columns(){ return _columns; }
void clear() {
for(int i=0;i<_rows;i++){
for(int j=0; j<_cols_array;j++) { _bits[i][j] = B00000000; }
}
}
void write(unsigned int row, unsigned int column, int value){
byte b = _bits[row][ column/8 + (column%8 ? 1 : 0) ];
unsigned int bit = column%8;
if (value) { b |= (1 << bit); } else { b &= ~(1 << bit); }
_bits[row][ column/8 + (column%8 ? 1 : 0) ] = b;
}
void write(byte value){
for(int i=0;i<_rows;i++){
for(int j=0; j<_cols_array;j++) {
_bits[i][j] = value ? B11111111 : B00000000;
}
}
}
int read(unsigned int row, unsigned int column){
byte b = _bits[row][ column/8 + (column%8 ? 1 : 0) ];
unsigned int bit = column%8;
return (b & (1 << bit)) != 0;
}
void toggle(unsigned int row, unsigned int column){ write(row, column, !read(row, column)); }
void toggle(){ for(int i=0;i<_rows;i++){ for(int j=0; j<_columns;j++) { toggle(i,j); } } }
};
/*************************************************************************************************************
*******************************FIM BIT ARRAY *****************************************************************
**************************************************************************************************************/
/*************************************************************************************************************
************************************CLASSE UNIQUE RANDOM******************************************************
**************************************************************************************************************
mais informações aqui: http://fabianoallex.blogspot.com.br/2015/09/arduino-numeros-aleatorio-repetidos-e.html
*************************************************************************************************************/
class UniqueRandom{
private:
int _index;
int _min;
int _max;
int _size;
int* _list;
void _init(int min, int max) {
_list = 0;
if (min < max) { _min = min; _max = max; } else { _min = max; _max = min; }
_size = _max - _min;
_index = 0;
}
public:
UniqueRandom(int max) { _init(0, max); randomize(); } //construtor com 1 parametro
UniqueRandom(int min, int max) { _init(min, max); randomize(); } //construtor com 2 parametros
void randomize() {
_index = 0;
if (_list == 0) { _list = (int*) malloc(size() * sizeof(int)); }
for (int i=0; i<size(); i++) { _list[i] = _min+i; } //preenche a lista do menor ao maior valor
//embaralha a lista
for (int i=0; i<size(); i++) {
int r = random(0, size()); //sorteia uma posição qualquer
int aux = _list[i];
_list[i] = _list[r];
_list[r] = aux;
}
}
int next() { //retorna o proximo numero da lista
int n = _list[_index++];
if (_index >= size() ) { _index = 0;} //após recuper o ultimo numero, recomeça na posicao 0
return n;
}
void first() { _index = 0; }
boolean eof() { return size() == _index+1; }
int size() { return _size; }
int getIndex() { return _index; }
~UniqueRandom(){ free ( _list ); } //destrutor
};
/*************************************************************************************************************
************************************FIM CLASSE UNIQUE RANDOM**************************************************
*************************************************************************************************************/
/*************************************************************************************************************
*******************************CLASSE WALL DEFENDER GAME******************************************************
**************************************************************************************************************/
struct Position {
byte lin : 4; //maximo 15
byte col : 4; //maximo 15, mas será no máximo 6 EM DISPLAY 16X2 E TERÁ 12 EM DISPLAY 20X4. PARA MAIS DE 15 LINHAS. ALTERAR AQUI
};
const int WALL_DEFENDER_MAX_WALL_HIGH = 10; //quantidade maxima de obstáculos na tela
const int WALL_DEFENDER_MAX_ENEMY = 10; //quantidade maxima de obstáculos na tela
const int WALL_DEFENDER_TIME_INIT = 700; //tempo entre deslocamento dos obstáculos
const int WALL_DEFENDER_TIME_INC = 10; //incremento da velocidade
enum WallDefenderGameStatus { WALL_GAME_ON, WALL_GAME_OVER };
enum WallShootStatus {WALL_SHOOT_LEFT, WALL_SHOOT_RIGHT, WALL_SHOOT_STOP};
enum Direction { DIR_STOP, DIR_TOP, DIR_LEFT, DIR_BOTTOM, DIR_RIGHT};
struct Enemy{
Position pos;
Direction dir;
};
class WallDefenderGame {
private:
BitArray2D * _display;
Position _position;
Position _shoot;
WallShootStatus _shootStatus;
Position _wall[WALL_DEFENDER_MAX_WALL_HIGH];
Enemy _enemys[WALL_DEFENDER_MAX_ENEMY];
byte _lengthWall;
byte _controlEnemys : 2; //máximo = 3 B11
byte _countUpdate : 3; //maximo = 5 B101
Direction _direction;
unsigned long _last_millis;
unsigned long _last_millis_player;
unsigned long _last_millis_shoot;
int _time;
int _score;
int _lengthEnemy;
WallDefenderGameStatus _wallDefenderGameStatus;
UniqueRandom * _ur; //utilizado no game over
void _gameOver(){
_wallDefenderGameStatus = WALL_GAME_OVER;
_direction = DIR_STOP;
_time = 20;
}
void _inc_speed(){
_score++;
_time -= WALL_DEFENDER_TIME_INC;
}
void _generateWall() {
_lengthWall = 0;
for (int i=0;i<(_display->rows()-2)*2;i++) {
_wall[i].lin = i<(_display->rows()-2) ? i+1 : i+1-_display->rows()+2;
_wall[i].col = i<(_display->rows()-2) ? _display->columns()/2-1 : _display->columns()/2;
_lengthWall++;
}
}
void _start(){
for (int i=0; i<WALL_DEFENDER_MAX_ENEMY; i++){
_enemys[i].dir == DIR_STOP;
_enemys[i].pos.lin=0;
_enemys[i].pos.col=0;
}
_score = 0;
_time = WALL_DEFENDER_TIME_INIT;
_last_millis = 0;
_last_millis_player = 0;
_position.lin = 0;
_position.col = _display->columns() / 2 + 1;
_direction = DIR_STOP;
_wallDefenderGameStatus = WALL_GAME_ON;
_shootStatus = WALL_SHOOT_STOP;
_generateWall();
}
void _generateEnemys() {
Serial.println(_lengthEnemy);
if (_controlEnemys == 0 && _lengthEnemy < WALL_DEFENDER_MAX_ENEMY){
_lengthEnemy++;
int r1 = random(0, _display->rows());
int r2 = random(0, 2);
for (int i=0; i<WALL_DEFENDER_MAX_ENEMY; i++){
if (_enemys[i].dir == DIR_STOP){
_enemys[i].dir = (r2 == 0) ? DIR_RIGHT : DIR_LEFT;
_enemys[i].pos.col = (r2 == 0) ? 0 : _display->columns()-1;
_enemys[i].pos.lin = r1;
break;
}
}
}
_controlEnemys = (_controlEnemys < 2) ? _controlEnemys+1 : 0; //a cada 4 atualizacoes gera novos obstáculos
}
void _runShoot(){
if (_shootStatus == WALL_SHOOT_LEFT ) { if (_shoot.col == 0) { _shootStatus = WALL_SHOOT_STOP; } else { _shoot.col--; } }
if (_shootStatus == WALL_SHOOT_RIGHT) { if (_shoot.col == _display->columns()-1) { _shootStatus = WALL_SHOOT_STOP; } else { _shoot.col++; } }
}
void _runGameOver(){
int r = _ur->next();
int lin = (r / _display->columns());
int col = (r % _display->columns());
_display->write(lin, col, HIGH );
if ( _ur->getIndex() == (_ur->size()-1) ) {
_ur->randomize();
_ur->first();
_start();
Serial.println("teste");
}
}
void _runPlayer(){
if (_direction == DIR_TOP && _position.lin > 0 ) { _position.lin--; }
if (_direction == DIR_BOTTOM && _position.lin < _display->rows()-1 ) { _position.lin++; }
if (_direction == DIR_LEFT && _position.col > 0 ) { _position.col--; }
if (_direction == DIR_RIGHT && _position.col < _display->columns()-1 ) { _position.col++; }
_direction = DIR_STOP; //depois de mover, mantem parado.
}
void _runEnemys() {
/*move os inimigos*/
byte contRemove = 0;
for (int i=0; i<WALL_DEFENDER_MAX_ENEMY; i++) {
if (_enemys[i].dir == DIR_RIGHT && _enemys[i].pos.col == _display->columns()-1){ contRemove++; _enemys[i].dir = DIR_STOP; }
if (_enemys[i].dir == DIR_LEFT && _enemys[i].pos.col == 0) { contRemove++; _enemys[i].dir = DIR_STOP; }
if (_enemys[i].dir != DIR_STOP ) { _enemys[i].dir == DIR_LEFT ? _enemys[i].pos.col-- : _enemys[i].pos.col++; }
}
if (contRemove > 0){ _lengthEnemy -= contRemove; }
_generateEnemys();
_countUpdate++;
if (_countUpdate >= 5){
_inc_speed();
_countUpdate = 0;
}
}
void _updateDisplay(){
//verifica se o tiro colidiu com o inimigo
for (int i=0; i<WALL_DEFENDER_MAX_ENEMY; i++){
if (_enemys[i].dir != DIR_STOP ) {
if (_enemys[i].pos.lin == _shoot.lin && _enemys[i].pos.col == _shoot.col) { _lengthEnemy--; _enemys[i].dir = DIR_STOP; _shootStatus = WALL_SHOOT_STOP;}
if (_enemys[i].pos.lin == _position.lin && _enemys[i].pos.col == _position.col) { _gameOver(); }
}
}
//verifica se colidiu na parede
for (int i=0; i<WALL_DEFENDER_MAX_ENEMY; i++){
for (int p=0; p<_lengthWall; p++) {
if (_enemys[i].dir != DIR_STOP ) {
if (_enemys[i].pos.lin == _wall[p].lin && _enemys[i].pos.col == _wall[p].col) { _gameOver(); }
}
}
}
if (_wallDefenderGameStatus == WALL_GAME_ON) { _display->clear(); }
for (int lin=0; lin<_display->rows(); lin++) {
for (int col=0; col<_display->columns(); col++) {
for (int p=0; p<_lengthWall; p++){
boolean val = _wall[p].col==col && _wall[p].lin==lin;
if (val) { _display->write( lin, col, val ); break;}
}
for (int p=0; p<WALL_DEFENDER_MAX_ENEMY; p++) {
if (_enemys[p].dir != DIR_STOP){
boolean val = _enemys[p].pos.col==col && _enemys[p].pos.lin==lin;
if (val) { _display->write( lin, col, val ); break;}
}
}
}
}
_display->write(_position.lin, _position.col, HIGH);
if (_shootStatus != WALL_SHOOT_STOP) { _display->write(_shoot.lin, _shoot.col, HIGH); }
}
public:
WallDefenderGame(BitArray2D * display){
_display = display;
_ur = new UniqueRandom( _display->rows() * _display->columns() );
_lengthWall = 0;
_lengthEnemy = 0;
_start();
}
void left() { _direction = DIR_LEFT; }
void right() { _direction = DIR_RIGHT; }
void top() { _direction = DIR_TOP; }
void bottom() { _direction = DIR_BOTTOM; }
int getScore(){ return _score; }
Position getPositionPlayer() { return _position; }
void shoot(){
if (_shootStatus == WALL_SHOOT_STOP){
_shoot.lin = _position.lin;
_shoot.col = _position.col;
_shootStatus = (_shoot.col <= _display->columns() / 2-1) ? WALL_SHOOT_LEFT : WALL_SHOOT_RIGHT;
}
}
int update(){
int r = false;
if (millis() - _last_millis > _time) {
r = true;
_last_millis = millis();
if (_wallDefenderGameStatus == WALL_GAME_ON) { _runEnemys(); }
if (_wallDefenderGameStatus == WALL_GAME_OVER) { _runGameOver(); }
}
if (millis() - _last_millis_player > 70){
r = true;
_last_millis_player = millis();
if (_wallDefenderGameStatus == WALL_GAME_ON) { _runPlayer(); }
if (_wallDefenderGameStatus == WALL_GAME_OVER) { _runGameOver(); }
}
if (millis() - _last_millis_shoot > 50){
r = true;
_last_millis_shoot = millis();
if (_wallDefenderGameStatus == WALL_GAME_ON) { _runShoot(); }
if (_wallDefenderGameStatus == WALL_GAME_OVER) { _runGameOver(); }
}
if (r) { _updateDisplay(); }
return r; //r-->indica se houve mudança no display
}
};
/*************************************************************************************************************
*******************************FIM CLASSE WALL DEFENDER GAME**************************************************
**************************************************************************************************************/
/*************************************************************************************************************
************************************CLASSE ROTARY ENCODER*****************************************************
mais informações: http://fabianoallex.blogspot.com.br/2016/05/arduino-rotary-encoder.html
*************************************************************************************************************/
#define ROTARY_NO_BUTTON 255
struct RotaryEncoderLimits{
int min;
int max;
};
class RotaryEncoder {
private:
byte _pin_clk;
byte _pin_dt;
byte _pin_sw;
volatile byte _num_results;
volatile int _result;
volatile int * _results;
byte _index_result;
RotaryEncoderLimits * _limits;
byte _a : 1;
byte _b : 1;
public:
RotaryEncoder(byte pin_clk, byte pin_dt, byte pin_sw = ROTARY_NO_BUTTON, byte num_results=1, RotaryEncoderLimits * limits=0){ //parametro do botao opcional
_pin_clk = pin_clk;
_pin_dt = pin_dt;
_pin_sw = pin_sw;
pinMode(_pin_clk, INPUT);
pinMode(_pin_dt, INPUT);
if (_pin_sw != ROTARY_NO_BUTTON){
pinMode(_pin_sw, INPUT);
digitalWrite(_pin_sw, HIGH);
}
if (num_results == 0) { num_results = 1; }
_num_results = num_results;
_results = new int[_num_results];
for (int i; i<_num_results; i++){ _results[i] = (limits) ? limits[i].min : 0; }
_index_result = 0;
_limits = limits;
_a = false;
_b = false;
}
byte getIndex() { return _index_result; }
void next() { _index_result++; if (_index_result >= _num_results) { _index_result = 0; } }
void update_a() {
_result = 0;
delay (1);
if( digitalRead(_pin_clk) != _a ) {
_a = !_a;
if ( _a && !_b ) { _result = -1; }
}
if (_results[_index_result]+_result >= _limits[_index_result].min &&
_results[_index_result]+_result <= _limits[_index_result].max ) {
_results[_index_result] += _result;
}
}
void update_b() {
_result = 0;
delay (1);
if( digitalRead(_pin_dt) != _b ) {
_b = !_b;
if ( _b && !_a ) { _result = +1; }
}
if (_results[_index_result]+_result >= _limits[_index_result].min &&
_results[_index_result]+_result <= _limits[_index_result].max ) {
_results[_index_result] += _result;
}
}
int read(){ return _result; } //retorn -1, 0 ou 1.
int getValue(int index=-1) { //retorna o valor da variável corrente ou a passada como parametro
if (index < 0 ){ return _results[_index_result]; }
return _results[index];
}
void setValue(int value, int index=-1){ _results[ (index==-1) ? _index_result : index] = value; } //caso a variável inicializa em determinado valor diferente de zero, utilizar esse método.
int buttonRead(){ return (_pin_sw == ROTARY_NO_BUTTON) ? LOW : digitalRead(_pin_sw); }
};
/*************************************************************************************************************
************************************FIM CLASSE ROTARY ENCODER*************************************************
*************************************************************************************************************/
/*************************************************************************************************************
*******************************CLASSE GAME LCD****************************************************************
**************************************************************************************************************/
/*
0 1 2 3 4 5 6
*** *** *** ***
*** *** *** ***
*** *** *** ***
*** *** *** ***
*** *** *** ***
*** *** *** ***
*/
byte c0[8] = {B11111, B11111, B00000, B11111, B11111, B00000, B11111, B11111 };
byte c1[8] = {B00000, B00000, B00000, B11111, B11111, B00000, B11111, B11111 };
byte c2[8] = {B00000, B00000, B00000, B00000, B00000, B00000, B11111, B11111 };
byte c3[8] = {B11111, B11111, B00000, B11111, B11111, B00000, B00000, B00000 };
byte c4[8] = {B11111, B11111, B00000, B00000, B00000, B00000, B11111, B11111 };
byte c5[8] = {B00000, B00000, B00000, B11111, B11111, B00000, B00000, B00000 };
byte c6[8] = {B11111, B11111, B00000, B00000, B00000, B00000, B00000, B00000 };
class GameLCD {
private:
LiquidCrystal * _lcd; //ponteiro para um objeto lcd
public:
void createChars() {
_lcd->createChar(0, c0);
_lcd->createChar(1, c1);
_lcd->createChar(2, c2);
_lcd->createChar(3, c3);
_lcd->createChar(4, c4);
_lcd->createChar(5, c5);
_lcd->createChar(6, c6);
}
GameLCD(LiquidCrystal * lcd) { _lcd = lcd; }
void write(byte col, byte row, byte val){
_lcd->setCursor(col, row);
if (val == B000) { _lcd->print(" "); }
if (val == B111) { _lcd->write((byte)0); }
if (val == B011) { _lcd->write((byte)1); }
if (val == B001) { _lcd->write((byte)2); }
if (val == B110) { _lcd->write((byte)3); }
if (val == B101) { _lcd->write((byte)4); }
if (val == B010) { _lcd->write((byte)5); }
if (val == B100) { _lcd->write((byte)6); }
}
};
/*************************************************************************************************************
*******************************FIM CLASSE GAME LCD***********************************************************
**************************************************************************************************************/
/*************************************************************************************************************
*******************************DECLARACAO DOS OBJETOS*********************************************************
**************************************************************************************************************/
/*
tamanho display real
* * * * * * * * * * * * * * * *
* * * * * * * * * * * * * * * *
* * * * * * * * * * * * * * * *
* * * * * * * * * * * * * * * *
* * * * * * * * * * * * * * * *
* * * * * * * * * * * * * * * *
*/
const int LINHAS = 6;
const int COLUNAS = 16;
LiquidCrystal lcd(12, 11, 10, 9, 8, 7);
GameLCD gameLcd(&lcd);
BitArray2D ba(LINHAS, COLUNAS); //8 linhas e 20 colunas, usada para armazenar o estado do display
WallDefenderGame wall(&ba);
RotaryEncoderLimits lim[] = { {-1000,1000} }; //limites máximos e mínimos que as variaveis podem atingir
RotaryEncoder re(A0, A1, 4, 1, lim); //pino clk, pino dt, pino sw, variaveis, limites
/*************************************************************************************************************
*******************************FIM DECLARACAO DOS OBJETOS*****************************************************
**************************************************************************************************************/
/*************************************************************************************************************
*******************************TRATAMENTO DAS INTERRUPÇÕES****************************************************
**************************************************************************************************************/
//interrupções dos pinos A0 e A1 via Pin Change Interrupt
ISR(PCINT1_vect) {
volatile static byte lastVal_a0 = LOW;
volatile static byte lastVal_a1 = LOW;
byte val_a0 = digitalRead(A0);
byte val_a1 = digitalRead(A1);
if (lastVal_a0 != val_a0){ re.update_a(); lastVal_a0 = val_a0; }
if (lastVal_a1 != val_a1){ re.update_b(); lastVal_a1 = val_a1; }
}
void setup_interrupts(){
//-----PCI - Pin Change Interrupt ----
pinMode(A0,INPUT); // set Pin as Input (default)
digitalWrite(A0,HIGH); // enable pullup resistor
pinMode(A1,INPUT); // set Pin as Input (default)
digitalWrite(A1,HIGH); // enable pullup resistor
cli();
PCICR |= 0b00000010; // habilita a porta C - Pin Change Interrupts
PCMSK1 |= 0b00000011; // habilita interrupção da porta c nos pinos: PCINT8 (A0) e PCINT9(A1)
sei();
}
/*************************************************************************************************************
*******************************FIM TRATAMENTO DAS INTERRUPÇÕES****************************************************
**************************************************************************************************************/
void update_display() {
for (int col=0; col<ba.columns(); col++) {
byte lin_lcd = 0;
byte cont = 0;
byte val = 0;
for (int lin=0; lin<ba.rows(); lin++) {
if (cont == 0){ val = 0; }
val = val << 1;
val = val | ba.read(lin, col);
cont++;
if (cont == 3) {
gameLcd.write(col, lin_lcd, val) ;
cont = 0;
lin_lcd++;
}
}
}
}
void setup() {
Serial.begin(9600);
setup_interrupts();
lcd.begin(16, 2);
gameLcd.createChars();
randomSeed(analogRead(A2));
re.setValue(0, 0); //inicializa o rotary em 0
}
void loop() {
static int val_encoder = 0;
static boolean change = false;
//anti-horario
if (re.getValue(0) < val_encoder && !change && wall.getPositionPlayer().lin > 0 && wall.getPositionPlayer().col == 9){ wall.top(); change = true; }
if (re.getValue(0) < val_encoder && !change && wall.getPositionPlayer().lin == 0 && wall.getPositionPlayer().col >= 7){ wall.left(); change = true; }
if (re.getValue(0) < val_encoder && !change && wall.getPositionPlayer().lin < 5 && wall.getPositionPlayer().col == 6){ wall.bottom(); change = true; }
if (re.getValue(0) < val_encoder && !change && wall.getPositionPlayer().lin == 5 && wall.getPositionPlayer().col <= 9){ wall.right(); change = true; }
//horario
if (re.getValue(0) > val_encoder && !change && wall.getPositionPlayer().lin > 0 && wall.getPositionPlayer().col == 6){ wall.top(); change = true; }
if (re.getValue(0) > val_encoder && !change && wall.getPositionPlayer().lin == 5 && wall.getPositionPlayer().col >= 7){ wall.left(); change = true; }
if (re.getValue(0) > val_encoder && !change && wall.getPositionPlayer().lin < 5 && wall.getPositionPlayer().col == 9){ wall.bottom(); change = true; }
if (re.getValue(0) > val_encoder && !change && wall.getPositionPlayer().lin == 0 && wall.getPositionPlayer().col <= 8){ wall.right(); change = true; }
val_encoder = re.getValue(0);
if ( wall.update() ) {
change = false;
update_display();
}
//controla o click do botao do enconder
static byte b = HIGH; //pra ler apenas uma vez o botao ao pressionar
if( re.buttonRead() == LOW && b != re.buttonRead() ) {
wall.shoot(); //dispara o tiro
delay(50); //debounce meia boca
}
b = re.buttonRead();
}
</pre>
<br />
<br />
<b>Atualização 13/06/2016 - Versão com display de leds 8x8</b><br />
<br />
Implementei uma versão em um display mais apropriado, duas matrizes de leds 8x8. Como a implementação do jogo foi feita utilizando conceitos de orientação a objetos, foi fácil fazer esta versão.<br />
<br />
Alguns detalhes sobre a implementação: Para controlar a matriz de leds foi criada uma classe baseada na biblioteca LedControl (na verdade um ctrl+c ctrl+v), mas com algumas modificações, visando deixar a atualização do display mais rápida, mas o código todo está na própria sketch e não em uma biblioteca a parte. Outro detalhe, é que os dois displays, como poder ser visto no vídeo, não estão "alinhados", eles estão cada um posicionado de uma maneira, ou seja, isso significa que foi necessário criar uma rotina para converter linhas e colunas de maneira a mostrar os dados corretamente. Isso foi feito para facilitar o arranjo dos displays da forma mais conveniente.<br />
<br />
<b>Vídeo:</b><br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<iframe allowfullscreen="" class="YOUTUBE-iframe-video" data-thumbnail-src="https://i9.ytimg.com/vi/LBVgnoU8u38/default.jpg?sqp=CKDT-roF&rs=AOn4CLCYRDjfKHBbGqsXe6UejJv-SBavOQ" frameborder="0" height="415" src="https://www.youtube.com/embed/LBVgnoU8u38?feature=player_embedded" width="660"></iframe></div>
<br />
<br />
<b>Código-fonte:</b><br />
<br />
<pre class="brush: html" style="background-color: #eeeeee; border: 1px dashed rgb(153, 153, 153); overflow: auto; padding: 5px; width: 100%;">/*
Fabiano A. Arndt - 2016
www.youtube.com/user/fabianoallex
www.facebook.com/dicasarduino
fabianoallex@gmail.com
*/
/*************************************************************************************************************
*******************************BIT ARRAY *********************************************************************
**************************************************************************************************************
mais informações aqui: http://fabianoallex.blogspot.com.br/2015/09/arduino-array-de-bits.html
**************************************************************************************************************/
class BitArray2D {
private:
unsigned int _rows;
unsigned int _columns;
unsigned int _cols_array; //pra cada 8 colunas, 1 byte é usado
byte** _bits;
public:
BitArray2D(unsigned int rows, unsigned int columns){
_rows = rows;
_columns = columns;
_cols_array = columns/8 + (_columns%8 ? 1 : 0) + 1; //divide por 8 o número de colunas
_bits = (byte **)malloc(_rows * sizeof(byte *));
for(int i=0;i<_rows;i++){ _bits[i] = (byte *)malloc( _cols_array * sizeof(byte)); } //cria varios arrays
clear();
}
unsigned int rows(){ return _rows; }
unsigned int columns(){ return _columns; }
void clear() {
for(int i=0;i<_rows;i++){
for(int j=0; j<_cols_array;j++) { _bits[i][j] = B00000000; }
}
}
void write(unsigned int row, unsigned int column, int value){
byte b = _bits[row][ column/8 + (column%8 ? 1 : 0) ];
unsigned int bit = column%8;
if (value) { b |= (1 << bit); } else { b &= ~(1 << bit); }
_bits[row][ column/8 + (column%8 ? 1 : 0) ] = b;
}
void write(byte value){
for(int i=0;i<_rows;i++){
for(int j=0; j<_cols_array;j++) {
_bits[i][j] = value ? B11111111 : B00000000;
}
}
}
int read(unsigned int row, unsigned int column){
byte b = _bits[row][ column/8 + (column%8 ? 1 : 0) ];
unsigned int bit = column%8;
return (b & (1 << bit)) != 0;
}
void toggle(unsigned int row, unsigned int column){ write(row, column, !read(row, column)); }
void toggle(){ for(int i=0;i<_rows;i++){ for(int j=0; j<_columns;j++) { toggle(i,j); } } }
};
/*************************************************************************************************************
*******************************FIM BIT ARRAY *****************************************************************
**************************************************************************************************************/
/*************************************************************************************************************
************************************CLASSE UNIQUE RANDOM******************************************************
**************************************************************************************************************
mais informações aqui: http://fabianoallex.blogspot.com.br/2015/09/arduino-numeros-aleatorio-repetidos-e.html
*************************************************************************************************************/
class UniqueRandom{
private:
int _index;
int _min;
int _max;
int _size;
int* _list;
void _init(int min, int max) {
_list = 0;
if (min < max) { _min = min; _max = max; } else { _min = max; _max = min; }
_size = _max - _min;
_index = 0;
}
public:
UniqueRandom(int max) { _init(0, max); randomize(); } //construtor com 1 parametro
UniqueRandom(int min, int max) { _init(min, max); randomize(); } //construtor com 2 parametros
void randomize() {
_index = 0;
if (_list == 0) { _list = (int*) malloc(size() * sizeof(int)); }
for (int i=0; i<size(); i++) { _list[i] = _min+i; } //preenche a lista do menor ao maior valor
//embaralha a lista
for (int i=0; i<size(); i++) {
int r = random(0, size()); //sorteia uma posição qualquer
int aux = _list[i];
_list[i] = _list[r];
_list[r] = aux;
}
}
int next() { //retorna o proximo numero da lista
int n = _list[_index++];
if (_index >= size() ) { _index = 0;} //após recuper o ultimo numero, recomeça na posicao 0
return n;
}
void first() { _index = 0; }
boolean eof() { return size() == _index+1; }
int size() { return _size; }
int getIndex() { return _index; }
~UniqueRandom(){ free ( _list ); } //destrutor
};
/*************************************************************************************************************
************************************FIM CLASSE UNIQUE RANDOM**************************************************
*************************************************************************************************************/
/*************************************************************************************************************
*******************************LEDCONTROL ALTERADA************************************************************
mais informações: http://fabianoallex.blogspot.com.br/2015/09/arduino-alteracoes-na-biblioteca.html
**************************************************************************************************************/
//the opcodes for the MAX7221 and MAX7219
#define OP_DECODEMODE 9
#define OP_INTENSITY 10
#define OP_SCANLIMIT 11
#define OP_SHUTDOWN 12
#define OP_DISPLAYTEST 15
class LedControl {
private :
byte spidata[16];
byte * status;
int SPI_MOSI;
int SPI_CLK;
int SPI_CS;
int maxDevices;
int _auto_send;
void spiTransfer(int addr, volatile byte opcode, volatile byte data) {
int offset = addr*2;
int maxbytes = maxDevices*2;
for(int i=0;i<maxbytes;i++) { spidata[i]=(byte)0; }
spidata[offset+1] = opcode;
spidata[offset] = data;
digitalWrite(SPI_CS,LOW);
for(int i=maxbytes;i>0;i--) { shiftOut(SPI_MOSI,SPI_CLK,MSBFIRST,spidata[i-1]); }
digitalWrite(SPI_CS,HIGH);
}
public:
LedControl(int dataPin, int clkPin, int csPin, int numDevices) {
_auto_send = true;
SPI_MOSI = dataPin;
SPI_CLK = clkPin;
SPI_CS = csPin;
maxDevices = numDevices;
pinMode(SPI_MOSI, OUTPUT);
pinMode(SPI_CLK, OUTPUT);
pinMode(SPI_CS, OUTPUT);
digitalWrite(SPI_CS, HIGH);
status = new byte[maxDevices * 8]; //instancia o array de acordo com a quantia de displays usados
for(int i=0;i<maxDevices * 8 ;i++) { status[i]=0x00; }
for(int i=0;i<maxDevices;i++) {
spiTransfer(i, OP_DISPLAYTEST,0);
setScanLimit(i, 7); //scanlimit is set to max on startup
spiTransfer(i, OP_DECODEMODE,0); //decode is done in source
clearDisplay(i);
shutdown(i,true); //we go into shutdown-mode on startup
}
}
void startWrite() { _auto_send = false; };
void send() {
for (int j=0; j<maxDevices; j++) {
int offset = j*8;
for(int i=0;i<8;i++) { spiTransfer(j, i+1, status[offset+i]); }
}
_auto_send = true;
}
int getDeviceCount(){ return maxDevices; }
void shutdown(int addr, bool b){
if(addr<0 || addr>=maxDevices) return;
spiTransfer(addr, OP_SHUTDOWN, b ? 0 : 1);
}
void setScanLimit(int addr, int limit){
if(addr<0 || addr>=maxDevices) return;
if(limit>=0 && limit<8) spiTransfer(addr, OP_SCANLIMIT,limit);
}
void setIntensity(int addr, int intensity) {
if(addr<0 || addr>=maxDevices) { return; }
if(intensity>=0 && intensity<16) { spiTransfer(addr, OP_INTENSITY, intensity); }
}
void clearDisplay(int addr){
if(addr<0 || addr>=maxDevices) return;
int offset = addr*8;
for(int i=0;i<8;i++) {
status[offset+i] = 0;
if (_auto_send) { spiTransfer(addr, i+1, status[offset+i]); }
}
}
void setLed(int addr, int row, int column, boolean state) {
if(addr<0 || addr>=maxDevices) { return; }
if(row<0 || row>7 || column<0 || column>7) { return; }
int offset = addr*8;
byte val = B10000000 >> column;
if(state) { status[offset+row] = status[offset+row] | val; } else { val=~val; status[offset+row] = status[offset+row]&val; }
if (_auto_send) { spiTransfer(addr, row+1, status[offset+row]); }
}
void setRow(int addr, int row, byte value) {
if(addr<0 || addr>=maxDevices) return;
if(row<0 || row>7) return;
int offset = addr*8;
status[offset+row] = value;
if (_auto_send) { spiTransfer(addr, row+1, status[offset+row]); }
}
void setColumn(int addr, int col, byte value) {
if(addr<0 || addr>=maxDevices) return;
if(col<0 || col>7) return;
byte val;
for(int row=0; row<8; row++) {
val=value >> (7-row);
val=val & 0x01;
setLed(addr,row,col,val);
}
}
};
/*************************************************************************************************************
*******************************FIM LEDCONTROL ALTERADA********************************************************
**************************************************************************************************************/
/*************************************************************************************************************
************************************CLASSE ROTARY ENCODER*****************************************************
mais informações: http://fabianoallex.blogspot.com.br/2016/05/arduino-rotary-encoder.html
*************************************************************************************************************/
#define ROTARY_NO_BUTTON 255
struct RotaryEncoderLimits{
int min;
int max;
};
class RotaryEncoder {
private:
byte _pin_clk;
byte _pin_dt;
byte _pin_sw;
volatile byte _num_results;
volatile int _result;
volatile int * _results;
byte _index_result;
RotaryEncoderLimits * _limits;
byte _a : 1;
byte _b : 1;
public:
RotaryEncoder(byte pin_clk, byte pin_dt, byte pin_sw = ROTARY_NO_BUTTON, byte num_results=1, RotaryEncoderLimits * limits=0){ //parametro do botao opcional
_pin_clk = pin_clk;
_pin_dt = pin_dt;
_pin_sw = pin_sw;
pinMode(_pin_clk, INPUT);
pinMode(_pin_dt, INPUT);
if (_pin_sw != ROTARY_NO_BUTTON){
pinMode(_pin_sw, INPUT);
digitalWrite(_pin_sw, HIGH);
}
if (num_results == 0) { num_results = 1; }
_num_results = num_results;
_results = new int[_num_results];
for (int i; i<_num_results; i++){ _results[i] = (limits) ? limits[i].min : 0; }
_index_result = 0;
_limits = limits;
_a = false;
_b = false;
}
byte getIndex() { return _index_result; }
void next() { _index_result++; if (_index_result >= _num_results) { _index_result = 0; } }
void update_a() {
_result = 0;
delay (1);
if( digitalRead(_pin_clk) != _a ) {
_a = !_a;
if ( _a && !_b ) { _result = -1; }
}
if (_results[_index_result]+_result >= _limits[_index_result].min &&
_results[_index_result]+_result <= _limits[_index_result].max ) {
_results[_index_result] += _result;
}
}
void update_b() {
_result = 0;
delay (1);
if( digitalRead(_pin_dt) != _b ) {
_b = !_b;
if ( _b && !_a ) { _result = +1; }
}
if (_results[_index_result]+_result >= _limits[_index_result].min &&
_results[_index_result]+_result <= _limits[_index_result].max ) {
_results[_index_result] += _result;
}
}
int read(){ return _result; } //retorn -1, 0 ou 1.
int getValue(int index=-1) { //retorna o valor da variável corrente ou a passada como parametro
if (index < 0 ){ return _results[_index_result]; }
return _results[index];
}
void setValue(int value, int index=-1){ _results[ (index==-1) ? _index_result : index] = value; } //caso a variável inicializa em determinado valor diferente de zero, utilizar esse método.
int buttonRead(){ return (_pin_sw == ROTARY_NO_BUTTON) ? LOW : digitalRead(_pin_sw); }
};
/*************************************************************************************************************
************************************FIM CLASSE ROTARY ENCODER*************************************************
*************************************************************************************************************/
/*************************************************************************************************************
*******************************CLASSE WALL DEFENDER GAME******************************************************
**************************************************************************************************************/
struct Position {
int lin ; //maximo 15
int col ; //maximo 15, mas será no máximo 6 EM DISPLAY 16X2 E TERÁ 12 EM DISPLAY 20X4. PARA MAIS DE 15 LINHAS. ALTERAR AQUI
};
const int WALL_DEFENDER_MAX_WALL_HIGH = 15; //quantidade maxima de inimigos na tela
const int WALL_DEFENDER_MAX_ENEMY = 10; //quantidade maxima de obstáculos na tela
const int WALL_DEFENDER_TIME_INIT = 700; //tempo entre deslocamento dos inimigos
const int WALL_DEFENDER_TIME_INC = 10; //incremento da velocidade
enum WallDefenderGameStatus { WALL_GAME_ON, WALL_GAME_OVER };
enum WallShootStatus {WALL_SHOOT_LEFT, WALL_SHOOT_RIGHT, WALL_SHOOT_STOP};
enum Direction { DIR_STOP, DIR_TOP, DIR_LEFT, DIR_BOTTOM, DIR_RIGHT};
struct Enemy{
Position pos;
Direction dir;
};
class WallDefenderGame {
private:
BitArray2D * _display;
Position _position;
Position _shoot;
WallShootStatus _shootStatus;
Position _wall[WALL_DEFENDER_MAX_WALL_HIGH];
Enemy _enemys[WALL_DEFENDER_MAX_ENEMY];
byte _lengthWall;
byte _controlEnemys : 2; //máximo = 3 B11
byte _countUpdate : 3; //maximo = 5 B101
Direction _direction;
unsigned long _last_millis;
unsigned long _last_millis_player;
unsigned long _last_millis_shoot;
int _time;
int _score;
int _lengthEnemy;
WallDefenderGameStatus _wallDefenderGameStatus;
UniqueRandom * _ur; //utilizado no game over
void _gameOver(){
_wallDefenderGameStatus = WALL_GAME_OVER;
_direction = DIR_STOP;
_time = 20;
}
void _inc_speed(){
_score++;
_time -= WALL_DEFENDER_TIME_INC;
}
void _generateWall() {
_lengthWall = 0;
for (int i=0;i<(_display->rows()-2)*2;i++) {
_wall[i].lin = i<(_display->rows()-2) ? i+1 : i+1-_display->rows()+2;
_wall[i].col = i<(_display->rows()-2) ? _display->columns()/2-1 : _display->columns()/2;
_lengthWall++;
}
}
void _start(){
for (int i=0; i<WALL_DEFENDER_MAX_ENEMY; i++){
_enemys[i].dir == DIR_STOP;
_enemys[i].pos.lin=0;
_enemys[i].pos.col=0;
}
_score = 0;
_time = WALL_DEFENDER_TIME_INIT;
_last_millis = 0;
_last_millis_player = 0;
_position.lin = 0;
_position.col = _display->columns() / 2 + 1;
_direction = DIR_STOP;
_wallDefenderGameStatus = WALL_GAME_ON;
_shootStatus = WALL_SHOOT_STOP;
_generateWall();
Serial.println("a");
}
void _generateEnemys() {
Serial.println(_lengthEnemy);
if (_controlEnemys == 0 && _lengthEnemy < WALL_DEFENDER_MAX_ENEMY){
_lengthEnemy++;
int r1 = random(0, _display->rows());
int r2 = random(0, 2);
for (int i=0; i<WALL_DEFENDER_MAX_ENEMY; i++){
if (_enemys[i].dir == DIR_STOP){
_enemys[i].dir = (r2 == 0) ? DIR_RIGHT : DIR_LEFT;
_enemys[i].pos.col = (r2 == 0) ? 0 : _display->columns()-1;
_enemys[i].pos.lin = r1;
break;
}
}
}
_controlEnemys = (_controlEnemys < 2) ? _controlEnemys+1 : 0; //a cada 4 atualizacoes gera novos obstáculos
}
void _runShoot(){
if (_shootStatus == WALL_SHOOT_LEFT ) { if (_shoot.col == 0) { _shootStatus = WALL_SHOOT_STOP; } else { _shoot.col--; } }
if (_shootStatus == WALL_SHOOT_RIGHT) { if (_shoot.col == _display->columns()-1) { _shootStatus = WALL_SHOOT_STOP; } else { _shoot.col++; } }
}
void _runGameOver(){
int r = _ur->next();
int lin = (r / _display->columns());
int col = (r % _display->columns());
_display->write(lin, col, HIGH );
if ( _ur->getIndex() == (_ur->size()-1) ) {
_ur->randomize();
_ur->first();
_start();
Serial.println("teste");
}
}
void _runPlayer(){
if (_direction == DIR_TOP && _position.lin > 0 ) { _position.lin--; }
if (_direction == DIR_BOTTOM && _position.lin < _display->rows()-1 ) { _position.lin++; }
if (_direction == DIR_LEFT && _position.col > 0 ) { _position.col--; }
if (_direction == DIR_RIGHT && _position.col < _display->columns()-1 ) { _position.col++; }
_direction = DIR_STOP; //depois de mover, mantem parado.
}
void _runEnemys() {
/*move os inimigos*/
byte contRemove = 0;
for (int i=0; i<WALL_DEFENDER_MAX_ENEMY; i++) {
if (_enemys[i].dir == DIR_RIGHT && _enemys[i].pos.col == _display->columns()-1){ contRemove++; _enemys[i].dir = DIR_STOP; }
if (_enemys[i].dir == DIR_LEFT && _enemys[i].pos.col == 0) { contRemove++; _enemys[i].dir = DIR_STOP; }
if (_enemys[i].dir != DIR_STOP ) { _enemys[i].dir == DIR_LEFT ? _enemys[i].pos.col-- : _enemys[i].pos.col++; }
}
if (contRemove > 0){ _lengthEnemy -= contRemove; }
_generateEnemys();
_countUpdate++;
if (_countUpdate >= 5){
_inc_speed();
_countUpdate = 0;
}
}
void _updateDisplay(){
//verifica se o tiro colidiu com o inimigo
for (int i=0; i<WALL_DEFENDER_MAX_ENEMY; i++){
if (_enemys[i].dir != DIR_STOP ) {
if (_enemys[i].pos.lin == _shoot.lin && _enemys[i].pos.col == _shoot.col) { _lengthEnemy--; _enemys[i].dir = DIR_STOP; _shootStatus = WALL_SHOOT_STOP;}
if (_enemys[i].pos.lin == _position.lin && _enemys[i].pos.col == _position.col) { _gameOver(); }
}
}
//verifica se colidiu na parede
for (int i=0; i<WALL_DEFENDER_MAX_ENEMY; i++){
for (int p=0; p<_lengthWall; p++) {
if (_enemys[i].dir != DIR_STOP ) {
if (_enemys[i].pos.lin == _wall[p].lin && _enemys[i].pos.col == _wall[p].col) { _gameOver(); }
}
}
}
if (_wallDefenderGameStatus == WALL_GAME_ON) { _display->clear(); }
for (int lin=0; lin<_display->rows(); lin++) {
for (int col=0; col<_display->columns(); col++) {
for (int p=0; p<_lengthWall; p++){
boolean val = _wall[p].col==col && _wall[p].lin==lin;
if (val) { _display->write( lin, col, val ); break;}
}
for (int p=0; p<WALL_DEFENDER_MAX_ENEMY; p++) {
if (_enemys[p].dir != DIR_STOP){
boolean val = _enemys[p].pos.col==col && _enemys[p].pos.lin==lin;
if (val) { _display->write( lin, col, val ); break;}
}
}
}
}
_display->write(_position.lin, _position.col, HIGH);
if (_shootStatus != WALL_SHOOT_STOP) { _display->write(_shoot.lin, _shoot.col, HIGH); }
}
public:
WallDefenderGame(BitArray2D * display){
_display = display;
_ur = new UniqueRandom( _display->rows() * _display->columns() );
_lengthWall = 0;
_lengthEnemy = 0;
_start();
}
void left() { _direction = DIR_LEFT; }
void right() { _direction = DIR_RIGHT; }
void top() { _direction = DIR_TOP; }
void bottom() { _direction = DIR_BOTTOM; }
int getScore(){ return _score; }
Position getPositionPlayer() { return _position; }
void shoot(){
if (_shootStatus == WALL_SHOOT_STOP){
_shoot.lin = _position.lin;
_shoot.col = _position.col;
_shootStatus = (_shoot.col <= _display->columns() / 2-1) ? WALL_SHOOT_LEFT : WALL_SHOOT_RIGHT;
}
}
int update(){
int r = false;
if (millis() - _last_millis > _time) {
r = true;
_last_millis = millis();
if (_wallDefenderGameStatus == WALL_GAME_ON) { _runEnemys(); }
if (_wallDefenderGameStatus == WALL_GAME_OVER) { _runGameOver(); }
}
if (millis() - _last_millis_player > 50){
r = true;
_last_millis_player = millis();
if (_wallDefenderGameStatus == WALL_GAME_ON) { _runPlayer(); }
if (_wallDefenderGameStatus == WALL_GAME_OVER) { _runGameOver(); }
}
if (millis() - _last_millis_shoot > 50){
r = true;
_last_millis_shoot = millis();
if (_wallDefenderGameStatus == WALL_GAME_ON) { _runShoot(); }
if (_wallDefenderGameStatus == WALL_GAME_OVER) { _runGameOver(); }
}
if (r) { _updateDisplay(); }
return r; //r-->indica se houve mudança no display
}
};
/*************************************************************************************************************
*******************************FIM CLASSE WALL DEFENDER GAME**************************************************
**************************************************************************************************************/
/*************************************************************************************************************
*******************************DISPLAY************************************************************************
**************************************************************************************************************/
/*
rotation indica qual parte do display estará para cima
TOP
L . . . . . . . . R
E . . . . . . . . I
F . . . . . . . . G
T . . . . . . . . H
. . . . . . . . T
. . . . . . . .
. . . . . . . .
. . . . . . . .
BOTTOM
*/
enum Rotation {TOP, LEFT, BOTTOM, RIGHT};
struct Display {
int index;
Position position;
Rotation rotation;
};
/*************************************************************************************************************
*******************************FIM DISPLAY********************************************************************
**************************************************************************************************************/
/*************************************************************************************************************
*******************************DECLARACAO DOS OBJETOS*********************************************************
**************************************************************************************************************/
/*
tamanho display real
* * * * * * * * * * * * * * * *
* * * * * * * * * * * * * * * *
* * * * * * * * * * * * * * * *
* * * * * * * * * * * * * * * *
* * * * * * * * * * * * * * * *
* * * * * * * * * * * * * * * *
* * * * * * * * * * * * * * * *
* * * * * * * * * * * * * * * *
*/
const int LINHAS = 8;
const int COLUNAS = 16;
Display displays_8x8[] = {
{0, {0,0}, LEFT},
{1, {0,8}, RIGHT}
};
BitArray2D ba(LINHAS, COLUNAS); //8 linhas e 16 colunas, usada para armazenar o estado do display
WallDefenderGame wall(&ba);
RotaryEncoderLimits lim[] = { {-1000,1000} }; //limites máximos e mínimos que as variaveis podem atingir
RotaryEncoder re(A0, A1, 4, 1, lim); //pino clk, pino dt, pino sw, qtd variaveis, limites
LedControl lc=LedControl(10,12,11,2); //pin 10: DataIn ; pin 12: CLK ; pin 11: LOAD --- 2 display 8x8
/*************************************************************************************************************
*******************************FIM DECLARACAO DOS OBJETOS*****************************************************
**************************************************************************************************************/
/*************************************************************************************************************
*******************************TRATAMENTO DAS INTERRUPÇÕES****************************************************
**************************************************************************************************************/
//interrupções dos pinos A0 e A1 via Pin Change Interrupt
ISR(PCINT1_vect) {
volatile static byte lastVal_a0 = LOW;
volatile static byte lastVal_a1 = LOW;
byte val_a0 = digitalRead(A0);
byte val_a1 = digitalRead(A1);
if (lastVal_a0 != val_a0){ re.update_a(); lastVal_a0 = val_a0; }
if (lastVal_a1 != val_a1){ re.update_b(); lastVal_a1 = val_a1; }
}
void setup_interrupts(){
//-----PCI - Pin Change Interrupt ----
pinMode(A0,INPUT); // set Pin as Input (default)
digitalWrite(A0,HIGH); // enable pullup resistor
pinMode(A1,INPUT); // set Pin as Input (default)
digitalWrite(A1,HIGH); // enable pullup resistor
cli();
PCICR |= 0b00000010; // habilita a porta C - Pin Change Interrupts
PCMSK1 |= 0b00000011; // habilita interrupção da porta c nos pinos: PCINT8 (A0) e PCINT9(A1)
sei();
}
/*************************************************************************************************************
*******************************FIM TRATAMENTO DAS INTERRUPÇÕES****************************************************
**************************************************************************************************************/
void update_display() {
lc.startWrite();
for (int lin=0; lin<ba.rows(); lin++) {
for (int col=0; col<ba.columns(); col++) {
for (int i=0; i<sizeof(displays_8x8)/sizeof(Display); i++) {
int l = lin - displays_8x8[i].position.lin;
int c = col - displays_8x8[i].position.col;
if (l>=0 && l<=7 && c>=0 && c<=7) {
if (displays_8x8[i].rotation == BOTTOM) { c=7-c; l=7-l; }
if (displays_8x8[i].rotation == LEFT) { int aux=c; c=l; l=7-aux; }
if (displays_8x8[i].rotation == RIGHT) { int aux=l; l=c; c=7-aux; }
lc.setLed(displays_8x8[i].index, l, c, ba.read(lin, col) );
}
}
}
}
lc.send();
}
void setup() {
Serial.begin(9600);
lc.shutdown(0,false);
lc.setIntensity(0,1);
lc.clearDisplay(0);
lc.shutdown(1,false);
lc.setIntensity(1,1);
lc.clearDisplay(1);
setup_interrupts();
randomSeed(analogRead(A2));
re.setValue(0, 0); //inicializa o rotary em 0
}
void loop() {
static int val_encoder = 0;
static boolean change = false;
//anti-horario
if (re.getValue(0) < val_encoder && !change && wall.getPositionPlayer().lin > 0 && wall.getPositionPlayer().col == 9){ wall.top(); change = true; }
if (re.getValue(0) < val_encoder && !change && wall.getPositionPlayer().lin == 0 && wall.getPositionPlayer().col >= 7){ wall.left(); change = true; }
if (re.getValue(0) < val_encoder && !change && wall.getPositionPlayer().lin < 7 && wall.getPositionPlayer().col == 6){ wall.bottom(); change = true; }
if (re.getValue(0) < val_encoder && !change && wall.getPositionPlayer().lin == 7 && wall.getPositionPlayer().col <= 9){ wall.right(); change = true; }
//horario
if (re.getValue(0) > val_encoder && !change && wall.getPositionPlayer().lin > 0 && wall.getPositionPlayer().col == 6){ wall.top(); change = true; }
if (re.getValue(0) > val_encoder && !change && wall.getPositionPlayer().lin == 7 && wall.getPositionPlayer().col >= 7){ wall.left(); change = true; }
if (re.getValue(0) > val_encoder && !change && wall.getPositionPlayer().lin < 7 && wall.getPositionPlayer().col == 9){ wall.bottom(); change = true; }
if (re.getValue(0) > val_encoder && !change && wall.getPositionPlayer().lin == 0 && wall.getPositionPlayer().col <= 8){ wall.right(); change = true; }
val_encoder = re.getValue(0);
if ( wall.update() ) {
change = false;
update_display();
}
//controla o click do botao do enconder
static byte b = HIGH; //pra ler apenas uma vez o botao ao pressionar
if( re.buttonRead() == LOW && b != re.buttonRead() ) {
wall.shoot(); //dispara o tiro
delay(50); //debounce meia boca
}
b = re.buttonRead();
}
</pre>
<br />
<br />Fabiano Arndthttp://www.blogger.com/profile/15268923442674744187noreply@blogger.com2tag:blogger.com,1999:blog-5513833072608837410.post-43925133220904183082016-06-06T08:15:00.000-04:002016-06-16T10:13:50.257-04:00Arduino - Race Game com display LCDSemana passada postei uma implementação minha do clássico Snake Game em um display LCD 16x2. Baseado naquele exemplo, implementei um jogo de corrida. Apesar de simples ficou legalzinho.<br />
<br />
Vídeo:<br />
<div class="separator" style="clear: both; text-align: center;">
<iframe allowfullscreen="" class="YOUTUBE-iframe-video" data-thumbnail-src="https://i9.ytimg.com/vi/lVmGilUGi8A/default.jpg?sqp=COzN1boF&rs=AOn4CLDcugSvGVZQNojqirSkUhirCHJlmQ" frameborder="0" height="415" src="https://www.youtube.com/embed/lVmGilUGi8A?feature=player_embedded" width="660"></iframe></div>
<br />
<br />
Código-Fonte:<br />
<br />
<pre class="brush: html" style="background-color: #eeeeee; border: 1px dashed rgb(153, 153, 153); overflow: auto; padding: 5px; width: 100%;"> /*
Fabiano A. Arndt - 2016
www.youtube.com/user/fabianoallex
www.facebook.com/dicasarduino
fabianoallex@gmail.com
*/
#include <LiquidCrystal.h>
/*************************************************************************************************************
*******************************BIT ARRAY *********************************************************************
**************************************************************************************************************
mais informações aqui: http://fabianoallex.blogspot.com.br/2015/09/arduino-array-de-bits.html
**************************************************************************************************************/
class BitArray{
private:
int _num_bits; //quantidade de bits a serem gerenciados
int _num_bytes; //quantidade de bytes utilizados para armazenar os bits a serem gerenciados
byte * _bytes; //array de bytes onde estarão armazenados os bits
public:
BitArray(int num_bits){
_num_bits = num_bits;
_num_bytes = _num_bits/8 + (_num_bits%8 ? 1 : 0) + 1;
_bytes = (byte *)(malloc( _num_bytes * sizeof(byte) ) );
}
void write(int index, byte value) {
byte b = _bytes[ index/8 + (index%8 ? 1 : 0) ];
unsigned int bit = index%8;
if (value) { b |= (1 << bit); } else { b &= ~(1 << bit); }
_bytes[ index/8 + (index%8 ? 1 : 0) ] = b;
}
void write(byte value) { for(int j=0; j<_num_bytes;j++) { _bytes[j] = value ? B11111111 : B00000000; } }
int read(int index) {
byte b = _bytes[ index/8 + (index%8 ? 1 : 0) ];
unsigned int bit = index%8;
return (b & (1 << bit)) != 0;
}
~BitArray(){ free ( _bytes ); }
};
class BitArray2D {
private:
unsigned int _rows;
unsigned int _columns;
unsigned int _cols_array; //pra cada 8 colunas, 1 byte é usado
byte** _bits;
public:
BitArray2D(unsigned int rows, unsigned int columns){
_rows = rows;
_columns = columns;
_cols_array = columns/8 + (_columns%8 ? 1 : 0) + 1; //divide por 8 o número de colunas
_bits = (byte **)malloc(_rows * sizeof(byte *));
for(int i=0;i<_rows;i++){ _bits[i] = (byte *)malloc( _cols_array * sizeof(byte)); } //cria varios arrays
clear();
}
unsigned int rows(){ return _rows; }
unsigned int columns(){ return _columns; }
void clear() {
for(int i=0;i<_rows;i++){
for(int j=0; j<_cols_array;j++) { _bits[i][j] = B00000000; }
}
}
void write(unsigned int row, unsigned int column, int value){
byte b = _bits[row][ column/8 + (column%8 ? 1 : 0) ];
unsigned int bit = column%8;
if (value) { b |= (1 << bit); } else { b &= ~(1 << bit); }
_bits[row][ column/8 + (column%8 ? 1 : 0) ] = b;
}
void write(byte value){
for(int i=0;i<_rows;i++){
for(int j=0; j<_cols_array;j++) {
_bits[i][j] = value ? B11111111 : B00000000;
}
}
}
int read(unsigned int row, unsigned int column){
byte b = _bits[row][ column/8 + (column%8 ? 1 : 0) ];
unsigned int bit = column%8;
return (b & (1 << bit)) != 0;
}
void toggle(unsigned int row, unsigned int column){ write(row, column, !read(row, column)); }
void toggle(){ for(int i=0;i<_rows;i++){ for(int j=0; j<_columns;j++) { toggle(i,j); } } }
};
/*************************************************************************************************************
*******************************FIM BIT ARRAY *****************************************************************
**************************************************************************************************************/
/*************************************************************************************************************
************************************CLASSE UNIQUE RANDOM******************************************************
**************************************************************************************************************
mais informações aqui: http://fabianoallex.blogspot.com.br/2015/09/arduino-numeros-aleatorio-repetidos-e.html
*************************************************************************************************************/
class UniqueRandom{
private:
int _index;
int _min;
int _max;
int _size;
int* _list;
void _init(int min, int max) {
_list = 0;
if (min < max) { _min = min; _max = max; } else { _min = max; _max = min; }
_size = _max - _min;
_index = 0;
}
public:
UniqueRandom(int max) { _init(0, max); randomize(); } //construtor com 1 parametro
UniqueRandom(int min, int max) { _init(min, max); randomize(); } //construtor com 2 parametros
void randomize() {
_index = 0;
if (_list == 0) { _list = (int*) malloc(size() * sizeof(int)); }
for (int i=0; i<size(); i++) { _list[i] = _min+i; } //preenche a lista do menor ao maior valor
//embaralha a lista
for (int i=0; i<size(); i++) {
int r = random(0, size()); //sorteia uma posição qualquer
int aux = _list[i];
_list[i] = _list[r];
_list[r] = aux;
}
}
int next() { //retorna o proximo numero da lista
int n = _list[_index++];
if (_index >= size() ) { _index = 0;} //após recuper o ultimo numero, recomeça na posicao 0
return n;
}
int size() { return _size; }
~UniqueRandom(){ free ( _list ); } //destrutor
};
/*************************************************************************************************************
************************************FIM CLASSE UNIQUE RANDOM**************************************************
*************************************************************************************************************/
/*************************************************************************************************************
*******************************CLASSE RACE GAME***************************************************************
**************************************************************************************************************/
struct Position {
byte lin : 4; //maximo 15
byte col : 4; //maximo 15, mas será no máximo 6 EM DISPLAY 16X2 E TERÁ 12 EM DISPLAY 20X4. PARA MAIS DE 15 LINHAS. ALTERAR AQUI
};
const int RACE_MAX_OBSTACLES = 30; //quantidade maxima de obstáculos na tela
const int RACE_TIME_INIT = 500; //tempo entre deslocamento dos obstáculos
const int RACE_TIME_INC = 15; //incremento da velocidade
enum RaceGameStatus { RACE_GAME_ON, RACE_GAME_OVER };
enum Direction { DIR_STOP, DIR_TOP, DIR_LEFT, DIR_BOTTOM, DIR_RIGHT};
class RaceGame{
private:
BitArray2D * _display;
Position _position;
Position _obstacles_positions[RACE_MAX_OBSTACLES];
Direction _direction;
byte _lengthObstacles;
byte _controlObstacle : 2; //máximo = 3 B11
byte _isCountdown : 1; //apenas 1 ou 0 -------- contagem regressiva
byte _countUpdate : 3; //maximo = 5 B101
byte _countdown : 3; //ma´ximo = 5 B101
unsigned long _last_millis;
unsigned long _last_millis_car;
unsigned long _millisCountdown;
int _time;
int _score;
RaceGameStatus _raceGameStatus;
UniqueRandom * _ur; //utilizado no game over
UniqueRandom * _urObstacles; //utilizado no game over
void _gameOver(){
_raceGameStatus = RACE_GAME_OVER;
_direction = DIR_STOP;
_time = 20;
}
void _inc_speed(){
//_length++;
_score++;
_time -= RACE_TIME_INC;
}
void _runGameOver(){
int r = _ur->next();
int lin = (r / _display->columns());
int col = (r % _display->columns());
_display->write(lin, col, HIGH );
//if ( r>=(_ur->size()-1) || _direction != DIR_STOP ) {
if ( r>=(_ur->size()-1) ) {
_ur->randomize();
start();
}
}
void _generateObstacles() {
if (_controlObstacle == 0){
_urObstacles->randomize();
//quantidade de obstaculos estara entre 1 e quantidade de linhas -1
int numObstacles = random(0, _display->rows()-4) + 4;
if (_lengthObstacles + numObstacles > RACE_MAX_OBSTACLES) {
numObstacles = RACE_MAX_OBSTACLES - _lengthObstacles;
}
for (int i=0; i<numObstacles; i++){
_lengthObstacles++;
_obstacles_positions[_lengthObstacles-1].lin = _urObstacles->next();
_obstacles_positions[_lengthObstacles-1].col = _display->columns()-1;
}
}
_controlObstacle = (_controlObstacle < 3) ? _controlObstacle+1 : 0; //a cada 4 atualizacoes gera novos obstáculos
}
void _runCountdown(){
for (int lin=0; lin<_display->rows(); lin++) {
for (int col=0; col<_display->columns(); col++) {
_display->write( lin, col, 0 );
}
}
unsigned long dif = millis() - _millisCountdown;
if ( dif < 1000){
_display->write( 1, 3, 1 ); //5
_display->write( 1, 4, 1 );
_display->write( 1, 5, 1 );
_display->write( 2, 3, 1 );
_display->write( 3, 3, 1 );
_display->write( 3, 4, 1 );
_display->write( 3, 5, 1 );
_display->write( 4, 5, 1 );
_display->write( 5, 5, 1 );
_display->write( 5, 4, 1 );
_display->write( 5, 3, 1 );
} else if (dif < 2000) {
_display->write( 1, 3, 1 ); //4
_display->write( 1, 5, 1 );
_display->write( 2, 3, 1 );
_display->write( 2, 5, 1 );
_display->write( 3, 3, 1 );
_display->write( 3, 4, 1 );
_display->write( 3, 5, 1 );
_display->write( 4, 5, 1 );
_display->write( 5, 5, 1 );
} else if (dif < 3000) {
_display->write( 1, 3, 1 ); //3
_display->write( 1, 4, 1 );
_display->write( 1, 5, 1 );
_display->write( 2, 5, 1 );
_display->write( 3, 3, 1 );
_display->write( 3, 4, 1 );
_display->write( 3, 5, 1 );
_display->write( 4, 5, 1 );
_display->write( 5, 5, 1 );
_display->write( 5, 4, 1 );
_display->write( 5, 3, 1 );
} else if (dif < 4000) {
_display->write( 1, 3, 1 ); //2
_display->write( 1, 4, 1 );
_display->write( 1, 5, 1 );
_display->write( 2, 5, 1 );
_display->write( 3, 3, 1 );
_display->write( 3, 4, 1 );
_display->write( 3, 5, 1 );
_display->write( 4, 3, 1 );
_display->write( 5, 5, 1 );
_display->write( 5, 4, 1 );
_display->write( 5, 3, 1 );
} else if (dif < 5000) {
_display->write( 1, 5, 1 ); //1
_display->write( 2, 5, 1 );
_display->write( 3, 5, 1 );
_display->write( 4, 5, 1 );
_display->write( 5, 5, 1 );
} else {
_isCountdown = 0;
}
}
void _runObastacles(){
/*move os obstáculos*/
byte contRemove = 0;
for (int i=0; i<_lengthObstacles; i++) {
if (_obstacles_positions[i].col == 0 ) { contRemove++;} //B1111 --> linha inexistente no display. significa que será removido do array
_obstacles_positions[i].col--;
}
if (contRemove > 0){
for (int i=0; i<_lengthObstacles-contRemove; i++) {
_obstacles_positions[i] = _obstacles_positions[i+contRemove];
}
_lengthObstacles -= contRemove;
}
_generateObstacles();
_countUpdate++;
if (_countUpdate >= 5){
_inc_speed();
_countUpdate = 0;
}
}
void _runCar() {
if (_direction == DIR_TOP ) { _position.lin--; }
if (_direction == DIR_BOTTOM ) { _position.lin++; }
if (_direction == DIR_LEFT ) { _position.col--; }
if (_direction == DIR_RIGHT ) { _position.col++; }
_direction = DIR_STOP; //depois de mover, mantem parado.
//verifica se ultrapassou o limite do display
if (_position.lin < 0) { _gameOver(); }
if (_position.lin >= _display->rows() ) { _gameOver(); }
if (_position.col < 0) { _gameOver(); }
if (_position.col >= _display->columns() ) { _gameOver(); }
//verifica se colidiu nos obstáculos
for (int i=0; i<_lengthObstacles; i++){
if (_obstacles_positions[i].lin == _position.lin && _obstacles_positions[i].col == _position.col) {
_gameOver();
}
}
//update display
for (int lin=0; lin<_display->rows(); lin++) {
for (int col=0; col<_display->columns(); col++) {
for (int p=0; p<_lengthObstacles; p++){
boolean val = _obstacles_positions[p].col==col && _obstacles_positions[p].lin==lin;
_display->write( lin, col, val );
if (val) {break;}
}
}
}
_display->write(_position.lin, _position.col, HIGH);
}
public:
RaceGame(BitArray2D * display){
_display = display;
_ur = new UniqueRandom( _display->rows() * _display->columns() );
_urObstacles = new UniqueRandom( _display->rows() );
start();
}
void start(){
_score = 0;
_time = RACE_TIME_INIT;
_last_millis = 0;
_position.lin = _display->rows() / 2;
_position.col = 0;
_direction = DIR_STOP;
_lengthObstacles = 0;
_raceGameStatus = RACE_GAME_ON;
_countUpdate = 0;
_generateObstacles();
_isCountdown = 1;
_countdown = 5;
_millisCountdown = millis();
_last_millis_car = millis();
}
void left() { _direction = DIR_LEFT; }
void right() { _direction = DIR_RIGHT; }
void top() { _direction = DIR_TOP; }
void bottom() { _direction = DIR_BOTTOM; }
int getScore(){ return _score; }
int update(){
int r = false;
if (_isCountdown){
if (millis() - _last_millis_car > 20){
_last_millis_car = millis();
_runCountdown();
return true;
}
return false;
}
if (millis() - _last_millis > _time) {
r = true;
_last_millis = millis();
if (_raceGameStatus == RACE_GAME_ON) { _runObastacles(); }
if (_raceGameStatus == RACE_GAME_OVER) { _runGameOver(); }
}
if (millis() - _last_millis_car > 20){
r = true;
_last_millis_car = millis();
if (_raceGameStatus == RACE_GAME_ON) { _runCar(); }
if (_raceGameStatus == RACE_GAME_OVER) { _runGameOver(); }
}
return r; //r-->indica se houve mudança no display
}
};
/*************************************************************************************************************
*******************************FIM CLASSE RACE GAME***********************************************************
**************************************************************************************************************/
/*************************************************************************************************************
************************************CLASSE ROTARY ENCODER*****************************************************
mais informações: http://fabianoallex.blogspot.com.br/2016/05/arduino-rotary-encoder.html
*************************************************************************************************************/
#define ROTARY_NO_BUTTON 255
struct RotaryEncoderLimits{
int min;
int max;
};
class RotaryEncoder {
private:
byte _pin_clk;
byte _pin_dt;
byte _pin_sw;
volatile byte _num_results;
volatile int _result;
volatile int * _results;
byte _index_result;
RotaryEncoderLimits * _limits;
boolean _a;
boolean _b;
public:
RotaryEncoder(byte pin_clk, byte pin_dt, byte pin_sw = ROTARY_NO_BUTTON, byte num_results=1, RotaryEncoderLimits * limits=0){ //parametro do botao opcional
_pin_clk = pin_clk;
_pin_dt = pin_dt;
_pin_sw = pin_sw;
pinMode(_pin_clk, INPUT);
pinMode(_pin_dt, INPUT);
if (_pin_sw != ROTARY_NO_BUTTON){
pinMode(_pin_sw, INPUT);
digitalWrite(_pin_sw, HIGH);
}
if (num_results == 0) { num_results = 1; }
_num_results = num_results;
_results = new int[_num_results];
for (int i; i<_num_results; i++){ _results[i] = (limits) ? limits[i].min : 0; }
_index_result = 0;
_limits = limits;
_a = false;
_b = false;
}
byte getIndex() { return _index_result; }
void next() { _index_result++; if (_index_result >= _num_results) { _index_result = 0; } }
void update_a() {
_result = 0;
delay (1);
if( digitalRead(_pin_clk) != _a ) {
_a = !_a;
if ( _a && !_b ) { _result = -1; }
}
if (_results[_index_result]+_result >= _limits[_index_result].min &&
_results[_index_result]+_result <= _limits[_index_result].max ) {
_results[_index_result] += _result;
}
}
void update_b() {
_result = 0;
delay (1);
if( digitalRead(_pin_dt) != _b ) {
_b = !_b;
if ( _b && !_a ) { _result = +1; }
}
if (_results[_index_result]+_result >= _limits[_index_result].min &&
_results[_index_result]+_result <= _limits[_index_result].max ) {
_results[_index_result] += _result;
}
}
int read(){ return _result; } //retorn -1, 0 ou 1.
int getValue(int index=-1) { //retorna o valor da variável corrente ou a passada como parametro
if (index < 0 ){ return _results[_index_result]; }
return _results[index];
}
void setValue(int value, int index=-1){ _results[ (index==-1) ? _index_result : index] = value; } //caso a variável inicializa em determinado valor diferente de zero, utilizar esse método.
int buttonRead(){ return (_pin_sw == ROTARY_NO_BUTTON) ? LOW : digitalRead(_pin_sw); }
};
/*************************************************************************************************************
************************************FIM CLASSE ROTARY ENCODER*************************************************
*************************************************************************************************************/
/*************************************************************************************************************
*******************************CLASSE RACE GAME LCD***********************************************************
**************************************************************************************************************/
/*
0 1 2 3 4 5 6
*** *** *** ***
*** *** *** ***
*** *** *** ***
*** *** *** ***
*** *** *** ***
*** *** *** ***
*/
byte c0[8] = {B11111, B11111, B00000, B11111, B11111, B00000, B11111, B11111 };
byte c1[8] = {B00000, B00000, B00000, B11111, B11111, B00000, B11111, B11111 };
byte c2[8] = {B00000, B00000, B00000, B00000, B00000, B00000, B11111, B11111 };
byte c3[8] = {B11111, B11111, B00000, B11111, B11111, B00000, B00000, B00000 };
byte c4[8] = {B11111, B11111, B00000, B00000, B00000, B00000, B11111, B11111 };
byte c5[8] = {B00000, B00000, B00000, B11111, B11111, B00000, B00000, B00000 };
byte c6[8] = {B11111, B11111, B00000, B00000, B00000, B00000, B00000, B00000 };
class RaceGameLCD {
private:
LiquidCrystal * _lcd; //ponteiro para um objeto lcd
public:
void createChars() {
_lcd->createChar(0, c0);
_lcd->createChar(1, c1);
_lcd->createChar(2, c2);
_lcd->createChar(3, c3);
_lcd->createChar(4, c4);
_lcd->createChar(5, c5);
_lcd->createChar(6, c6);
}
RaceGameLCD(LiquidCrystal * lcd) { _lcd = lcd; }
void write(byte col, byte row, byte val){
_lcd->setCursor(col, row);
if (val == B000) { _lcd->print(" "); }
if (val == B111) { _lcd->write((byte)0); }
if (val == B011) { _lcd->write((byte)1); }
if (val == B001) { _lcd->write((byte)2); }
if (val == B110) { _lcd->write((byte)3); }
if (val == B101) { _lcd->write((byte)4); }
if (val == B010) { _lcd->write((byte)5); }
if (val == B100) { _lcd->write((byte)6); }
}
};
/*************************************************************************************************************
*******************************FIM CLASSE RACE LCD***********************************************************
**************************************************************************************************************/
/*************************************************************************************************************
*******************************DECLARACAO DOS OBJETOS*********************************************************
**************************************************************************************************************/
/*
tamanho display real
* * * * * * * * * * * * * * * *
* * * * * * * * * * * * * * * *
* * * * * * * * * * * * * * * *
* * * * * * * * * * * * * * * *
* * * * * * * * * * * * * * * *
* * * * * * * * * * * * * * * *
*/
const int LINHAS = 6;
const int COLUNAS = 16;
LiquidCrystal lcd(12, 11, 10, 9, 8, 7);
RaceGameLCD raceGameLcd(&lcd);
BitArray2D ba(LINHAS, COLUNAS); //8 linhas e 20 colunas, usada para armazenar o estado do display
RaceGame race(&ba);
RotaryEncoderLimits lim[] = { {-1000,1000} }; //limites máximos e mínimos que as variaveis podem atingir
RotaryEncoder re(A0, A1, 4, 1, lim); //pino clk, pino dt, pino sw, variaveis, limites
/*************************************************************************************************************
*******************************FIM DECLARACAO DOS OBJETOS*****************************************************
**************************************************************************************************************/
/*************************************************************************************************************
*******************************TRATAMENTO DAS INTERRUPÇÕES****************************************************
**************************************************************************************************************/
//interrupções dos pinos A0 e A1 via Pin Change Interrupt
ISR(PCINT1_vect) {
volatile static byte lastVal_a0 = LOW;
volatile static byte lastVal_a1 = LOW;
byte val_a0 = digitalRead(A0);
byte val_a1 = digitalRead(A1);
if (lastVal_a0 != val_a0){ re.update_a(); lastVal_a0 = val_a0; }
if (lastVal_a1 != val_a1){ re.update_b(); lastVal_a1 = val_a1; }
}
void setup_interrupts(){
//-----PCI - Pin Change Interrupt ----
pinMode(A0,INPUT); // set Pin as Input (default)
digitalWrite(A0,HIGH); // enable pullup resistor
pinMode(A1,INPUT); // set Pin as Input (default)
digitalWrite(A1,HIGH); // enable pullup resistor
cli();
PCICR |= 0b00000010; // habilita a porta C - Pin Change Interrupts
PCMSK1 |= 0b00000011; // habilita interrupção da porta c nos pinos: PCINT8 (A0) e PCINT9(A1)
sei();
}
/*************************************************************************************************************
*******************************FIM TRATAMENTO DAS INTERRUPÇÕES****************************************************
**************************************************************************************************************/
void update_display() {
for (int col=0; col<ba.columns(); col++) {
byte lin_lcd = 0;
byte cont = 0;
byte val = 0;
for (int lin=0; lin<ba.rows(); lin++) {
if (cont == 0){ val = 0; }
val = val << 1;
val = val | ba.read(lin, col);
cont++;
if (cont == 3) {
raceGameLcd.write(col, lin_lcd, val) ;
cont = 0;
lin_lcd++;
}
}
}
}
void setup() {
setup_interrupts();
lcd.begin(16, 2);
raceGameLcd.createChars();
randomSeed(analogRead(A2));
re.setValue(0, 0); //inicializa o rotary em 0
}
void loop() {
static int val_encoder = 0;
static boolean change = false;
if (re.getValue(0) < val_encoder && !change){
race.bottom();
change = true;
}
if (re.getValue(0) > val_encoder && !change ){
race.top();
change = true;
}
val_encoder = re.getValue(0);
if ( race.update() ) {
change = false;
update_display();
}
//controla o click do botao do enconder
static byte b = HIGH; //pra ler apenas uma vez o botao ao pressionar
if( re.buttonRead() == LOW && b != re.buttonRead() ) {
re.next(); //passa para a próxima variável (index)
delay(200); //debounce meia boca
}
b = re.buttonRead();
}
</pre>
<br />Fabiano Arndthttp://www.blogger.com/profile/15268923442674744187noreply@blogger.com1tag:blogger.com,1999:blog-5513833072608837410.post-33776813425880427712016-05-31T09:18:00.001-04:002016-06-16T10:13:50.252-04:00Arduino - Snake Game com display LCD e Rotary EncoderMinha terceira versão do jogo Snake, agora com um display lcd controlado por um rotary encoder. As versões anteriores foram uma em matriz de led e outra em um display 5110. Como o programa foi desenvolvido Orientado a Objeto, foi fácil reaproveitar o código, já que a classe principal do jogo não se alterou.<br />
<br />
Versão com matriz de led: <a href="http://fabianoallex.blogspot.com.br/2015/10/arduino-snake-game-jogo-da-cobrinha.html">http://fabianoallex.blogspot.com.br/2015/10/arduino-snake-game-jogo-da-cobrinha.html</a><br />
<br />
versão com display 5110: <a href="http://fabianoallex.blogspot.com.br/2016/01/arduino-display-lcd-nokia-5110.html">http://fabianoallex.blogspot.com.br/2016/01/arduino-display-lcd-nokia-5110.html</a><br />
<br />
<br />
Vídeo:<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<iframe allowfullscreen="" class="YOUTUBE-iframe-video" data-thumbnail-src="https://i.ytimg.com/s_vi/In1RD3-msJ8/default.jpg?sqp=CNSctroF&rs=AOn4CLAyAhS1Q7hWWLSvo9rJ6DthnXihSA" frameborder="0" height="415" src="https://www.youtube.com/embed/In1RD3-msJ8?feature=player_embedded" width="660"></iframe></div>
<br />
Código-fonte:<br />
<br />
<pre class="brush: html" style="background-color: #eeeeee; border: 1px dashed rgb(153, 153, 153); overflow: auto; padding: 5px; width: 100%;">/*
Fabiano A. Arndt - 2016
www.youtube.com/user/fabianoallex
www.facebook.com/dicasarduino
fabianoallex@gmail.com
*/
#include <LiquidCrystal.h>
/*************************************************************************************************************
*******************************BIT ARRAY *********************************************************************
**************************************************************************************************************
mais informações aqui: http://fabianoallex.blogspot.com.br/2015/09/arduino-array-de-bits.html
**************************************************************************************************************/
class BitArray{
private:
int _num_bits; //quantidade de bits a serem gerenciados
int _num_bytes; //quantidade de bytes utilizados para armazenar os bits a serem gerenciados
byte * _bytes; //array de bytes onde estarão armazenados os bits
public:
BitArray(int num_bits){
_num_bits = num_bits;
_num_bytes = _num_bits/8 + (_num_bits%8 ? 1 : 0) + 1;
_bytes = (byte *)(malloc( _num_bytes * sizeof(byte) ) );
}
void write(int index, byte value) {
byte b = _bytes[ index/8 + (index%8 ? 1 : 0) ];
unsigned int bit = index%8;
if (value) { b |= (1 << bit); } else { b &= ~(1 << bit); }
_bytes[ index/8 + (index%8 ? 1 : 0) ] = b;
}
void write(byte value) { for(int j=0; j<_num_bytes;j++) { _bytes[j] = value ? B11111111 : B00000000; } }
int read(int index) {
byte b = _bytes[ index/8 + (index%8 ? 1 : 0) ];
unsigned int bit = index%8;
return (b & (1 << bit)) != 0;
}
~BitArray(){ free ( _bytes ); }
};
class BitArray2D {
private:
unsigned int _rows;
unsigned int _columns;
unsigned int _cols_array; //pra cada 8 colunas, 1 byte é usado
byte** _bits;
public:
BitArray2D(unsigned int rows, unsigned int columns){
_rows = rows;
_columns = columns;
_cols_array = columns/8 + (_columns%8 ? 1 : 0) + 1; //divide por 8 o número de colunas
_bits = (byte **)malloc(_rows * sizeof(byte *));
for(int i=0;i<_rows;i++){ _bits[i] = (byte *)malloc( _cols_array * sizeof(byte)); } //cria varios arrays
clear();
}
unsigned int rows(){ return _rows; }
unsigned int columns(){ return _columns; }
void clear() {
for(int i=0;i<_rows;i++){
for(int j=0; j<_cols_array;j++) { _bits[i][j] = B00000000; }
}
}
void write(unsigned int row, unsigned int column, int value){
byte b = _bits[row][ column/8 + (column%8 ? 1 : 0) ];
unsigned int bit = column%8;
if (value) { b |= (1 << bit); } else { b &= ~(1 << bit); }
_bits[row][ column/8 + (column%8 ? 1 : 0) ] = b;
}
void write(byte value){
for(int i=0;i<_rows;i++){
for(int j=0; j<_cols_array;j++) {
_bits[i][j] = value ? B11111111 : B00000000;
}
}
}
int read(unsigned int row, unsigned int column){
byte b = _bits[row][ column/8 + (column%8 ? 1 : 0) ];
unsigned int bit = column%8;
return (b & (1 << bit)) != 0;
}
void toggle(unsigned int row, unsigned int column){ write(row, column, !read(row, column)); }
void toggle(){ for(int i=0;i<_rows;i++){ for(int j=0; j<_columns;j++) { toggle(i,j); } } }
};
/*************************************************************************************************************
*******************************FIM BIT ARRAY *****************************************************************
**************************************************************************************************************/
/*************************************************************************************************************
************************************CLASSE UNIQUE RANDOM******************************************************
**************************************************************************************************************
mais informações aqui: http://fabianoallex.blogspot.com.br/2015/09/arduino-numeros-aleatorio-repetidos-e.html
*************************************************************************************************************/
class UniqueRandom{
private:
int _index;
int _min;
int _max;
int _size;
int* _list;
void _init(int min, int max) {
_list = 0;
if (min < max) { _min = min; _max = max; } else { _min = max; _max = min; }
_size = _max - _min;
_index = 0;
}
public:
UniqueRandom(int max) { _init(0, max); randomize(); } //construtor com 1 parametro
UniqueRandom(int min, int max) { _init(min, max); randomize(); } //construtor com 2 parametros
void randomize() {
_index = 0;
if (_list == 0) { _list = (int*) malloc(size() * sizeof(int)); }
for (int i=0; i<size(); i++) { _list[i] = _min+i; } //preenche a lista do menor ao maior valor
//embaralha a lista
for (int i=0; i<size(); i++) {
int r = random(0, size()); //sorteia uma posição qualquer
int aux = _list[i];
_list[i] = _list[r];
_list[r] = aux;
}
}
int next() { //retorna o proximo numero da lista
int n = _list[_index++];
if (_index >= size() ) { _index = 0;} //após recuper o ultimo numero, recomeça na posicao 0
return n;
}
int size() { return _size; }
~UniqueRandom(){ free ( _list ); } //destrutor
};
/*************************************************************************************************************
************************************FIM CLASSE UNIQUE RANDOM**************************************************
*************************************************************************************************************/
/*************************************************************************************************************
*******************************CLASSE SNAKE GAME**************************************************************
**************************************************************************************************************/
struct Position {
int lin;
int col;
};
const int SNAKE_MAX_LEN = 30; //tamanho maximo da cobra
const int SNAKE_TIME_INIT = 500; //tempo entre deslocamento da cobra (velocidade)
const int SNAKE_TIME_INC = 15; //incremento da velocidade
enum Direction { DIR_STOP, DIR_TOP, DIR_LEFT, DIR_BOTTOM, DIR_RIGHT};
enum SnakeStatus { SNAKE_GAME_ON, SNAKE_GAME_OVER };
class SnakeGame{
private:
BitArray2D * _display;
Position _snake_positions[SNAKE_MAX_LEN];
Position _apple;
int _length;
Direction _direction;
unsigned long _last_millis;
int _time;
int _score;
SnakeStatus _snakeStatus;
UniqueRandom * _ur; //utilizado no game over
void _generateApple() {
int lin, col;
boolean random_ok = false;
while (!random_ok) {
random_ok = true;
lin = random(0, _display->rows()-1);
col = random(0, _display->columns()-1);
for (int p=0; p<_length; p++){
if (_snake_positions[p].col==col && _snake_positions[p].lin==lin){ //verifica se gerou em um local que não seja a cobra
random_ok = false;
break;
}
}
}
_apple.lin = lin;
_apple.col = col;
}
void _gameOver(){
_snakeStatus = SNAKE_GAME_OVER;
_direction = DIR_STOP;
_time = 20;
}
void _inc_length(){
_length++; _score++;
_time -= SNAKE_TIME_INC;
}
void _runGameOver(){
int r = _ur->next();
int lin = (r / _display->columns());
int col = (r % _display->columns());
_display->write(lin, col, HIGH );
//if ( r>=(_ur->size()-1) || _direction != DIR_STOP ) {
if ( r>=(_ur->size()-1) ) {
_ur->randomize();
start();
}
}
void _run(){
for (int i=_length-1; i>0; i--){
_snake_positions[i].lin = _snake_positions[i-1].lin;
_snake_positions[i].col = _snake_positions[i-1].col;
}
if (_direction == DIR_TOP ) { _snake_positions[0].lin--; }
if (_direction == DIR_BOTTOM ) { _snake_positions[0].lin++; }
if (_direction == DIR_LEFT ) { _snake_positions[0].col--; }
if (_direction == DIR_RIGHT ) { _snake_positions[0].col++; }
//verifica se ultrapassou o limite do display
if (_snake_positions[0].lin < 0) { _gameOver(); }
if (_snake_positions[0].lin >= _display->rows() ) { _gameOver(); }
if (_snake_positions[0].col < 0) { _gameOver(); }
if (_snake_positions[0].col >= _display->columns() ) { _gameOver(); }
//verifica se colidiu na cobra
for (int i=_length-1; i>0; i--){
if (_snake_positions[i].lin == _snake_positions[0].lin && _snake_positions[i].col == _snake_positions[0].col) {
_gameOver();
}
}
//verifica se comeu a maça
if (_snake_positions[0].col == _apple.col && _snake_positions[0].lin == _apple.lin){
_inc_length();
if (_length > SNAKE_MAX_LEN) { _length = SNAKE_MAX_LEN; } else {
_snake_positions[_length-1].lin = _snake_positions[_length-2].lin;
_snake_positions[_length-1].col = _snake_positions[_length-2].col;
}
_generateApple();
}
//update display
for (int lin=0; lin<_display->rows(); lin++) {
for (int col=0; col<_display->columns(); col++) {
for (int p=0; p<_length; p++){
boolean val = _snake_positions[p].col==col && _snake_positions[p].lin==lin;
_display->write( lin, col, val );
if (val) {break;}
}
}
}
_display->write(_apple.lin, _apple.col, HIGH);
//--
}
public:
SnakeGame(BitArray2D * display){
_display = display;
_ur = new UniqueRandom( _display->rows() * _display->columns() );
start();
}
void start(){
_length = 1;
_score = 0;
_time = SNAKE_TIME_INIT;
_last_millis = 0;
_snake_positions[0].lin = _display->rows() / 2;
_snake_positions[0].col = _display->columns() / 2;
_direction = DIR_STOP;
_snakeStatus = SNAKE_GAME_ON;
_generateApple();
}
void left() { if (_direction == DIR_RIGHT) return; _direction = DIR_LEFT; }
void right() { if (_direction == DIR_LEFT) return; _direction = DIR_RIGHT; }
void top() { if (_direction == DIR_BOTTOM) return; _direction = DIR_TOP; }
void bottom() { if (_direction == DIR_TOP) return; _direction = DIR_BOTTOM; }
int getScore(){ return _score; }
Direction getDirection(){ return _direction; }
int update(){
int r = false;
if (millis() - _last_millis > _time) {
r = true;
_last_millis = millis();
if (_snakeStatus == SNAKE_GAME_ON) { _run(); }
if (_snakeStatus == SNAKE_GAME_OVER) { _runGameOver(); }
}
return r; //r-->indica se houve mudança no display
}
};
/*************************************************************************************************************
*******************************FIM CLASSE SNAKE GAME**********************************************************
**************************************************************************************************************/
/*************************************************************************************************************
************************************CLASSE ROTARY ENCODER*****************************************************
mais informações: http://fabianoallex.blogspot.com.br/2016/05/arduino-rotary-encoder.html
*************************************************************************************************************/
#define ROTARY_NO_BUTTON 255
struct RotaryEncoderLimits{
int min;
int max;
};
class RotaryEncoder {
private:
byte _pin_clk;
byte _pin_dt;
byte _pin_sw;
volatile byte _num_results;
volatile int _result;
volatile int * _results;
byte _index_result;
RotaryEncoderLimits * _limits;
boolean _a;
boolean _b;
public:
RotaryEncoder(byte pin_clk, byte pin_dt, byte pin_sw = ROTARY_NO_BUTTON, byte num_results=1, RotaryEncoderLimits * limits=0){ //parametro do botao opcional
_pin_clk = pin_clk;
_pin_dt = pin_dt;
_pin_sw = pin_sw;
pinMode(_pin_clk, INPUT);
pinMode(_pin_dt, INPUT);
if (_pin_sw != ROTARY_NO_BUTTON){
pinMode(_pin_sw, INPUT);
digitalWrite(_pin_sw, HIGH);
}
if (num_results == 0) { num_results = 1; }
_num_results = num_results;
_results = new int[_num_results];
for (int i; i<_num_results; i++){ _results[i] = (limits) ? limits[i].min : 0; }
_index_result = 0;
_limits = limits;
_a = false;
_b = false;
}
byte getIndex() { return _index_result; }
void next() { _index_result++; if (_index_result >= _num_results) { _index_result = 0; } }
void update_a() {
_result = 0;
delay (1);
if( digitalRead(_pin_clk) != _a ) {
_a = !_a;
if ( _a && !_b ) { _result = -1; }
}
if (_results[_index_result]+_result >= _limits[_index_result].min &&
_results[_index_result]+_result <= _limits[_index_result].max ) {
_results[_index_result] += _result;
}
}
void update_b() {
_result = 0;
delay (1);
if( digitalRead(_pin_dt) != _b ) {
_b = !_b;
if ( _b && !_a ) { _result = +1; }
}
if (_results[_index_result]+_result >= _limits[_index_result].min &&
_results[_index_result]+_result <= _limits[_index_result].max ) {
_results[_index_result] += _result;
}
}
int read(){ return _result; } //retorn -1, 0 ou 1.
int getValue(int index=-1) { //retorna o valor da variável corrente ou a passada como parametro
if (index < 0 ){ return _results[_index_result]; }
return _results[index];
}
void setValue(int value, int index=-1){ _results[ (index==-1) ? _index_result : index] = value; } //caso a variável inicializa em determinado valor diferente de zero, utilizar esse método.
int buttonRead(){ return (_pin_sw == ROTARY_NO_BUTTON) ? LOW : digitalRead(_pin_sw); }
};
/*************************************************************************************************************
************************************FIM CLASSE ROTARY ENCODER*************************************************
*************************************************************************************************************/
/*************************************************************************************************************
*******************************CLASSE SNAKE LCD***************************************************************
**************************************************************************************************************/
/*
0 1 2 3 4 5 6
*** *** *** ***
*** *** *** ***
*** *** *** ***
*** *** *** ***
*** *** *** ***
*** *** *** ***
*/
byte c0[8] = {B11111, B11111, B00000, B11111, B11111, B00000, B11111, B11111 };
byte c1[8] = {B00000, B00000, B00000, B11111, B11111, B00000, B11111, B11111 };
byte c2[8] = {B00000, B00000, B00000, B00000, B00000, B00000, B11111, B11111 };
byte c3[8] = {B11111, B11111, B00000, B11111, B11111, B00000, B00000, B00000 };
byte c4[8] = {B11111, B11111, B00000, B00000, B00000, B00000, B11111, B11111 };
byte c5[8] = {B00000, B00000, B00000, B11111, B11111, B00000, B00000, B00000 };
byte c6[8] = {B11111, B11111, B00000, B00000, B00000, B00000, B00000, B00000 };
class SnakeLCD {
private:
LiquidCrystal * _lcd; //ponteiro para um objeto lcd
public:
void createChars() {
_lcd->createChar(0, c0);
_lcd->createChar(1, c1);
_lcd->createChar(2, c2);
_lcd->createChar(3, c3);
_lcd->createChar(4, c4);
_lcd->createChar(5, c5);
_lcd->createChar(6, c6);
}
SnakeLCD(LiquidCrystal * lcd) { _lcd = lcd; }
void write(byte col, byte row, byte val){
_lcd->setCursor(col, row);
if (val == B000) { _lcd->print(" "); }
if (val == B111) { _lcd->write((byte)0); }
if (val == B011) { _lcd->write((byte)1); }
if (val == B001) { _lcd->write((byte)2); }
if (val == B110) { _lcd->write((byte)3); }
if (val == B101) { _lcd->write((byte)4); }
if (val == B010) { _lcd->write((byte)5); }
if (val == B100) { _lcd->write((byte)6); }
}
};
/*************************************************************************************************************
*******************************FIM CLASSE SNAKE LCD***********************************************************
**************************************************************************************************************/
/*************************************************************************************************************
*******************************DECLARACAO DOS OBJETOS*********************************************************
**************************************************************************************************************/
/*
tamanho display real
* * * * * * * * * * * * * * * *
* * * * * * * * * * * * * * * *
* * * * * * * * * * * * * * * *
* * * * * * * * * * * * * * * *
* * * * * * * * * * * * * * * *
* * * * * * * * * * * * * * * *
*/
const int LINHAS = 6;
const int COLUNAS = 16;
LiquidCrystal lcd(12, 11, 10, 9, 8, 7);
SnakeLCD snakeLcd(&lcd);
BitArray2D ba(LINHAS, COLUNAS); //8 linhas e 20 colunas, usada para armazenar o estado do display
SnakeGame snake(&ba);
RotaryEncoderLimits lim[] = { {-1000,1000} }; //limites máximos e mínimos que as variaveis podem atingir
RotaryEncoder re(A0, A1, 4, 1, lim); //pino clk, pino dt, pino sw, variaveis, limites
/*************************************************************************************************************
*******************************FIM DECLARACAO DOS OBJETOS*****************************************************
**************************************************************************************************************/
/*************************************************************************************************************
*******************************TRATAMENTO DAS INTERRUPÇÕES****************************************************
**************************************************************************************************************/
//interrupções dos pinos A0 e A1 via Pin Change Interrupt
ISR(PCINT1_vect) {
volatile static byte lastVal_a0 = LOW;
volatile static byte lastVal_a1 = LOW;
byte val_a0 = digitalRead(A0);
byte val_a1 = digitalRead(A1);
if (lastVal_a0 != val_a0){ re.update_a(); lastVal_a0 = val_a0; }
if (lastVal_a1 != val_a1){ re.update_b(); lastVal_a1 = val_a1; }
}
void setup_interrupts(){
//-----PCI - Pin Change Interrupt ----
pinMode(A0,INPUT); // set Pin as Input (default)
digitalWrite(A0,HIGH); // enable pullup resistor
pinMode(A1,INPUT); // set Pin as Input (default)
digitalWrite(A1,HIGH); // enable pullup resistor
cli();
PCICR |= 0b00000010; // habilita a porta C - Pin Change Interrupts
PCMSK1 |= 0b00000011; // habilita interrupção da porta c nos pinos: PCINT8 (A0) e PCINT9(A1)
sei();
}
/*************************************************************************************************************
*******************************FIM TRATAMENTO DAS INTERRUPÇÕES****************************************************
**************************************************************************************************************/
void update_display() {
for (int col=0; col<ba.columns(); col++) {
byte lin_lcd = 0;
byte cont = 0;
byte val = 0;
for (int lin=0; lin<ba.rows(); lin++) {
if (cont == 0){ val = 0; }
val = val << 1;
val = val | ba.read(lin, col);
cont++;
if (cont == 3) {
snakeLcd.write(col, lin_lcd, val) ;
cont = 0;
lin_lcd++;
}
}
}
}
void setup() {
Serial.begin(9600);
setup_interrupts();
lcd.begin(16, 2);
snakeLcd.createChars();
randomSeed(analogRead(A2));
re.setValue(0, 0); //inicializa o rotary em 0
}
void loop() {
static int val_encoder = 0;
static boolean change = false;
if (re.getValue(0) < val_encoder && !change){
if (snake.getDirection() == DIR_STOP) {snake.right(); } else
if (snake.getDirection() == DIR_TOP) { snake.left(); } else
if (snake.getDirection() == DIR_RIGHT) { snake.top(); } else
if (snake.getDirection() == DIR_LEFT) { snake.bottom(); } else
if (snake.getDirection() == DIR_BOTTOM) { snake.right(); }
change = true;
}
if (re.getValue(0) > val_encoder && !change ){
if (snake.getDirection() == DIR_STOP) {snake.right(); } else
if (snake.getDirection() == DIR_TOP) { snake.right(); } else
if (snake.getDirection() == DIR_RIGHT) { snake.bottom(); } else
if (snake.getDirection() == DIR_LEFT) { snake.top(); } else
if (snake.getDirection() == DIR_BOTTOM) { snake.left(); }
change = true;
}
val_encoder = re.getValue(0);
if ( snake.update() ) {
change = false;
update_display();
Serial.println(val_encoder);
}
//controla o click do botao do enconder
static byte b = HIGH; //pra ler apenas uma vez o botao ao pressionar
if( re.buttonRead() == LOW && b != re.buttonRead() ) {
re.next(); //passa para a próxima variável (index)
delay(200); //debounce meia boca
}
b = re.buttonRead();
}
</pre>
<br />
<br />Fabiano Arndthttp://www.blogger.com/profile/15268923442674744187noreply@blogger.com2tag:blogger.com,1999:blog-5513833072608837410.post-79587291173930580492016-05-25T15:33:00.001-04:002016-06-16T10:13:50.278-04:00Arduino - Entrada de Texto com Rotary EncoderEm algumas aplicações, temos eventualmente a necessidade de que o usuário digite algum texto. A maneira mais comum de se fazer isso é utilizando um teclado. Mas nem sempre essa é uma alternativa viável. Algum tempo atrás mostrei como digitar texto com teclado matricial numérico, como pode ser visto nesse link: <a href="http://fabianoallex.blogspot.com.br/2015/05/arduino-teclado-4x3-texto-lcd.html">http://fabianoallex.blogspot.com.br/2015/05/arduino-teclado-4x3-texto-lcd.html</a> e nesse outro, uma versão parecida, mas utilizando um controle remoto: <a href="http://fabianoallex.blogspot.com.br/2015/05/arduino-digitar-texto-com-controle.html">http://fabianoallex.blogspot.com.br/2015/05/arduino-digitar-texto-com-controle.html</a>. Porém, as vezes, nem uma dessas soluções é a mais viável de se utilizar, caso a entrada de dados seja algo muito eventual, ou ainda quando queremos algo que não ocupe tanto espaço.<br />
<br />
Pensando nisso, aproveitei que estava com a mão na massa com o meu último artigo sobre Rotary Encoders, e desenvolvi uma classe pra escrever texto em um display LCD através de um Rotary Encoder.<br />
<br />
Para maiores detalhes sobre o Rotary Encoder veja o artigo completo aqui: <a href="http://fabianoallex.blogspot.com.br/2016/05/arduino-rotary-encoder.html">http://fabianoallex.blogspot.com.br/2016/05/arduino-rotary-encoder.html</a><br />
<br />
Vídeo:<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<iframe allowfullscreen="" class="YOUTUBE-iframe-video" data-thumbnail-src="https://i.ytimg.com/s_vi/ba5SlzycbU0/default.jpg?sqp=CLj6l7oF&rs=AOn4CLD8waDdaJVzfXoXNhVGiOa2yx0HTw" frameborder="0" height="415" src="https://www.youtube.com/embed/ba5SlzycbU0?feature=player_embedded" width="660"></iframe></div>
<br />
<br />
Código-Fonte:<br />
<br />
<pre class="brush: html" style="background-color: #eeeeee; border: 1px dashed rgb(153, 153, 153); overflow: auto; padding: 5px; width: 100%;">/*
Fabiano A. Arndt - 2016
www.youtube.com/user/fabianoallex
www.facebook.com/dicasarduino
fabianoallex@gmail.com
*/
#include <LiquidCrystal.h>
/*************************************************************************************************************
************************************CLASSE ROTARY ENCODER*****************************************************
*************************************************************************************************************/
#define ROTARY_NO_BUTTON 255
struct RotaryEncoderLimits{
int min;
int max;
};
class RotaryEncoder {
private:
byte _pin_clk;
byte _pin_dt;
byte _pin_sw;
volatile byte _num_results;
volatile int _result;
volatile int * _results;
byte _index_result;
RotaryEncoderLimits * _limits;
boolean _a;
boolean _b;
public:
RotaryEncoder(byte pin_clk, byte pin_dt, byte pin_sw = ROTARY_NO_BUTTON, byte num_results=1, RotaryEncoderLimits * limits=0){ //parametro do botao opcional
_pin_clk = pin_clk;
_pin_dt = pin_dt;
_pin_sw = pin_sw;
pinMode(_pin_clk, INPUT);
pinMode(_pin_dt, INPUT);
if (_pin_sw != ROTARY_NO_BUTTON){
pinMode(_pin_sw, INPUT);
digitalWrite(_pin_sw, HIGH);
}
if (num_results == 0) { num_results = 1; }
_num_results = num_results;
_results = new int[_num_results];
for (int i; i<_num_results; i++){ _results[i] = (limits) ? limits[i].min : 0; }
_index_result = 0;
_limits = limits;
_a = false;
_b = false;
}
byte getIndex() { return _index_result; }
void next() { _index_result++; if (_index_result >= _num_results) { _index_result = 0; } }
void update_a() {
_result = 0;
delay (1);
if( digitalRead(_pin_clk) != _a ) {
_a = !_a;
if ( _a && !_b ) { _result = -1; }
}
if (_results[_index_result]+_result >= _limits[_index_result].min &&
_results[_index_result]+_result <= _limits[_index_result].max ) {
_results[_index_result] += _result;
}
}
void update_b() {
_result = 0;
delay (1);
if( digitalRead(_pin_dt) != _b ) {
_b = !_b;
if ( _b && !_a ) { _result = +1; }
}
if (_results[_index_result]+_result >= _limits[_index_result].min &&
_results[_index_result]+_result <= _limits[_index_result].max ) {
_results[_index_result] += _result;
}
}
int read(){ return _result; } //retorn -1, 0 ou 1.
int getValue(int index=-1) { //retorna o valor da variável corrente ou a passada como parametro
if (index < 0 ){ return _results[_index_result]; }
return _results[index];
}
void setValue(int value) { _results[_index_result] = value; } //caso a variável inicializa em determinado valor diferente de zero, utilizar esse método.
void setValue(int index, int value){ _results[index] = value; } //caso a variável inicializa em determinado valor diferente de zero, utilizar esse método.
int buttonRead(){ return (_pin_sw == ROTARY_NO_BUTTON) ? LOW : digitalRead(_pin_sw); }
};
/*************************************************************************************************************
************************************FIM CLASSE ROTARY ENCODER*************************************************
*************************************************************************************************************/
/*************************************************************************************************************
************************************CLASSE ROTARY KEYBOARD****************************************************
*************************************************************************************************************/
const char teclas[] = {"abcdefghijklmnopqrstuvxwyz 0123456789"}; //caracteres a serem escolhidos
const unsigned long time_char = 1200; //1200 milissegundos pra desconsiderar a ultima tecla
class RotaryKeyBoard {
private:
unsigned long _millis_last_char;
char _last_char;
String _palavra;
RotaryEncoder * _re;
void _set_last_char(char c, int ind_palavra){
if ( ind_palavra == 1 && _last_char != '\0' ) { _palavra += _last_char; }
_last_char = c;
_millis_last_char = millis();
}
void _add(char c) {
if ( is_timing() ) {
_set_last_char( (teclas[_re->getValue(0)] == '\0') ? _last_char = teclas[0] : _last_char = teclas[_re->getValue(0)] , 0 );
return ;
}
_set_last_char (c, 1);
}
public:
RotaryKeyBoard(RotaryEncoder * re){
_re = re;
_millis_last_char = millis();
_last_char = '\0';
_palavra = "";
update();
backspace();
_re->setValue(0, 0);
}
char get_last_char() { return _last_char; }
int is_timing() { return ( (millis() - time_char) < _millis_last_char ); }
String get_palavra() { return (_last_char) ? _palavra + _last_char : _palavra; }
void backspace(){
if (_palavra.length() >= 1){
_last_char = _palavra[_palavra.length()-1];
_palavra = _palavra.substring(0, _palavra.length()-1);
} else {
_last_char = '\0';
}
}
void update() {
static char tecla_anterior = '\0';
char tecla = teclas[_re->getValue(0)];
if (tecla != tecla_anterior){
if (tecla) { _add(tecla); }
}
tecla_anterior = tecla;
}
};
/*************************************************************************************************************
************************************FIM CLASSE ROTARY KEYBOARD************************************************
*************************************************************************************************************/
/*************************************************************************************************************
************************************DECLARACOES DOS OBJETOS***************************************************
*************************************************************************************************************/
LiquidCrystal lcd(12, 11, 10, 9, 8, 7);
RotaryEncoderLimits lim[] = { {0, sizeof(teclas)-1 } }; //limites máximos e mínimos que as variaveis podem atingir
RotaryEncoder re(A0, A1, 4, 1, lim); //pino clk, pino dt, pino sw, variaveis
RotaryKeyBoard teclado(&re);
/*************************************************************************************************************
************************************FIM DECLARACOES DOS OBJETOS***********************************************
*************************************************************************************************************/
/*************************************************************************************************************
************************************TRATAMENTO DAS INTERRUPÇÕES***********************************************
*************************************************************************************************************/
ISR(PCINT1_vect) {
volatile static byte lastVal_a0 = LOW;
volatile static byte lastVal_a1 = LOW;
byte val_a0 = digitalRead(A0);
byte val_a1 = digitalRead(A1);
if (lastVal_a0 != val_a0){ re.update_a(); lastVal_a0 = val_a0; }
if (lastVal_a1 != val_a1){ re.update_b(); lastVal_a1 = val_a1; }
}
void setup_interrupts(){
pinMode(A0,INPUT); // set Pin as Input (default)
digitalWrite(A0,HIGH); // enable pullup resistor
pinMode(A1,INPUT); // set Pin as Input (default)
digitalWrite(A1,HIGH); // enable pullup resistor
cli();
PCICR |= 0b00000010; // habilita a porta C - Pin Change Interrupts
PCMSK1 |= 0b00000011; // habilita interrupção da porta c nos pinos: PCINT8 (A0) e PCINT9(A1)
sei();
}
/*************************************************************************************************************
************************************FIM TRATAMENTO DAS INTERRUPÇÕES*******************************************
*************************************************************************************************************/
/*************************************************************************************************************
************************************SETUP / LOOP**************************************************************
*************************************************************************************************************/
void setup() {
setup_interrupts();
lcd.begin(16, 2);
Serial.begin(9600);
}
void loop() {
teclado.update();
lcd.clear();
lcd.setCursor(0, 0);
lcd.print(teclado.get_palavra());
lcd.cursor();
lcd.setCursor(teclado.get_palavra().length() - (teclado.is_timing() ? 1 : 0 ), 0);
//controla o click do botao do enconder
static byte b = HIGH; //pra ler apenas uma vez o botao ao pressionar
if( re.buttonRead() == LOW && b != re.buttonRead() ) {
teclado.backspace();
delay(200);
}
b = re.buttonRead();
delay(100);
}
/*************************************************************************************************************
************************************FIM SETUP / LOOP**************************************************************
*************************************************************************************************************/
</pre>
<br />Fabiano Arndthttp://www.blogger.com/profile/15268923442674744187noreply@blogger.com2tag:blogger.com,1999:blog-5513833072608837410.post-14325670611713981462016-05-20T17:26:00.000-04:002016-06-16T10:13:50.241-04:00Arduino - Rotary EncoderMuito comum em rádios de carros, o Rotary Encoder é um componente que se assemelha muito a um potenciômetro, porém com algumas diferenças importantes em seu funcionamento.<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh7Oo-avD6ZOQMZme0hMwQZ39EObBZRy3bRRVwQuZIWs5ZK-d9HrsBBBWd5SGBM7eDddZzK9MB8jpSy3PSI6c-Hz1fas9019Dgh8lhfCZdoa-rFuB7p3pasESgXn8DiUfiEpzt52dotAqE/s1600/radio.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="200" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh7Oo-avD6ZOQMZme0hMwQZ39EObBZRy3bRRVwQuZIWs5ZK-d9HrsBBBWd5SGBM7eDddZzK9MB8jpSy3PSI6c-Hz1fas9019Dgh8lhfCZdoa-rFuB7p3pasESgXn8DiUfiEpzt52dotAqE/s400/radio.jpg" width="400" /></a></div>
<span style="background-color: white;">Obs. Há diversos modelos de Encoders com as mais variadas finalidades, como por exemplo as que são utilizados em mouse de computador (aquela rodinha utilizada para o scroll), mas o que iremos abordar aqui são os chamados Rotary Encoders do tipo "Quadrature Enconder".</span><br />
<div>
<br /></div>
<div>
Enquanto um potenciômetro possui limites físico de mínimo e máximo, o que limita o ângulo de giro do mesmo, um Rotary Encoder não os possui, podendo ser rotacionado infinitamente para qualquer um dos sentidos: horário e anti-horário. Isso significa que há diferenças na forma de trabalharmos com cada um pois em um potenciômetro a leitura é feita de modo analógico, ou seja, basicamente o potenciômetro é um divisor de tensão variável que tem seu valor alterado de acordo com a posição física do mesmo, enquanto que um Rotary Encoder, a informação que ele nos dá, não é relacionada à posição física em que o eixo se encontra, e sim baseado no sentido ao qual ele está girando ou se está parado, de modo que conforme ele gira, são emitidos sinais através de dois pinos que indicam o sentido de giro. Há ainda um botão em seu próprio eixo, o qual é muito útil em diversas aplicações.<br />
<br />
Existem vantagens e desvantagens em seu uso em comparação com potenciômetros. Em geral um único Rotary Encoder pode ser utilizado pra configurar diferentes parâmetros em uma mesma aplicação, como em um rádio de carro, onde o mesmo pode ser utilizado pra aumentar ou diminuir o volume, busca por estações de rádio, ajustes no som como balanço, agudo, grave, etc, o que seria mais complicado de se fazer com um potenciômetro.<br />
<br />
Outra vantagem de um Rotary Encoder é que ele pode ser utilizado em paralelo com outras interfaces. Por exemplo, imagine que você tenha que projetar um dimmer com ajuste através de um Rotary Encoder, mas você também gostaria de fazer esse mesmo ajuste através de uma página Web ou ainda de um celular via bluetooth. Ou seja, você teria diversas interfaces para controlar o mesmo parâmetro, o que seria um pouco mais complicado de se fazer com um potenciômetro, já que se um potenciômetro está na metade, por exemplo, seria impossível fazer esse ajuste a partir de qualquer outro lugar, pois fisicamente o potenciômetro continuaria na mesma posição.<br />
<br />
Porém em várias aplicações, esses ajustes ou configurações, devem ser “lembrados” (ou seja, gravados em algum lugar), para que quando a aplicação for desligada não perca os dados informados, algo que não seria necessário com o uso de potenciômetros, pois o estado do potenciômetro se mantem mesmo desligado, ao contrário do Rotary Encoder.<br />
<br />
Nas duas imagens abaixo, podemos ver que internamente um Rotary Encoder é composto por um eixo rotativo (Rotating shaft), o qual possui fixado a si um disco (Slit disk) com várias fendas espaçadas igualmente. Oposto a esse disco há um segundo disco (Fixed slit) com as mesmas fendas igualmente espaçadas. Ambos os discos ficam entremeio a um led e um par de fotos-transistores. Conforme o eixo gira, a luz emitida pelo diodo é interrompida/liberada pelos discos, de modo que cada um dos fotos-transistores emitam sinais em momentos diferentes.<br />
<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
</div>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjekBSu_l2BmS7jxjw0mvYoZajHj7Q2u3UxKiz7IdZkGtE0YbpDfb2HmcxK03GHPOMv-FWPHkLEDRm1k-EFdKVPX567j55m6TtGPHNEHg6EWQilgFVS9skLwRwzk6bt-U5YpTgswSoK238/s1600/rp_principle_e.gif" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="207" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjekBSu_l2BmS7jxjw0mvYoZajHj7Q2u3UxKiz7IdZkGtE0YbpDfb2HmcxK03GHPOMv-FWPHkLEDRm1k-EFdKVPX567j55m6TtGPHNEHg6EWQilgFVS9skLwRwzk6bt-U5YpTgswSoK238/s400/rp_principle_e.gif" width="400" /></a></div>
<div class="separator" style="clear: both; text-align: center;">
<br /></div>
<div class="separator" style="clear: both; text-align: left;">
Com isso é possível, baseado em qual dos fotos-transistores (Quad input A ou B) foi a 1 (ou HIGH) primeiro, saber se está sendo girado o eixo no sentido horário ou anti-horário. A partir da contagem dos passos que são dados em determinada direção é possível saber o quanto o Rotary girou.</div>
<div class="separator" style="clear: both; text-align: center;">
<br /></div>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhal_I6Opq6523CJOVZCbHVFEKgnH_MpEAvZV98GzbrF3bYYJUxdbsm2XTl1-_q9BiayfmwclaxvHwU1ISnxoa_K8tP2wpJYjbAx2tYN4UbhIjn3O7djjf2mQ6W8oMlrkV44HVM4F5BVaI/s1600/cont_sens3.gif" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="316" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhal_I6Opq6523CJOVZCbHVFEKgnH_MpEAvZV98GzbrF3bYYJUxdbsm2XTl1-_q9BiayfmwclaxvHwU1ISnxoa_K8tP2wpJYjbAx2tYN4UbhIjn3O7djjf2mQ6W8oMlrkV44HVM4F5BVaI/s400/cont_sens3.gif" width="400" /></a></div>
<br />
<div class="separator" style="clear: both; text-align: center;">
</div>
<br />
<br />
Em nossos exemplos iremos utilizar um módulo igual ao mostrado na imagem abaixo, composto por um Rotary encoder e dois resistores de pull-up, um ligado ao pino CLK e outro ao Pino DT, há ainda no próprio módulo, espaço para ser ligado um resitor de pull-up ao pino do botão, porém o resistor não vem conectado ao módulo, como pode ser visto na imagem abaixo a esquerda:<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiRK-skAXGELZQOAZ41OD5ZWstpMdfwIHQLDvh9qTO1QGaO1V0xcQ3d57wlhu4b7nLtk_yKDSPzfNCFkbJSyUrPSOUI30CBGT07Cgzb-VnxqkUGZJPU79_Lglq01U6z2xQCHi5FCDDwSy0/s1600/r1.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="200" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiRK-skAXGELZQOAZ41OD5ZWstpMdfwIHQLDvh9qTO1QGaO1V0xcQ3d57wlhu4b7nLtk_yKDSPzfNCFkbJSyUrPSOUI30CBGT07Cgzb-VnxqkUGZJPU79_Lglq01U6z2xQCHi5FCDDwSy0/s200/r1.jpg" width="200" /></a><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEicXK3AFpUfHSwXaXhogQyJuOBO8KVC7G3EZ-aLRC_la6y0mA8eHAaGa9mf5nwrUktCxMH3T_sIYogr4Fb3Qd951mKfJbZE1vzdZVZ4VuftzgLoaAZFq4GWkkvyPllbt_bkhJSKq6oVCJk/s1600/r2.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="200" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEicXK3AFpUfHSwXaXhogQyJuOBO8KVC7G3EZ-aLRC_la6y0mA8eHAaGa9mf5nwrUktCxMH3T_sIYogr4Fb3Qd951mKfJbZE1vzdZVZ4VuftzgLoaAZFq4GWkkvyPllbt_bkhJSKq6oVCJk/s200/r2.jpg" width="200" /></a></div>
<br />
<br />
<b>Exemplo sem interrupções</b><br />
<br />
No nosso primeiro exemplo vamos ver como implementar uma classe pra manipular o Rotary Encoder, mas sem o uso de Interrupções. Em geral, vários dos exemplos que encontramos na internet se baseiam na comunicação com o Arduino, através do uso de interrupções, porém, nem sempre é viável utilizá-las, pois a quantidade de interrupções de um microcontrolador são limitadas e podem já estar em uso por outros dispositivos, ou mesmo que não estejam sendo usadas, podemos estar trabalhando em uma aplicação que não tenha rotinas que demandem tempo de processamento muito alto, o que poderia permitir o uso sem maiores problemas de uma solução sem interrupções. Uma desvantagem desse método, é que podem ocorrer perdas de leituras durante o uso do enconder caso o Arduino esteja executando outra tarefa no exato momento de mudança de estado do encoder. Em geral, perder alguns pulsos ao utilizar o Encoder não é tão crítico assim, já que o próprio usuário tem o feedback visual e faz a correção até alcançar o valor desejado, porém, se o encoder estiver sendo utilizado pra detectar os giros de um motor, por exemplo, perder pulsos não é aceitável e pode levar a erros de execução da aplicação, logo, essa solução não seria a mais adequada. Entretanto, o que vai definir qual tipo de solução a ser utilizada serão as necessidades do seu projeto. Nesse artigo veremos soluções com e sem interrupções.<br />
<br />
Ligação<br />
<br />
<div style="text-align: center;">
encoder | arduino</div>
<div style="text-align: center;">
clk --> pino 2</div>
<div style="text-align: center;">
dt --> pino 3</div>
<div style="text-align: center;">
sw --> pino 8</div>
<b><br /></b>
</div>
<pre class="brush: html" style="background-color: #eeeeee; border: 1px dashed rgb(153, 153, 153); overflow: auto; padding: 5px; width: 100%;">/**********************************************************************************
************************************CLASSE ROTARY ENCODER**************************
**********************************************************************************/
class RotaryEncoder {
private:
byte _pin_clk;
byte _pin_dt;
byte _pin_sw;
int _result;
public:
RotaryEncoder(byte pin_clk, byte pin_dt, byte pin_sw = 255){ //parametro do botao opcional
_pin_clk = pin_clk;
_pin_dt = pin_dt;
_pin_sw = pin_sw;
pinMode(_pin_clk, INPUT);
pinMode(_pin_dt, INPUT);
if (_pin_sw != 255){
pinMode(_pin_sw, INPUT);
digitalWrite(_pin_sw, HIGH);
}
}
void update() {
static int oldA = HIGH;
static int oldB = HIGH;
static unsigned long lmillis = 0;
_result = 0;
if (millis() - 1 > lmillis){ //previne leituras falsas
int newA = digitalRead(_pin_clk);
int newB = digitalRead(_pin_dt);
if ( (newA != oldA || newB != oldB) && (oldA == HIGH && newA == LOW) ) { _result = (-oldB * 2 + 1); }
oldA = newA;
oldB = newB;
lmillis = millis();
}
}
int read(){ return _result; } //retorn -1, 0 ou 1.
int buttonRead(){ return (_pin_sw == 255) ? LOW : digitalRead(_pin_sw); } //retorna a leitura do pino do botão do encoder, caso tenha sido utilizado o botão
};
/**********************************************************************************
************************************FIM CLASSE ROTARY ENCODER**********************
**********************************************************************************/
RotaryEncoder re(3, 2, 8); //pinos clk, dt, sw
int val = 0;
void setup() { Serial.begin(9600); }
void loop() {
int pressionou = false;
re.update(); //faz a leitura do rotary encoder
if(re.buttonRead() == LOW) {
if (val != 0) { pressionou = true; }
val = 0;
}
if (re.read() != 0 || pressionou) { //se rotacionou ou pressionou o botão --> -1, 0 ou 1
val += re.read(); //incrementa ou decrementa
Serial.print(val);
Serial.print(" ");
Serial.println(re.read());
}
}
</pre>
<div>
<br /></div>
<div>
<br />
O que fizemos no código anterior foi criar uma classe que gerencia um Rotary Encoder a qual apenas nos diz em qual sentido o encoder girou ou se está parado. Porém a variável que acumula os incrementos e decrementos, não é gerenciada pela classe e sim externamente.<br />
<br />
No nosso próximo exemplo vamos incluir algumas funcionalidades extras, onde a própria classe irá gerenciar a variável incrementada, mas com a possibilidade de serem gerenciadas mais de uma variável, além disso permitiremos definir limites mínimos e máximos para cada variável. Isso é útil quando o mesmo encoder é utilizado para configurar vários parâmetros diferentes, como acontece no rádio de um carro, onde o mesmo é utilizado para aumentar o volume, balanço, grave, etc.<br />
<br />
Controlando múltiplos parâmetros com o mesmo encoder:<br />
<br />
<pre class="brush: html" style="background-color: #eeeeee; border: 1px dashed rgb(153, 153, 153); overflow: auto; padding: 5px; width: 100%;">/**********************************************************************************
************************************CLASSE ROTARY ENCODER**************************
**********************************************************************************/
#define ROTARY_NO_BUTTON 255
struct RotaryEncoderLimits{
int min;
int max;
};
class RotaryEncoder {
private:
byte _pin_clk;
byte _pin_dt;
byte _pin_sw;
byte _num_results;
int _result;
int * _results;
byte _index_result;
RotaryEncoderLimits * _limits;
public:
RotaryEncoder(byte pin_clk, byte pin_dt, byte pin_sw = ROTARY_NO_BUTTON, byte num_results=1, RotaryEncoderLimits * limits=0){ //parametro do botao opcional
_pin_clk = pin_clk;
_pin_dt = pin_dt;
_pin_sw = pin_sw;
pinMode(_pin_clk, INPUT);
pinMode(_pin_dt, INPUT);
if (_pin_sw != ROTARY_NO_BUTTON){
pinMode(_pin_sw, INPUT);
digitalWrite(_pin_sw, HIGH);
}
if (num_results == 0) { num_results = 1; }
_num_results = num_results;
_results = new int[_num_results];
for (int i; i<_num_results; i++){ _results[i] = (limits) ? limits[i].min : 0; }
_index_result = 0;
_limits = limits;
}
byte getIndex() { return _index_result; }
void next() { _index_result++; if (_index_result >= _num_results) { _index_result = 0; } }
void update() {
static int oldA = HIGH;
static int oldB = HIGH;
static unsigned long lmillis = 0;
_result = 0;
if (millis() - 1 > lmillis){ //previne leituras falsas
int newA = digitalRead(_pin_clk);
int newB = digitalRead(_pin_dt);
if ( (newA != oldA || newB != oldB) && (oldA == HIGH && newA == LOW) ) { _result = (-oldB * 2 + 1); }
oldA = newA;
oldB = newB;
lmillis = millis();
}
if (_results[_index_result]+_result >= _limits[_index_result].min &&
_results[_index_result]+_result <= _limits[_index_result].max ) {
_results[_index_result] += _result;
}
}
int read(){ return _result; } //retorn -1, 0 ou 1.
int getValue() {return _results[_index_result]; } //retorna o valor da variável
void setValue(int value){ _results[_index_result] = value; } //caso a variável inicializa em determinado valor diferente de zero, utilizar esse método.
int buttonRead(){ return (_pin_sw == ROTARY_NO_BUTTON) ? LOW : digitalRead(_pin_sw); }
};
/**********************************************************************************
************************************FIM CLASSE ROTARY ENCODER**********************
**********************************************************************************/
RotaryEncoderLimits lim[] = { {0,10}, {-5,5}, {-32768, 32767} };
RotaryEncoder re(3, 2, 8, 3, lim); //pinos clk, dt, sw, 3 valores diferentes serão controlados
byte lastButton = HIGH;
void setup() { Serial.begin(9600); }
void loop() {
int pressionou = false;
re.update(); //primeiro fazemos a chamada para o update para que seja feita a leitura do rotary encoder
if( re.buttonRead() == LOW && lastButton != re.buttonRead() ) { //a cada clique, passamos a gerenciar a próxima variável
re.next();
pressionou = true;
lastButton = re.buttonRead();
delay(300); //debounce meia boca
} else {
lastButton = re.buttonRead();
}
if (re.read() != 0 || pressionou) { //se rotacionou ou pressionou o botão --> -1, 0 ou 1
Serial.print("index: ");
Serial.print(re.getIndex());
Serial.print(" = ");
Serial.println(re.getValue());
}
}
</pre>
<br />
<span style="font-family: inherit;"><b>
Exemplo com uma interrupção</b></span><br />
<span style="font-family: inherit;"><b><br /></b></span>
Agora iremos modificar o código anterior, que executava apenas no loop, para que possamos controlar as chamadas ao método update através do uso de uma única interrupção. <br />
<br />
A vantagem do uso de interrupções, é que ela nos dá mais garantias de que não iremos perder pulsos, pois a resposta se dá imediatamente com a detecção da interrupção.<br />
<br />
Nos códigos anteriores atualizávamos nosso encoder através de chamadas ao método update dentro do próprio loop. Como agora devemos executar a chamada do método update através de uma interrupção, foi criada uma função chamada interrupt_re, a qual será responsável pela chamada ao método update da classe. Após ser executado o update, os dados internos da classe serão atualizados e poderão ser utilizados da maneira que necessitarmos em nosso programa.<br />
<br />
Essa classe detecta de maneira muito precisa os incrementos (sentido horário) e decrementos (sentido anti-horário) de estados na maioria dos casos, mas em uma determinada situação, ele perde a leitura de decremento, mas apenas quando a leitura anterior foi um incremento, ou seja, imagine que você esteja girando o encoder no sentido horário, fazendo vários incrementos, e em determinado momento resolve girar no sentido contrário, realizando alguns decrementos. Esse primeiro pulso, no sentido contrário, será perdido, não sendo detectado, porém a partir dos próximos decrementos a leitura se dá normalmente. Em várias aplicações, esse problema não chega a ser prejudicial ao usuário, mas cabe a você como programador avaliar isso.<br />
<br />
<pre class="brush: html" style="background-color: #eeeeee; border: 1px dashed rgb(153, 153, 153); overflow: auto; padding: 5px; width: 100%;">/**********************************************************************************
************************************CLASSE ROTARY ENCODER**************************
**********************************************************************************/
#define ROTARY_NO_BUTTON 255
struct RotaryEncoderLimits{
int min;
int max;
};
class RotaryEncoder {
private:
byte _pin_clk;
byte _pin_dt;
byte _pin_sw;
byte _num_results;
volatile int _result;
volatile int * _results;
byte _index_result;
RotaryEncoderLimits * _limits;
public:
RotaryEncoder(byte pin_clk, byte pin_dt, byte pin_sw = ROTARY_NO_BUTTON, byte num_results=1, RotaryEncoderLimits * limits=0){ //parametro do botao opcional
_pin_clk = pin_clk;
_pin_dt = pin_dt;
_pin_sw = pin_sw;
pinMode(_pin_clk, INPUT);
pinMode(_pin_dt, INPUT);
if (_pin_sw != ROTARY_NO_BUTTON){
pinMode(_pin_sw, INPUT);
digitalWrite(_pin_sw, HIGH);
}
if (num_results == 0) { num_results = 1; }
_num_results = num_results;
_results = new int[_num_results];
for (int i; i<_num_results; i++){ _results[i] = (limits) ? limits[i].min : 0; }
_index_result = 0;
_limits = limits;
}
byte getIndex() { return _index_result; }
void next() { _index_result++; if (_index_result >= _num_results) { _index_result = 0; } }
void update() {
static int oldA = HIGH;
static int oldB = HIGH;
_result = 0;
int newA = digitalRead(_pin_clk);
int newB = digitalRead(_pin_dt);
if ( (newA != oldA || newB != oldB) && (oldA == HIGH && newA == LOW ) ) { _result = (- oldB * 2 + 1); }
oldA = newA;
oldB = newB;
if (_results[_index_result]+_result >= _limits[_index_result].min &&
_results[_index_result]+_result <= _limits[_index_result].max ) {
_results[_index_result] += _result;
}
}
int read(){ return _result; } //retorn -1, 0 ou 1.
int getValue() {return _results[_index_result]; } //retorna o valor da variável corrente
void setValue(int value){ _results[_index_result] = value; } //caso a variável inicializa em determinado valor diferente de zero, utilizar esse método.
int buttonRead(){ return (_pin_sw == ROTARY_NO_BUTTON) ? LOW : digitalRead(_pin_sw); }
};
/**********************************************************************************
************************************FIM CLASSE ROTARY ENCODER**********************
**********************************************************************************/
RotaryEncoderLimits lim[] = { {0,10}, {-5,5}, {-32768, 32767} }; //limites máximos e mínimos que as variaveis podem atingir
RotaryEncoder re(2, 3, 8, 3, lim); //pino clk, pino dt, pino sw, variaveis
//executado pela interrupcao
void interrupt_re() {
re.update();
if (re.read() != 0 ) { //se rotacionou --> -1, 0 ou 1
Serial.print("index: ");
Serial.print(re.getIndex());
Serial.print(" = ");
Serial.print(re.getValue());
Serial.print(" - ");
Serial.println(re.read() > 0 ? "horario" : "anti-horario");
}
}
void setup() {
Serial.begin(9600);
attachInterrupt(INT1, interrupt_re, CHANGE);
}
void loop() {
static byte b = HIGH; //pra ler apenas uma vez o botao ao pressionar
if( re.buttonRead() == LOW && b != re.buttonRead() ) {
re.next(); //passa para a próxima variável (index)
Serial.print("index: ");
Serial.print(re.getIndex());
Serial.print(" = ");
Serial.print(re.getValue());
Serial.println(" - Click");
b = re.buttonRead();
delay(200); //debounce meia boca
} else {
b = re.buttonRead();
}
}
</pre>
</div>
<div>
<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEghJU_eyx9d9FPahvE_H7Yn8a8MmL6zHqFKPvNjRlctaT6j7AJHqj3kSS9-vtdZobpZuOe9I6J-hKTqOULpIpbqQ18ZROKA1LCmMK6w0pixo4Z0umpqLOWVFVbL9wCCvAx0_WXM0PoO12w/s1600/serial.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="640" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEghJU_eyx9d9FPahvE_H7Yn8a8MmL6zHqFKPvNjRlctaT6j7AJHqj3kSS9-vtdZobpZuOe9I6J-hKTqOULpIpbqQ18ZROKA1LCmMK6w0pixo4Z0umpqLOWVFVbL9wCCvAx0_WXM0PoO12w/s640/serial.png" width="442" /></a></div>
<br />
<br />
<br />
<b>Exemplo com duas interrupções</b><br />
<br />
Esse próximo exemplo é o mais seguro se tratando de evitar perdas de pulsos, já que ele se utiliza de duas interrupções, uma para o pino de clock e outra para o pino dt do encoder.<br />
<br />
Como pode ser visto agora, a classe possui dois métodos update, um a e outro b, que irão ser chamados de acordo com a detecção da interrupção de cada um dos pinos.<br />
<br />
<br />
<pre class="brush: html" style="background-color: #eeeeee; border: 1px dashed rgb(153, 153, 153); overflow: auto; padding: 5px; width: 100%;">/**********************************************************************************
************************************CLASSE ROTARY ENCODER**************************
**********************************************************************************/
#define ROTARY_NO_BUTTON 255
struct RotaryEncoderLimits{
int min;
int max;
};
class RotaryEncoder {
private:
byte _pin_clk;
byte _pin_dt;
byte _pin_sw;
volatile byte _num_results;
volatile int _result;
volatile int * _results;
byte _index_result;
RotaryEncoderLimits * _limits;
boolean _a;
boolean _b;
public:
RotaryEncoder(byte pin_clk, byte pin_dt, byte pin_sw = ROTARY_NO_BUTTON, byte num_results=1, RotaryEncoderLimits * limits=0){ //parametro do botao opcional
_pin_clk = pin_clk;
_pin_dt = pin_dt;
_pin_sw = pin_sw;
pinMode(_pin_clk, INPUT);
pinMode(_pin_dt, INPUT);
if (_pin_sw != ROTARY_NO_BUTTON){
pinMode(_pin_sw, INPUT);
digitalWrite(_pin_sw, HIGH);
}
if (num_results == 0) { num_results = 1; }
_num_results = num_results;
_results = new int[_num_results];
for (int i; i<_num_results; i++){ _results[i] = (limits) ? limits[i].min : 0; }
_index_result = 0;
_limits = limits;
_a = false;
_b = false;
}
byte getIndex() { return _index_result; }
void next() { _index_result++; if (_index_result >= _num_results) { _index_result = 0; } }
void update_a() {
_result = 0;
delay (1);
if( digitalRead(_pin_clk) != _a ) {
_a = !_a;
if ( _a && !_b ) { _result = -1; }
}
if (_results[_index_result]+_result >= _limits[_index_result].min &&
_results[_index_result]+_result <= _limits[_index_result].max ) {
_results[_index_result] += _result;
}
}
void update_b() {
_result = 0;
delay (1);
if( digitalRead(_pin_dt) != _b ) {
_b = !_b;
if ( _b && !_a ) { _result = +1; }
}
if (_results[_index_result]+_result >= _limits[_index_result].min &&
_results[_index_result]+_result <= _limits[_index_result].max ) {
_results[_index_result] += _result;
}
}
int read(){ return _result; } //retorn -1, 0 ou 1.
int getValue() {return _results[_index_result]; } //retorna o valor da variável corrente
void setValue(int value){ _results[_index_result] = value; } //caso a variável inicializa em determinado valor diferente de zero, utilizar esse método.
int buttonRead(){ return (_pin_sw == ROTARY_NO_BUTTON) ? LOW : digitalRead(_pin_sw); }
};
/**********************************************************************************
************************************FIM CLASSE ROTARY ENCODER**********************
**********************************************************************************/
RotaryEncoderLimits lim[] = { {0,10}, {-5,5}, {-32768, 32767} }; //limites máximos e mínimos que as variaveis podem atingir
RotaryEncoder re(2, 3, 8, 3, lim); //pino clk, pino dt, pino sw, variaveis
//executados pela interrupcões
void interrupt_re_a() { re.update_a(); print(); }
void interrupt_re_b() { re.update_b(); print(); }
void print(){
if (re.read() != 0 ) { //se rotacionou --> -1, 0 ou 1
Serial.print("index: ");
Serial.print(re.getIndex());
Serial.print(" = ");
Serial.print(re.getValue());
Serial.print(" - ");
Serial.println(re.read() > 0 ? "horario" : "anti-horario");
}
}
void setup() {
Serial.begin(9600);
attachInterrupt(INT0, interrupt_re_a, CHANGE);
attachInterrupt(INT1, interrupt_re_b, CHANGE);
}
void loop() {
static byte b = HIGH; //pra ler apenas uma vez o botao ao pressionar
if( re.buttonRead() == LOW && b != re.buttonRead() ) {
re.next(); //passa para a próxima variável (index)
Serial.print("index: ");
Serial.print(re.getIndex());
Serial.print(" = ");
Serial.print(re.getValue());
Serial.println(" - Click");
b = re.buttonRead();
delay(200); //debounce meia boca
} else {
b = re.buttonRead();
}
}
</pre>
<br />
<br />
<b>Conclusão</b><br />
<br />
Espero que as rotinas apresentadas aqui sejam úteis em seus projetos, pois como podemos ver há diversas situações a serem consideradas, por isso procurei abordar algumas delas, pois ao procurarmos soluções na internet sobre o assunto, há bastante coisas, e muitas delas não funcionam de acordo com o que precisamos e os próprios exemplos encontrados não são muito claros em seu funcionamento.<br />
<br />
No início do artigo eu citei a necessidade que algumas aplicações têm de serem gravados os valores atuais do encoder para não se perderem ao serem reiniciadas (por exemplo o volume do som do carro). Porém acabei não implementado essa funcionalidade nos exemplos apresentados, pois deixaria o código um pouco mais complexo. Mas não é muito difícil, a partir dos exemplos, implementar tais funções. A solução mais recomendada nesses casos é utilizar a eeprom.<br />
<br />
<br />
<b>Atualização - 21/05/2016</b><br />
<br />
Fiz um exemplo de como utilizar o encoder em um display lcd. Vou deixar o código e vídeo abaixo. Caso eu venha a fazer outros exemplos, irei adicionando aqui nesse mesmo artigo.<br />
<br />
Para mais detalhes sobre como gerar barras de progresso no display LCD veja esse artigo: <a href="http://fabianoallex.blogspot.com.br/2015/10/arduino-lcd-progress-bar-barra-de.html">http://fabianoallex.blogspot.com.br/2015/10/arduino-lcd-progress-bar-barra-de.html</a><br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<iframe allowfullscreen="" class="YOUTUBE-iframe-video" data-thumbnail-src="https://i.ytimg.com/s_vi/oMO2TJDvOAY/default.jpg?sqp=CLCrg7oF&rs=AOn4CLBOOjIPIWbiQftoWpK8-eWAlO9fuw" frameborder="0" height="415" src="https://www.youtube.com/embed/oMO2TJDvOAY?feature=player_embedded" width="660"></iframe></div>
<br />
<br />
Código do exemplo do vídeo:<br />
<br />
<pre class="brush: html" style="background-color: #eeeeee; border: 1px dashed rgb(153, 153, 153); overflow: auto; padding: 5px; width: 100%;">/*
Fabiano A. Arndt - 2015
www.youtube.com/user/fabianoallex
www.facebook.com/dicasarduino
fabianoallex@gmail.com
*/
#include <LiquidCrystal.h>
#include <SPI.h>
/*************************************************************************************************************
*******************************CLASSE LCD PROGRESS BAR********************************************************
**************************************************************************************************************/
byte c1[8] = {B10000, B10000, B10000, B10000, B10000, B10000, B10000, B10000};
byte c2[8] = {B11000, B11000, B11000, B11000, B11000, B11000, B11000, B11000};
byte c3[8] = {B11100, B11100, B11100, B11100, B11100, B11100, B11100, B11100};
byte c4[8] = {B11110, B11110, B11110, B11110, B11110, B11110, B11110, B11110};
byte c5[8] = {B11111, B11111, B11111, B11111, B11111, B11111, B11111, B11111};
class LCDProgressBar {
private:
LiquidCrystal * _lcd;
int _row;
int _col;
int _len;
int _perc; /*0..100*/
public:
void createChars() {
_lcd->createChar(0, c1);
_lcd->createChar(1, c2);
_lcd->createChar(2, c3);
_lcd->createChar(3, c4);
_lcd->createChar(4, c5);
}
LCDProgressBar(LiquidCrystal * lcd, int row, int col, int len) {
_lcd = lcd; _row = row; _col = col; _len = len;
}
void setPerc(int perc){
_perc = perc;
if (perc > 100) { _perc = 100; }
if (perc < 000) { _perc = 000; }
_lcd->setCursor(_col, _row);
for (int i=0; i<(_len);i++) { _lcd->print(" "); }
_lcd->setCursor(_col, _row);
int bars = 5 * _len * _perc / 100;
int div = bars / 5; //divisao
int resto = bars % 5; //resto
for (int i=0; i<div; i++) { _lcd->write((byte)4); } //pinta todo o quadro
if (resto > 0 ) { _lcd->write((byte)(resto-1)); } //pinta o quadro com a quantidade de barras proporcional
}
};
/*************************************************************************************************************
*******************************FIM CLASSE LCD PROGRESS BAR****************************************************
**************************************************************************************************************/
/*************************************************************************************************************
************************************CLASSE ROTARY ENCODER*****************************************************
*************************************************************************************************************/
#define ROTARY_NO_BUTTON 255
struct RotaryEncoderLimits{
int min;
int max;
};
class RotaryEncoder {
private:
byte _pin_clk;
byte _pin_dt;
byte _pin_sw;
volatile byte _num_results;
volatile int _result;
volatile int * _results;
byte _index_result;
RotaryEncoderLimits * _limits;
boolean _a;
boolean _b;
public:
RotaryEncoder(byte pin_clk, byte pin_dt, byte pin_sw = ROTARY_NO_BUTTON, byte num_results=1, RotaryEncoderLimits * limits=0){ //parametro do botao opcional
_pin_clk = pin_clk;
_pin_dt = pin_dt;
_pin_sw = pin_sw;
pinMode(_pin_clk, INPUT);
pinMode(_pin_dt, INPUT);
if (_pin_sw != ROTARY_NO_BUTTON){
pinMode(_pin_sw, INPUT);
digitalWrite(_pin_sw, HIGH);
}
if (num_results == 0) { num_results = 1; }
_num_results = num_results;
_results = new int[_num_results];
for (int i; i<_num_results; i++){ _results[i] = (limits) ? limits[i].min : 0; }
_index_result = 0;
_limits = limits;
_a = false;
_b = false;
}
byte getIndex() { return _index_result; }
void next() { _index_result++; if (_index_result >= _num_results) { _index_result = 0; } }
void update_a() {
_result = 0;
delay (1);
if( digitalRead(_pin_clk) != _a ) {
_a = !_a;
if ( _a && !_b ) { _result = -1; }
}
if (_results[_index_result]+_result >= _limits[_index_result].min &&
_results[_index_result]+_result <= _limits[_index_result].max ) {
_results[_index_result] += _result;
}
}
void update_b() {
_result = 0;
delay (1);
if( digitalRead(_pin_dt) != _b ) {
_b = !_b;
if ( _b && !_a ) { _result = +1; }
}
if (_results[_index_result]+_result >= _limits[_index_result].min &&
_results[_index_result]+_result <= _limits[_index_result].max ) {
_results[_index_result] += _result;
}
}
int read(){ return _result; } //retorn -1, 0 ou 1.
int getValue(int index=-1) { //retorna o valor da variável corrente ou a passada como parametro
if (index < 0 ){ return _results[_index_result]; }
return _results[index];
}
void setValue(int value){ _results[_index_result] = value; } //caso a variável inicializa em determinado valor diferente de zero, utilizar esse método.
int buttonRead(){ return (_pin_sw == ROTARY_NO_BUTTON) ? LOW : digitalRead(_pin_sw); }
};
/*************************************************************************************************************
************************************FIM CLASSE ROTARY ENCODER*************************************************
*************************************************************************************************************/
LiquidCrystal lcd(12, 11, 10, 9, 8, 7);
LCDProgressBar lcdBar1(&lcd, 0, 10, 6); //inclui uma barra no lcd, primeira linha, coluna 10. tamanho 6
LCDProgressBar lcdBar2(&lcd, 1, 10, 6); //inclui outra barra no lcd, segunda linha, coluna 10. tamanho 6
RotaryEncoderLimits lim[] = { {0,20}, {0,50} }; //limites máximos e mínimos que as variaveis podem atingir
RotaryEncoder re(2, 3, 4, 2, lim); //pino clk, pino dt, pino sw, variaveis
void interrupt_re_a() { re.update_a(); }
void interrupt_re_b() { re.update_b(); }
void setup() {
attachInterrupt(INT0, interrupt_re_a, CHANGE);
attachInterrupt(INT1, interrupt_re_b, CHANGE);
Serial.begin(9600);
lcdBar1.createChars();
pinMode(5, OUTPUT);
analogWrite(5, 100); //utilizado para aumentar o contraste
lcd.begin(16, 2);
}
void loop() {
lcd.setCursor(0, 0);
int value = re.getValue(0);
int perc = value/20.0 * 100;
if (value < 10) {lcd.print("00");} else {
if (value < 100) {lcd.print("0");} }
lcd.print(value);
lcd.print("/");
lcd.print(20);
char c = ((re.getIndex() == 0)) ? '>' : ' ';
lcd.setCursor(8, 0);
lcd.print(c);
lcdBar1.setPerc(perc); //atualização da primeira barra de progresso
lcd.setCursor(0, 1);
value = re.getValue(1);
perc = value/50.0 * 100;
if (value < 10) {lcd.print("00");} else {
if (value < 100) {lcd.print("0");} }
lcd.print(value);
lcd.print("/");
lcd.print(50);
c = ((re.getIndex() == 1)) ? '>' : ' ';
lcd.setCursor(8, 1);
lcd.print(c);
lcdBar2.setPerc(perc); //atualização da segunda barra de progresso
//controla o click do botao do enconder
static byte b = HIGH; //pra ler apenas uma vez o botao ao pressionar
if( re.buttonRead() == LOW && b != re.buttonRead() ) {
re.next(); //passa para a próxima variável (index)
delay(200); //debounce meia boca
}
b = re.buttonRead();
delay(100);
}
</pre>
<br />
<b>Atualização 22/05/2016 - Exemplo por timer</b><br />
<br />
Nas duas primeiras versões que mostrei no início do artigo, abordei exemplos que não se baseiam em interrupções, mas na atualização apenas dentro do loop, pois muitas vezes as interrupções disponíveis já podem estar em uso por outros dispositivos. Porém, como explicado inicialmente, o nosso loop precisa ser executado numa frequência alta para que qualquer movimento no encoder seja detectado. Qualquer processamento feito dentro do loop que demore um pouco mais do que o normal ou o uso de delays podem causar erros na leitura do encoder. Pensando nesses casos, onde não temos nem pinos de interrupções externas disponíveis e nem a garantia de execução numa frequencia alta no loop, criei uma outra solução, baseada em timers. Ou seja, foi criada uma interrupção por timer que constantemente verifica se houve leituras no encoder. Quanto maior a frequência de execução do timer, mais seguro será a execução da rotina, porém, mais processamento é exigido. Os valores mais apropriados para a frequência de execução do timer, foram entre 100 e 500 Hz, ou seja, executado entre 100 e 500 vezes por segundo. Valores mais próximos de 100 só tiveram leituras erradas quando o encoder foi girado um pouco mais rápido que o normal. Se isso ocorrer em sua aplicação o mais indicado é utilizar um valor mais próximo de 500Hz.<br />
<br />
Código:<br />
<br />
<pre class="brush: html" style="background-color: #eeeeee; border: 1px dashed rgb(153, 153, 153); overflow: auto; padding: 5px; width: 100%;">/**********************************************************************************
************************************CLASSE ROTARY ENCODER**************************
**********************************************************************************/
#define ROTARY_NO_BUTTON 255
struct RotaryEncoderLimits{
int min;
int max;
};
class RotaryEncoder {
private:
byte _pin_clk;
byte _pin_dt;
byte _pin_sw;
byte _num_results;
int _result;
int * _results;
byte _index_result;
RotaryEncoderLimits * _limits;
public:
RotaryEncoder(byte pin_clk, byte pin_dt, byte pin_sw = ROTARY_NO_BUTTON, byte num_results=1, RotaryEncoderLimits * limits=0){ //parametro do botao opcional
_pin_clk = pin_clk;
_pin_dt = pin_dt;
_pin_sw = pin_sw;
pinMode(_pin_clk, INPUT);
pinMode(_pin_dt, INPUT);
if (_pin_sw != ROTARY_NO_BUTTON){
pinMode(_pin_sw, INPUT);
digitalWrite(_pin_sw, HIGH);
}
if (num_results == 0) { num_results = 1; }
_num_results = num_results;
_results = new int[_num_results];
_index_result = 0;
_limits = limits;
for (int i; i<_num_results; i++){ _results[i] = (limits) ? limits[i].min : 0; }
}
byte getIndex() { return _index_result; }
void next() { _index_result++; if (_index_result >= _num_results) { _index_result = 0; } }
void update() {
static int oldA = HIGH;
static int oldB = HIGH;
static unsigned long lmillis = 0;
_result = 0;
if (millis() - 1 > lmillis){ //previne leituras falsas
int newA = digitalRead(_pin_clk);
int newB = digitalRead(_pin_dt);
if ( (newA != oldA || newB != oldB) && (oldA == HIGH && newA == LOW) ) { _result = (oldB * 2 - 1); }
oldA = newA;
oldB = newB;
lmillis = millis();
}
if (_results[_index_result]+_result >= _limits[_index_result].min &&
_results[_index_result]+_result <= _limits[_index_result].max ) {
_results[_index_result] += _result;
}
}
int read(){ return _result; } //retorn -1, 0 ou 1.
int getValue() {return _results[_index_result]; } //retorna o valor da variável
void setValue(int value){ _results[_index_result] = value; } //caso a variável inicializa em determinado valor diferente de zero, utilizar esse método.
int buttonRead(){ return (_pin_sw == ROTARY_NO_BUTTON) ? LOW : digitalRead(_pin_sw); }
};
/**********************************************************************************
************************************FIM CLASSE ROTARY ENCODER**********************
**********************************************************************************/
#define val_freq 500 //frequencia em hz para a execucao do timer1. quanto mais rápido for girado o encoder, maior deve ser essa frequencia. o resultado mais adequando foi 500x por segundo
#define freq(x) 65536 - (16000000 / 1024 / x) //calcula a frequencia a ser utilizada pelo timer1 na nossa rotina abaixo. x indica o valor da frequencia desejada
RotaryEncoderLimits lim[] = { {0,10}, {-5,5}, {-32768, 32767} };
RotaryEncoder re(3, 2, 4, 3, lim); //pinos clk, dt, sw, 3 valores diferentes serão controlados
byte lastButton = HIGH;
//interrupção do TIMER1
ISR(TIMER1_OVF_vect) {
//atençao. essa rotina irá repetir várias vezes por segundo, portanto não adicione nada que seja muito lento aqui.
TCNT1 = freq(val_freq);
int pressionou = false;
re.update(); //primeiro fazemos a chamada para o update para que seja feita a leitura do rotary encoder
if( re.buttonRead() == LOW && lastButton != re.buttonRead() ) { //a cada clique, passamos a gerenciar a próxima variável
re.next();
pressionou = true;
lastButton = re.buttonRead();
delay(300); //debounce meia boca
} else {
lastButton = re.buttonRead();
}
if (re.read() != 0 || pressionou) { //se rotacionou ou pressionou o botão --> -1, 0 ou 1
Serial.print("index: ");
Serial.print(re.getIndex());
Serial.print(" = ");
Serial.print(re.getValue());
Serial.print(" - ");
Serial.println(re.read() > 0 ? "horario" : "anti-horario");
}
}
void setup() {
Serial.begin(9600);
// Configuração do timer1
TCCR1A = 0; //confira timer para operação normal pinos OC1A e OC1B desconectados
TCCR1B = 0; //limpa registrador
TCCR1B |= (1<<CS10)|(1 << CS12); // configura prescaler para 1024: CS12 = 1 e CS10 = 1
TCNT1 = freq(val_freq); // inicia o timer na frequencia ajustada na variavel val_freq. ajustar para frequencia mais indicada
TIMSK1 |= (1 << TOIE1); // habilita a interrupção do TIMER1
}
void loop() {
//pra simular um loop lento, adicionei um delay de 2 segundos
Serial.println(" - - - - - - - - teste - - - - - - - - ");
delay(2000);
}
</pre>
<br />
<br />
<b>Atualização 24/05/2016 - Exemplo utilizando Pin Change Interrupts - PCI</b><br />
<br />
O exemplo mais preciso mostrado nesse artigo foi aquele utilizando interrupções externas, porém como vimos elas são bem limitadas, pois no caso do Arduino Uno temos apenas duas, e utiliza-las significa que se precisarmos de interrupções para outros dispositivos, poderemos vir a ter problemas. Porém mesmo que não possamos utilizar nenhuma das interrupções disponíveis, ainda assim temos outro recurso que o Arduino nos oferece, que é utilizar interrupções PCI - Pin Change Interrupts.<br />
<br />
Na verdade essas interrupções estão associadas não a um pino específico, mas sim a um grupo de pinos, que seriam todos os pinos de uma determinada porta do Arduino. No Arduino Uno, por exemplo, temos 3 portas que são: PortB, PortC e PortD. Quando qualquer um dos pinos de uma determinada porta muda de estado, a interrupção referente a porta ao qual o pino pertence é disparada. Não vou entrar em detalhes do funcionamento desse tipo de interrupção aqui, até porque não é o foco, então sugiro uma leitura a respeito para entender melhor essa parte da programação do próximo exemplo.<br />
<br />
Nesse próximo exemplo, utilizamos os pinos A0 e A1 como entradas dos pinos Clk e Dt do Encoder, como pode ser visto no código abaixo. Além disso utilizamos um display LCD para mostrar o resultado das leituras do encoder. Em relação ao outro exemplo com o display LCD, foi alterada a progress bar.<br />
<br />
Vídeo:<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<iframe allowfullscreen="" class="YOUTUBE-iframe-video" data-thumbnail-src="https://i.ytimg.com/s_vi/LrGjFag1WTE/default.jpg?sqp=CNS9kroF&rs=AOn4CLB_2xeyyg1231cyFLFe8yrDVtxehA" frameborder="0" height="415" src="https://www.youtube.com/embed/LrGjFag1WTE?feature=player_embedded" width="660"></iframe></div>
<br />
<br />
Código:<br />
<br />
<pre class="brush: html" style="background-color: #eeeeee; border: 1px dashed rgb(153, 153, 153); overflow: auto; padding: 5px; width: 100%;">/*
Fabiano A. Arndt - 2016
www.youtube.com/user/fabianoallex
www.facebook.com/dicasarduino
fabianoallex@gmail.com
*/
#include <LiquidCrystal.h>
/*************************************************************************************************************
************************************CLASSE ROTARY ENCODER*****************************************************
*************************************************************************************************************/
#define ROTARY_NO_BUTTON 255
struct RotaryEncoderLimits{
int min;
int max;
};
class RotaryEncoder {
private:
byte _pin_clk;
byte _pin_dt;
byte _pin_sw;
volatile byte _num_results;
volatile int _result;
volatile int * _results;
byte _index_result;
RotaryEncoderLimits * _limits;
boolean _a;
boolean _b;
public:
RotaryEncoder(byte pin_clk, byte pin_dt, byte pin_sw = ROTARY_NO_BUTTON, byte num_results=1, RotaryEncoderLimits * limits=0){ //parametro do botao opcional
_pin_clk = pin_clk;
_pin_dt = pin_dt;
_pin_sw = pin_sw;
pinMode(_pin_clk, INPUT);
pinMode(_pin_dt, INPUT);
if (_pin_sw != ROTARY_NO_BUTTON){
pinMode(_pin_sw, INPUT);
digitalWrite(_pin_sw, HIGH);
}
if (num_results == 0) { num_results = 1; }
_num_results = num_results;
_results = new int[_num_results];
for (int i; i<_num_results; i++){ _results[i] = (limits) ? limits[i].min : 0; }
_index_result = 0;
_limits = limits;
_a = false;
_b = false;
}
byte getIndex() { return _index_result; }
void next() { _index_result++; if (_index_result >= _num_results) { _index_result = 0; } }
void update_a() {
_result = 0;
delay (1);
if( digitalRead(_pin_clk) != _a ) {
_a = !_a;
if ( _a && !_b ) { _result = -1; }
}
if (_results[_index_result]+_result >= _limits[_index_result].min &&
_results[_index_result]+_result <= _limits[_index_result].max ) {
_results[_index_result] += _result;
}
}
void update_b() {
_result = 0;
delay (1);
if( digitalRead(_pin_dt) != _b ) {
_b = !_b;
if ( _b && !_a ) { _result = +1; }
}
if (_results[_index_result]+_result >= _limits[_index_result].min &&
_results[_index_result]+_result <= _limits[_index_result].max ) {
_results[_index_result] += _result;
}
}
int read(){ return _result; } //retorn -1, 0 ou 1.
int getValue(int index=-1) { //retorna o valor da variável corrente ou a passada como parametro
if (index < 0 ){ return _results[_index_result]; }
return _results[index];
}
void setValue(int value){ _results[_index_result] = value; } //caso a variável inicializa em determinado valor diferente de zero, utilizar esse método.
int buttonRead(){ return (_pin_sw == ROTARY_NO_BUTTON) ? LOW : digitalRead(_pin_sw); }
};
/*************************************************************************************************************
************************************FIM CLASSE ROTARY ENCODER*************************************************
*************************************************************************************************************/
/*************************************************************************************************************
*******************************CLASSE LCD PROGRESS BAR********************************************************
**************************************************************************************************************/
/*
0 1 2 3 4 5 6 7
***** ***** ***** ***** ***** ***** ***** *****
* ** *** **** ***** * *** ***
* ** *** **** ***** * *** ***
* ** *** **** ***** * *** ***
* ** *** **** ***** * *** ***
* ** *** **** ***** * *** ***
* ** *** **** ***** * *** ***
***** ***** ***** ***** ***** ***** ***** *****
*/
byte c0[8] = {B11111, B00000, B00000, B00000, B00000, B00000, B00000, B11111};
byte c1[8] = {B11111, B10000, B10000, B10000, B10000, B10000, B10000, B11111};
byte c2[8] = {B11111, B11000, B11000, B11000, B11000, B11000, B11000, B11111};
byte c3[8] = {B11111, B11100, B11100, B11100, B11100, B11100, B11100, B11111};
byte c4[8] = {B11111, B11110, B11110, B11110, B11110, B11110, B11110, B11111};
byte c5[8] = {B11111, B11111, B11111, B11111, B11111, B11111, B11111, B11111};
byte c6[8] = {B11111, B10111, B10111, B10111, B10111, B10111, B10111, B11111};
byte c7[8] = {B11111, B00111, B00111, B00111, B00111, B00111, B00111, B11111};
class LCDProgressBar {
private:
LiquidCrystal * _lcd; //ponteiro para um objeto lcd
int _row;
int _col;
int _len;
int _perc; /*0..100*/
public:
void createChars() {
_lcd->createChar(0, c0); _lcd->createChar(1, c1); _lcd->createChar(2, c2);
_lcd->createChar(3, c3); _lcd->createChar(4, c4); _lcd->createChar(5, c5);
_lcd->createChar(6, c6); _lcd->createChar(7, c7);
}
LCDProgressBar(LiquidCrystal * lcd, int row, int col, int len) {
_lcd = lcd; _row = row; _col = col; _len = len;
}
void setPerc(int perc) {
_perc = perc;
if (perc > 100) { _perc = 100; }
if (perc < 000) { _perc = 000; }
_lcd->setCursor(_col, _row);
if (_len == 1){
_lcd->write((byte)0);
int bars = 5 * _perc / 100;
int div = bars / 5; //divisao
int resto = bars % 5; //resto
_lcd->setCursor(_col, _row);
if (div > 0) { _lcd->write((byte)5); } //pinta todo o quadro
if (resto > 0 ) { _lcd->write((byte)(resto)); } //pinta o quadro com a quantidade de barras proporcional
} else {
int bars = (5*(_len-2) + 4) * _perc / 100; // -2 --> desconsidera os blocos de inicio e fim ; 4 --> considera as duas barras de cada um dos blocos de inicio e fim
//preenche com caracteres vazio
for (int i=0; i<(_len);i++) { _lcd->write((byte)0); }
//reposiciona no bloco inicial
_lcd->setCursor(_col, _row);
//preenche o primeiro bloco da barra
if (bars == 0) { _lcd->write((byte)3); }
if (bars == 1) { _lcd->write((byte)4); }
if (bars >= 2) { _lcd->write((byte)5); }
int div = bars / 5; //divisao
int resto = bars % 5; //resto
for (int i=0; i<div; i++) { _lcd->write((byte)5); } //pinta todo o quadro
if (resto > 0) { _lcd->write((byte)(resto-1)); } //pinta o quadro com a quantidade de barras proporcional
//reposiciona no bloco final
_lcd->setCursor(_col+_len-1, _row);
//preenche ultimo bloco da barra
if ((5*(_len-2) + 4) - bars == 0) { _lcd->write((byte)5); }
if ((5*(_len-2) + 4) - bars == 1) { _lcd->write((byte)6); }
if ((5*(_len-2) + 4) - bars >= 2) { _lcd->write((byte)7); }
}
}
};
/*************************************************************************************************************
*******************************FIM CLASSE LCD PROGRESS BAR****************************************************
**************************************************************************************************************/
LiquidCrystal lcd(12, 11, 10, 9, 8, 7);
LCDProgressBar lcdBar1(&lcd, 0, 9, 6); //inclui uma barra no lcd, primeira linha, coluna 9. tamanho 6
LCDProgressBar lcdBar2(&lcd, 1, 11, 4); //inclui outra barra no lcd, segunda linha, coluna 11. tamanho 4
RotaryEncoderLimits lim[] = { {0,30}, {0,13} }; //limites máximos e mínimos que as variaveis podem atingir
RotaryEncoder re(A0, A1, 4, 2, lim); //pino clk, pino dt, pino sw, variaveis
//interrupções dos pinos A0 e A1 via Pin Change Interrupt
ISR(PCINT1_vect) {
volatile static byte lastVal_a0 = LOW;
volatile static byte lastVal_a1 = LOW;
byte val_a0 = digitalRead(A0);
byte val_a1 = digitalRead(A1);
if (lastVal_a0 != val_a0){ re.update_a(); lastVal_a0 = val_a0; }
if (lastVal_a1 != val_a1){ re.update_b(); lastVal_a1 = val_a1; }
}
void indicador_rotary(){
char c = ((re.getIndex() == 0)) ? '>' : ' ';
lcd.setCursor(7, 0);
lcd.print(c);
c = ((re.getIndex() == 1)) ? '>' : ' ';
lcd.setCursor(7, 1);
lcd.print(c);
}
void setup() {
//-----PCI - Pin Change Interrupt ----
pinMode(A0,INPUT); // set Pin as Input (default)
digitalWrite(A0,HIGH); // enable pullup resistor
pinMode(A1,INPUT); // set Pin as Input (default)
digitalWrite(A1,HIGH); // enable pullup resistor
cli();
PCICR |= 0b00000010; // habilita a porta C - Pin Change Interrupts
PCMSK1 |= 0b00000011; // habilita interrupção da porta c nos pinos: PCINT8 (A0) e PCINT9(A1)
sei();
//----fim PCI----
Serial.begin(9600);
lcdBar1.createChars();
lcd.begin(16, 2);
indicador_rotary();
}
void loop() {
static int value1 = -1;
static int value2 = -1;
if (value1 != re.getValue(0)) {
Serial.println(".");
lcd.setCursor(0, 0);
value1 = re.getValue(0);
int perc = value1/30.0 * 100;
if (value1 < 10) {lcd.print("00");} else {
if (value1 < 100) {lcd.print("0");} }
lcd.print(value1);
lcd.print("/");
lcd.print(30);
lcdBar1.setPerc(perc); //atualização da primeira barra de progresso
}
if (value2 != re.getValue(1)) {
Serial.println("*");
lcd.setCursor(0, 1);
value2 = re.getValue(1);
int perc = value2/13.0 * 100;
if (value2 < 10) {lcd.print("00");} else {
if (value2 < 100) {lcd.print("0");} }
lcd.print(value2);
lcd.print("/");
lcd.print(13);
lcdBar2.setPerc(perc); //atualização da segunda barra de progresso
}
//controla o click do botao do enconder
static byte b = HIGH; //pra ler apenas uma vez o botao ao pressionar
if( re.buttonRead() == LOW && b != re.buttonRead() ) {
re.next(); //passa para a próxima variável (index)
indicador_rotary();
delay(200); //debounce meia boca
}
b = re.buttonRead();
delay(100);
}</pre>
</div>
<br />
<br />Fabiano Arndthttp://www.blogger.com/profile/15268923442674744187noreply@blogger.com2tag:blogger.com,1999:blog-5513833072608837410.post-38565946593550280722016-05-04T13:22:00.001-04:002016-06-16T10:13:50.255-04:00Arduino - Luminária com efeito fade-inProjeto de uma luminária com efeito fade-in para 3 lâmpadas.<br />
<br />
<b>vídeo:</b><br />
<div class="separator" style="clear: both; text-align: center;">
<iframe allowfullscreen="" class="YOUTUBE-iframe-video" data-thumbnail-src="https://i.ytimg.com/s_vi/RnPJi0PkNVE/default.jpg?sqp=CJTcqLkF&rs=AOn4CLBDpr6YXM4s-6GxfEnkm4RzADKfow" frameborder="0" height="415" src="https://www.youtube.com/embed/RnPJi0PkNVE?feature=player_embedded" width="660"></iframe></div>
<br />
<b>código-fonte:</b><br />
<b><br /></b>
<br />
<pre class="brush: html" style="background-color: #eeeeee; border: 1px dashed rgb(153, 153, 153); overflow: auto; padding: 5px; width: 100%;">/**********************************************************************************
************************************CLASSE UNIQUE RANDOM***************************
**********************************************************************************/
class UniqueRandom{
private:
int _index;
int _min;
int _max;
int _size;
int* _list;
void _init(int min, int max) {
_list = 0;
if (min < max) { _min = min; _max = max; } else { _min = max; _max = min; }
_size = _max - _min;
_index = 0;
}
public:
UniqueRandom(int max) { _init(0, max); randomize(); } //construtor com 1 parametro
UniqueRandom(int min, int max) { _init(min, max); randomize(); } //construtor com 2 parametros
void randomize() {
if (_list == 0) { _list = (int*) malloc(size() * sizeof(int)); }
for (int i=0; i<size(); i++) { _list[i] = _min+i; } //preenche a lista do menor ao maior valor
//embaralha a lista
for (int i=0; i<size(); i++) {
int r = random(0, size()); //sorteia uma posição qualquer
int aux = _list[i];
_list[i] = _list[r];
_list[r] = aux;
}
}
int next() { //retorna o proximo numero da lista
int n = _list[_index++];
if (_index >= size() ) { _index = 0;} //após recuper o ultimo numero, recomeça na posicao 0
return n;
}
int size() { return _size; }
~UniqueRandom(){ free ( _list ); } //destrutor
};
/**********************************************************************************
************************************FIM CLASSE UNIQUE RANDOM***********************
**********************************************************************************/
const int pins_pwm[] = {9, 10, 11};
const int num_pins = 3;
UniqueRandom ur(0, 3); //declaracao do objeto unique random
void setup() {
for (int i=0;i<num_pins;i++){ pinMode(pins_pwm[i], OUTPUT); }
randomSeed(analogRead(0));
ur.randomize();
efeito_02(random(14+3));
}
void efeito_02(int d){
for (int i=0;i<num_pins;i++){
efeito_02_on(pins_pwm[ur.next()], d);
}
}
void efeito_02_on(int pin, int d){
for (int i=0; i<256;i++ ){
analogWrite(pin, i);
delay(d);
if (i<=50) { i++; }
if (i>50) { i +=4; }
if (i>185) { i +=8; }
}
analogWrite(pin, 255);
}
void loop() {}</pre>
<br />
<b>Fade-Off</b><br />
<br />
Nesse segundo vídeo, mostro uma ideia de como fazer o contrário, efeito de fade-off, ou seja, desligar as lâmpadas gradualmente.<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<iframe allowfullscreen="" class="YOUTUBE-iframe-video" data-thumbnail-src="https://i.ytimg.com/vi/JarDzMxhEYU/0.jpg" frameborder="0" height="415" src="https://www.youtube.com/embed/JarDzMxhEYU?feature=player_embedded" width="660"></iframe></div>
<br />Fabiano Arndthttp://www.blogger.com/profile/15268923442674744187noreply@blogger.com0tag:blogger.com,1999:blog-5513833072608837410.post-8760981769006843862016-04-12T08:20:00.004-04:002016-06-16T10:13:50.281-04:00Arduino - Memória EEPROM - AT24C256Essa semana postei um artigo no site sistemas embarcados. O Assunto foi memória EEPROM, interna e externa. No caso da memória externa foi abordado o CI AT24C256. Segue o link para o artigo completo: <a href="http://www.sistemasembarcados.org/2016/04/arduino-memoria-eeprom.html">http://www.sistemasembarcados.org/2016/04/arduino-memoria-eeprom.html</a><br />
<br />
Veja também o vídeo sobre o AT24C256.<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<iframe allowfullscreen="" class="YOUTUBE-iframe-video" data-thumbnail-src="https://i.ytimg.com/vi/Dsvo-87srfk/0.jpg" frameborder="0" height="415" src="https://www.youtube.com/embed/Dsvo-87srfk?feature=player_embedded" width="660"></iframe></div>
<br />
<br />
Veja também o vídeo que mostra a classe feita para facilitar a gravação e leitura da memória:<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<iframe allowfullscreen="" class="YOUTUBE-iframe-video" data-thumbnail-src="https://i.ytimg.com/s_vi/FMrixUez93c/default.jpg?sqp=CPC9ubgF&rs=AOn4CLARLPBX3BAFKhAErrFXpwq472xvag" frameborder="0" height="415" src="https://www.youtube.com/embed/FMrixUez93c?feature=player_embedded" width="660"></iframe></div>
<br />
<br />
Código-fonte:<br />
<br />
<pre class="brush: html" style="background-color: #eeeeee; border: 1px dashed rgb(153, 153, 153); overflow: auto; padding: 5px; width: 100%;">/*
Baseado nos exemplos abaixo:
http://hobbytronics.co.uk/eeprom-page-write
http://playground.arduino.cc/Code/EEPROMWriteAnything
Fabiano A. Arndt
www.youtube.com/fabianoallex
www.facebook.com/dicasarduino
*/
#include <Wire.h>
/*************************************************************************************************************
*******************************CLASSE EE24CXXX - CI I2C EEPROM AT24C128/256***********************************
**************************************************************************************************************/
class EE24CXXX {
private:
byte _device_address;
public:
EE24CXXX(byte device_address){ _device_address = device_address; }
void write(unsigned int eeaddress, unsigned char * data, unsigned int data_len);
void read (unsigned int eeaddress, unsigned char * data, unsigned int data_len);
template <class T> int write(unsigned int eeaddress, const T& value);
template <class T> int read(unsigned int eeaddress, T& value);
};
void EE24CXXX::write(unsigned int eeaddress, unsigned char * data, unsigned int data_len) {
unsigned int address;
unsigned int page_space;
unsigned int page=0;
unsigned int num_writes;
unsigned char first_write_size;
unsigned char last_write_size;
unsigned char write_size;
page_space = int(((eeaddress/64) + 1)*64)-eeaddress; // Calculate space available in first page
if (page_space>16) { // Calculate first write size
first_write_size = page_space-((page_space/16)*16);
if (first_write_size==0) { first_write_size=16; }
} else {
first_write_size = page_space;
}
if (data_len>first_write_size) { last_write_size = (data_len-first_write_size)%16; } // calculate size of last write
num_writes = (data_len>first_write_size) ? ((data_len-first_write_size)/16)+2 : 1; // Calculate how many writes we need
unsigned char i=0, counter=0;
address = eeaddress;
for (page=0; page<num_writes; page++) {
if (page == 0) {
write_size = first_write_size;
} else if(page == (num_writes-1)) {
write_size = last_write_size;
} else {
write_size = 16;
}
Wire.beginTransmission(_device_address);
Wire.write((int)((address) >> 8)); // MSB
Wire.write((int)((address) & 0xFF)); // LSB
counter = 0;
do {
Wire.write((byte) data[i++]);
counter++;
} while(counter<write_size);
Wire.endTransmission();
address += write_size; // Increment address for next write
delay(5); // needs 5ms for page write
}
}
void EE24CXXX::read(unsigned int eeaddress, unsigned char * data, unsigned int data_len){
unsigned char i = 0;
unsigned int size = data_len;
unsigned int j=0;
while (size > 0){
Wire.beginTransmission(_device_address);
eeaddress += j*28;
Wire.write((int)(eeaddress >> 8)); // MSB
Wire.write((int)(eeaddress & 0xFF)); // LSB
Wire.endTransmission();
if (size >= 28) {
Wire.requestFrom(_device_address, (unsigned int) 28);
size -= 28;
} else {
Wire.requestFrom(_device_address, (unsigned int) size);
size = 0;
}
while(Wire.available()) { data[i++] = Wire.read(); }
j++;
}
}
template <class T> int EE24CXXX::write(unsigned int eeaddress, const T& value) {
const byte* p = (const byte*)(const void*)&value;
unsigned char data[sizeof(value)+1];
for (int i=0; i<sizeof(value); i++) { data[i] = *p++; }
data[sizeof(value)] = '\n';
write(eeaddress, data, sizeof(value));
return sizeof(value);
}
template <class T> int EE24CXXX::read(unsigned int eeaddress, T& value) {
byte * p = (byte*)(void*)&value;
unsigned char c[sizeof(value)];
read(eeaddress, c, sizeof(value));
for (int i=0; i<sizeof(value); i++) { *p++ = c[i]; }
return sizeof(value);
}
/*************************************************************************************************************
*******************************FIM - CLASSE EE24CXXX - CI I2C EEPROM AT24C128/256*****************************
**************************************************************************************************************/
EE24CXXX m(0x50);
struct teste{
int a;
int b;
int c;
};
void setup() {
Serial.begin(9600);
Wire.begin();
char i[] = {"1234567890"};
char j[10];
m.write(18522, i); //grava na memória a quantidade de bytes ocupada pelo tipo de dados passado
int count = m.read(18522, j); //carrega da memória a quantidade de bytes
Serial.print("Quant: ");
Serial.println(count);
Serial.print("Valor: ");
Serial.println(j);
}
void loop() { }
</pre>
<br />Fabiano Arndthttp://www.blogger.com/profile/15268923442674744187noreply@blogger.com3tag:blogger.com,1999:blog-5513833072608837410.post-62901231849526204692016-04-08T09:10:00.003-04:002016-06-16T10:13:50.265-04:00Arduino - Série de vídeos sobre montagens de CIsComecei há alguns dias uma série de vídeos rápidos sobre montagens de CIs. Conforme foram sendo feitos, essa lista aqui vai sendo atualizada. Inscreva-se no Canal para acompanhar todos os vídeos.<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<iframe allowfullscreen="" class="YOUTUBE-iframe-video" data-thumbnail-src="https://i.ytimg.com/vi/PnfR642g86E/0.jpg" frameborder="0" height="415" src="https://www.youtube.com/embed/PnfR642g86E?feature=player_embedded" width="660"></iframe></div>
<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<iframe allowfullscreen="" class="YOUTUBE-iframe-video" data-thumbnail-src="https://i.ytimg.com/vi/GNk_bPVAudc/0.jpg" frameborder="0" height="415" src="https://www.youtube.com/embed/GNk_bPVAudc?feature=player_embedded" width="660"></iframe></div>
<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<iframe allowfullscreen="" class="YOUTUBE-iframe-video" data-thumbnail-src="https://i.ytimg.com/vi/pq9cK433sPA/0.jpg" frameborder="0" height="415" src="https://www.youtube.com/embed/pq9cK433sPA?feature=player_embedded" width="660"></iframe></div>
<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<iframe allowfullscreen="" class="YOUTUBE-iframe-video" data-thumbnail-src="https://i.ytimg.com/vi/uUVXFbIFFq4/0.jpg" frameborder="0" height="415" src="https://www.youtube.com/embed/uUVXFbIFFq4?feature=player_embedded" width="660"></iframe></div>
<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<iframe allowfullscreen="" class="YOUTUBE-iframe-video" data-thumbnail-src="https://i.ytimg.com/vi/wfDdrJEu7wI/0.jpg" frameborder="0" height="415" src="https://www.youtube.com/embed/wfDdrJEu7wI?feature=player_embedded" width="660"></iframe></div>
<div class="separator" style="clear: both; text-align: center;">
<br /></div>
<div class="separator" style="clear: both; text-align: center;">
<br /></div>
<div class="separator" style="clear: both; text-align: center;">
<iframe allowfullscreen="" class="YOUTUBE-iframe-video" data-thumbnail-src="https://i.ytimg.com/vi/N8Z5ob0lNZg/0.jpg" frameborder="0" height="415" src="https://www.youtube.com/embed/N8Z5ob0lNZg?feature=player_embedded" width="660"></iframe></div>
<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<iframe allowfullscreen="" class="YOUTUBE-iframe-video" data-thumbnail-src="https://i.ytimg.com/vi/FV-ZGn2-_8o/0.jpg" frameborder="0" height="415" src="https://www.youtube.com/embed/FV-ZGn2-_8o?feature=player_embedded" width="660"></iframe></div>
<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<iframe allowfullscreen="" class="YOUTUBE-iframe-video" data-thumbnail-src="https://i.ytimg.com/vi/_p5KGm0itxI/0.jpg" frameborder="0" height="415" src="https://www.youtube.com/embed/_p5KGm0itxI?feature=player_embedded" width="660"></iframe></div>
<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<iframe allowfullscreen="" class="YOUTUBE-iframe-video" data-thumbnail-src="https://i.ytimg.com/vi/waOFfNsNZuw/0.jpg" frameborder="0" height="415" src="https://www.youtube.com/embed/waOFfNsNZuw?feature=player_embedded" width="660"></iframe></div>
<br />Fabiano Arndthttp://www.blogger.com/profile/15268923442674744187noreply@blogger.com0tag:blogger.com,1999:blog-5513833072608837410.post-56335849755991320452016-04-08T08:47:00.003-04:002016-06-16T10:13:50.262-04:00Arduino - Instalação de driver para Nano V3 (CH340G) no win7Faz um tempo já que comprei alguns Arduinos Nano, mas acabaram ficando um bom tempo guardado. Hoje resolvi utilizá-los (tentar) e tive algumas dificuldades para tentar instalar os drivers corretos no windows 7. Vários artigos explicam como fazer, mas não obtive sucesso com nenhum.<br />
<br />
Depois de muito tentar, resolvi procurar os drivers do próprio CI (CH340G, da foto abaixo) que veio com o Arduino Nano que comprei, e encontrei um vídeo, que através do qual consegui resolver o problema. E como dor de barriga não dá uma vez só, resolve criar esse pequeno artigo, pra caso eu venha precisar novamente, eu ter os passos necessários, e claro, para também compartilhar com quem encontrou o mesmo problema.<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjzDbpBhnxxKcDR88PUkECkgYyoUH5z474wRdImqwKI4550o36f2HnFJTuKJaZoYkEn4YXMsnXWt-QriRG31Yq962Bi0m-wa6fMd5xCpmHaPFqC7BmKrAX_YcYptyQKPvV4WhSJJNdL6fE/s1600/20160408_082551.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="360" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjzDbpBhnxxKcDR88PUkECkgYyoUH5z474wRdImqwKI4550o36f2HnFJTuKJaZoYkEn4YXMsnXWt-QriRG31Yq962Bi0m-wa6fMd5xCpmHaPFqC7BmKrAX_YcYptyQKPvV4WhSJJNdL6fE/s640/20160408_082551.jpg" width="640" /></a></div>
<br />
Vídeo que explica o procedimento:<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<iframe allowfullscreen="" class="YOUTUBE-iframe-video" data-thumbnail-src="https://i.ytimg.com/vi/6SHPd_rPTFo/0.jpg" frameborder="0" height="415" src="https://www.youtube.com/embed/6SHPd_rPTFo?feature=player_embedded" width="660"></iframe></div>
<br />
o arquivo citado no vídeo, encontra-se nesse link: <a href="http://www.mediafire.com/download/c9pyi2u7g93iqt6/CH341SER.ZIP">http://www.mediafire.com/download/c9pyi2u7g93iqt6/CH341SER.ZIP</a><br />
<br />
ou se preferirem, aqui no blog mesmo:<br />
<br />
<a href="https://drive.google.com/file/d/0B9ZzXhiNwNSrMW1KNEUxajZYUjg/view?usp=sharing">https://drive.google.com/file/d/0B9ZzXhiNwNSrMW1KNEUxajZYUjg/view?usp=sharing</a><br />
<br />
de resto, só seguir os passos mostrado no vídeo, apesar de não ser português nem inglês, tem como acompanhar.<br />
<br />
<br />Fabiano Arndthttp://www.blogger.com/profile/15268923442674744187noreply@blogger.com0tag:blogger.com,1999:blog-5513833072608837410.post-65587647966197591112016-03-05T21:54:00.001-04:002016-03-05T21:54:29.831-04:00Eletronica - Divisor de TensãoDivisor de tensão<br />
<br />
Basicamente um divisor de tensão é algo parecido com o circuito abaixo:<br />
<br />
<br />
<span style="font-family: "courier new" , "courier" , monospace;"> R1 R2</span><br />
<span style="font-family: "courier new" , "courier" , monospace;">Vin-----/\/\/\---+---/\/\/\-----GND</span><br />
<span style="font-family: "courier new" , "courier" , monospace;"> |</span><br />
<span style="font-family: "courier new" , "courier" , monospace;"> |</span><br />
<span style="font-family: "courier new" , "courier" , monospace;"> Vout</span><br />
<br />
<br />
A tensão obtida em Vout será dada pela seguinte fórmula:<br />
<br />
Vout = R2 / (R1+R2) * Vin<br />
<br />
É comum precisarmos utilizar divisões de tensão pra reduzir a tensão de 5V para 3.3V. Isso significa que precisamos ter um Vout de 2/3 de Vin. Há várias combinações de resistores comerciais que se aproximam desses valores, como por exemplo, um resitor de 10 Kohms para R1 e um de 20 Kohms para R2, ou um resistor de 330 ohms para R1 e 680 ohms para R2. Ou ainda diversos outros resistores que tem uma mesma relação aproximada de 2/3 entre eles.<br />
<br />
Mas caso você não tenha em mãos os resistores com os valores corretos para ter essa redução, uma solução bem simples, é utilizar 3 resistores de mesmo valor (o valor não importa, o importante é que os 3 sejam exatamente o mesmo valor).<br />
<br />
Tendo os 3 resistores iguais, basta combiná-los na forma de um dos dois circuitos abaixo, que a tensão de saída obtida será sempre dada pela fórmula abaixo:<br />
<br />
Vout = 2/3 Vin.<br />
<br />
como exemplo, imaginemos uma entrada de 5V<br />
<br />
2/3 * 5V ==> 3.33V<br />
<br />
<br />
<span style="font-family: "courier new" , "courier" , monospace;">(1)</span><br />
<span style="font-family: "courier new" , "courier" , monospace;"> R R R</span><br />
<span style="font-family: "courier new" , "courier" , monospace;">Vin-----/\/\/\---+---</span><span style="font-family: "courier new" , "courier" , monospace;">/\/\/\----</span><span style="font-family: "courier new" , "courier" , monospace;">/\/\/\</span><span style="font-family: "courier new" , "courier" , monospace;">-----GND</span><br />
<span style="font-family: "courier new" , "courier" , monospace;"> |</span><br />
<span style="font-family: "courier new" , "courier" , monospace;"> |</span><br />
<span style="font-family: "courier new" , "courier" , monospace;"> Vout</span><br />
<div>
<br /></div>
<div>
<span style="font-family: "courier new" , "courier" , monospace;">OU</span><br />
<span style="font-family: "courier new" , "courier" , monospace;"><br /></span>
<span style="font-family: "courier new" , "courier" , monospace;">(2)</span></div>
<div>
<span style="font-family: "courier new" , "courier" , monospace;"> R</span></div>
<div>
<span style="font-family: "courier new" , "courier" , monospace;"> +--/\/\/\--+ </span><span style="font-family: "courier new" , "courier" , monospace;"> </span></div>
<div>
<span style="font-family: "courier new" , "courier" , monospace;"> | R | R</span><br />
<span style="font-family: "courier new" , "courier" , monospace;">Vin--+--/\/\/\--+---+---</span><span style="font-family: "courier new" , "courier" , monospace;">/\/\/\</span><span style="font-family: "courier new" , "courier" , monospace;">-----GND</span><br />
<span style="font-family: "courier new" , "courier" , monospace;"> |</span><br />
<span style="font-family: "courier new" , "courier" , monospace;"> |</span><br />
<span style="font-family: "courier new" , "courier" , monospace;"> Vout</span></div>
<div>
<span style="font-family: "courier new" , "courier" , monospace;"><br /></span></div>
<div>
<span style="font-family: "courier new" , "courier" , monospace;"><br /></span></div>
<div>
Pra ficar um pouco mais claro, matematicamente falando, vamos analisar a fórmula inicial do cálculo do divisor de tensão, que é:<br />
<br />
<br />
Vout = R2 / (R1+R2) * Vin<br />
<br />
Considerando, 3 Resistores de valores R, teríamos o seguinte para o circuito (1):<br />
<br />
Como temos dois resistores em Série no lugar de R2, devemos somar os dois resistores, que será 2R.<br />
<br />
<span style="font-family: "courier new" , "courier" , monospace;"></span><br />
Vout = R2 / (R1+R2) * Vin<br />
Vout = 2R / (2R + R) * Vin<br />
Vout = 2R / 3R * Vin<br />
<br />
como R/R é igual a 1, podemos cortar os dois R<br />
e como resultados teremos:<br />
<br />
Vout = 2/3 * Vin<br />
ou<br />
Vout = 0.66666 * Vin<br />
<br />
Ou seja, nossa fórmula não irá depender do valor de R, bastando apenas que ele seja diferente de zero.<br />
<br />
<br />
no circuito (2) temos no lugar de R1, dois resistores de valor R em paralelo. Como sabemos, quando dois resistores de mesmo valor estão em séries, sua resistencia equivalente, é o valor de R divido por 2. ou seja, a metade do valor. nesse caso, a fórmula ficara da seguinte maneira:<br />
<br />
Vout = R2 / (R1+R2) * Vin<br />
Vout = R / (R + R/2) * Vin<br />
Vout = R / (R + 0.5 R) * Vin<br />
Vout = R / 1.5 R * Vin<br />
<br />
como R/R é igual a 1, podemos cortar os dois R<br />
e como resultados teremos:<br />
<br />
Vout = 1/1.5 * Vin<br />
Vout = 0.6666 * Vin<br />
<br />
Como vemos, temos o mesmo resultado que o circuito (1)<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<iframe allowfullscreen="" class="YOUTUBE-iframe-video" data-thumbnail-src="https://i.ytimg.com/s_vi/j9-dCU5l3fE/default.jpg?sqp=CMiZ7rYF&rs=AOn4CLBM5jQm3ptbb1w8Gjq2yxAPbUJnzw" frameborder="0" height="415" src="https://www.youtube.com/embed/j9-dCU5l3fE?feature=player_embedded" width="660"></iframe></div>
<br />
<br />
<br />
<br /></div>
Fabiano Arndthttp://www.blogger.com/profile/15268923442674744187noreply@blogger.com0tag:blogger.com,1999:blog-5513833072608837410.post-51856142110998908652016-02-24T11:33:00.003-04:002016-02-25T16:26:27.870-04:00Oracle SQL - Comandos SQL para OracleOs vídeos mostrados aqui são parte de uma série de vídeos voltadas a apresentar os comandos SQL do oracle para iniciantes ou pessoas com pouca experiência com banco de dados.<br />
<br />
A intenção não é entrar em muitos detalhes, apenas dar uma ideia geral de forma rápida sobre os comandos apresentados. Alguns dos comandos apresentados aqui, podem exigir consulta extra a outras documentações.<br />
<br />
Os testes foram feitos na versão 11g.<br />
<br />
Comandos Básicos<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<iframe allowfullscreen="" class="YOUTUBE-iframe-video" data-thumbnail-src="https://i.ytimg.com/s_vi/aUw5Vd6TDZk/default.jpg?sqp=CPiYt7YF&rs=AOn4CLAVY4Ha2FnqzOuUCpM6HK0t2xCQag" frameborder="0" height="415" src="https://www.youtube.com/embed/aUw5Vd6TDZk?feature=player_embedded" width="660"></iframe></div>
<br />
Funções Simples<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<iframe allowfullscreen="" class="YOUTUBE-iframe-video" data-thumbnail-src="https://i.ytimg.com/s_vi/2yW85YSw42s/default.jpg?sqp=CPiYt7YF&rs=AOn4CLBOLGbI_FDXgGXZCdtG0SUndv_XcA" frameborder="0" height="415" src="https://www.youtube.com/embed/2yW85YSw42s?feature=player_embedded" width="660"></iframe></div>
<div class="separator" style="clear: both; text-align: center;">
<br /></div>
Comandos Básicos DDL<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<iframe allowfullscreen="" class="YOUTUBE-iframe-video" data-thumbnail-src="https://i.ytimg.com/s_vi/AG6BN3Q1S8M/default.jpg?sqp=CPiYt7YF&rs=AOn4CLBviRTfO-dmNn0QHfkMOkLXD9lmLg" frameborder="0" height="415" src="https://www.youtube.com/embed/AG6BN3Q1S8M?feature=player_embedded" width="660"></iframe></div>
<br />
Campo auto-incremento<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<iframe allowfullscreen="" class="YOUTUBE-iframe-video" data-thumbnail-src="https://i.ytimg.com/s_vi/dRncYaOUze0/default.jpg?sqp=CPiYt7YF&rs=AOn4CLDIDsVOGiPmNfLGPbwfKbxfl7mxcg" frameborder="0" height="415" src="https://www.youtube.com/embed/dRncYaOUze0?feature=player_embedded" width="660"></iframe></div>
<br />
Sub-consultas<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<iframe allowfullscreen="" class="YOUTUBE-iframe-video" data-thumbnail-src="https://i.ytimg.com/s_vi/pvvDgYHiWw4/default.jpg?sqp=CPiYt7YF&rs=AOn4CLBvyrNL4CMgBIObRcwuXYeEMnfWCA" frameborder="0" height="415" src="https://www.youtube.com/embed/pvvDgYHiWw4?feature=player_embedded" width="660"></iframe></div>
<br />
Consultas hierárquicas e tabelas autoreferenciadas<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<iframe allowfullscreen="" class="YOUTUBE-iframe-video" data-thumbnail-src="https://i.ytimg.com/vi/iMEJKGAxFi0/0.jpg" frameborder="0" height="415" src="https://www.youtube.com/embed/iMEJKGAxFi0?feature=player_embedded" width="660"></iframe></div>
<br />
<br />
Group by estendido - Rollup e Cube<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<iframe allowfullscreen="" class="YOUTUBE-iframe-video" data-thumbnail-src="https://i.ytimg.com/vi/lNtCNx-wpS4/0.jpg" frameborder="0" height="415" src="https://www.youtube.com/embed/lNtCNx-wpS4?feature=player_embedded" width="660"></iframe></div>
<br />
Junções internas e externas<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<iframe allowfullscreen="" class="YOUTUBE-iframe-video" data-thumbnail-src="https://i.ytimg.com/vi/cULY2wHioVM/0.jpg" frameborder="0" height="415" src="https://www.youtube.com/embed/cULY2wHioVM?feature=player_embedded" width="660"></iframe></div>
<br />
<br />
Inscreva-se no canal do Youtube para próximos vídeosFabiano Arndthttp://www.blogger.com/profile/15268923442674744187noreply@blogger.com0tag:blogger.com,1999:blog-5513833072608837410.post-71771177835770710712016-02-05T09:49:00.003-03:002016-06-16T10:13:50.273-04:00Arduino - Dicas de ProgramaçãoLink com todos os artigos e vídeos da série Dicas de Programação para Arduino:<br />
<br />
<a href="http://fabianoallex.blogspot.com.br/2014/07/arduino-dicas-de-programacao-01-video.html">http://fabianoallex.blogspot.com.br/2014/07/arduino-dicas-de-programacao-01-video.html</a><br />
<a href="http://fabianoallex.blogspot.com.br/2014/07/arduino-dicas-de-programacao-02-segundo.html">http://fabianoallex.blogspot.com.br/2014/07/arduino-dicas-de-programacao-02-segundo.html</a><br />
<a href="http://fabianoallex.blogspot.com.br/2014/08/arduino-dicas-de-programacao-03.html">http://fabianoallex.blogspot.com.br/2014/08/arduino-dicas-de-programacao-03.html</a><br />
<a href="http://fabianoallex.blogspot.com.br/2014/09/arduino-dicas-de-programacao-04.html">http://fabianoallex.blogspot.com.br/2014/09/arduino-dicas-de-programacao-04.html</a><br />
<a href="https://www.youtube.com/watch?v=V96LLuPKFCw">https://www.youtube.com/watch?v=V96LLuPKFCw</a> - 05 - Cadê o main?<br />
<a href="http://fabianoallex.blogspot.com.br/2014/12/arduino-dicas-de-programacao-06-tempo.html">http://fabianoallex.blogspot.com.br/2014/12/arduino-dicas-de-programacao-06-tempo.html</a><br />
<a href="https://www.youtube.com/watch?v=XuLR1Sh3HhQ">https://www.youtube.com/watch?v=XuLR1Sh3HhQ</a> - 07 - Bitwise<br />
<a href="http://fabianoallex.blogspot.com.br/2015/09/arduino-dicas-de-programacao-08-int-x.html">http://fabianoallex.blogspot.com.br/2015/09/arduino-dicas-de-programacao-08-int-x.html</a><br />
<a href="http://fabianoallex.blogspot.com.br/2016/01/arduino-dicas-de-programacao-bit-fields.html">http://fabianoallex.blogspot.com.br/2016/01/arduino-dicas-de-programacao-bit-fields.html</a><br />
<br />
<br />Fabiano Arndthttp://www.blogger.com/profile/15268923442674744187noreply@blogger.com0tag:blogger.com,1999:blog-5513833072608837410.post-72336244720692577482016-01-28T15:37:00.004-03:002016-06-16T10:13:50.275-04:00Arduino - Dicas de Programação - Bit FieldsQuando se trata de programação de microcontroladores, precisamos estar sempre atento ao tamanho do data type a ser usado ao declarar uma variável, pois em geral contamos com poucos bytes a disposição para nossos programas.<br />
<br />
Eu já fiz um vídeo (veja <a href="https://www.youtube.com/watch?v=XuLR1Sh3HhQ" target="_blank">aqui</a>) explicando como utilizar operações de bitwise, que se bem utilizadas nos garante uma boa economia de bytes. Mas nesse artigo quero falar sobre outro recurso que a liguagem C nos oferece: Os Bit Fields.<br />
<br />
Bit fields são utilizados em conjunto com structs, que pra quem não sabe, são estruturas de dados que podem ser definidas pelo programador, para representar dados compostos, como por exemplo, dados sobre uma pessoa, que pode ter o nome, idade, etc.<br />
<br />
Exemplo de struct:<br />
<br />
<pre class="prettyprint notranslate prettyprinted" style="background-color: #eeeeee; border-radius: 0px; border: 1px solid rgb(214, 214, 214); box-sizing: border-box; font-family: Menlo, Monaco, Consolas, 'Courier New', monospace; font-size: 12px; line-height: 16px; margin-bottom: 10px; overflow: auto; padding: 5px; width: 100%;"><span class="kwd" style="box-sizing: border-box; color: #000088;">struct</span><span class="pln" style="box-sizing: border-box; color: #313131;"> Pessoa </span><span class="pun" style="box-sizing: border-box; color: #666600;">{</span><span class="pln" style="box-sizing: border-box;"><span style="color: #313131;">
</span><span style="color: #000088;">char nome[10]</span></span><span class="pun" style="box-sizing: border-box; color: #666600;">;</span><span class="pln" style="box-sizing: border-box; color: #313131;">
</span><span class="pln" style="box-sizing: border-box;"><span style="color: #000088;">int idade</span></span><span class="pun" style="box-sizing: border-box; color: #666600;">;</span><span class="pln" style="box-sizing: border-box; color: #313131;">
</span><span class="pun" style="box-sizing: border-box; color: #666600;">}</span><span class="pun" style="box-sizing: border-box; color: #666600;">;
</span></pre>
<div>
<br />
Além disso bit fields também podem ser utilizados em unions e classes.<br />
<br /></div>
<div>
Vamos a um exmeplo: É comum precisarmos utilizar flags em nossos programas, que são variáveis que irão receber possivelmente apenas dois valores, como 0 e 1, ou true e false, etc. Nesses casos é comum utilizarmos variáveis do tipo boolean, byte ou pior ainda int. As variáveis do tipo boolean e do tipo byte, possuem tamanho de 1 byte, já um int precisa de 2 bytes. Se a nossa intenção é armazenar valores que podem assumir no máximo dois valores, em teoria não precisaríamos de 1 byte completo (lembrando que um byte tem 8 bits), já que um único bit nesse caso seria suficiente para representar o nosso flag.</div>
<div>
<br /></div>
<div>
Infelizmente não temos como declarar variáveis do tipo bit, mas temos o recurso de Bit Fields que são muito fáceis de serem usados. Vamos a um exemplo:</div>
<div>
<br /></div>
<div>
<pre class="prettyprint notranslate prettyprinted" style="background-color: #eeeeee; border-radius: 0px; border: 1px solid rgb(214, 214, 214); box-sizing: border-box; font-family: Menlo, Monaco, Consolas, 'Courier New', monospace; font-size: 12px; line-height: 16px; margin-bottom: 10px; overflow: auto; padding: 5px; width: 100%;"><span class="kwd" style="box-sizing: border-box; color: #000088;">struct</span><span class="pln" style="box-sizing: border-box; color: #313131;"> Flags </span><span class="pun" style="box-sizing: border-box; color: #666600;">{</span><span class="pln" style="box-sizing: border-box;"><span style="color: #313131;">
</span><span style="color: #000088;">byte flag_1 : 1;</span></span><span class="pln" style="box-sizing: border-box; color: #313131;">
</span><span class="pun" style="box-sizing: border-box; color: #666600;">}</span><span class="pun" style="box-sizing: border-box; color: #666600;">;</span></pre>
</div>
<div>
<br /></div>
<div>
Veja que adicionamos um sinal de ":" e após isso o número 1. Isso significa que estamos limitando o tamanho da variável flag_1 para que tenha apenas um bit, logo ela somente irá representar valores que podem ser 1 ou 0. Porém na prática, ainda não temos nenhuma economia de memória, pois apesar da variável flag_1 ter sido limitada no que pode representar, a memória alocada fisicamente ainda será de um byte por variável do tipo Flags.</div>
<div>
<br /></div>
<div>
Mas isso acontece, pois tivemos a necessidade de apenas um flag, se nós tivéssemos que utilizar dois, três ou mais, aí sim começaríamos a nos beneficiar dos bit fields. Então vamos incluir mais alguns flags nessa estrutura:</div>
<div>
<br /></div>
<div>
<pre class="prettyprint notranslate prettyprinted" style="background-color: #eeeeee; border-radius: 0px; border: 1px solid rgb(214, 214, 214); box-sizing: border-box; margin-bottom: 10px; overflow: auto; padding: 5px; width: 100%;"><span class="kwd" style="box-sizing: border-box; color: #000088; font-family: "menlo" , "monaco" , "consolas" , "courier new" , monospace; font-size: 12px; line-height: 16px;">struct</span><span class="pln" style="box-sizing: border-box; color: #313131; font-family: "menlo" , "monaco" , "consolas" , "courier new" , monospace; font-size: 12px; line-height: 16px;"> Flags </span><span class="pun" style="box-sizing: border-box; color: #666600; font-family: "menlo" , "monaco" , "consolas" , "courier new" , monospace; font-size: 12px; line-height: 16px;">{</span><span class="pln" style="box-sizing: border-box;"><span style="color: #313131; font-family: "menlo" , "monaco" , "consolas" , "courier new" , monospace; font-size: 12px; line-height: 16px;">
</span><span style="color: #000088; font-family: "menlo" , "monaco" , "consolas" , "courier new" , monospace; font-size: 12px; line-height: 16px;">byte flag_1 : 1;
</span><span style="color: #313131;"><span style="font-family: "menlo" , "monaco" , "consolas" , "courier new" , monospace;"><span style="font-size: 12px; line-height: 16px;"> byte flag_2 : 1;
byte flag_3 : 1;
byte flag_4 : 1;
byte flag_5 : 1;
byte flag_6 : 1;
byte flag_7 : 1;
byte flag_8 : 1;
</span></span></span></span><span class="pun" style="box-sizing: border-box; color: #666600; font-family: "menlo" , "monaco" , "consolas" , "courier new" , monospace; font-size: 12px; line-height: 16px;">}</span><span class="pun" style="box-sizing: border-box; color: #666600; font-family: "menlo" , "monaco" , "consolas" , "courier new" , monospace; font-size: 12px; line-height: 16px;">;</span></pre>
</div>
<div>
<br /></div>
<div>
Agora temos 8 flags e ainda assim o tamanho que uma variável do tipo Flags ocupará é de apenas 1 byte. Isso por que cada um dos bits fará parte de um único byte. Caso adicionássemos um "flag_9 : 1" aí um novo byte teria que ser alocado.</div>
<div>
<br /></div>
<div>
Apesar desse exemplo utilizar apenas 1 bit pra cada variável da struct, poderíamos ter outras quantidades de bits. Por exemplo, uma variável que tenha valores possíveis entre 0 e 7, poderia ser declarada da seguinte maneira:</div>
<div>
<br /></div>
<div>
<pre class="prettyprint notranslate prettyprinted" style="background-color: #eeeeee; border-radius: 0px; border: 1px solid rgb(214, 214, 214); box-sizing: border-box; margin-bottom: 10px; overflow: auto; padding: 5px; width: 100%;"><span class="kwd" style="box-sizing: border-box; color: #000088; font-family: "menlo" , "monaco" , "consolas" , "courier new" , monospace; font-size: 12px; line-height: 16px;">struct</span><span class="pln" style="box-sizing: border-box; color: #313131; font-family: "menlo" , "monaco" , "consolas" , "courier new" , monospace; font-size: 12px; line-height: 16px;"> MeuBitField </span><span class="pun" style="box-sizing: border-box; color: #666600; font-family: "menlo" , "monaco" , "consolas" , "courier new" , monospace; font-size: 12px; line-height: 16px;">{</span><span class="pln" style="box-sizing: border-box;"><span style="color: #313131; font-family: "menlo" , "monaco" , "consolas" , "courier new" , monospace; font-size: 12px; line-height: 16px;">
</span><span style="color: #000088; font-family: "menlo" , "monaco" , "consolas" , "courier new" , monospace; font-size: 12px; line-height: 16px;">byte valor_1 : 3; /*0..7 ou 000 ... 111*/
</span><span style="color: #313131;"><span style="font-family: "menlo" , "monaco" , "consolas" , "courier new" , monospace;"><span style="font-size: 12px; line-height: 16px;"> byte valor_2 : 3;
byte flag_1 : 1;
byte flag_2 : 1;
</span></span></span></span><span class="pun" style="box-sizing: border-box; color: #666600; font-family: "menlo" , "monaco" , "consolas" , "courier new" , monospace; font-size: 12px; line-height: 16px;">}</span><span class="pun" style="box-sizing: border-box; color: #666600; font-family: "menlo" , "monaco" , "consolas" , "courier new" , monospace; font-size: 12px; line-height: 16px;">;</span></pre>
</div>
<div>
<br /></div>
<div>
Para provar que esse recurso realmente funciona, veja as imagens abaixo, onde na primeira compilação, sem o uso de bit fields tivemos o uso de 17 bytes. Enquanto que na segunda vez, já com o uso dos bit fields, tivemos o uso de apenas 10 bytes. E para provarmos que uma variável do tipo Flags irá consumir apenas 1 byte, compilamos também o código vazio, apenas com o loop e setup sem nenhum código. O que consumiu 9 bytes.<br />
<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh1wLW51qlQKg12L628CpCsJaJr85iUiNL7bnlcSyukZGnB0eE5xnG6hPqgP2i7hZYjXBv3dp48VUqQuAcjSJf16v3grKlkWdXMnuAmIhChJcT47gS-PMPy7qSxP4QDCa3L_Yq0e8Tg4zg/s1600/bit+fields+1.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="640" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh1wLW51qlQKg12L628CpCsJaJr85iUiNL7bnlcSyukZGnB0eE5xnG6hPqgP2i7hZYjXBv3dp48VUqQuAcjSJf16v3grKlkWdXMnuAmIhChJcT47gS-PMPy7qSxP4QDCa3L_Yq0e8Tg4zg/s640/bit+fields+1.png" width="478" /></a></div>
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjeG2rjUF8A-3zVpMsMw3wOf3zrKl2rqvBRIELKwmGQr9VjpvIYjQO3kZE4QUV6O4-pofSlOmLYDVZwFCqlQ9ZTqmHq7tM2YLGMCLkZRRu0C-Bc2sHMDwMc45zWb4C4Rw8AWKkOTNEaKmQ/s1600/bit+fields+2.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="640" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjeG2rjUF8A-3zVpMsMw3wOf3zrKl2rqvBRIELKwmGQr9VjpvIYjQO3kZE4QUV6O4-pofSlOmLYDVZwFCqlQ9ZTqmHq7tM2YLGMCLkZRRu0C-Bc2sHMDwMc45zWb4C4Rw8AWKkOTNEaKmQ/s640/bit+fields+2.png" width="478" /></a></div>
<br /></div>
<div>
<br /></div>
<div>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhS_43o0Re44K9E8ghy64lYpiWU_EPvoZc7zXy0Hz251a8vJgN9NBQs55l44PkITyqIRiHw29D_FLzDM0gGeNlDOSd8APg7KUWcxeor2Xcn2lollCObStQ2HtPiBPvnh9qbhLOJbJRMELg/s1600/bit+fields+3.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="640" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhS_43o0Re44K9E8ghy64lYpiWU_EPvoZc7zXy0Hz251a8vJgN9NBQs55l44PkITyqIRiHw29D_FLzDM0gGeNlDOSd8APg7KUWcxeor2Xcn2lollCObStQ2HtPiBPvnh9qbhLOJbJRMELg/s640/bit+fields+3.png" width="478" /></a></div>
<br />
<br />
<b>Orientação a Objetos</b><br />
<br />
Uma desculpa que sempre vejo que alguns programadores dão para não usarem orientação a objetos em programas para microcontroladores é que objetos ocupam muita memória. Mas geralmente essas afirmações ficam apenas nisso, ninguém se dá ao trabalho de realmente tirar a prova ou demonstrar que objetos consomem mais memória que programação estrutural.<br />
<br />
Um bom exemplo de que dá pra programar Orientado a objetos com código enxuto, é que classes também podem ter bit fields.<br />
<br />
Veja:</div>
<div>
<br />
<pre class="prettyprint notranslate prettyprinted" style="background-color: #eeeeee; border-radius: 0px; border: 1px solid rgb(214, 214, 214); box-sizing: border-box; margin-bottom: 10px; overflow: auto; padding: 5px; width: 100%;"><span style="color: #000088; font-family: "menlo" , "monaco" , "consolas" , "courier new" , monospace;"><span style="font-size: 12px; line-height: 16px;">class Teste {
private:
byte _x : 1;
byte _y : 1;
public:
void setX(boolean x){ _x = x; }
void setY(boolean y){ _y = y; }
boolean getX(){ return _x; }
boolean getY(){ return _y; }
};
Teste t;
void setup() {
t.setX(true);
t.setX(false);
}
void loop() { }</span></span></pre>
</div>
Fabiano Arndthttp://www.blogger.com/profile/15268923442674744187noreply@blogger.com4tag:blogger.com,1999:blog-5513833072608837410.post-47433607849577486102016-01-26T11:24:00.001-03:002016-06-16T10:13:50.260-04:00Arduino - Display LCD Nokia 5110<br />
Barato e de baixo consumo, o display LCD 5110 da Nokia é uma ótima opção para diversos projetos que precisam saídas gráficas, textos, etc. É monocromático e possui uma resolução de 84 x 48 pixels (o que dá 4032 pixels).<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEinVOAFM0EO5LOkNF1Ee-n0C90UCT2yLJPZunxvQHKQG0yaFHf8TrU2dBtlntLGHRnX5H1r-2gJpH38E_4VBajXXtJxwlTXNS6MNSGZRGy2kvUA4xtfSPUFNXl6_NCimp4tiUEcMbv_TKE/s1600/20160119_105658.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="360" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEinVOAFM0EO5LOkNF1Ee-n0C90UCT2yLJPZunxvQHKQG0yaFHf8TrU2dBtlntLGHRnX5H1r-2gJpH38E_4VBajXXtJxwlTXNS6MNSGZRGy2kvUA4xtfSPUFNXl6_NCimp4tiUEcMbv_TKE/s640/20160119_105658.jpg" width="640" /></a></div>
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiya_0ZAI-ZQ8wKST3jt0Wq5Pt2GPk0Dwm-FrNA035YMNpUmmc59Zch0IBqUeNWdVAuB9DwpbkkwoJBEXsItmabTh6bvNJG6934xqO7MMfm4crLzLcy_zZRas1xlulHtYz00q9ftfAIdMk/s1600/20160120_125607.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="360" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiya_0ZAI-ZQ8wKST3jt0Wq5Pt2GPk0Dwm-FrNA035YMNpUmmc59Zch0IBqUeNWdVAuB9DwpbkkwoJBEXsItmabTh6bvNJG6934xqO7MMfm4crLzLcy_zZRas1xlulHtYz00q9ftfAIdMk/s640/20160120_125607.jpg" width="640" /></a></div>
<br />
Esse Display foi utilizado no famoso Nokia 5110, lançado em 1998 (Há 18 anos), o qual tinha como grande vantagem o baixo consumo de energia, chegando a bateria a durar vários dias.<br />
<br />
<span style="font-size: x-small;">Observação: Talvez você não queira se preocupar com os detalhes de funcionamento do display (e do CI PCD8544) e apenas queira uma biblioteca que faça o "serviço sujo". Mas o objetivo desse artigo não é abordar o uso de nenhuma biblioteca, e sim explicar como o display funciona mostrando algumas formas de fazer isso sem o uso de bibliotecas de terceiros.</span><br />
<br />
Para entendermos exatamente como o display funciona, nada melhor que conhecermos o seu CI, o PCD8544 da Philips, portanto é uma boa ideia darmos uma estudada no datasheet para entendermos melhor como fazer a comunicação entre Arduino e o display:<br />
<br />
<a href="https://www.sparkfun.com/datasheets/LCD/Monochrome/Nokia5110.pdf">https://www.sparkfun.com/datasheets/LCD/Monochrome/Nokia5110.pdf</a><br />
<br />
Na memória do PCD8544 esses dados são divididos em 6 <i>banks</i> onde cada <i>bank</i> armazena 84 bytes, ficando um byte por coluna.<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
</div>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgL2n19dkUf91Q8uDkoSJMn07WhDXtNONQh5dweCr6sso7l2ghhm2OngDrqM8qD-buf2dfKfqSa5THnqIF87FHtDkODKo3n01gnS6R_E7T5D56VQhuA7fA6MvhzEzfzA4miJ4hl_3Y3q1E/s1600/PCD8544.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="406" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgL2n19dkUf91Q8uDkoSJMn07WhDXtNONQh5dweCr6sso7l2ghhm2OngDrqM8qD-buf2dfKfqSa5THnqIF87FHtDkODKo3n01gnS6R_E7T5D56VQhuA7fA6MvhzEzfzA4miJ4hl_3Y3q1E/s640/PCD8544.png" width="640" /></a></div>
<br />
Do ponto de vista do display, o primeiro <i>bank</i> representa as 8 primeiras linhas do display, o segundo <i>bank</i> representa as próximas 8 linhas (da 9ª a 16ª linha) e assim por diante. Já os primeiros bytes de todos os <i>banks</i> representam a primeira coluna do display, os segundos bytes representam a segunda coluna e assim por diante, até a 83ª coluna.<br />
<br />
Agora que sabemos como os dados são organizados, precisaremos entender como enviar as informações para o mesmo. Para isso é importante conhecermos alguns dos pinos desses CI, pois é através deles que o Arduino irá se comunicar com o display.<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjGY7i6l096ZT5O3tA7xHSHXQE9_10lKlm4uRcAPrZNGlQ47b1pmurcixvK6mFc8Sa-cQdu3PPLlOUv05_c1MXLk2oiAvcCKP1UuCMCQCWPdcYucx5kM3VpJ42nhxhR-Xoi1FsLJVP6PH4/s1600/PCD8544_pins.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="371" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjGY7i6l096ZT5O3tA7xHSHXQE9_10lKlm4uRcAPrZNGlQ47b1pmurcixvK6mFc8Sa-cQdu3PPLlOUv05_c1MXLk2oiAvcCKP1UuCMCQCWPdcYucx5kM3VpJ42nhxhR-Xoi1FsLJVP6PH4/s400/PCD8544_pins.png" width="400" /></a></div>
<br />
<br />
<br />
SDIN - Utilizado para enviar os dados seriais.<br />
SCLK - Clock externo<br />
D/C - Seleciona se enviaremos dados ou comandos.<br />
SCE - Indica se o clock está habilitado, ou seja, se está ativado. Ativado em Low.<br />
RES - Utilizado para Resetar o display. Ativado em Low.<br />
<br />
<br />
<b>Escrita de dados e comandos</b><br />
<b><br /></b>
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg_os7E15oS2R07CcLXGhzGnOXja4mNMgM1aR4DXcldqo6OvUjRITserrpB36K5802zE_R7A6in6ccILkiBlpVvhtHx4aQTdW1Cz9GJvVjUyHxM6P47GbmcFW2bICl_GZ9cJAeA7XpORtU/s1600/PCD8544_comandos.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="434" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg_os7E15oS2R07CcLXGhzGnOXja4mNMgM1aR4DXcldqo6OvUjRITserrpB36K5802zE_R7A6in6ccILkiBlpVvhtHx4aQTdW1Cz9GJvVjUyHxM6P47GbmcFW2bICl_GZ9cJAeA7XpORtU/s640/PCD8544_comandos.png" width="640" /></a></div>
<b><br /></b>
<br />
Para enviar os dados para o display, precisamos passar D/C para 1 e enviar o byte contendo os dados que irão setar os pixels de determinada área do display. Nesse ponto precisamos estar atendo ao modo que o display está configurado para a escrita. Se é Vertical (V=1) ou Horizontal (V=0). Para definir o modo de entrada (vertical ou horizontal) devemos utilizar o comando "Function set", mostrado na tabela acima, através do bit DB1.<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhk4jAlupQji5GKdtM-3gVcmNDLSEqd__8AZzYSYlwAAdmZu5-AUewyz3r26kZwhyphenhyphencCCjY4mokLvH-lyT2FQTuO-lOAXEIxPKRELgmWzP1UgIaTpPJe6bbS06ls0V7l6Iq1aMA9gTtXBf4/s1600/PCD8544_endere%25C3%25A7amento.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="549" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhk4jAlupQji5GKdtM-3gVcmNDLSEqd__8AZzYSYlwAAdmZu5-AUewyz3r26kZwhyphenhyphencCCjY4mokLvH-lyT2FQTuO-lOAXEIxPKRELgmWzP1UgIaTpPJe6bbS06ls0V7l6Iq1aMA9gTtXBf4/s640/PCD8544_endere%25C3%25A7amento.png" width="640" /></a></div>
<br />
Se o modo definido for o Vertical, o primeiro byte recebido pelo display será endereçado para o primeiro byte do primeiro bank (Y-address = 0). O segundo byte recebido pelo display será endereçado para o primeiro byte do segundo bank e assim por diantes, ou seja, o endereçamento é feito verticalmente.<br />
<br />
Já se o modo definido for o Horizontal, o primeiro byte recebido pelo display será endereçado para o primeiro byte do primeiro bank (Y-address = 0). O segundo byte recebido pelo display será endereçado para o segundo byte do primeiro bank e assim por diante, ou seja, o endereçamento é feito horizontalmente.<br />
<br />
Nem sempre vamos querer atualizar o display a partir da primeira linha e primeira coluna, as vezes podemos querer atualizar apenas uma determinada área do display. Isso economiza processamento e tempo. Nesses casos devemos antes de enviar os dados para o display, posicionar a partir de que ponto queremos atualizar os dados do display, informando linha (y-address) e coluna (x-address). Para isso utilizamos os comandos "Set Y address of
RAM" e "Set X address of
RAM" mostrados na tabela de comandos na imagem mais acima.<br />
<br />
Agora que entendemos um pouco melhor o funcionamento do display e de como se comunicar com ele, vamos por a mão na massa.<br />
<br />
Vamos fazer a montagem do circuito, seguindo as orientações a baixo. Como são vários exemplos, esse mesmo diagrama será aplicada a todos os exemplos desse artigo.<br />
<br />
Atenção. Existem dois modelos desses displays, um de 5V (geralmente da cor azul) e outro de 3.3V (Geralmente da cor vermelha). O display que eu utilizei era de 3.3V. Infelizmente segui algumas orientações que diziam que o display apesar de ser 3.3V poderia ter seus pinos de comunicação ligados direto aos pinos do Arduino, bastando ligar apenas o Vcc ao 3.3V do Arduino. De fato funcionou por alguns dias, porém depois parou de funcionar por uns dias, mas acabou funcionando novamente, mas depois disso o display tem mostrado um comportamento estranho, falhando, as vezes ficando mais fraco. Não sei se a causa foi exatamente essa, mas por via das dúvidas acho que o mais indicado é utilizar um divisor de tensão na hora de ligar os pinos do Arduino nos pinos do display.<br />
<br />
Apesar da imagem abaixo está mostrando a ligação direta entre o Arduino e o display sugiro utilizar resistores entre os pinos 1, 2, 3, 4 e 5. Para mais detalhes, veja o artigo do fabricante: <a href="https://learn.sparkfun.com/tutorials/graphic-lcd-hookup-guide">https://learn.sparkfun.com/tutorials/graphic-lcd-hookup-guide</a><br />
<br />
Montagem:<br />
<br />
<br />
<div style="text-align: center;">
<b>Arduino | LCD NOKIA 5110</b></div>
<div style="text-align: center;">
12 | 1 RST </div>
<div style="text-align: center;">
11 | 2 CE </div>
<div style="text-align: center;">
10 | 3 DC </div>
<div style="text-align: center;">
9 | 4 DIN </div>
<div style="text-align: center;">
8 | 5 CLK </div>
<div style="text-align: center;">
3.3V | 6 VCC </div>
<div style="text-align: center;">
3 | 7 LIGHT (resistor 200 Ohm)</div>
<div style="text-align: center;">
GND | 8 GND </div>
<div style="text-align: center;">
<br /></div>
<div style="text-align: center;">
<b>Arduino | Potenciômetro </b> </div>
<div style="text-align: center;">
5v | 1 </div>
<div style="text-align: center;">
A0 | 2 </div>
<div style="text-align: center;">
GND | 3 </div>
<br />
<div class="separator" style="clear: both; text-align: center;">
</div>
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgxHIe7F_JPe29HG1hUZ-eUZ18fRYZN_vJ56zLKrXl7T6255dy4illLPOUGex-CcdTrw2VKoW5HG0Qkv6YwD1yJqOBKIZPWQ7OJfFgMrqq578WI-7IOp550oYVcJqhkKTUJuoP5SMArOD4/s1600/NOKIA+5110_bb.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="640" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgxHIe7F_JPe29HG1hUZ-eUZ18fRYZN_vJ56zLKrXl7T6255dy4illLPOUGex-CcdTrw2VKoW5HG0Qkv6YwD1yJqOBKIZPWQ7OJfFgMrqq578WI-7IOp550oYVcJqhkKTUJuoP5SMArOD4/s640/NOKIA+5110_bb.png" width="472" /></a></div>
<br />
<br />
Com o circuito montado vamos então ao nosso primeiro exemplo, que é um código bem simples apenas para demonstrar o que foi exemplificado inicialmente. No caso iremos mostrar como enviar os bytes pelo endereçamento horizontal e vertical.<br />
<br />
Aviso: os códigos de exemplo utilizados aqui fazem uso frequente de operações de bitwise. Caso ainda não domine esses conceitos, neste vídeo explico: <a href="https://www.youtube.com/watch?v=XuLR1Sh3HhQ">https://www.youtube.com/watch?v=XuLR1Sh3HhQ</a><br />
<br />
<b>Código </b>:<br />
<br />
<pre class="brush: html" style="background-color: #eeeeee; border: 1px dashed rgb(153, 153, 153); overflow: auto; padding: 5px; width: 100%;">/*
Retirado do datasheet
Table 1 Instruction set
+------------+-----+-----------------------------------------------+-----------------------------+
| | | COMMAND BYTE | |
| | +-----------------------------------------------+ |
|INSTRUCTION | D/C | DB7 | DB6 | DB5 | DB4 | DB3 | DB2 | DB1 | DB0 | DESCRIPTION |
+------------+-----+-----------------------------------------------+-----------------------------+
|(H = 0 or 1) |
+------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----------------------------+
|NOP | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |No Operation |
+------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----------------------------+
|Func. set | 0 | 0 | 0 | 1 | 0 | 0 | PD | V | H |power down control; |
| | | | | | | | | | |entry mode; |
| | | | | | | | | | |extended instruction set |
| | | | | | | | | | |control (H)| |
+------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----------------------------+
|Write Data | 1 | D7 | D6 | D5 | D4 | D3 | D2 | D1 | D0 |Write Data to display |
+------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----------------------------+
|(H = 0) use basic instruction set |
+------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----------------------------+
|Reseverd | 0 | 0 | 0 | 0 | 0 | 0 | 1 | X | X |Do not use |
+------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----------------------------+
|Disp Control| 0 | 0 | 0 | 0 | 0 | 1 | D | 0 | E |Sets Display Configuration |
| | | | | | | | | | |DE |
| | | | | | | | | | |00 - display blank |
| | | | | | | | | | |01 - all display segment on |
| | | | | | | | | | |10 - normal mode |
| | | | | | | | | | |11 - inverse video mode |
+------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----------------------------+
|Reseverd | 0 | 0 | 0 | 0 | 1 | X | X | X | X |Do not use |
+------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----------------------------+
|SetY ad. RAM| 0 | 0 | 1 | 0 | 0 | 0 | y2 | y1 | y0 |sets Y-address of RAM; |
| | | | | | | | | | |0 ≤ Y ≤ 5 |
+------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----------------------------+
|SetX ad. RAM| 0 | 1 | x6 | x5 | x4 | x3 | x2 | x1 | x0 |sets Y-address of RAM; |
| | | | | | | | | | |0 ≤ x ≤ 83 |
+------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----------------------------+
|(H = 1) use extended instruction set |
+------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----------------------------+
|Reseverd | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 |Do not use |
+------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----------------------------+
|Reseverd | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | X |Do not use |
+------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----------------------------+
|Temp Control| 0 | 0 | 0 | 0 | 0 | 0 | 1 | Tc1 | Tc0 |set Temperature Coefficient |
+------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----------------------------+
|Reseverd | 0 | 0 | 0 | 0 | 0 | 1 | X | X | X |Do not use |
+------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----------------------------+
|Bias System | 0 | 0 | 0 | 0 | 1 | 0 | BS2 | BS1 | BS0 |set Bias System (BSx) |
+------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----------------------------+
|Reseverd | 0 | 1 |VOP6 |VOP5 |VOP4 |VOP3 |VOP2 |VOP1 |VOP0 |write VOP to register |
+------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----------------------------+
*/
#define LCD_NOKIA_DATA HIGH
#define LCD_NOKIA_CMD LOW
const int pin_sclk = 8; // pin 8 - Serial clock out (SCLK)
const int pin_din = 9; // pin 9 - Serial data out (DIN)
const int pin_dc = 10; // pin 10 - Data/Command select (D/C)
const int pin_cs = 11; // pin 11 - LCD chip select (CS/CE)
const int pin_rst = 12; // pin 12 - LCD reset (RST)
void lcd_write(byte mode, byte w){
digitalWrite(pin_dc, mode);
digitalWrite(pin_cs, LOW);
shiftOut(pin_din, pin_sclk, MSBFIRST, w);
digitalWrite(pin_cs, HIGH);
}
void setup() {
pinMode(pin_din, OUTPUT);
pinMode(pin_sclk, OUTPUT);
pinMode(pin_dc, OUTPUT);
pinMode(pin_rst, OUTPUT);
pinMode(pin_cs, OUTPUT);
digitalWrite(pin_rst, LOW);
digitalWrite(pin_rst, HIGH);
lcd_write(LCD_NOKIA_CMD, B00100001 ); // LCD Extended Commands. 00100001
lcd_write(LCD_NOKIA_CMD, B10101111 ); // Set LCD Vop (Contraste) 10101111
lcd_write(LCD_NOKIA_CMD, B00000100 ); // Set Temp coefficent 00000100
lcd_write(LCD_NOKIA_CMD, B00010100 ); // LCD bias mode 1:48 00010100
lcd_write(LCD_NOKIA_CMD, B00100000 ); // LCD Basic Commands. 00100000
lcd_write(LCD_NOKIA_CMD, B00001100 ); // LCD no modo normal 00001100
lcd_write(LCD_NOKIA_CMD, B01000000); // X-ADDRESS = 0
lcd_write(LCD_NOKIA_CMD, B10000000); // Y-ADDRESS = 0
for (int i=0; i<504; i++){
lcd_write(LCD_NOKIA_DATA, B00000000);
}
}
void loop() {
static boolean horizontal = true;
for (int i=0; i<504; i++){
lcd_write(LCD_NOKIA_DATA, B11111111);
delay(20);
}
for (int i=0; i<504; i++){
lcd_write(LCD_NOKIA_DATA, B00000000);
delay(20);
}
horizontal = !horizontal;
if (horizontal) {
lcd_write(LCD_NOKIA_CMD, B00100000 ); // LCD Basic Commands. 00100000
} else {
lcd_write(LCD_NOKIA_CMD, B00100010 ); // LCD Basic Commands. 00100000
}
}
</pre>
<br />
<br />
Vídeo:<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<iframe allowfullscreen="" class="YOUTUBE-iframe-video" data-thumbnail-src="https://i.ytimg.com/s_vi/iMH0TSUZ1A4/default.jpg?sqp=CKjr-LQF&rs=AOn4CLAiPm2UDCOTn2E3-XmE5jw2QfuE8Q" frameborder="0" height="415" src="https://www.youtube.com/embed/iMH0TSUZ1A4?feature=player_embedded" width="660"></iframe></div>
<br />
<br />
Veja que os pixels são atualizados de oito em oito, isso porque os pixels são enviados agrupados em um mesmo byte, como pode ser observado na primeira imagem desse artigo. Isso significa que não é possível enviar informação de um único pixel (bit) para o display isoladamente. Mas caso queiramos alterar um único pixel do display, será necessário reenviar todo o byte ao qual o pixel pertence, alterando apenas o bit referente ao pixel ao qual se deseja modificar. Porém isso faz com que antes de enviarmos o byte todo, tenhamos que saber de antemão qual é o estados dos demais pixels. Infelizmente não temos como "perguntar" ao display qual é o estado atual de um determinado byte antes de fazermos a alteração, isso nos exige manter na memória do Arduino o estado atual do(s) byte(s) a ser(em) alterado(s). <br />
<br />
No próximo exemplo vamos ver como alterar pixel por pixel de maneira sequencial. Nesse caso será preciso também implementar as funções "Set Y address of RAM" e "Set X address of RAM", pois cada vez que alteramos um dos pixels, o display faz o incremento do endereçamento de forma automática para o próximo byte a ser recebido. Como estamos alterando sequencialmente, temos que a cada atualização reposicionar o x-address e o y-address a ser atualizado.<br />
<br />
<pre class="brush: html" style="background-color: #eeeeee; border: 1px dashed rgb(153, 153, 153); overflow: auto; padding: 5px; width: 100%;"><span style="background-color: transparent;">/*
Retirado do datasheet
Table 1 Instruction set
+------------+-----+-----------------------------------------------+-----------------------------+
| | | COMMAND BYTE | |
| | +-----------------------------------------------+ |
|INSTRUCTION | D/C | DB7 | DB6 | DB5 | DB4 | DB3 | DB2 | DB1 | DB0 | DESCRIPTION |
+------------+-----+-----------------------------------------------+-----------------------------+
|(H = 0 or 1) |
+------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----------------------------+
|NOP | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |No Operation |
+------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----------------------------+
|Func. set | 0 | 0 | 0 | 1 | 0 | 0 | PD | V | H |power down control; |
| | | | | | | | | | |entry mode; |
| | | | | | | | | | |extended instruction set |
| | | | | | | | | | |control (H)| |
+------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----------------------------+
|Write Data | 1 | D7 | D6 | D5 | D4 | D3 | D2 | D1 | D0 |Write Data to display |
+------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----------------------------+
|(H = 0) use basic instruction set |
+------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----------------------------+
|Reseverd | 0 | 0 | 0 | 0 | 0 | 0 | 1 | X | X |Do not use |
+------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----------------------------+
|Disp Control| 0 | 0 | 0 | 0 | 0 | 1 | D | 0 | E |Sets Display Configuration |
| | | | | | | | | | |DE |
| | | | | | | | | | |00 - display blank |
| | | | | | | | | | |01 - all display segment on |
| | | | | | | | | | |10 - normal mode |
| | | | | | | | | | |11 - inverse video mode |
+------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----------------------------+
|Reseverd | 0 | 0 | 0 | 0 | 1 | X | X | X | X |Do not use |
+------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----------------------------+
|SetY ad. RAM| 0 | 0 | 1 | 0 | 0 | 0 | y2 | y1 | y0 |sets Y-address of RAM; |
| | | | | | | | | | |0 ≤ Y ≤ 5 |
+------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----------------------------+
|SetX ad. RAM| 0 | 1 | x6 | x5 | x4 | x3 | x2 | x1 | x0 |sets Y-address of RAM; |
| | | | | | | | | | |0 ≤ x ≤ 83 |
+------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----------------------------+
|(H = 1) use extended instruction set |
+------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----------------------------+
|Reseverd | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 |Do not use |
+------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----------------------------+
|Reseverd | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | X |Do not use |
+------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----------------------------+
|Temp Control| 0 | 0 | 0 | 0 | 0 | 0 | 1 | Tc1 | Tc0 |set Temperature Coefficient |
+------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----------------------------+
|Reseverd | 0 | 0 | 0 | 0 | 0 | 1 | X | X | X |Do not use |
+------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----------------------------+
|Bias System | 0 | 0 | 0 | 0 | 1 | 0 | BS2 | BS1 | BS0 |set Bias System (BSx) |
+------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----------------------------+
|Reseverd | 0 | 1 |VOP6 |VOP5 |VOP4 |VOP3 |VOP2 |VOP1 |VOP0 |write VOP to register |
+------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----------------------------+
*/
#define LCD_NOKIA_DATA HIGH
#define LCD_NOKIA_CMD LOW
const int pin_sclk = 8; // pin 8 - Serial clock out (SCLK)
const int pin_din = 9; // pin 9 - Serial data out (DIN)
const int pin_dc = 10; // pin 10 - Data/Command select (D/C)
const int pin_cs = 11; // pin 11 - LCD chip select (CS/CE)
const int pin_rst = 12; // pin 12 - LCD reset (RST)
void lcd_write(byte mode, byte w){
digitalWrite(pin_dc, mode);
digitalWrite(pin_cs, LOW);
shiftOut(pin_din, pin_sclk, MSBFIRST, w);
digitalWrite(pin_cs, HIGH);
}
void lcd_position(int l, int c) {
lcd_write( LCD_NOKIA_CMD, B01000000 + l);
lcd_write( LCD_NOKIA_CMD, B10000000 + c);
}
void lcd_clear(){
for (int i=0; i<504; i++){
lcd_write(LCD_NOKIA_DATA, B00000000);
}
}
void setup() {
pinMode(pin_din, OUTPUT);
pinMode(pin_sclk, OUTPUT);
pinMode(pin_dc, OUTPUT);
pinMode(pin_rst, OUTPUT);
pinMode(pin_cs, OUTPUT);
digitalWrite(pin_rst, LOW);
digitalWrite(pin_rst, HIGH);
lcd_write(LCD_NOKIA_CMD, B00100001 ); // LCD Extended Commands. 00100001 + ENDERECAMENTO VERTICAL
lcd_write(LCD_NOKIA_CMD, B10101111 ); // Set LCD Vop (Contraste) 10101111
lcd_write(LCD_NOKIA_CMD, B00000100 ); // Set Temp coefficent 00000100
lcd_write(LCD_NOKIA_CMD, B00010100 ); // LCD bias mode 1:48 00010100
lcd_write(LCD_NOKIA_CMD, B00100000 ); // LCD Basic Commands. 00100000
lcd_write(LCD_NOKIA_CMD, B00001100 ); // LCD no modo normal 00001100
lcd_position(0,0);
lcd_clear();
}
void loop() {
//percorre horizontalmente
for(int i=0; i<6; i++){
for(int j=0; j<84; j++){
byte b = 0;
for (int k=0;k<8;k++){
lcd_position(i,j);
b = b << 1 | B00000001; // 1 11 111 1111 11111 111111 1111111 11111111
lcd_write(LCD_NOKIA_DATA, b );
if (j>10 || i>0 ){ delay(5); } else { delay(200); }
}
}
}
//percorre verticalmente
for(int j=0; j<84; j++){
for(int i=0; i<6; i++){
byte b = B11111111;
for (int k=0;k<8;k++){
lcd_position(i,j);
b = b << 1;
lcd_write(LCD_NOKIA_DATA, b );
if (j>10 || i>0 ){ delay(5); } else { delay(200); }
}
}
}
}</span>
</pre>
<div>
<br />
<br />
Nesse exemplo foi implementada uma função chamada <span style="font-family: "courier new" , "courier" , monospace;">lcd_position(int l, int c). </span>Os parâmetros l e c referem-se a posição dos bytes na memória do display, e não à posição linha/coluna dos pixels. Linha vai de 0 a 5 e coluna de 0 a 83.<br />
<br />
<b>Vídeo do exemplo anterior</b><br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<iframe allowfullscreen="" class="YOUTUBE-iframe-video" data-thumbnail-src="https://i.ytimg.com/s_vi/s0NA-BTRlJo/default.jpg?sqp=CMyTibUF&rs=AOn4CLB7SlkEj-rxeMCAdbj2kqyCs0jvhg" frameborder="0" height="415" src="https://www.youtube.com/embed/s0NA-BTRlJo?feature=player_embedded" width="660"></iframe></div>
<br />
<br />
Nesse próximo exemplo vamos implementar uma função que recebe a posição linha e coluna e o valor (HIGH/LOW) de um determinado pixel, mas agora considerando as 48 linhas e 84 colunas, ou seja, o posicionamento será referente aos pixels e não aos bytes na memória.<br />
<br /></div>
Como nossa intenção é alterar um pixel qualquer do display, precisamos saber de antemão qual o estado dos demais pixels que estão representados no mesmo byte. Pra isso será necessário criarmos um buffer que armazene informações de todos os dados enviados para o display.<br />
<br />
Para criar um buffer que armazene o estado de todos os pixels desse display, é necessário alocar na memória pelo menos 84x6 bytes, o que dá 504 bytes. Pode não parecer muito, mas declarar um array para armazenar essa quantidade de bytes, significa utilizar 25% da memória de um Arduino Uno, por exemplo.<br />
<br />
<b>Código-fonte:</b><br />
<br />
<pre class="brush: html" style="background-color: #eeeeee; border: 1px dashed rgb(153, 153, 153); overflow: auto; padding: 5px; width: 100%;">/*
Retirado do datasheet
Table 1 Instruction set
+------------+-----+-----------------------------------------------+-----------------------------+
| | | COMMAND BYTE | |
| | +-----------------------------------------------+ |
|INSTRUCTION | D/C | DB7 | DB6 | DB5 | DB4 | DB3 | DB2 | DB1 | DB0 | DESCRIPTION |
+------------+-----+-----------------------------------------------+-----------------------------+
|(H = 0 or 1) |
+------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----------------------------+
|NOP | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |No Operation |
+------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----------------------------+
|Func. set | 0 | 0 | 0 | 1 | 0 | 0 | PD | V | H |power down control; |
| | | | | | | | | | |entry mode; |
| | | | | | | | | | |extended instruction set |
| | | | | | | | | | |control (H)| |
+------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----------------------------+
|Write Data | 1 | D7 | D6 | D5 | D4 | D3 | D2 | D1 | D0 |Write Data to display |
+------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----------------------------+
|(H = 0) use basic instruction set |
+------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----------------------------+
|Reseverd | 0 | 0 | 0 | 0 | 0 | 0 | 1 | X | X |Do not use |
+------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----------------------------+
|Disp Control| 0 | 0 | 0 | 0 | 0 | 1 | D | 0 | E |Sets Display Configuration |
| | | | | | | | | | |DE |
| | | | | | | | | | |00 - display blank |
| | | | | | | | | | |01 - all display segment on |
| | | | | | | | | | |10 - normal mode |
| | | | | | | | | | |11 - inverse video mode |
+------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----------------------------+
|Reseverd | 0 | 0 | 0 | 0 | 1 | X | X | X | X |Do not use |
+------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----------------------------+
|SetY ad. RAM| 0 | 0 | 1 | 0 | 0 | 0 | y2 | y1 | y0 |sets Y-address of RAM; |
| | | | | | | | | | |0 ≤ Y ≤ 5 |
+------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----------------------------+
|SetX ad. RAM| 0 | 1 | x6 | x5 | x4 | x3 | x2 | x1 | x0 |sets Y-address of RAM; |
| | | | | | | | | | |0 ≤ x ≤ 83 |
+------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----------------------------+
|(H = 1) use extended instruction set |
+------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----------------------------+
|Reseverd | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 |Do not use |
+------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----------------------------+
|Reseverd | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | X |Do not use |
+------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----------------------------+
|Temp Control| 0 | 0 | 0 | 0 | 0 | 0 | 1 | Tc1 | Tc0 |set Temperature Coefficient |
+------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----------------------------+
|Reseverd | 0 | 0 | 0 | 0 | 0 | 1 | X | X | X |Do not use |
+------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----------------------------+
|Bias System | 0 | 0 | 0 | 0 | 1 | 0 | BS2 | BS1 | BS0 |set Bias System (BSx) |
+------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----------------------------+
|Reseverd | 0 | 1 |VOP6 |VOP5 |VOP4 |VOP3 |VOP2 |VOP1 |VOP0 |write VOP to register |
+------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----------------------------+
*/
#define LCD_NOKIA_DATA HIGH
#define LCD_NOKIA_CMD LOW
const int pin_sclk = 8; // pin 8 - Serial clock out (SCLK)
const int pin_din = 9; // pin 9 - Serial data out (DIN)
const int pin_dc = 10; // pin 10 - Data/Command select (D/C)
const int pin_cs = 11; // pin 11 - LCD chip select (CS/CE)
const int pin_rst = 12; // pin 12 - LCD reset (RST)
void setup() {
pinMode(pin_din, OUTPUT);
pinMode(pin_sclk, OUTPUT);
pinMode(pin_dc, OUTPUT);
pinMode(pin_rst, OUTPUT);
pinMode(pin_cs, OUTPUT);
digitalWrite(pin_rst, LOW);
digitalWrite(pin_rst, HIGH);
lcd_write(LCD_NOKIA_CMD, B00100001 ); // LCD Extended Commands. 00100001
lcd_write(LCD_NOKIA_CMD, B10101111 ); // Set LCD Vop (Contraste) 10101111
lcd_write(LCD_NOKIA_CMD, B00000100 ); // Set Temp coefficent 00000100
lcd_write(LCD_NOKIA_CMD, B00010100 ); // LCD bias mode 1:48 00010100
lcd_write(LCD_NOKIA_CMD, B00100000 ); // LCD Basic Commands. 00100000
lcd_write(LCD_NOKIA_CMD, B00001100 ); // LCD no modo normal 00001100
lcd_position(0,0);
lcd_clear();
}
byte lcd_buffer[504];
void lcd_write(byte mode, byte w){
digitalWrite(pin_dc, mode);
digitalWrite(pin_cs, LOW);
shiftOut(pin_din, pin_sclk, MSBFIRST, w);
digitalWrite(pin_cs, HIGH);
}
void lcd_position(int l, int c) {
lcd_write( LCD_NOKIA_CMD, B01000000 + l);
lcd_write( LCD_NOKIA_CMD, B10000000 + c);
}
void lcd_clear(){
for (int i=0; i<504; i++){
lcd_buffer[i] = B00000000;
lcd_write(LCD_NOKIA_DATA, lcd_buffer[i]);
}
}
void lcd_set_pixel(int l, int c, boolean val){
int bit = l%8;
l = l/8;
lcd_position(l, c);
if (val) {
lcd_buffer[l*84+c] |= (1 << bit);
} else {
lcd_buffer[l*84+c] &= ~(1 << bit);
}
lcd_write(LCD_NOKIA_DATA, lcd_buffer[l*84+c]);
}
void loop() {
//horizontal
for (int i=0; i<4032; i++){
int linha = i/84;
int coluna = i%84;
lcd_set_pixel( linha, coluna, true);
if (i<200 ){ delay(20); } else {delay(5);}
}
//vertical
for (int i=0; i<4032; i++){
int linha = i%48;
int coluna = i/48;
lcd_set_pixel( linha, coluna, false);
if (i<200 ){ delay(20); } else {delay(5);}
}
}
</pre>
<br />
O Exemplo acima, serve apenas como demonstração de como setar pixels individualmente, mas como a gente pode ver, enviar pixel a pixel nem sempre é uma boa ideia, já que é preciso fazer sempre o reenvio de outras informações que já estão no display, o que deixa a atualização um pouco lenta. Nesse caso seria interessante implementar uma função separada, responsável apenas por enviar os dados, assim seria possível fazer todas as alterações necessárias no buffer e posteriormente, de uma vez só, enviar os dados do buffer para o display. Como nem sempre todo o display é modificado entre uma mudança de tela e outra, é interessante também implementar algumas funcionalidades que atualize apenas parte do display que foi alterada.<br />
<br />
Nesse próximo exemplo vamos criar uma classe que gerencia as alterações e envio de dados para o display, além disso terá rotinas para gerar pixel, linhas, retângulos (preenchidos ou não) e clear, para limpar toda a tela. Outras rotinas podem ser implementadas, como gerar círculos, triângulos e outras formas geométricas. Mas o ideal é implementar essas rotinas apenas se realmente forem necessárias, caso o projeto assim exija, evitando assim a implementação de muitas rotinas desnecessárias que só deixariam o código "inchado".<br />
<br />
<br />
<b>Código-fonte</b><br />
<br />
<pre class="brush: html" style="background-color: #eeeeee; border: 1px dashed rgb(153, 153, 153); overflow: auto; padding: 5px; width: 100%;">/*
Retirado do datasheet
Table 1 Instruction set
+------------+-----+-----------------------------------------------+-----------------------------+
| | | COMMAND BYTE | |
| | +-----------------------------------------------+ |
|INSTRUCTION | D/C | DB7 | DB6 | DB5 | DB4 | DB3 | DB2 | DB1 | DB0 | DESCRIPTION |
+------------+-----+-----------------------------------------------+-----------------------------+
|(H = 0 or 1) |
+------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----------------------------+
|NOP | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |No Operation |
+------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----------------------------+
|Func. set | 0 | 0 | 0 | 1 | 0 | 0 | PD | V | H |power down control; |
| | | | | | | | | | |entry mode; |
| | | | | | | | | | |extended instruction set |
| | | | | | | | | | |control (H)| |
+------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----------------------------+
|Write Data | 1 | D7 | D6 | D5 | D4 | D3 | D2 | D1 | D0 |Write Data to display |
+------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----------------------------+
|(H = 0) use basic instruction set |
+------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----------------------------+
|Reseverd | 0 | 0 | 0 | 0 | 0 | 0 | 1 | X | X |Do not use |
+------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----------------------------+
|Disp Control| 0 | 0 | 0 | 0 | 0 | 1 | D | 0 | E |Sets Display Configuration |
| | | | | | | | | | |DE |
| | | | | | | | | | |00 - display blank |
| | | | | | | | | | |01 - all display segment on |
| | | | | | | | | | |10 - normal mode |
| | | | | | | | | | |11 - inverse video mode |
+------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----------------------------+
|Reseverd | 0 | 0 | 0 | 0 | 1 | X | X | X | X |Do not use |
+------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----------------------------+
|SetY ad. RAM| 0 | 0 | 1 | 0 | 0 | 0 | y2 | y1 | y0 |sets Y-address of RAM; |
| | | | | | | | | | |0 ≤ Y ≤ 5 |
+------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----------------------------+
|SetX ad. RAM| 0 | 1 | x6 | x5 | x4 | x3 | x2 | x1 | x0 |sets Y-address of RAM; |
| | | | | | | | | | |0 ≤ x ≤ 83 |
+------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----------------------------+
|(H = 1) use extended instruction set |
+------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----------------------------+
|Reseverd | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 |Do not use |
+------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----------------------------+
|Reseverd | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | X |Do not use |
+------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----------------------------+
|Temp Control| 0 | 0 | 0 | 0 | 0 | 0 | 1 | Tc1 | Tc0 |set Temperature Coefficient |
+------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----------------------------+
|Reseverd | 0 | 0 | 0 | 0 | 0 | 1 | X | X | X |Do not use |
+------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----------------------------+
|Bias System | 0 | 0 | 0 | 0 | 1 | 0 | BS2 | BS1 | BS0 |set Bias System (BSx) |
+------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----------------------------+
|Reseverd | 0 | 1 |VOP6 |VOP5 |VOP4 |VOP3 |VOP2 |VOP1 |VOP0 |write VOP to register |
+------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----------------------------+
*/
#define LCD_NOKIA_DATA HIGH
#define LCD_NOKIA_CMD LOW
#define LCD_PIXEL_ROWS 48
#define LCD_PIXEL_COLS 84
#define LCD_BUF_SIZE LCD_PIXEL_ROWS * LCD_PIXEL_COLS / 8 // 504
#define LCD_BUF_STATE_SIZE LCD_BUF_SIZE / 8 // 63
#define sgn(x) ((x<0)?-1:((x>0)?1:0))
const int pin_sclk = 8; // pin 8 - Serial clock out (SCLK)
const int pin_din = 9; // pin 9 - Serial data out (DIN)
const int pin_dc = 10; // pin 10 - Data/Command select (D/C)
const int pin_cs = 11; // pin 11 - LCD chip select (CS/CE)
const int pin_rst = 12; // pin 12 - LCD reset (RST)
class Display{
private:
byte _min_byte_state;
byte _max_byte_state;
byte _pos_linha;
byte _pos_coluna;
byte _lcd_buffer[LCD_BUF_SIZE];
byte _lcd_buffer_state[LCD_BUF_STATE_SIZE]; //504 / 8 = 63 --> cada bit representa o estado de um dos bytes do buffer
void _write(byte mode, byte w){
digitalWrite(pin_dc, mode);
digitalWrite(pin_cs, LOW);
shiftOut(pin_din, pin_sclk, MSBFIRST, w);
digitalWrite(pin_cs, HIGH);
if (mode == LCD_NOKIA_DATA) {
if (++_pos_coluna >= LCD_PIXEL_COLS) {
_pos_coluna = 0;
if ( ++_pos_linha>= 6) { _pos_linha = 0; }
}
}
}
void _position(int l, int c) {
if (_pos_coluna != c){ _write( LCD_NOKIA_CMD, B10000000 + c); _pos_coluna = c;}
if (_pos_linha != l){ _write( LCD_NOKIA_CMD, B01000000 + l); _pos_linha = l;}
}
public:
Display() {
pinMode(pin_din, OUTPUT);
pinMode(pin_sclk, OUTPUT);
pinMode(pin_dc, OUTPUT);
pinMode(pin_rst, OUTPUT);
pinMode(pin_cs, OUTPUT);
digitalWrite(pin_rst, LOW);
digitalWrite(pin_rst, HIGH);
_write(LCD_NOKIA_CMD, B00100001 ); // LCD Extended Commands. 00100001 + ENDERECAMENTO HORIZONTAL
_write(LCD_NOKIA_CMD, B10101111 ); // Set LCD Vop (Contraste) 10101111
_write(LCD_NOKIA_CMD, B00000100 ); // Set Temp coefficent 00000100
_write(LCD_NOKIA_CMD, B00010100 ); // LCD bias mode 1:48 00010100
_write(LCD_NOKIA_CMD, B00100000 ); // LCD Basic Commands. 00100000
_write(LCD_NOKIA_CMD, B00001100 ); // LCD no modo normal 00001100
_write(LCD_NOKIA_CMD, B01000000); // POSICAO-X 0
_write(LCD_NOKIA_CMD, B10000000); // POSICAO-Y 0
_min_byte_state = LCD_BUF_STATE_SIZE-1;
_max_byte_state = 0;
clear(true);
}
/*48 x 84*/
void pixel(int l, int c, boolean val){
if (l>=LCD_PIXEL_ROWS || c>= LCD_PIXEL_COLS || l <0 || c<0){ return; }
int bit = l%8;
l = l/8;
byte temp = _lcd_buffer[l*LCD_PIXEL_COLS+c]; //faz uma copia do estado atual do byte a ser alterado
if (val) {
_lcd_buffer[l*LCD_PIXEL_COLS+c] |= (1 << bit);
} else {
_lcd_buffer[l*LCD_PIXEL_COLS+c] &= ~(1 << bit);
}
//se houver alteração no byte, altera o status para alterado.
if (temp != _lcd_buffer[l*LCD_PIXEL_COLS+c] ){
byte idx = (l*LCD_PIXEL_COLS+c)/8; //indice do byte de status
_lcd_buffer_state[ idx ] |= (1 << ((l*LCD_PIXEL_COLS+c)%8 )); //inverte o status do byte alterado
if (_min_byte_state > idx) { _min_byte_state = idx; }
if (_max_byte_state < idx) { _max_byte_state = idx; }
}
}
void line(int x1, int y1, int x2, int y2){
/* http://www.brackeen.com/vga/source/djgpp20/lines.c.html */
int i,dx,dy,sdx,sdy,dxabs,dyabs,x,y,px,py;
dx = x2-x1; /* the horizontal distance of the line */
dy = y2-y1; /* the vertical distance of the line */
dxabs = abs(dx);
dyabs = abs(dy);
sdx = sgn(dx);
sdy = sgn(dy);
x = dyabs>>1;
y = dxabs>>1;
px = x1;
py = y1;
pixel(x1,y1,true);
if (dxabs>=dyabs) { /* the line is more horizontal than vertical */
for(i=0;i<dxabs;i++) {
y+=dyabs;
if (y>=dxabs) { y-=dxabs; py+=sdy; }
px+=sdx;
pixel(px,py,true);
}
} else { /* the line is more vertical than horizontal */
for(i=0;i<dyabs;i++) {
x+=dxabs;
if (x>=dyabs) { x-=dyabs; px+=sdx; }
py+=sdy;
pixel(px,py,true);
}
}
}
void rectangle(int left,int top, int right, int bottom, boolean fill=false) {
if (fill){
if (left>right) { //swap
left ^= right;
right ^= left;
left ^= right;
}
if (top>bottom) { //swap
top ^= bottom;
bottom ^= top;
top ^= bottom;
}
for (int i=0; i<bottom-top; i++) {
line(left,top+i,right,top+i);
}
} else {
line(left,top,right,top);
line(left,top,left,bottom);
line(right,top,right,bottom);
line(left,bottom,right,bottom);
}
}
void clear(boolean _send=false){
for (int i=0; i<LCD_BUF_SIZE; i++) { _lcd_buffer[i] = B00000000; }
for (int i=0; i<LCD_BUF_STATE_SIZE; i++) { _lcd_buffer_state[i] = B11111111; }
_min_byte_state = 0;
_max_byte_state = LCD_BUF_STATE_SIZE-1;
if (_send){ send(); }
}
void send(){
//faz uma busca pra saber quais dos bytes foram alterados
for (int i=_min_byte_state;i<=_max_byte_state;i++){
if (_lcd_buffer_state[i] == 0) { continue; }
for (int j=0;j<8;j++){
if bitRead( _lcd_buffer_state[i], j ) {
_position( (i*8+j)/LCD_PIXEL_COLS, (i*8+j)%LCD_PIXEL_COLS );
_write(LCD_NOKIA_DATA, _lcd_buffer[i*8+j]);
}
}
_lcd_buffer_state[i] = B00000000;
}
_min_byte_state = LCD_BUF_STATE_SIZE-1;
_max_byte_state = 0;
}
};
Display d; //declaração do objeto display
void setup() {
randomSeed(analogRead(A0));
}
void loop() {
for (int i=0; i<25; i++){
d.line(random(LCD_PIXEL_ROWS),random(LCD_PIXEL_COLS),random(LCD_PIXEL_ROWS),random(LCD_PIXEL_COLS));
d.send();
delay(500);
}
d.clear();
for (int i=0; i<25; i++){
d.rectangle(random(LCD_PIXEL_ROWS),random(LCD_PIXEL_COLS),random(LCD_PIXEL_ROWS),random(LCD_PIXEL_COLS));
d.send();
delay(500);
}
d.clear();
for (int i=0; i<25; i++){
d.rectangle(random(LCD_PIXEL_ROWS),random(LCD_PIXEL_COLS),random(LCD_PIXEL_ROWS),random(LCD_PIXEL_COLS), true);
d.send();
delay(500);
}
d.clear();
}
</pre>
<br />
Vídeo<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<iframe allowfullscreen="" class="YOUTUBE-iframe-video" data-thumbnail-src="https://i.ytimg.com/s_vi/PWnheV6vi-Y/default.jpg?sqp=CLSHmLUF&rs=AOn4CLB3ihbn8ucyxPTd8VhnHrVyLj6r7w" frameborder="0" height="415" src="https://www.youtube.com/embed/PWnheV6vi-Y?feature=player_embedded" width="660"></iframe></div>
<br />
<h3>
Snake Game</h3>
Nesse próximo exemplo vamos implementar o jogo Snake, o famoso jogo da cobrinha. Pra isso vou me basear em um exemplo já mostrado aqui no blog, utilizando matriz de led controlados pelo max7219. Veja o artigo nesse link: <a href="http://fabianoallex.blogspot.com.br/2015/10/arduino-snake-game-jogo-da-cobrinha.html">http://fabianoallex.blogspot.com.br/2015/10/arduino-snake-game-jogo-da-cobrinha.html</a><br />
<br />
Com poucas modificações foi possível adaptar o exemplo de lá para rodar no nosso display. A principal alteração foi diminuirmos a resolução do jogo. Se mantivéssemos como no original, a cobrinha teria a largura de 1 pixel apenas, o que deixaria o jogo muito pequeno. Pra resolver isso incluímos uma constante que multiplica o tamanho do pixel do jogo, no exemplo abaixo, 1 pixel do jogo é representado por 16 (4x4) pixels do display. O que torna o jogo mais agradável de ser visualizado.<br />
<br />
Como a implementação do jogo é servir apenas de exemplo, não foi implementados com botões para controlar a cobrinha, e sim utilizei a saída Serial, como já feito no exemplo da matriz de leds.<br />
<br />
<b>Arquivo .bat</b><br />
<br />
<pre class="brush: html" style="background-color: #eeeeee; border: 1px dashed rgb(153, 153, 153); overflow: auto; padding: 5px; width: 100%;">MODE COM6 BAUD=9600 PARITY=n DATA=8
:LOOP
CHOICE /C:1235 /M "1: LEFT; 2: BOTTOM; 3: RIGHT; 4: TOP"
IF errorlevel 4 GOTO TOP
IF errorlevel 3 GOTO RIGHT
IF errorlevel 2 GOTO BOTTOM
IF errorlevel 1 GOTO LEFT
:RIGHT
ECHO b > COM6
GOTO END
:LEFT
ECHO a > COM6
GOTO END
:TOP
ECHO c > COM6
GOTO END
:BOTTOM
ECHO d > COM6
:END
CLS
GOTO LOOP
:SAIR
</pre>
<br />
<br />
<br />
<b>Código-fonte:</b><br />
<br />
<br />
<pre class="brush: html" style="background-color: #eeeeee; border: 1px dashed rgb(153, 153, 153); overflow: auto; padding: 5px; width: 100%;">/*************************************************************************************************************
*******************************CLASSE DISPLAY NOKIA 5110******************************************************
**************************************************************************************************************/
/*
Retirado do datasheet
Table 1 Instruction set
+------------+-----+-----------------------------------------------+-----------------------------+
| | | COMMAND BYTE | |
| | +-----------------------------------------------+ |
|INSTRUCTION | D/C | DB7 | DB6 | DB5 | DB4 | DB3 | DB2 | DB1 | DB0 | DESCRIPTION |
+------------+-----+-----------------------------------------------+-----------------------------+
|(H = 0 or 1) |
+------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----------------------------+
|NOP | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |No Operation |
+------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----------------------------+
|Func. set | 0 | 0 | 0 | 1 | 0 | 0 | PD | V | H |power down control; |
| | | | | | | | | | |entry mode; |
| | | | | | | | | | |extended instruction set |
| | | | | | | | | | |control (H)| |
+------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----------------------------+
|Write Data | 1 | D7 | D6 | D5 | D4 | D3 | D2 | D1 | D0 |Write Data to display |
+------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----------------------------+
|(H = 0) use basic instruction set |
+------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----------------------------+
|Reseverd | 0 | 0 | 0 | 0 | 0 | 0 | 1 | X | X |Do not use |
+------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----------------------------+
|Disp Control| 0 | 0 | 0 | 0 | 0 | 1 | D | 0 | E |Sets Display Configuration |
| | | | | | | | | | |DE |
| | | | | | | | | | |00 - display blank |
| | | | | | | | | | |01 - all display segment on |
| | | | | | | | | | |10 - normal mode |
| | | | | | | | | | |11 - inverse video mode |
+------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----------------------------+
|Reseverd | 0 | 0 | 0 | 0 | 1 | X | X | X | X |Do not use |
+------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----------------------------+
|SetY ad. RAM| 0 | 0 | 1 | 0 | 0 | 0 | y2 | y1 | y0 |sets Y-address of RAM; |
| | | | | | | | | | |0 ≤ Y ≤ 5 |
+------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----------------------------+
|SetX ad. RAM| 0 | 1 | x6 | x5 | x4 | x3 | x2 | x1 | x0 |sets Y-address of RAM; |
| | | | | | | | | | |0 ≤ x ≤ 83 |
+------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----------------------------+
|(H = 1) use extended instruction set |
+------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----------------------------+
|Reseverd | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 |Do not use |
+------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----------------------------+
|Reseverd | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | X |Do not use |
+------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----------------------------+
|Temp Control| 0 | 0 | 0 | 0 | 0 | 0 | 1 | Tc1 | Tc0 |set Temperature Coefficient |
+------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----------------------------+
|Reseverd | 0 | 0 | 0 | 0 | 0 | 1 | X | X | X |Do not use |
+------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----------------------------+
|Bias System | 0 | 0 | 0 | 0 | 1 | 0 | BS2 | BS1 | BS0 |set Bias System (BSx) |
+------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----------------------------+
|Reseverd | 0 | 1 |VOP6 |VOP5 |VOP4 |VOP3 |VOP2 |VOP1 |VOP0 |write VOP to register |
+------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----------------------------+
*/
#define LCD_NOKIA_DATA HIGH
#define LCD_NOKIA_CMD LOW
#define LCD_PIXEL_ROWS 48
#define LCD_PIXEL_COLS 84
#define LCD_BUF_SIZE LCD_PIXEL_ROWS * LCD_PIXEL_COLS / 8 // 504
#define LCD_BUF_STATE_SIZE LCD_BUF_SIZE / 8 // 63
#define sgn(x) ((x<0)?-1:((x>0)?1:0))
const int pin_sclk = 8; // pin 8 - Serial clock out (SCLK)
const int pin_din = 9; // pin 9 - Serial data out (DIN)
const int pin_dc = 10; // pin 10 - Data/Command select (D/C)
const int pin_cs = 11; // pin 11 - LCD chip select (CS/CE)
const int pin_rst = 12; // pin 12 - LCD reset (RST)
class Display{
private:
byte _min_byte_state;
byte _max_byte_state;
byte _pos_linha;
byte _pos_coluna;
byte _lcd_buffer[LCD_BUF_SIZE];
byte _lcd_buffer_state[LCD_BUF_STATE_SIZE]; //504 / 8 = 63 --> cada bit representa o estado de um dos bytes do buffer
void _write(byte mode, byte w){
digitalWrite(pin_dc, mode);
digitalWrite(pin_cs, LOW);
shiftOut(pin_din, pin_sclk, MSBFIRST, w);
digitalWrite(pin_cs, HIGH);
if (mode == LCD_NOKIA_DATA) {
if (++_pos_coluna >= LCD_PIXEL_COLS) {
_pos_coluna = 0;
if ( ++_pos_linha>= 6) { _pos_linha = 0; }
}
}
}
void _position(int l, int c) {
if (_pos_coluna != c){ _write( LCD_NOKIA_CMD, B10000000 + c); _pos_coluna = c;}
if (_pos_linha != l){ _write( LCD_NOKIA_CMD, B01000000 + l); _pos_linha = l;}
}
public:
Display() {
pinMode(pin_din, OUTPUT);
pinMode(pin_sclk, OUTPUT);
pinMode(pin_dc, OUTPUT);
pinMode(pin_rst, OUTPUT);
pinMode(pin_cs, OUTPUT);
digitalWrite(pin_rst, LOW);
digitalWrite(pin_rst, HIGH);
_write(LCD_NOKIA_CMD, B00100001 ); // LCD Extended Commands. 00100001 + ENDERECAMENTO HORIZONTAL
_write(LCD_NOKIA_CMD, B10101111 ); // Set LCD Vop (Contraste) 10101111
_write(LCD_NOKIA_CMD, B00000100 ); // Set Temp coefficent 00000100
_write(LCD_NOKIA_CMD, B00010100 ); // LCD bias mode 1:48 00010100
_write(LCD_NOKIA_CMD, B00100000 ); // LCD Basic Commands. 00100000
_write(LCD_NOKIA_CMD, B00001100 ); // LCD no modo normal 00001100
_write(LCD_NOKIA_CMD, B01000000); // POSICAO-X 0
_write(LCD_NOKIA_CMD, B10000000); // POSICAO-Y 0
_min_byte_state = LCD_BUF_STATE_SIZE-1;
_max_byte_state = 0;
clear(true);
}
int rows() { return LCD_PIXEL_ROWS; }
int columns() { return LCD_PIXEL_COLS; }
/*48 x 84*/
void pixel(int l, int c, boolean val){
if (l>=LCD_PIXEL_ROWS || c>= LCD_PIXEL_COLS || l <0 || c<0){ return; }
int bit = l%8;
l = l/8;
byte temp = _lcd_buffer[l*LCD_PIXEL_COLS+c]; //faz uma copia do estado atual do byte a ser alterado
if (val) {
_lcd_buffer[l*LCD_PIXEL_COLS+c] |= (1 << bit);
} else {
_lcd_buffer[l*LCD_PIXEL_COLS+c] &= ~(1 << bit);
}
//se houver alteração no byte, altera o status para alterado.
if (temp != _lcd_buffer[l*LCD_PIXEL_COLS+c] ){
byte idx = (l*LCD_PIXEL_COLS+c)/8; //indice do byte de status
_lcd_buffer_state[ idx ] |= (1 << ((l*LCD_PIXEL_COLS+c)%8 )); //inverte o status do byte alterado
if (_min_byte_state > idx) { _min_byte_state = idx; }
if (_max_byte_state < idx) { _max_byte_state = idx; }
}
}
//OS METODOS LINE E RECTANGLE FORAM REMOVIDOS, POIS NESSE EXEMPLO NÃO FORAM USADOS.
//MAS CASO DESEJE UTILIZA-LOS, BASTA COPIAR DOS DEMAIS EXEMPLOS DESSE ARTIGO.
void clear(boolean _send=false){
for (int i=0; i<LCD_BUF_SIZE; i++) { _lcd_buffer[i] = B00000000; }
for (int i=0; i<LCD_BUF_STATE_SIZE; i++) { _lcd_buffer_state[i] = B11111111; }
_min_byte_state = 0;
_max_byte_state = LCD_BUF_STATE_SIZE-1;
if (_send){ send(); }
}
void send(){
//faz uma busca pra saber quais dos bytes foram alterados
for (int i=_min_byte_state;i<=_max_byte_state;i++){
if (_lcd_buffer_state[i] == 0) { continue; }
for (int j=0;j<8;j++){
if bitRead( _lcd_buffer_state[i], j ) {
_position( (i*8+j)/LCD_PIXEL_COLS, (i*8+j)%LCD_PIXEL_COLS );
_write(LCD_NOKIA_DATA, _lcd_buffer[i*8+j]);
}
}
_lcd_buffer_state[i] = B00000000;
}
_min_byte_state = LCD_BUF_STATE_SIZE-1;
_max_byte_state = 0;
}
};
/*************************************************************************************************************
*******************************FIM CLASSE DISPLAY NOKIA 5110**************************************************
**************************************************************************************************************/
/*************************************************************************************************************
*******************************CLASSE SNAKE GAME**************************************************************
**************************************************************************************************************/
#define SNAKE_PIXEL_LEN 4 /*tamanho do pixel da cobra em relação do display*/
struct Position { byte lin; byte col; };
const int SNAKE_MAX_LEN = 50; //tamanho maximo da cobra
const int SNAKE_TIME_INIT = 500; //tempo entre deslocamento da cobra (velocidade)
const int SNAKE_TIME_INC = 15; //incremento da velocidade
enum Direction { DIR_STOP, DIR_TOP, DIR_LEFT, DIR_BOTTOM, DIR_RIGHT};
enum SnakeStatus { SNAKE_GAME_ON, SNAKE_GAME_OVER };
class SnakeGame{
private:
Display * _display;
Position _snake_positions[SNAKE_MAX_LEN];
Position _apple;
int _length;
Direction _direction;
unsigned long _last_millis;
int _time;
int _score;
SnakeStatus _snakeStatus;
void _generateApple() {
int lin, col;
boolean random_ok = false;
while (!random_ok) {
random_ok = true;
lin = random(0, _display->rows()/SNAKE_PIXEL_LEN-1);
col = random(0, _display->columns()/SNAKE_PIXEL_LEN-1);
for (int p=0; p<_length; p++){
if (_snake_positions[p].col==col && _snake_positions[p].lin==lin){ //verifica se gerou em um local que não seja a cobra
random_ok = false;
break;
}
}
}
_apple.lin = lin;
_apple.col = col;
}
void _gameOver(){
_snakeStatus = SNAKE_GAME_OVER;
_direction = DIR_STOP;
_time = 20;
}
void _inc_length(){
_length++; _score++;
_time -= SNAKE_TIME_INC;
}
void _runGameOver(){
int r = random(_display->columns() * _display->rows());
int lin = (r / _display->columns()) / SNAKE_PIXEL_LEN;
int col = (r % _display->columns()) / SNAKE_PIXEL_LEN;
for (int i=0;i<SNAKE_PIXEL_LEN;i++){
for (int j=0;j<SNAKE_PIXEL_LEN;j++){
_display->pixel( lin*SNAKE_PIXEL_LEN+i, col*SNAKE_PIXEL_LEN+j, true );
}
}
if ( _direction != DIR_STOP ) { start(); }
}
void _run(){
for (int i=_length-1; i>0; i--){
_snake_positions[i].lin = _snake_positions[i-1].lin;
_snake_positions[i].col = _snake_positions[i-1].col;
}
if (_direction == DIR_TOP ) { _snake_positions[0].lin--; }
if (_direction == DIR_BOTTOM ) { _snake_positions[0].lin++; }
if (_direction == DIR_LEFT ) { _snake_positions[0].col--; }
if (_direction == DIR_RIGHT ) { _snake_positions[0].col++; }
//verifica se ultrapassou o limite do display
if (_snake_positions[0].lin < 0) { _gameOver(); }
if (_snake_positions[0].lin >= _display->rows()/SNAKE_PIXEL_LEN ) { _gameOver(); }
if (_snake_positions[0].col < 0) { _gameOver(); }
if (_snake_positions[0].col >= _display->columns()/SNAKE_PIXEL_LEN ) { _gameOver(); }
//verifica se colidiu na cobra
for (int i=_length-1; i>0; i--){
if (_snake_positions[i].lin == _snake_positions[0].lin && _snake_positions[i].col == _snake_positions[0].col) {
_gameOver();
}
}
//verifica se comeu a maça
if (_snake_positions[0].col == _apple.col && _snake_positions[0].lin == _apple.lin){
_inc_length();
if (_length > SNAKE_MAX_LEN) { _length = SNAKE_MAX_LEN; } else {
_snake_positions[_length-1].lin = _snake_positions[_length-2].lin;
_snake_positions[_length-1].col = _snake_positions[_length-2].col;
}
_generateApple();
}
//update display
for (int lin=0; lin<_display->rows()/SNAKE_PIXEL_LEN; lin++) {
for (int col=0; col<_display->columns()/SNAKE_PIXEL_LEN; col++) {
for (int p=0; p<_length; p++){
boolean val = _snake_positions[p].col==col && _snake_positions[p].lin==lin;
for (int i=0;i<SNAKE_PIXEL_LEN;i++){
for (int j=0;j<SNAKE_PIXEL_LEN;j++){
_display->pixel( lin*SNAKE_PIXEL_LEN+i, col*SNAKE_PIXEL_LEN+j, val );
}
}
if (val) {break;}
}
}
}
for (int i=0;i<SNAKE_PIXEL_LEN;i++){
for (int j=0;j<SNAKE_PIXEL_LEN;j++){
_display->pixel( _apple.lin*SNAKE_PIXEL_LEN+i, _apple.col*SNAKE_PIXEL_LEN+j, true );
}
}
}
public:
SnakeGame(Display * d){
_display = d;
start();
}
void start(){
_length = 1;
_score = 0;
_time = SNAKE_TIME_INIT;
_last_millis = 0;
_snake_positions[0].lin = _display->rows() / (2 * SNAKE_PIXEL_LEN);
_snake_positions[0].col = _display->columns() / (2 * SNAKE_PIXEL_LEN);
_direction = DIR_STOP;
_snakeStatus = SNAKE_GAME_ON;
_generateApple();
}
void left() { if (_direction == DIR_RIGHT) return; _direction = DIR_LEFT; }
void right() { if (_direction == DIR_LEFT) return; _direction = DIR_RIGHT; }
void top() { if (_direction == DIR_BOTTOM) return; _direction = DIR_TOP; }
void bottom() { if (_direction == DIR_TOP) return; _direction = DIR_BOTTOM; }
int getScore(){ return _score; }
int update(){
int r = false;
if (millis() - _last_millis > _time) {
r = true;
_last_millis = millis();
if (_snakeStatus == SNAKE_GAME_ON) { _run(); }
if (_snakeStatus == SNAKE_GAME_OVER) { _runGameOver(); }
}
return r; //r-->indica se houve mudança no display
}
};
/*************************************************************************************************************
*******************************FIM CLASSE SNAKE GAME**********************************************************
**************************************************************************************************************/
Display d; //declaração do objeto display
SnakeGame snake(&d);
void setup() {
randomSeed(analogRead(A0));
Serial.begin(9600);
}
void loop() {
int c = Serial.read();
if (c == 97) { snake.left(); } //a ->
if (c == 98) { snake.right(); } //b ->
if (c == 99) { snake.top(); } //c ->
if (c == 100) { snake.bottom(); } //d ->
if ( snake.update() ) { d.send(); }
}
</pre>
<br />
<br />
<b>Vídeo</b><br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<iframe allowfullscreen="" class="YOUTUBE-iframe-video" data-thumbnail-src="https://i.ytimg.com/s_vi/W_xRuNbuN48/default.jpg?sqp=CPDwmLUF&rs=AOn4CLDv2eakOuMIxDGqbk0yXeFKIW1l2Q" frameborder="0" height="415" src="https://www.youtube.com/embed/W_xRuNbuN48?feature=player_embedded" width="660"></iframe></div>
<br />
<h3>
Textos e Números</h3>
Até agora nossos exemplos não tiveram nada de texto nem de números, apenas manipulações gráficas. Isso foi proposital, para que possamos entender melhor o processo de envio de dados para o display. Uma das vantagens de utilizarmos bibliotecas prontas é que em poucas linhas teríamos qualquer texto na tela do display muito facilmente, até porque alguém já fez todo o trabalho de mapear cada um dos caracteres e símbolos que poderíamos querer usar, além de fazer isso em vários formatos e tamanhos diferentes, ou seja, bibliotecas prontas são bem mais completas e podem oferecer vários recursos... Apesar da intenção aqui não ser reinventar a roda e sim aprendermos mais do que simplesmente usar o que está pronto, devemos ter em mente que alguns projetos não exigem mais do que algumas poucas letras ou palavras, então nesses casos vale a pena sim termos soluções mais simples e eficientes, evitando utilizar recursos demais que poderiam ser economizados. Obs. Mais abaixo, abordo ainda como criar soluções sem uso de buffers.<br />
<br />
<b>Código-fonte:</b><br />
<br />
<pre class="brush: html" style="background-color: #eeeeee; border: 1px dashed rgb(153, 153, 153); overflow: auto; padding: 5px; width: 100%;">/*
Retirado do datasheet
Table 1 Instruction set
+------------+-----+-----------------------------------------------+-----------------------------+
| | | COMMAND BYTE | |
| | +-----------------------------------------------+ |
|INSTRUCTION | D/C | DB7 | DB6 | DB5 | DB4 | DB3 | DB2 | DB1 | DB0 | DESCRIPTION |
+------------+-----+-----------------------------------------------+-----------------------------+
|(H = 0 or 1) |
+------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----------------------------+
|NOP | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |No Operation |
+------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----------------------------+
|Func. set | 0 | 0 | 0 | 1 | 0 | 0 | PD | V | H |power down control; |
| | | | | | | | | | |entry mode; |
| | | | | | | | | | |extended instruction set |
| | | | | | | | | | |control (H)| |
+------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----------------------------+
|Write Data | 1 | D7 | D6 | D5 | D4 | D3 | D2 | D1 | D0 |Write Data to display |
+------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----------------------------+
|(H = 0) use basic instruction set |
+------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----------------------------+
|Reseverd | 0 | 0 | 0 | 0 | 0 | 0 | 1 | X | X |Do not use |
+------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----------------------------+
|Disp Control| 0 | 0 | 0 | 0 | 0 | 1 | D | 0 | E |Sets Display Configuration |
| | | | | | | | | | |DE |
| | | | | | | | | | |00 - display blank |
| | | | | | | | | | |01 - all display segment on |
| | | | | | | | | | |10 - normal mode |
| | | | | | | | | | |11 - inverse video mode |
+------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----------------------------+
|Reseverd | 0 | 0 | 0 | 0 | 1 | X | X | X | X |Do not use |
+------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----------------------------+
|SetY ad. RAM| 0 | 0 | 1 | 0 | 0 | 0 | y2 | y1 | y0 |sets Y-address of RAM; |
| | | | | | | | | | |0 ≤ Y ≤ 5 |
+------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----------------------------+
|SetX ad. RAM| 0 | 1 | x6 | x5 | x4 | x3 | x2 | x1 | x0 |sets Y-address of RAM; |
| | | | | | | | | | |0 ≤ x ≤ 83 |
+------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----------------------------+
|(H = 1) use extended instruction set |
+------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----------------------------+
|Reseverd | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 |Do not use |
+------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----------------------------+
|Reseverd | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | X |Do not use |
+------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----------------------------+
|Temp Control| 0 | 0 | 0 | 0 | 0 | 0 | 1 | Tc1 | Tc0 |set Temperature Coefficient |
+------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----------------------------+
|Reseverd | 0 | 0 | 0 | 0 | 0 | 1 | X | X | X |Do not use |
+------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----------------------------+
|Bias System | 0 | 0 | 0 | 0 | 1 | 0 | BS2 | BS1 | BS0 |set Bias System (BSx) |
+------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----------------------------+
|Reseverd | 0 | 1 |VOP6 |VOP5 |VOP4 |VOP3 |VOP2 |VOP1 |VOP0 |write VOP to register |
+------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----------------------------+
*/
static const PROGMEM byte ASCII[][5] = {
{ 0x00, 0x00, 0x00, 0x00, 0x00 } // 20 (space)
,{ 0x00, 0x00, 0x5f, 0x00, 0x00 } // 21 !
,{ 0x00, 0x07, 0x00, 0x07, 0x00 } // 22 "
,{ 0x14, 0x7f, 0x14, 0x7f, 0x14 } // 23 #
,{ 0x24, 0x2a, 0x7f, 0x2a, 0x12 } // 24 $
,{ 0x23, 0x13, 0x08, 0x64, 0x62 } // 25 %
,{ 0x36, 0x49, 0x55, 0x22, 0x50 } // 26 &
,{ 0x00, 0x05, 0x03, 0x00, 0x00 } // 27 '
,{ 0x00, 0x1c, 0x22, 0x41, 0x00 } // 28 (
,{ 0x00, 0x41, 0x22, 0x1c, 0x00 } // 29 )
,{ 0x14, 0x08, 0x3e, 0x08, 0x14 } // 2a *
,{ 0x08, 0x08, 0x3e, 0x08, 0x08 } // 2b +
,{ 0x00, 0x50, 0x30, 0x00, 0x00 } // 2c ,
,{ 0x08, 0x08, 0x08, 0x08, 0x08 } // 2d -
,{ 0x00, 0x60, 0x60, 0x00, 0x00 } // 2e .
,{ 0x20, 0x10, 0x08, 0x04, 0x02 } // 2f /
,{ 0x3e, 0x51, 0x49, 0x45, 0x3e } // 30 0
,{ 0x00, 0x42, 0x7f, 0x40, 0x00 } // 31 1
,{ 0x42, 0x61, 0x51, 0x49, 0x46 } // 32 2
,{ 0x21, 0x41, 0x45, 0x4b, 0x31 } // 33 3
,{ 0x18, 0x14, 0x12, 0x7f, 0x10 } // 34 4
,{ 0x27, 0x45, 0x45, 0x45, 0x39 } // 35 5
,{ 0x3c, 0x4a, 0x49, 0x49, 0x30 } // 36 6
,{ 0x01, 0x71, 0x09, 0x05, 0x03 } // 37 7
,{ 0x36, 0x49, 0x49, 0x49, 0x36 } // 38 8
,{ 0x06, 0x49, 0x49, 0x29, 0x1e } // 39 9
,{ 0x00, 0x36, 0x36, 0x00, 0x00 } // 3a :
,{ 0x00, 0x56, 0x36, 0x00, 0x00 } // 3b ;
,{ 0x08, 0x14, 0x22, 0x41, 0x00 } // 3c <
,{ 0x14, 0x14, 0x14, 0x14, 0x14 } // 3d =
,{ 0x00, 0x41, 0x22, 0x14, 0x08 } // 3e >
,{ 0x02, 0x01, 0x51, 0x09, 0x06 } // 3f ?
,{ 0x32, 0x49, 0x79, 0x41, 0x3e } // 40 @
,{ 0x7e, 0x11, 0x11, 0x11, 0x7e } // 41 A
,{ 0x7f, 0x49, 0x49, 0x49, 0x36 } // 42 B
,{ 0x3e, 0x41, 0x41, 0x41, 0x22 } // 43 C
,{ 0x7f, 0x41, 0x41, 0x22, 0x1c } // 44 D
,{ 0x7f, 0x49, 0x49, 0x49, 0x41 } // 45 E
,{ 0x7f, 0x09, 0x09, 0x09, 0x01 } // 46 F
,{ 0x3e, 0x41, 0x49, 0x49, 0x7a } // 47 G
,{ 0x7f, 0x08, 0x08, 0x08, 0x7f } // 48 H
,{ 0x00, 0x41, 0x7f, 0x41, 0x00 } // 49 I
,{ 0x20, 0x40, 0x41, 0x3f, 0x01 } // 4a J
,{ 0x7f, 0x08, 0x14, 0x22, 0x41 } // 4b K
,{ 0x7f, 0x40, 0x40, 0x40, 0x40 } // 4c L
,{ 0x7f, 0x02, 0x0c, 0x02, 0x7f } // 4d M
,{ 0x7f, 0x04, 0x08, 0x10, 0x7f } // 4e N
,{ 0x3e, 0x41, 0x41, 0x41, 0x3e } // 4f O
,{ 0x7f, 0x09, 0x09, 0x09, 0x06 } // 50 P
,{ 0x3e, 0x41, 0x51, 0x21, 0x5e } // 51 Q
,{ 0x7f, 0x09, 0x19, 0x29, 0x46 } // 52 R
,{ 0x46, 0x49, 0x49, 0x49, 0x31 } // 53 S
,{ 0x01, 0x01, 0x7f, 0x01, 0x01 } // 54 T
,{ 0x3f, 0x40, 0x40, 0x40, 0x3f } // 55 U
,{ 0x1f, 0x20, 0x40, 0x20, 0x1f } // 56 V
,{ 0x3f, 0x40, 0x38, 0x40, 0x3f } // 57 W
,{ 0x63, 0x14, 0x08, 0x14, 0x63 } // 58 X
,{ 0x07, 0x08, 0x70, 0x08, 0x07 } // 59 Y
,{ 0x61, 0x51, 0x49, 0x45, 0x43 } // 5a Z
,{ 0x00, 0x7f, 0x41, 0x41, 0x00 } // 5b [
,{ 0x02, 0x04, 0x08, 0x10, 0x20 } // 5c backslash
,{ 0x00, 0x41, 0x41, 0x7f, 0x00 } // 5d ]
,{ 0x04, 0x02, 0x01, 0x02, 0x04 } // 5e ^
,{ 0x40, 0x40, 0x40, 0x40, 0x40 } // 5f _
,{ 0x00, 0x01, 0x02, 0x04, 0x00 } // 60 `
,{ 0x20, 0x54, 0x54, 0x54, 0x78 } // 61 a
,{ 0x7f, 0x48, 0x44, 0x44, 0x38 } // 62 b
,{ 0x38, 0x44, 0x44, 0x44, 0x20 } // 63 c
,{ 0x38, 0x44, 0x44, 0x48, 0x7f } // 64 d
,{ 0x38, 0x54, 0x54, 0x54, 0x18 } // 65 e
,{ 0x08, 0x7e, 0x09, 0x01, 0x02 } // 66 f
,{ 0x0c, 0x52, 0x52, 0x52, 0x3e } // 67 g
,{ 0x7f, 0x08, 0x04, 0x04, 0x78 } // 68 h
,{ 0x00, 0x44, 0x7d, 0x40, 0x00 } // 69 i
,{ 0x20, 0x40, 0x44, 0x3d, 0x00 } // 6a j
,{ 0x7f, 0x10, 0x28, 0x44, 0x00 } // 6b k
,{ 0x00, 0x41, 0x7f, 0x40, 0x00 } // 6c l
,{ 0x7c, 0x04, 0x18, 0x04, 0x78 } // 6d m
,{ 0x7c, 0x08, 0x04, 0x04, 0x78 } // 6e n
,{ 0x38, 0x44, 0x44, 0x44, 0x38 } // 6f o
,{ 0x7c, 0x14, 0x14, 0x14, 0x08 } // 70 p
,{ 0x08, 0x14, 0x14, 0x18, 0x7c } // 71 q
,{ 0x7c, 0x08, 0x04, 0x04, 0x08 } // 72 r
,{ 0x48, 0x54, 0x54, 0x54, 0x20 } // 73 s
,{ 0x04, 0x3f, 0x44, 0x40, 0x20 } // 74 t
,{ 0x3c, 0x40, 0x40, 0x20, 0x7c } // 75 u
,{ 0x1c, 0x20, 0x40, 0x20, 0x1c } // 76 v
,{ 0x3c, 0x40, 0x30, 0x40, 0x3c } // 77 w
,{ 0x44, 0x28, 0x10, 0x28, 0x44 } // 78 x
,{ 0x0c, 0x50, 0x50, 0x50, 0x3c } // 79 y
,{ 0x44, 0x64, 0x54, 0x4c, 0x44 } // 7a z
,{ 0x00, 0x08, 0x36, 0x41, 0x00 } // 7b {
,{ 0x00, 0x00, 0x7f, 0x00, 0x00 } // 7c |
,{ 0x00, 0x41, 0x36, 0x08, 0x00 } // 7d }
,{ 0x10, 0x08, 0x08, 0x10, 0x08 } // 7e ~
,{ 0x78, 0x46, 0x41, 0x46, 0x78 } // 7f DEL
};
#define LCD_NOKIA_DATA HIGH
#define LCD_NOKIA_CMD LOW
#define LCD_PIXEL_ROWS 48
#define LCD_PIXEL_COLS 84
#define LCD_BUF_SIZE LCD_PIXEL_ROWS * LCD_PIXEL_COLS / 8 // 504
#define LCD_BUF_STATE_SIZE LCD_BUF_SIZE / 8 // 63
#define LCD_NUN_BANKS LCD_PIXEL_ROWS / 8
#define sgn(x) ((x<0)?-1:((x>0)?1:0))
const int pin_sclk = 8; // pin 8 - Serial clock out (SCLK)
const int pin_din = 9; // pin 9 - Serial data out (DIN)
const int pin_dc = 10; // pin 10 - Data/Command select (D/C)
const int pin_cs = 11; // pin 11 - LCD chip select (CS/CE)
const int pin_rst = 12; // pin 12 - LCD reset (RST)
class Display{
private:
byte _write_pos_bank; //posicoes utilizadas para escrever texto
byte _write_pos_col; //posicoes utilizadas para escrever texto
byte _min_byte_state;
byte _max_byte_state;
byte _pos_linha;
byte _pos_coluna;
byte _lcd_buffer[LCD_BUF_SIZE];
byte _lcd_buffer_state[LCD_BUF_STATE_SIZE]; //504 / 8 = 63 --> cada bit representa o estado de um dos bytes do buffer
void _write(byte mode, byte w){
digitalWrite(pin_dc, mode);
digitalWrite(pin_cs, LOW);
shiftOut(pin_din, pin_sclk, MSBFIRST, w);
digitalWrite(pin_cs, HIGH);
if (mode == LCD_NOKIA_DATA) {
if (++_pos_coluna >= LCD_PIXEL_COLS) {
_pos_coluna = 0;
if ( ++_pos_linha>= 6) { _pos_linha = 0; }
}
}
}
void _position(int l, int c) {
if (_pos_coluna != c){ _write( LCD_NOKIA_CMD, B10000000 + c); _pos_coluna = c;}
if (_pos_linha != l){ _write( LCD_NOKIA_CMD, B01000000 + l); _pos_linha = l;}
}
//se houver alteração no byte, altera o status para alterado.
void _write_buffer(int index, byte b) {
if (b != _lcd_buffer[index] ){
_lcd_buffer[index] = b;
byte idx = (index)/8; //indice do byte de status
_lcd_buffer_state[ idx ] |= 1 << (index)%8; //inverte o status do byte alterado
if (_min_byte_state > idx) { _min_byte_state = idx; }
if (_max_byte_state < idx) { _max_byte_state = idx; }
}
}
public:
Display() {
pinMode(pin_din, OUTPUT);
pinMode(pin_sclk, OUTPUT);
pinMode(pin_dc, OUTPUT);
pinMode(pin_rst, OUTPUT);
pinMode(pin_cs, OUTPUT);
digitalWrite(pin_rst, LOW);
digitalWrite(pin_rst, HIGH);
_write(LCD_NOKIA_CMD, B00100001 ); // LCD Extended Commands. 00100001 + ENDERECAMENTO HORIZONTAL
_write(LCD_NOKIA_CMD, B10101111 ); // Set LCD Vop (Contraste) 10101111
_write(LCD_NOKIA_CMD, B00000100 ); // Set Temp coefficent 00000100
_write(LCD_NOKIA_CMD, B00010100 ); // LCD bias mode 1:48 00010100
_write(LCD_NOKIA_CMD, B00100000 ); // LCD Basic Commands. 00100000
_write(LCD_NOKIA_CMD, B00001100 ); // LCD no modo normal 00001100
_write(LCD_NOKIA_CMD, B01000000); // POSICAO-X 0
_write(LCD_NOKIA_CMD, B10000000); // POSICAO-Y 0
_min_byte_state = LCD_BUF_STATE_SIZE-1;
_max_byte_state = 0;
clear(true);
}
void write_pos(byte bank, byte col){
if (col + 5 >= LCD_PIXEL_COLS) {
col -= (LCD_PIXEL_COLS - 6);
bank++ ;
if (bank >= LCD_NUN_BANKS ){ bank=0; }
}
_write_pos_bank = bank; //posicoes utilizadas para escrever texto
_write_pos_col = col; //posicoes utilizadas para escrever texto
}
void write(byte data) {
if (data < 0x20 || data > 0x7F) return; // Non-ASCII characters are not supported.
//if (col + 5 >= LCD_BUF_SIZE) { col -= (LCD_BUF_SIZE - 6); }
byte buf[6];
memcpy_P(buf, ASCII[data - 0x20], 5);
buf[5] = 0x00;
for (int i=0;i<6;i++){
_write_buffer(_write_pos_bank*LCD_PIXEL_COLS+_write_pos_col+i, buf[i]);
}
write_pos(_write_pos_bank, _write_pos_col+6);
}
void write(char * c){
int i = 0;
while ( c[i] != '\0') { write(c[i++]); }
}
/*48 x 84*/
void pixel(int l, int c, boolean val){
if (l>=LCD_PIXEL_ROWS || c>= LCD_PIXEL_COLS || l <0 || c<0){ return; }
int bit = l%8;
l = l/8;
byte b = _lcd_buffer[l*LCD_PIXEL_COLS+c]; //faz uma copia do estado atual do byte a ser alterado
if (val) {
b |= (1 << bit);
} else {
b &= ~(1 << bit);
}
_write_buffer(l*LCD_PIXEL_COLS+c, b);
}
void line(int x1, int y1, int x2, int y2){
/* http://www.brackeen.com/vga/source/djgpp20/lines.c.html */
int i,dx,dy,sdx,sdy,dxabs,dyabs,x,y,px,py;
dx = x2-x1; /* the horizontal distance of the line */
dy = y2-y1; /* the vertical distance of the line */
dxabs = abs(dx);
dyabs = abs(dy);
sdx = sgn(dx);
sdy = sgn(dy);
x = dyabs>>1;
y = dxabs>>1;
px = x1;
py = y1;
pixel(x1,y1,true);
if (dxabs>=dyabs) { /* the line is more horizontal than vertical */
for(i=0;i<dxabs;i++) {
y+=dyabs;
if (y>=dxabs) { y-=dxabs; py+=sdy; }
px+=sdx;
pixel(px,py,true);
}
} else { /* the line is more vertical than horizontal */
for(i=0;i<dyabs;i++) {
x+=dxabs;
if (x>=dyabs) { x-=dyabs; px+=sdx; }
py+=sdy;
pixel(px,py,true);
}
}
}
void rectangle(int left,int top, int right, int bottom, boolean fill=false) {
if (fill){
if (left>right) { //swap
left ^= right;
right ^= left;
left ^= right;
}
if (top>bottom) { //swap
top ^= bottom;
bottom ^= top;
top ^= bottom;
}
for (int i=0; i<bottom-top; i++) {
line(left,top+i,right,top+i);
}
} else {
line(left,top,right,top);
line(left,top,left,bottom);
line(right,top,right,bottom);
line(left,bottom,right,bottom);
}
}
void clear(boolean _send=false){
for (int i=0; i<LCD_BUF_SIZE; i++) { _write_buffer(i, B00000000); }
//for (int i=0; i<LCD_BUF_STATE_SIZE; i++) { _lcd_buffer_state[i] = B11111111; }
if (_send){ send(); }
}
void send(){
//faz uma busca pra saber quais dos bytes foram alterados
for (int i=_min_byte_state;i<=_max_byte_state;i++){
if (_lcd_buffer_state[i] == 0) { continue; }
for (int j=0;j<8;j++){
if bitRead( _lcd_buffer_state[i], j ) {
_position( (i*8+j)/LCD_PIXEL_COLS, (i*8+j)%LCD_PIXEL_COLS );
_write(LCD_NOKIA_DATA, _lcd_buffer[i*8+j]);
}
}
_lcd_buffer_state[i] = B00000000;
}
_min_byte_state = LCD_BUF_STATE_SIZE-1;
_max_byte_state = 0;
}
};
Display d; //declaração do objeto display
void setup() {
randomSeed(analogRead(A0));
Serial.begin(9600);
}
void loop() {
for (int i=0; i<LCD_PIXEL_COLS-6; i+=5){
d.clear();
d.write_pos(random(LCD_NUN_BANKS),i);
d.write("Arduino");
d.send();
delay(600);
}
}
</pre>
<br />
<br />
<b>Vídeo</b><br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<iframe allowfullscreen="" class="YOUTUBE-iframe-video" data-thumbnail-src="https://i.ytimg.com/s_vi/ad7C0_m4Nw0/default.jpg?sqp=CKCQmrUF&rs=AOn4CLA3wTxkBqqlYBz01KbduuEGIDDVzA" frameborder="0" height="415" src="https://www.youtube.com/embed/ad7C0_m4Nw0?feature=player_embedded" width="660"></iframe></div>
<br />
<br />
<h3>
Soluções que não utilizam Buffer</h3>
Nossos exemplos até agora se basearam em soluções que fazem uso de buffer, ou seja, mantém na memória do Arduino um cópia do estado do display, assim conseguimos fazer alterações em áreas específicas do display e enviar apenas o que de fato foi alterado. Isso dá uma liberdade maior na hora de criarmos telas mais elaboradas, porém isso nem sempre é conveniente, pois como já falado anteriormente, em um Arduino Uno, um buffer teria que ter 504 bytes (84 x 6). Isso sozinho representa 25% da memória total. Já no caso de um Attiny85, seria praticamente 100%, então, sem chances!<br />
<br />
No caso de uma solução sem buffer, temos a vantagem do uso não exagerado da memória, mas isso tem um preço, por exemplo, ao darmos um "clear" no display gerenciado por um buffer, bastaria apenas verificar quais partes do display não estão limpas, e alterar somente essas partes, já numa solução sem buffer, vamos ter que fazer uma varredura do início ao fim, alterando toda a área do display, mesmo já estando limpa. Em termos de programação acaba sendo mais simples, mas por outro lado, exige mais processamento. Se estamos programando computadores, essas questões muitas vezes são irrelevantes, pois em geral computadores possuem uma boa capacidade de memória e processamento, mas quando começamos a pensar em programação de microcontroladores precisamos estar atento.<br />
<br />
Para desenvolvermos soluções que não vão utilizar buffers, precisamos ter em mente exatamente o que o projeto vai exigir. Coisas como desenhar linhas, retângulos, círculos, etc. provavelmente terão que ser deixados de lado, já que elas exigem alterações de pixels individuais do display. Ou então utilizar soluções bem engenhosas, mas que tenham limites bem definidos.<br />
<br />
No nosso exemplo, vamos criar uma aplicação que apenas envia texto para o display. Nada além de texto. Necessidade essa que com certeza vai atender muitos projetos.<br />
<br />
Veja que após compilado o exemplo abaixo, usamos 1% apenas da memória disponível e tivemos o mesmo resultado do exemplo anterior com um código bem mais enxuto.<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg_w2jxhNOKeAl4Lv66L7oWTqla926tGzPXe5s9C84dACb7dRp5mggd1WqrLmgRoifXBNMcl9NzpQoIaH0xi4CsYRXJX_nu14DbC__ulDIaWHlMThaj2vcEllG1qi9Mskf3LzROOxkZCMM/s1600/nokia_5110_sem_buffer.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="640" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg_w2jxhNOKeAl4Lv66L7oWTqla926tGzPXe5s9C84dACb7dRp5mggd1WqrLmgRoifXBNMcl9NzpQoIaH0xi4CsYRXJX_nu14DbC__ulDIaWHlMThaj2vcEllG1qi9Mskf3LzROOxkZCMM/s640/nokia_5110_sem_buffer.png" width="562" /></a></div>
<br />
<br />
<b>Código-fonte:</b><br />
<br />
<pre class="brush: html" style="background-color: #eeeeee; border: 1px dashed rgb(153, 153, 153); overflow: auto; padding: 5px; width: 100%;">/*
Retirado do datasheet
Table 1 Instruction set
+------------+-----+-----------------------------------------------+-----------------------------+
| | | COMMAND BYTE | |
| | +-----------------------------------------------+ |
|INSTRUCTION | D/C | DB7 | DB6 | DB5 | DB4 | DB3 | DB2 | DB1 | DB0 | DESCRIPTION |
+------------+-----+-----------------------------------------------+-----------------------------+
|(H = 0 or 1) |
+------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----------------------------+
|NOP | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |No Operation |
+------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----------------------------+
|Func. set | 0 | 0 | 0 | 1 | 0 | 0 | PD | V | H |power down control; |
| | | | | | | | | | |entry mode; |
| | | | | | | | | | |extended instruction set |
| | | | | | | | | | |control (H)| |
+------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----------------------------+
|Write Data | 1 | D7 | D6 | D5 | D4 | D3 | D2 | D1 | D0 |Write Data to display |
+------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----------------------------+
|(H = 0) use basic instruction set |
+------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----------------------------+
|Reseverd | 0 | 0 | 0 | 0 | 0 | 0 | 1 | X | X |Do not use |
+------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----------------------------+
|Disp Control| 0 | 0 | 0 | 0 | 0 | 1 | D | 0 | E |Sets Display Configuration |
| | | | | | | | | | |DE |
| | | | | | | | | | |00 - display blank |
| | | | | | | | | | |01 - all display segment on |
| | | | | | | | | | |10 - normal mode |
| | | | | | | | | | |11 - inverse video mode |
+------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----------------------------+
|Reseverd | 0 | 0 | 0 | 0 | 1 | X | X | X | X |Do not use |
+------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----------------------------+
|SetY ad. RAM| 0 | 0 | 1 | 0 | 0 | 0 | y2 | y1 | y0 |sets Y-address of RAM; |
| | | | | | | | | | |0 ≤ Y ≤ 5 |
+------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----------------------------+
|SetX ad. RAM| 0 | 1 | x6 | x5 | x4 | x3 | x2 | x1 | x0 |sets Y-address of RAM; |
| | | | | | | | | | |0 ≤ x ≤ 83 |
+------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----------------------------+
|(H = 1) use extended instruction set |
+------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----------------------------+
|Reseverd | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 |Do not use |
+------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----------------------------+
|Reseverd | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | X |Do not use |
+------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----------------------------+
|Temp Control| 0 | 0 | 0 | 0 | 0 | 0 | 1 | Tc1 | Tc0 |set Temperature Coefficient |
+------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----------------------------+
|Reseverd | 0 | 0 | 0 | 0 | 0 | 1 | X | X | X |Do not use |
+------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----------------------------+
|Bias System | 0 | 0 | 0 | 0 | 1 | 0 | BS2 | BS1 | BS0 |set Bias System (BSx) |
+------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----------------------------+
|Reseverd | 0 | 1 |VOP6 |VOP5 |VOP4 |VOP3 |VOP2 |VOP1 |VOP0 |write VOP to register |
+------------+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----------------------------+
*/
//This table contains the hex values that represent pixels
//for a font that is 5 pixels wide and 8 pixels high
static const PROGMEM byte ASCII[][5] = {
{ 0x00, 0x00, 0x00, 0x00, 0x00 } // 20 (space)
,{ 0x00, 0x00, 0x5f, 0x00, 0x00 } // 21 !
,{ 0x00, 0x07, 0x00, 0x07, 0x00 } // 22 "
,{ 0x14, 0x7f, 0x14, 0x7f, 0x14 } // 23 #
,{ 0x24, 0x2a, 0x7f, 0x2a, 0x12 } // 24 $
,{ 0x23, 0x13, 0x08, 0x64, 0x62 } // 25 %
,{ 0x36, 0x49, 0x55, 0x22, 0x50 } // 26 &
,{ 0x00, 0x05, 0x03, 0x00, 0x00 } // 27 '
,{ 0x00, 0x1c, 0x22, 0x41, 0x00 } // 28 (
,{ 0x00, 0x41, 0x22, 0x1c, 0x00 } // 29 )
,{ 0x14, 0x08, 0x3e, 0x08, 0x14 } // 2a *
,{ 0x08, 0x08, 0x3e, 0x08, 0x08 } // 2b +
,{ 0x00, 0x50, 0x30, 0x00, 0x00 } // 2c ,
,{ 0x08, 0x08, 0x08, 0x08, 0x08 } // 2d -
,{ 0x00, 0x60, 0x60, 0x00, 0x00 } // 2e .
,{ 0x20, 0x10, 0x08, 0x04, 0x02 } // 2f /
,{ 0x3e, 0x51, 0x49, 0x45, 0x3e } // 30 0
,{ 0x00, 0x42, 0x7f, 0x40, 0x00 } // 31 1
,{ 0x42, 0x61, 0x51, 0x49, 0x46 } // 32 2
,{ 0x21, 0x41, 0x45, 0x4b, 0x31 } // 33 3
,{ 0x18, 0x14, 0x12, 0x7f, 0x10 } // 34 4
,{ 0x27, 0x45, 0x45, 0x45, 0x39 } // 35 5
,{ 0x3c, 0x4a, 0x49, 0x49, 0x30 } // 36 6
,{ 0x01, 0x71, 0x09, 0x05, 0x03 } // 37 7
,{ 0x36, 0x49, 0x49, 0x49, 0x36 } // 38 8
,{ 0x06, 0x49, 0x49, 0x29, 0x1e } // 39 9
,{ 0x00, 0x36, 0x36, 0x00, 0x00 } // 3a :
,{ 0x00, 0x56, 0x36, 0x00, 0x00 } // 3b ;
,{ 0x08, 0x14, 0x22, 0x41, 0x00 } // 3c <
,{ 0x14, 0x14, 0x14, 0x14, 0x14 } // 3d =
,{ 0x00, 0x41, 0x22, 0x14, 0x08 } // 3e >
,{ 0x02, 0x01, 0x51, 0x09, 0x06 } // 3f ?
,{ 0x32, 0x49, 0x79, 0x41, 0x3e } // 40 @
,{ 0x7e, 0x11, 0x11, 0x11, 0x7e } // 41 A
,{ 0x7f, 0x49, 0x49, 0x49, 0x36 } // 42 B
,{ 0x3e, 0x41, 0x41, 0x41, 0x22 } // 43 C
,{ 0x7f, 0x41, 0x41, 0x22, 0x1c } // 44 D
,{ 0x7f, 0x49, 0x49, 0x49, 0x41 } // 45 E
,{ 0x7f, 0x09, 0x09, 0x09, 0x01 } // 46 F
,{ 0x3e, 0x41, 0x49, 0x49, 0x7a } // 47 G
,{ 0x7f, 0x08, 0x08, 0x08, 0x7f } // 48 H
,{ 0x00, 0x41, 0x7f, 0x41, 0x00 } // 49 I
,{ 0x20, 0x40, 0x41, 0x3f, 0x01 } // 4a J
,{ 0x7f, 0x08, 0x14, 0x22, 0x41 } // 4b K
,{ 0x7f, 0x40, 0x40, 0x40, 0x40 } // 4c L
,{ 0x7f, 0x02, 0x0c, 0x02, 0x7f } // 4d M
,{ 0x7f, 0x04, 0x08, 0x10, 0x7f } // 4e N
,{ 0x3e, 0x41, 0x41, 0x41, 0x3e } // 4f O
,{ 0x7f, 0x09, 0x09, 0x09, 0x06 } // 50 P
,{ 0x3e, 0x41, 0x51, 0x21, 0x5e } // 51 Q
,{ 0x7f, 0x09, 0x19, 0x29, 0x46 } // 52 R
,{ 0x46, 0x49, 0x49, 0x49, 0x31 } // 53 S
,{ 0x01, 0x01, 0x7f, 0x01, 0x01 } // 54 T
,{ 0x3f, 0x40, 0x40, 0x40, 0x3f } // 55 U
,{ 0x1f, 0x20, 0x40, 0x20, 0x1f } // 56 V
,{ 0x3f, 0x40, 0x38, 0x40, 0x3f } // 57 W
,{ 0x63, 0x14, 0x08, 0x14, 0x63 } // 58 X
,{ 0x07, 0x08, 0x70, 0x08, 0x07 } // 59 Y
,{ 0x61, 0x51, 0x49, 0x45, 0x43 } // 5a Z
,{ 0x00, 0x7f, 0x41, 0x41, 0x00 } // 5b [
,{ 0x02, 0x04, 0x08, 0x10, 0x20 } // 5c backslash
,{ 0x00, 0x41, 0x41, 0x7f, 0x00 } // 5d ]
,{ 0x04, 0x02, 0x01, 0x02, 0x04 } // 5e ^
,{ 0x40, 0x40, 0x40, 0x40, 0x40 } // 5f _
,{ 0x00, 0x01, 0x02, 0x04, 0x00 } // 60 `
,{ 0x20, 0x54, 0x54, 0x54, 0x78 } // 61 a
,{ 0x7f, 0x48, 0x44, 0x44, 0x38 } // 62 b
,{ 0x38, 0x44, 0x44, 0x44, 0x20 } // 63 c
,{ 0x38, 0x44, 0x44, 0x48, 0x7f } // 64 d
,{ 0x38, 0x54, 0x54, 0x54, 0x18 } // 65 e
,{ 0x08, 0x7e, 0x09, 0x01, 0x02 } // 66 f
,{ 0x0c, 0x52, 0x52, 0x52, 0x3e } // 67 g
,{ 0x7f, 0x08, 0x04, 0x04, 0x78 } // 68 h
,{ 0x00, 0x44, 0x7d, 0x40, 0x00 } // 69 i
,{ 0x20, 0x40, 0x44, 0x3d, 0x00 } // 6a j
,{ 0x7f, 0x10, 0x28, 0x44, 0x00 } // 6b k
,{ 0x00, 0x41, 0x7f, 0x40, 0x00 } // 6c l
,{ 0x7c, 0x04, 0x18, 0x04, 0x78 } // 6d m
,{ 0x7c, 0x08, 0x04, 0x04, 0x78 } // 6e n
,{ 0x38, 0x44, 0x44, 0x44, 0x38 } // 6f o
,{ 0x7c, 0x14, 0x14, 0x14, 0x08 } // 70 p
,{ 0x08, 0x14, 0x14, 0x18, 0x7c } // 71 q
,{ 0x7c, 0x08, 0x04, 0x04, 0x08 } // 72 r
,{ 0x48, 0x54, 0x54, 0x54, 0x20 } // 73 s
,{ 0x04, 0x3f, 0x44, 0x40, 0x20 } // 74 t
,{ 0x3c, 0x40, 0x40, 0x20, 0x7c } // 75 u
,{ 0x1c, 0x20, 0x40, 0x20, 0x1c } // 76 v
,{ 0x3c, 0x40, 0x30, 0x40, 0x3c } // 77 w
,{ 0x44, 0x28, 0x10, 0x28, 0x44 } // 78 x
,{ 0x0c, 0x50, 0x50, 0x50, 0x3c } // 79 y
,{ 0x44, 0x64, 0x54, 0x4c, 0x44 } // 7a z
,{ 0x00, 0x08, 0x36, 0x41, 0x00 } // 7b {
,{ 0x00, 0x00, 0x7f, 0x00, 0x00 } // 7c |
,{ 0x00, 0x41, 0x36, 0x08, 0x00 } // 7d }
,{ 0x10, 0x08, 0x08, 0x10, 0x08 } // 7e ~
,{ 0x78, 0x46, 0x41, 0x46, 0x78 } // 7f DEL
};
#define LCD_NOKIA_DATA HIGH
#define LCD_NOKIA_CMD LOW
#define LCD_NOKIA_COLS 84
#define LCD_NOKIA_BANKS 6
const int pin_sclk = 8; // pin 8 - Serial clock out (SCLK)
const int pin_din = 9; // pin 9 - Serial data out (DIN)
const int pin_dc = 10; // pin 10 - Data/Command select (D/C)
const int pin_cs = 11; // pin 11 - LCD chip select (CS/CE)
const int pin_rst = 12; // pin 12 - LCD reset (RST)
class Display {
private:
byte _pos_linha; //bank
byte _pos_coluna;
void _write(byte mode, byte w){
digitalWrite(pin_dc, mode);
digitalWrite(pin_cs, LOW);
shiftOut(pin_din, pin_sclk, MSBFIRST, w);
digitalWrite(pin_cs, HIGH);
if (mode == LCD_NOKIA_DATA) {
if (++_pos_coluna >= LCD_NOKIA_COLS) {
_pos_coluna = 0;
if ( ++_pos_linha>= 6) { _pos_linha = 0; }
}
}
}
void _position(int l, int c) {
if (_pos_coluna != c){ _write( LCD_NOKIA_CMD, B10000000 + c); _pos_coluna = c;}
if (_pos_linha != l){ _write( LCD_NOKIA_CMD, B01000000 + l); _pos_linha = l;}
}
public:
Display() {
pinMode(pin_din, OUTPUT);
pinMode(pin_sclk, OUTPUT);
pinMode(pin_dc, OUTPUT);
pinMode(pin_rst, OUTPUT);
pinMode(pin_cs, OUTPUT);
digitalWrite(pin_rst, LOW);
digitalWrite(pin_rst, HIGH);
_write(LCD_NOKIA_CMD, B00100001 ); // LCD Extended Commands. 00100001 + ENDERECAMENTO HORIZONTAL
_write(LCD_NOKIA_CMD, B10101111 ); // Set LCD Vop (Contraste) 10101111
_write(LCD_NOKIA_CMD, B00000100 ); // Set Temp coefficent 00000100
_write(LCD_NOKIA_CMD, B00010100 ); // LCD bias mode 1:48 00010100
_write(LCD_NOKIA_CMD, B00100000 ); // LCD Basic Commands. 00100000
_write(LCD_NOKIA_CMD, B00001100 ); // LCD no modo normal 00001100
_write(LCD_NOKIA_CMD, B01000000); // POSICAO-X 0
_write(LCD_NOKIA_CMD, B10000000); // POSICAO-Y 0
clear();
}
void write_pos(byte bank, byte col){
if (col + 5 >= LCD_NOKIA_COLS) {
col -= (LCD_NOKIA_COLS - 6);
bank++ ;
if (bank >= LCD_NOKIA_BANKS ){ bank=0; }
}
_position(bank, col);
}
void write(byte data) { // Non-ASCII characters are not supported.
if (data < 0x20 || data > 0x7F) return;
byte b[5];
memcpy_P(b, ASCII[data - 0x20], 5);
for (int i=0;i<5;i++){ _write(LCD_NOKIA_DATA, b[i]); }
_write(LCD_NOKIA_DATA, B00000000);
write_pos(_pos_linha, _pos_coluna); //posiciona no lugar correto para o próximo caracter
}
void write(char * c){
int i = 0;
while ( c[i] != '\0') { write(c[i++]); }
}
void clear(){
_position( 0, 0 );
for (int i=0; i<LCD_NOKIA_COLS * LCD_NOKIA_BANKS; i++) { _write(LCD_NOKIA_DATA, B00000000); }
}
};
Display d; //declaração do objeto display
void setup() {
randomSeed(analogRead(A0));
}
void loop() {
for (int i=0; i<LCD_NOKIA_COLS-6; i+=5){
d.clear();
d.write_pos(random(LCD_NOKIA_BANKS),i);
d.write("Arduino");
delay(600);
}
}
</pre>
<br />
<h3>
Continua....</h3>
<div>
<br /></div>
<div>
É bem provável que esse artigo venha a ter algumas atualizações, já que tenho mais ideias em mente sobre esse display, então recomendo voltar aqui em breve.</div>
Fabiano Arndthttp://www.blogger.com/profile/15268923442674744187noreply@blogger.com1tag:blogger.com,1999:blog-5513833072608837410.post-30837617560612917982015-12-30T22:25:00.001-03:002016-06-16T10:13:50.247-04:00Arduino - Como gravar o bootloader (e sketch) em um Atmega328P-PU Standalone<div class="separator" style="clear: both; text-align: center;">
</div>
<div class="separator" style="clear: both; text-align: left;">
Nesse artigo vou tentar descrever os passos que segui até conseguir carregar o bootloader em um Atmega328p-PU. </div>
<div class="separator" style="clear: both; text-align: left;">
<br /></div>
<div class="separator" style="clear: both; text-align: left;">
Já fazia um tempo que eu havia comprado alguns kits para Arduino Standalone, como esse mostrado na imagem abaixo, mas que por preguiça acabaram ficando guardados. Hoje então resolvi fazer alguns testes com um e gravar o bootloader nele. Ele possui alguns componentes mínimos que geralmente são usados em placas Standalones, como o regulador de tensão, capacitores, o cristal oscilador, o Atmega328P e um slote onde CI será encaixado na placa.</div>
<div class="separator" style="clear: both; text-align: center;">
<br /></div>
<div class="separator" style="clear: both; text-align: center;">
</div>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi4O29oOk3naF1pc27UT1_Wws_XSiMs0ncRy1wMIqFj6oDYZH2TfnCBBsvyVfykilLQRbT58t58vz5OV-BrCZETccY1NMS1I57zfkZgEiGR87QiCjrIM-shQ4ysqleP6OOAiHqMh-1SKxo/s1600/20151230_130002.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="360" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi4O29oOk3naF1pc27UT1_Wws_XSiMs0ncRy1wMIqFj6oDYZH2TfnCBBsvyVfykilLQRbT58t58vz5OV-BrCZETccY1NMS1I57zfkZgEiGR87QiCjrIM-shQ4ysqleP6OOAiHqMh-1SKxo/s640/20151230_130002.jpg" width="640" /></a></div>
<br />
<br />
O Procedimento não é algo tão complicado, mas podemos dizer que é um pouco chato de se fazer, pelo menos foi a impressão que tive ao tentar e vendo vários comentários na internet de pessoas que tiveram as mesmas dificuldades. Por algum motivo não consegui logo de começo, mesmo seguindo os procedimentos descritos em alguns artigos por ai. Mas depois de insistir um pouco consegui sem maiores problemas, por isso resolvi escrever mostrando os passos que segui.<br />
<br />
Quando trabalhamos com o Arduino, os pinos do Atmega não ficam explicitos, então a imagem abaixo é bom guia pra nos orientarmos qual pino do Atmega é o seu correspondente do Arduino.<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi4uCFQI-nLCt_PzCfBuk_HQbA2XoYUd9zbjs1KKZAxrlsSQUehUgK6gcDZqftXrTNsC84NyuKfnTR4Ksyklsm6k5K3Az21QZUtMnrOdJ0aOzemCuI_BmbvOR5_XVlBQomJwWjnF5FeoEU/s1600/arduino_atmega328_Web-copy.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="452" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi4uCFQI-nLCt_PzCfBuk_HQbA2XoYUd9zbjs1KKZAxrlsSQUehUgK6gcDZqftXrTNsC84NyuKfnTR4Ksyklsm6k5K3Az21QZUtMnrOdJ0aOzemCuI_BmbvOR5_XVlBQomJwWjnF5FeoEU/s640/arduino_atmega328_Web-copy.jpg" width="640" /></a></div>
<br />
Mas antes de iniciarmos a ligação entre o Arduino e o Atmega, vamos deixar o Arduino Uno configurado para ser um programador ISP. Para isso, abra o arquivo que já está na própria IDE do Arduino, através do menu Arquivo --> exemplos --> ArduinoISP.<br />
<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj_J2JKbrwzGfX9v8Wk3shjidS6M3A24S5Pzoe2eVempsekaCJmZbmt6lQ4GMqo4X1PeLldh1kssleFylBRdvfvKhyphenhyphenUoPNdjiY0IOe4Zjs0MxccBVHlU2j2qod1RgBJw_RV6-dxRhsGZOM/s1600/arduino+pro+mini+-+uno+3.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="568" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj_J2JKbrwzGfX9v8Wk3shjidS6M3A24S5Pzoe2eVempsekaCJmZbmt6lQ4GMqo4X1PeLldh1kssleFylBRdvfvKhyphenhyphenUoPNdjiY0IOe4Zjs0MxccBVHlU2j2qod1RgBJw_RV6-dxRhsGZOM/s640/arduino+pro+mini+-+uno+3.png" width="640" /></a></div>
<br />
<br />
Com o arquivo Aberto, basta fazer o upload para o Arquivo Uno.<br />
<br />
Com o Arduino Uno Configurado, iremos agora fazer a ligação entre os componentes, conforme a imagem abaixo. Nesse esquema que está abaixo, inicialmente eu não tinha utilizado aquele resistor de pullup de 10K para o pino de reset do Atmega. Mas depois de tentar algumas vezes e não conseguir, encontrei um artigo no qual o mesmo foi utilizado, então resolvi mante-lo, apesar de posteriormente eu ter testado sem o mesmo, e o upload foi feito do mesmo jeito. Mas aconselho manter o resistor. No Arduino Uno, também podemos notar um capacitor eletrolítico de 10uF. Também não tenho certeza se ele é exatamente necessário, mas por desencargo, mantive o mesmo conectado<br />
<br />
<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj5_yFp2AIOK3fiXF-7ZFvIyuB2V4NCrnt8RiSEwq7sIiRW1b-_ZiZ-xjl7Qyu0QBNlhIl1ua_e8FtnV3pLfelKS63Gq_EPBThacsADfajyUcYQDoU2wYko9UG-OszmqfXzQgyuBW-q7Fk/s1600/Arduino+-+Standalone+-+esquematico.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="482" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj5_yFp2AIOK3fiXF-7ZFvIyuB2V4NCrnt8RiSEwq7sIiRW1b-_ZiZ-xjl7Qyu0QBNlhIl1ua_e8FtnV3pLfelKS63Gq_EPBThacsADfajyUcYQDoU2wYko9UG-OszmqfXzQgyuBW-q7Fk/s640/Arduino+-+Standalone+-+esquematico.png" width="640" /></a></div>
<div class="separator" style="clear: both; text-align: left;">
Pinagem:</div>
<div class="separator" style="clear: both; text-align: center;">
<br /></div>
<div class="separator" style="clear: both; text-align: center;">
<br /></div>
<div class="separator" style="clear: both; text-align: center;">
ATmega328 | Arduino Uno </div>
<div class="separator" style="clear: both; text-align: center;">
19 | 13</div>
<div class="separator" style="clear: both; text-align: center;">
18 | 12</div>
<div class="separator" style="clear: both; text-align: center;">
17 | 11</div>
<div class="separator" style="clear: both; text-align: center;">
1 (reset) | 10 </div>
<div class="separator" style="clear: both; text-align: center;">
7, 20 | 5V </div>
<div class="separator" style="clear: both; text-align: center;">
8, 22 | Gnd </div>
<div class="separator" style="clear: both; text-align: left;">
<br /></div>
<div class="separator" style="clear: both; text-align: left;">
Aqui podemos ver a montagem que eu fiz (ainda sem o resistor de 10K)</div>
<div class="separator" style="clear: both; text-align: center;">
<br /></div>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiVBho1wM0bHxTcKqlFMRv4cY6X6U_fe4_gkDHTDlfTXWbLWiPHpDyHFRWWOBnVRwtoErVUMUJO1PN7VT1N7jBGXKyZmhDzUllSzWHkbsA-fvGjbXej71bBnM5E9Fa9B_CSscaWu9hvrOU/s1600/20151230_143646.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="360" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiVBho1wM0bHxTcKqlFMRv4cY6X6U_fe4_gkDHTDlfTXWbLWiPHpDyHFRWWOBnVRwtoErVUMUJO1PN7VT1N7jBGXKyZmhDzUllSzWHkbsA-fvGjbXej71bBnM5E9Fa9B_CSscaWu9hvrOU/s640/20151230_143646.jpg" width="640" /></a></div>
<br />
<br />
Com todos os componentes conectados, agora é hora de configurarmos a IDE do Arduino para fazermos o upload do bootloader. Pra isso iremos configurar a IDE como se estivéssemos programando um "Arduino Duemilanove or Diecimila", indo em Ferramentas --> Placa --> Arduino Duemilanove or Diecimila, como mostrado na imagem abaixo.<br />
<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiNyXhLnmOTuBr4pwNZTOMrdUhtaqpXsaP6pUApyTsdE3HRwFcpc6yERM8jza0GSs7EvtuQYoU5gpbcXLPydrTAQEdpluo9s_P3Kjp2t7KCyzbl9f4JzUTMoxrS4g0CighJco0n2duXXAs/s1600/Arduino+-+Standalone+-+01.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="596" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiNyXhLnmOTuBr4pwNZTOMrdUhtaqpXsaP6pUApyTsdE3HRwFcpc6yERM8jza0GSs7EvtuQYoU5gpbcXLPydrTAQEdpluo9s_P3Kjp2t7KCyzbl9f4JzUTMoxrS4g0CighJco0n2duXXAs/s640/Arduino+-+Standalone+-+01.png" width="640" /></a></div>
<br />
<br />
O Próximo passo é informar que iremos utilizar o ATmega328. Pra isso vamos em ferramentas --> Processador --> ATmega328.<br />
<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhfXThs71r43YIyoD1gVbrfknPIU0XT7uX7mp6rw1EQ45qdheOITwgbNPMr4_EUDzRRaVYQXrten-rdms0GsL0zack6ndW06lMrjg4gO8KTdyt9kT-LJ5CLXtu775CnGLhVmZf9p386Q78/s1600/Arduino+-+Standalone+-+02.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="374" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhfXThs71r43YIyoD1gVbrfknPIU0XT7uX7mp6rw1EQ45qdheOITwgbNPMr4_EUDzRRaVYQXrten-rdms0GsL0zack6ndW06lMrjg4gO8KTdyt9kT-LJ5CLXtu775CnGLhVmZf9p386Q78/s640/Arduino+-+Standalone+-+02.png" width="640" /></a></div>
<br />
O próximo passo agora é configurar o Programador para "<b>Arduino as ISP</b>".<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEipfwTUgD7lG6WGrfh3fcOWcn7-rJ0eIFd4jwEVjOictAnfgFxzx5XWhY_Gaw5lsu7W799nxxgjNh2QbpTKa1gRbN2jk7T-Uri5H6oP4qN-Rs0D_SJkZoAVSyanusKCYJTeLyZq9fcgVlI/s1600/arduino+pro+mini+conf+3.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="552" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEipfwTUgD7lG6WGrfh3fcOWcn7-rJ0eIFd4jwEVjOictAnfgFxzx5XWhY_Gaw5lsu7W799nxxgjNh2QbpTKa1gRbN2jk7T-Uri5H6oP4qN-Rs0D_SJkZoAVSyanusKCYJTeLyZq9fcgVlI/s640/arduino+pro+mini+conf+3.png" width="640" /></a></div>
<br />
<br />
Agora, com todos os passos anteriores configurados, é só gravar o booteloder. Pra isso vá em Ferramentas --> Gravar Bootloader. Como mostrado na imagem abaixo. Aguarde terminar o procedimento. Se tudo ocorreu certo, o pino 13 irá piscar, caso tenha um led conectado a ele. Apesar que nas imagens não aparece o led ligado, é interessante para fins de testes ligar um resistor e um led em série no pino 19 do Atmega (Conhecido como pino 13 no Arduino).<br />
<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEinDVA34uDvXR5D1gexMDmiCDwGa5fXkydEC0nrazblYWogei7M3EFwtMy3uA2b0EnwvRcx7et_mMMeSjoM5re1DxSIQKHoeM4LiAIX6CprgeaVWIvFKm-60Z9Zkv2XGHPQoG2d8tlZWrs/s1600/Arduino+-+Standalone+-+03.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="428" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEinDVA34uDvXR5D1gexMDmiCDwGa5fXkydEC0nrazblYWogei7M3EFwtMy3uA2b0EnwvRcx7et_mMMeSjoM5re1DxSIQKHoeM4LiAIX6CprgeaVWIvFKm-60Z9Zkv2XGHPQoG2d8tlZWrs/s640/Arduino+-+Standalone+-+03.png" width="640" /></a></div>
<br />
Com o bootloader gravado, é hora de testar o upload de um sketch qualquer. Pra isso abra o sketch que você deseja gravar no Atmega. faça as ligações dos componentes que serão necessários e então faça o upload da sketch, através da Opção "Carregar usando o programador" como mostrado na imagem abaixo.<br />
<br />
Pra fugir um pouco do blink padrão do Arduino, fiz um blink "mais legal", que não utiliza o delay. e sim uma classe temporizadora desenvolvida por mim, que ainda não falei sobre ela, mas em breve farei um artigo explicando como ela funciona. O Código está mais abaixo.<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgF39_EDVFja2J4DPZg7kUSgZ-s1bhh9YLsDd9M-2-7ipNfLIrLNg9QBdSQSZSPLv8Ri7LtR9I8ERwfYC3KVUt6EBKyZnD6UV2e-tf-AS5RIIJxVCq5NOijpxlVb_4GZo963SI2s6fCTFA/s1600/arduino+pro+mini+conf+4.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="554" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgF39_EDVFja2J4DPZg7kUSgZ-s1bhh9YLsDd9M-2-7ipNfLIrLNg9QBdSQSZSPLv8Ri7LtR9I8ERwfYC3KVUt6EBKyZnD6UV2e-tf-AS5RIIJxVCq5NOijpxlVb_4GZo963SI2s6fCTFA/s640/arduino+pro+mini+conf+4.png" width="640" /></a></div>
<br />
<br />
<br />
<br />
<pre class="brush: html" style="background-color: #eeeeee; border: 1px dashed rgb(153, 153, 153); overflow: auto; padding: 5px; width: 100%;">/*********************************************************************************************************
************************************CLASSE MYTIMER********************************************************
**********************************************************************************************************/
struct MyTimerSequence{
unsigned long * times;
unsigned int size;
unsigned int repeat;
};
class MyTimer{
private:
boolean _enable;
unsigned long _mref;
unsigned long _mref_disable;
unsigned long _time_disable;
MyTimerSequence *_sequences;
long _lag;
int _quantidade;
int _index_time;
int _last_index_time;
int _index_sequence;
int _last_index_sequence;
void (*_onChanging)( int index_sequence, int index_time ); //ponteiro para funcao do evento onChanging
void (*_onEnable)();
void (*_onDisable)();
public:
MyTimer(MyTimerSequence * sequences, int quantidade, long lag, unsigned long mref){
_lag = lag;
_mref = mref;
_sequences = sequences;
_quantidade = quantidade;
_time_disable = 0;
_enable = false;
_undetermine();
}
void _undetermine(){
_index_time = -1;
_last_index_time = -1;
_index_sequence = -1;
_last_index_sequence = -1;
update();
}
void setSequences(MyTimerSequence * sequences, int quantidade){
_sequences = sequences;
_quantidade = quantidade;
_undetermine();
update();
};
void setMillisRef(unsigned long mref) { _mref = mref; _undetermine(); }
void setLag(long lag) { _lag = lag; _undetermine(); }
boolean isChanging() { return (( _index_time != _last_index_time)||( _index_sequence != _last_index_sequence))&&(_enable); }
boolean isIndexTime(int index_sequence, int index_time){ return ( _index_time == index_time)&&(_index_sequence == index_sequence)&&(_enable); }
void setOnChanging( void (*onChanging)(int, int) ) { _onChanging = onChanging; }
void setOnEnable ( void (*onEnable)() ) { _onEnable = onEnable; }
void setOnDisable ( void (*onDisable)() ) { _onDisable = onDisable; }
void update();
void enable() { _enable = true; if ( _onEnable ) { (*_onEnable )( ); } }
void disable() { _enable = false; if ( _onDisable ) { (*_onDisable)( ); } }
void setTimeDisable(unsigned long time){ _time_disable = time; _mref_disable = millis(); }
};
void MyTimer::update(){
if ((millis() - _time_disable > _mref_disable) && (_time_disable > 0)) { disable(); } //verifica se está configurado pra desabilitar por time
if (!_enable) { return; }
unsigned long s = 0;
for (int i=0; i<_quantidade;i++){
for (int j=0; j<_sequences[i].repeat; j++){
for (int k=0; k<_sequences[i].size; k++){
s += _sequences[i].times[k];
}
}
}
long adjustment = _mref % s;
long rest = (millis() + s - adjustment - _lag) % s;
_last_index_time = _index_time;
_last_index_sequence = _index_sequence;
boolean ind_break = false;
s = 0;
for (int i=0; i<_quantidade;i++){
for (int j=0; j<_sequences[i].repeat; j++){
for (int k=0; k<_sequences[i].size; k++) {
s += _sequences[i].times[k];
if (rest < s) {
_index_time = k;
_index_sequence = i;
ind_break = true;
break;
}
}
if (ind_break) { break; }
}
if (ind_break) { break; }
}
if ( isChanging() && _onChanging ) { (*_onChanging)( _index_sequence, _index_time ); }
}
/*********************************************************************************************************
************************************FIM CLASSE MYTIMER****************************************************
**********************************************************************************************************/
unsigned long seq01[] = {750};
unsigned long seq02[] = {60, 60};
unsigned long seq03[] = {200};
MyTimerSequence sequences[] = {
{ seq01, sizeof(seq01)/sizeof(unsigned long), 1},
{ seq02, sizeof(seq02)/sizeof(unsigned long), 10},
{ seq03, sizeof(seq03)/sizeof(unsigned long), 1},
{ seq02, sizeof(seq02)/sizeof(unsigned long), 6},
{ seq03, sizeof(seq03)/sizeof(unsigned long), 1},
{ seq02, sizeof(seq02)/sizeof(unsigned long), 3}
} ;
MyTimer t1(sequences, sizeof(sequences)/sizeof(MyTimerSequence), 0, 0); //tem um atraso de 100 milissegundos em relação a referencia 0
void setup() {
Serial.begin(9600);
pinMode(13, OUTPUT);
t1.setOnChanging( onChanging_t1 );
t1.enable();
}
void loop() {
t1.update();
}
void onChanging_t1(int sequence, int index ) {
digitalWrite(13, LOW);
if (index == 0 && (sequence == 1 || sequence == 3 || sequence == 5)){ digitalWrite(13, HIGH); }
}
</pre>
<br />
<br />
<b>E como fazer o upload através do Arduino Mega (ou outras placas Arduino)?</b><br />
<b><br /></b>
O Mesmo procedimento mostrado anteriormente também pode ser feito através do Arduino Mega, ou ainda com outros Arduinos. O Procedimento é fazer o upload da sketch ArduinoISP (mostrado lá no início) para o Arduino Mega e seguir os demais passos mostrados anteriormente, apenas tomando o cuidado na hora de ligar os pinos, que são diferentes do uno:<br />
<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
ATmega328 | Arduino Mega</div>
<div class="separator" style="clear: both; text-align: center;">
19 | 52 (SCK)</div>
<div class="separator" style="clear: both; text-align: center;">
18 | 50 (MISO)</div>
<div class="separator" style="clear: both; text-align: center;">
17 | 51 (MOSI)</div>
<div class="separator" style="clear: both; text-align: center;">
1 (reset) | 53 (RST)</div>
<div class="separator" style="clear: both; text-align: center;">
7, 20 | 5V </div>
<div class="separator" style="clear: both; text-align: center;">
8, 22 | Gnd </div>
<div class="separator" style="clear: both; text-align: center;">
<br /></div>
<div class="separator" style="clear: both; text-align: left;">
Para outras Placas, certifique-se de utilizar os pinos corretos, pra isso terá que descobrir quais são os pinos ISP: SCK, MISO, MOSI e RST</div>
<div class="separator" style="clear: both; text-align: left;">
<br /></div>
<div class="separator" style="clear: both; text-align: left;">
<br /></div>
Fabiano Arndthttp://www.blogger.com/profile/15268923442674744187noreply@blogger.com1tag:blogger.com,1999:blog-5513833072608837410.post-21564938765413440562015-12-29T14:14:00.001-03:002016-06-16T10:13:50.244-04:00Arduino - Programando o Pro mini com o Uno ou com o Mega<div class="separator" style="clear: both; text-align: left;">
Uma das coisas legais dos Arduinos, é que existem várias versões e cada versão com suas vantagens, seja em tamanho, custo, capacidade de processamento, etc. </div>
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhIcs8jcO81Bpx4pSu3odH-WnhRXm1TR6CbazvyCi33aCRDA26ugDg__4WkiIaIxYRqm46UD5eD9WTsWFgTzl12RAR1EmhP0Roj6x5WqxtARQ5BRd4WX0eexUUKsHo5x2EVcV-wXTPb83Y/s1600/20151226_143357.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="362" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhIcs8jcO81Bpx4pSu3odH-WnhRXm1TR6CbazvyCi33aCRDA26ugDg__4WkiIaIxYRqm46UD5eD9WTsWFgTzl12RAR1EmhP0Roj6x5WqxtARQ5BRd4WX0eexUUKsHo5x2EVcV-wXTPb83Y/s640/20151226_143357.png" width="640" /></a></div>
<br />
Uma das versões que eu gosto é o Pro mini que consegue reunir quase todos os itens mencionados anteriormente, pequeno, barato e poderoso, tudo em uma pequena plaquinha de 3,4 x 1,9 cm, 8 pinos analógicos, 14 pinos digitais, sendo 6 pwm, botão de reset, led no pino 13 e outro led indicador de energizado e opera a velocidade de 16MHz. Quanto a memória, o Pro Mini disponibiliza 16Kb para memória flash, sendo 2Kb utilizados pelo bootloader, 1Kb de SRAM e 512 bytes de EEPROM. Sem dúvida é uma boa candidata para tirar do papel aquele projeto que você já vem querendo implementar há algum tempo.<br />
<br />
Um detalhe importante é que os conectores não vêm soldados na placa, ficando a cargo de quem for utilizá-la escolher qual o melhor tipo de conector a ser utilizado, o que lhe dá a vantagem de poder soldar apenas os pinos que serão de fato necessários de acordo com o projeto a ser implementado. Uma dica quando for soldar os conectores, é usar um pouco de pasta de solda nos terminais, o que facilita bastante conseguir uma solda mais uniforme sem que sobre solda.<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgiMpTVxhZJLxKrS7LGUzhWYmVbZQCiPKf5XdxIHA907xDOO_GP7owjPSAjL6bpQnbv4pyPRbdnN7NNxq1qnYF0dWmkMYjFV9SDtpusaH9u_sVMUEnVOGrlGeo7OsvvK3XVgr23BZq-Z_Q/s1600/20151226_220218.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="360" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgiMpTVxhZJLxKrS7LGUzhWYmVbZQCiPKf5XdxIHA907xDOO_GP7owjPSAjL6bpQnbv4pyPRbdnN7NNxq1qnYF0dWmkMYjFV9SDtpusaH9u_sVMUEnVOGrlGeo7OsvvK3XVgr23BZq-Z_Q/s640/20151226_220218.png" width="640" /></a></div>
<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
</div>
<br />
<br />
Na imagem acima pode-se notar que os 4 primeiros conectores ficaram com a solda "grossa". Como não gostei muito do resultado tentei passar um pouco de pasta de solda, e o resultado foi bem melhor como pode ser observado nos pinos mais à direita.<br />
<br />
Em geral os conectores a serem soldados são aqueles que permitem conectar a placa na protoboard ou ainda usar jumper, mas caso vc tenha em mente utilizar alguma outro tipo de conector, tenha certeza que você conseguirá fazer o upload do código após a soldagem, pois dependendo do conector escolhido, o processo de upload pode ficar um pouco mais complicado. Se Notar que será complicado fazer o upload depois de conectar os fios, tente fazer o upload do programa antes de soldar os conectores.<br />
<br />
Para mais detalhes sobre as funcionalidades da plaquinha, vale a pena uma boa olhada na imagem abaixo:<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhxp3tRYejQpSwppyhyphenhyphenHW74YhkVFj6Gwc95QqMqtDRGBpEjJ91LTvbGaB7m1bthLzcJttTj9zZCRAn9rQb7zsdpbSjPF6HprStyyn7S_TaDp0llTPYYyU53PaSNXM0Tokrg8nvlLOvsQ6c/s1600/pro-mini.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="452" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhxp3tRYejQpSwppyhyphenhyphenHW74YhkVFj6Gwc95QqMqtDRGBpEjJ91LTvbGaB7m1bthLzcJttTj9zZCRAn9rQb7zsdpbSjPF6HprStyyn7S_TaDp0llTPYYyU53PaSNXM0Tokrg8nvlLOvsQ6c/s640/pro-mini.png" width="640" /></a></div>
<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
</div>
Quanto a alimentação pode ser feita diretamente pelo pino Vcc com tensão já regulada em 5V ou através do pino RAW em até 12V pois a placa conta com um regulador de tensão.<br />
<br />
Ele não é necessariamente o candidato ideal pra prototipagem, já que nesse quesito o Arduino Uno e o Mega ou outros são os mais indicados, por isso na hora de programá-lo, você irá precisar contar com alguns itens adicionais.<br />
<br />
<b>Conversor FTDI</b><br />
<br />
Uma das formas de programa-lo é utilizando um conversor FTDI, o qual possui um conector USB. Porém nesse artigo não irei abordar o seu uso (mas talvez futuramente eu inclua aqui explicações de como utilizá-lo).<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjvA2WQZxKclzxLs0z3wZh-u9Pa1xb9chZM-IPSlELF1sWWINKjt0kziFGNdcw1ivBjSDwJHsKcwuxcL-7jVwD6z8MfwJ62qOSDUcOFf1w8bbuqBoqQG4IHFWVeoTNInUc17jotmRvc-0c/s1600/conversor+FTDI.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjvA2WQZxKclzxLs0z3wZh-u9Pa1xb9chZM-IPSlELF1sWWINKjt0kziFGNdcw1ivBjSDwJHsKcwuxcL-7jVwD6z8MfwJ62qOSDUcOFf1w8bbuqBoqQG4IHFWVeoTNInUc17jotmRvc-0c/s1600/conversor+FTDI.jpg" /></a></div>
<br />
<b>Arduino Uno com o Atmega Desencaixado</b><br />
<br />
Outra possibilidade é utilizar um Arduino Uno sem o Atmega conectado na placa. Para isso siga as conexões mostradas abaixo mas com o Atmega desconectado.<br />
<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjcoPtyMD121QbNLi0ruIiCOMxWaATWsHwb-Ivj1PLoC5pGHS3RfSfSrWeSsnSy2jgj6J_o3Kim_jXmjLo95u2ncedt-IWUTMgPFGD5QaBj2dDiAGjmeXHpXlHlH9_x9hZVhg3ILMj3zfk/s1600/arduino+pro+mini+-+uno_bb.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="396" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjcoPtyMD121QbNLi0ruIiCOMxWaATWsHwb-Ivj1PLoC5pGHS3RfSfSrWeSsnSy2jgj6J_o3Kim_jXmjLo95u2ncedt-IWUTMgPFGD5QaBj2dDiAGjmeXHpXlHlH9_x9hZVhg3ILMj3zfk/s640/arduino+pro+mini+-+uno_bb.png" width="640" /></a></div>
<br />
Montado na protoboard com os pinos já soldados e com o Atmega removido:<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgTPJsZ_mytaKYz8eMVccM3qCK4kARwMjH4VVKIy7JSupd8-3bDAKRNm5LVb4h8CotewyoZHbRXT0yVWaIb0DDm6GvQhh5eAlXGQKRzusGgKROzgsBVvEl7C-YAfpJdzDL6Pvllv412hZE/s1600/20151226_151645.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="360" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgTPJsZ_mytaKYz8eMVccM3qCK4kARwMjH4VVKIy7JSupd8-3bDAKRNm5LVb4h8CotewyoZHbRXT0yVWaIb0DDm6GvQhh5eAlXGQKRzusGgKROzgsBVvEl7C-YAfpJdzDL6Pvllv412hZE/s640/20151226_151645.jpg" width="640" /></a></div>
<br />
Depois de tudo conectado é hora de configurar a IDE do Arduino para enviar o código para o Pro Mini. Para isso selecione a placa, conforme imagem a baixo.<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgJP-i_I2i0J62paGqKQUkReBKCRW3Qvst6uRLgsmB4NYlWXZbUezSOcVr6vsmEGK5gb5_nCOruuxPDlpanNzPo8ig0cM7bR6j47E0WbUJmyub3KxsOg08txQzWGD3RD5fGDzyhU0EO6sU/s1600/arduino+pro+mini+-+uno.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="640" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgJP-i_I2i0J62paGqKQUkReBKCRW3Qvst6uRLgsmB4NYlWXZbUezSOcVr6vsmEGK5gb5_nCOruuxPDlpanNzPo8ig0cM7bR6j47E0WbUJmyub3KxsOg08txQzWGD3RD5fGDzyhU0EO6sU/s640/arduino+pro+mini+-+uno.png" width="560" /></a></div>
<br />
<br />
Feito isso, selecione então o ATmega utilizado, no caso, ATMega328 (5V, 16MHz), como na imagem abaixo:<br />
<div class="separator" style="clear: both; text-align: center;">
<br /></div>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhuibvUhaut46j5WWNuyuht8CCX7HfRtH8zB8AqUWilrOOU9dcJtRVz40z8fa0yDfg74xRsahAXHb3SZMf0LEHj-Imhwy2Nn9hrcyX5UX9i8S-ECOfbUpoXO3WkYHfwcEg3FtadPljibMU/s1600/arduino+pro+mini+-+uno+2.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="366" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhuibvUhaut46j5WWNuyuht8CCX7HfRtH8zB8AqUWilrOOU9dcJtRVz40z8fa0yDfg74xRsahAXHb3SZMf0LEHj-Imhwy2Nn9hrcyX5UX9i8S-ECOfbUpoXO3WkYHfwcEg3FtadPljibMU/s640/arduino+pro+mini+-+uno+2.png" width="640" /></a></div>
<br />
<br />
Agora é só enviar o código. No Exemplo que utilizei, liguei um potenciômetro ao pino A1, um led no pino 13 e outro led no pino 5. O led no pino 13 irá ficar piscando enquanto que o led no pino pwm será controlado através da leitura do pino analógico A1.<br />
<br />
O Código possui uma classe temporizadora, a qual ainda irei fazer um outro artigo explicando melhor os detalhes de como ela funciona, mas já fiz outro artigo que abordo sobre temporização, que vale a pena dar uma olhada: <a href="http://fabianoallex.blogspot.com.br/2015/09/arduino-como-substituir-delay-pelo.html">http://fabianoallex.blogspot.com.br/2015/09/arduino-como-substituir-delay-pelo.html</a><br />
<br />
Mas o código aqui pode ser qualquer outro que esteja programado para o que você deseja executar.<br />
<br />
Código Utilizado:<br />
<br />
<br />
<br />
<pre class="brush: html" style="background-color: #eeeeee; border: 1px dashed rgb(153, 153, 153); overflow: auto; padding: 5px; width: 100%;">/*********************************************************************************************************
************************************CLASSE MYTIMER********************************************************
**********************************************************************************************************/
struct MyTimerSequence{
unsigned long * times;
unsigned int size;
unsigned int repeat;
};
class MyTimer{
private:
boolean _enable;
unsigned long _mref;
unsigned long _mref_disable;
unsigned long _time_disable;
MyTimerSequence *_sequences;
long _lag;
int _quantidade;
int _index_time;
int _last_index_time;
int _index_sequence;
int _last_index_sequence;
void (*_onChanging)( int index_sequence, int index_time ); //ponteiro para funcao do evento onChanging
void (*_onEnable)();
void (*_onDisable)();
public:
MyTimer(MyTimerSequence * sequences, int quantidade, long lag, unsigned long mref){
_lag = lag;
_mref = mref;
_sequences = sequences;
_quantidade = quantidade;
_time_disable = 0;
_enable = false;
_undetermine();
}
void _undetermine(){
_index_time = -1;
_last_index_time = -1;
_index_sequence = -1;
_last_index_sequence = -1;
update();
}
void setSequences(MyTimerSequence * sequences, int quantidade){
_sequences = sequences;
_quantidade = quantidade;
_undetermine();
update();
};
void setMillisRef(unsigned long mref) { _mref = mref; _undetermine(); }
void setLag(long lag) { _lag = lag; _undetermine(); }
boolean isChanging() { return (( _index_time != _last_index_time)||( _index_sequence != _last_index_sequence))&&(_enable); }
boolean isIndexTime(int index_sequence, int index_time){ return ( _index_time == index_time)&&(_index_sequence == index_sequence)&&(_enable); }
void setOnChanging( void (*onChanging)(int, int) ) { _onChanging = onChanging; }
void setOnEnable ( void (*onEnable)() ) { _onEnable = onEnable; }
void setOnDisable ( void (*onDisable)() ) { _onDisable = onDisable; }
void update();
void enable() { _enable = true; if ( _onEnable ) { (*_onEnable )( ); } }
void disable() { _enable = false; if ( _onDisable ) { (*_onDisable)( ); } }
void setTimeDisable(unsigned long time){ _time_disable = time; _mref_disable = millis(); }
};
void MyTimer::update(){
if ((millis() - _time_disable > _mref_disable) && (_time_disable > 0)) { disable(); } //verifica se está configurado pra desabilitar por time
if (!_enable) { return; }
unsigned long s = 0;
for (int i=0; i<_quantidade;i++){
for (int j=0; j<_sequences[i].repeat; j++){
for (int k=0; k<_sequences[i].size; k++){
s += _sequences[i].times[k];
}
}
}
long adjustment = _mref % s;
long rest = (millis() + s - adjustment - _lag) % s;
_last_index_time = _index_time;
_last_index_sequence = _index_sequence;
boolean ind_break = false;
s = 0;
for (int i=0; i<_quantidade;i++){
for (int j=0; j<_sequences[i].repeat; j++){
for (int k=0; k<_sequences[i].size; k++) {
s += _sequences[i].times[k];
if (rest < s) {
_index_time = k;
_index_sequence = i;
ind_break = true;
break;
}
}
if (ind_break) { break; }
}
if (ind_break) { break; }
}
if ( isChanging() && _onChanging ) { (*_onChanging)( _index_sequence, _index_time ); }
}
/*********************************************************************************************************
************************************FIM CLASSE MYTIMER****************************************************
**********************************************************************************************************/
unsigned long seq01[] = {750};
unsigned long seq02[] = {60, 60};
unsigned long seq03[] = {200};
MyTimerSequence sequences[] = {
{ seq01, sizeof(seq01)/sizeof(unsigned long), 1},
{ seq02, sizeof(seq02)/sizeof(unsigned long), 10},
{ seq03, sizeof(seq03)/sizeof(unsigned long), 1},
{ seq02, sizeof(seq02)/sizeof(unsigned long), 6},
{ seq03, sizeof(seq03)/sizeof(unsigned long), 1},
{ seq02, sizeof(seq02)/sizeof(unsigned long), 3}
} ;
MyTimer t1(sequences, sizeof(sequences)/sizeof(MyTimerSequence), 0, 0); //tem um atraso de 100 milissegundos em relação a referencia 0
void setup() {
Serial.begin(9600);
pinMode(13, OUTPUT);
pinMode(5, OUTPUT);
t1.setOnChanging( onChanging_t1 );
t1.enable();
}
void loop() {
t1.update();
analogWrite(5, analogRead(A1)/4);
}
void onChanging_t1(int sequence, int index ) {
digitalWrite(13, LOW);
if (index == 0 && (sequence == 1 || sequence == 3 || sequence == 5)){ digitalWrite(13, HIGH); }
}
</pre>
<b>Via ISP (Arduino Uno e Mega)</b><br />
<br />
Pra quem pretende utilizar o Arduino Pro Mini com uma certa frequencia, ter que retirar o ATMega do conector do Arduino Uno é um trabalho que pode se tornar um pouco chato, além de ter algum dos Arduinos Uno que não tem como desconectar o ATmega. Nesses casos, o mais recomendado é utilizar o a gravação através de ISP. Pra isso é necessário seguir alguns passos. Vou listá-los e mais abaixo demonstro como realizar cada um dos passos:<br />
<br />
Obs.: Esses mesmos passos podem ser seguidos utilizando um Arduino Mega.<br />
<br />
- 1º Passo: Fazer o upload do Sketch "ArduinoISP" para o Arduino Uno (ou Mega).<br />
- 2º Passo: Conectar os Cabos entre o Arduino Uno (ou Mega) e o Pro Mini.<br />
- 3º Passo: Mudar a Placa para Arduino Pro ou Pro mini.<br />
- 4º Passo: Mudar o Programador para "Arduino as ISP".<br />
- 5º Passo: Fazer Uploado via Programador (Ctrl+Shift+U).<br />
- 6º Passo: Voltar as configurações iniciais. (Esse passo é só para não ter problemas ao voltar a programar o arduino UNO posteriormente).<br />
<br />
<b>1º Passo: Fazer o upload do Sketch "ArduinoISP" para o Arduino Uno (ou Mega).</b><br />
<b><br /></b>
A Sketch <b>ArduinoISP</b> está na própria IDE do Arduino, bastando entrar no menu Arquivo, Exemplos e Abrir "ArduinoISP", conforme imagem abaixo:<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg0lC2NpVNKq2W4hP_soKbJiOFgrZhXVqlEk0GHUkBoN7yS9WsO6im9ZT25lA4AZoTbk6TeDugVc-dx82gmso4QdTxHByBjcGrpAvO2U5reDBUr6z-Y4v5nDQxzIr0pvU56A1phx0-u23A/s1600/arduino+pro+mini+-+uno+3.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="569" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg0lC2NpVNKq2W4hP_soKbJiOFgrZhXVqlEk0GHUkBoN7yS9WsO6im9ZT25lA4AZoTbk6TeDugVc-dx82gmso4QdTxHByBjcGrpAvO2U5reDBUr6z-Y4v5nDQxzIr0pvU56A1phx0-u23A/s640/arduino+pro+mini+-+uno+3.png" width="640" /></a></div>
<br />
Com o Arquivo aberto, faça o upload do arquivo para o Arduino Uno (ou Mega).<br />
<br />
<br />
<b>2º Passo: Conectar os Cabos entre o Arduino Uno (ou Mega) e o Pro Mini.</b><br />
<b><br /></b>
<b><i>Esquema Com o Arduino Uno:</i></b><br />
<br />
<div style="text-align: center;">
Pro mini | Arduino Uno</div>
<div style="text-align: center;">
13 | 13</div>
<div style="text-align: center;">
12 | 12</div>
<div style="text-align: center;">
11 | 11</div>
<div style="text-align: center;">
RST | 10 </div>
<div style="text-align: center;">
Vcc | 5V</div>
<div style="text-align: center;">
Gnd | Gnd </div>
<div style="text-align: center;">
<br /></div>
<div style="text-align: left;">
Ligar um capacitor de 10uF no Reset do arduino Uno e no Gnd.</div>
<div style="text-align: left;">
<br /></div>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEinlSTZc3m_wiDAiyWFX9IDCHSHaAq6GHlNV9YspkSxPWgnEsXd8KLt7-KSC19HR6G-pEcHReQ3IExmjxzKOh1AO-3rJR9pqh9EIn2BVCFwG8ctFeZtcFLt1QN4TD78gtBeXRuBXZ9IMhY/s1600/arduino+pro+mini+-+uno+4.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="640" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEinlSTZc3m_wiDAiyWFX9IDCHSHaAq6GHlNV9YspkSxPWgnEsXd8KLt7-KSC19HR6G-pEcHReQ3IExmjxzKOh1AO-3rJR9pqh9EIn2BVCFwG8ctFeZtcFLt1QN4TD78gtBeXRuBXZ9IMhY/s640/arduino+pro+mini+-+uno+4.png" width="596" /></a></div>
<div style="text-align: left;">
<br /></div>
<br />
<div class="separator" style="clear: both; text-align: center;">
</div>
<div class="separator" style="clear: both; text-align: center;">
</div>
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjmMCT58qYRsJgkS64wNfEOO3VECFYkBGDOUqSvbToSsmBZdwSByxRi4HOeigQ9P1PEcvtebjXtjx55y9fx05zbgR-C-sKH5SBlqDaA2yv-JSY6DM5-yxATbPY74R6vAu6j4qKrl7UBZeY/s1600/20151229_134731.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="360" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjmMCT58qYRsJgkS64wNfEOO3VECFYkBGDOUqSvbToSsmBZdwSByxRi4HOeigQ9P1PEcvtebjXtjx55y9fx05zbgR-C-sKH5SBlqDaA2yv-JSY6DM5-yxATbPY74R6vAu6j4qKrl7UBZeY/s640/20151229_134731.jpg" width="640" /></a></div>
<br />
<br />
<br />
<b><i>Esquema Com o Arduino Mega:</i></b><br />
<br />
<div style="text-align: center;">
Pro mini | Arduino Mega</div>
<div style="text-align: center;">
13 | 52</div>
<div style="text-align: center;">
12 | 50</div>
<div style="text-align: center;">
11 | 51</div>
<div style="text-align: center;">
RST | 53 </div>
<div style="text-align: center;">
Vcc | 5V</div>
<div style="text-align: center;">
Gnd | Gnd </div>
<div style="text-align: center;">
<br /></div>
Ligar um capacitor de 10uF no Reset do arduino Uno e no Gnd.<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh_rnZld5NYu8MI2ZzbnYsjUUo0lHgh0rxoesm1LbwFEQ2eICfG-FTWtarS86quQaFpyCYzEL30urbB83gn8RO7HlBVFJZOeGZhLvYvwUCVdKz5NhBbosKNoYoahek_wBayKWTIoklWPDY/s1600/arduino+pro+mini+-+mega.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="328" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh_rnZld5NYu8MI2ZzbnYsjUUo0lHgh0rxoesm1LbwFEQ2eICfG-FTWtarS86quQaFpyCYzEL30urbB83gn8RO7HlBVFJZOeGZhLvYvwUCVdKz5NhBbosKNoYoahek_wBayKWTIoklWPDY/s640/arduino+pro+mini+-+mega.png" width="640" /></a></div>
<br />
<br />
<br />
<b>3º Passo: Mudar a Placa para Arduino Pro ou Pro Mini.</b><br />
<br />
Menu Ferramentas --> Placa --> Arduino Pro ou Pro Mini<br />
<div class="separator" style="clear: both; text-align: center;">
<br /></div>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgzHYSHmckTOd6pFZRQSKuqSU-Oc_MF-NSsG6A1QEXwdUS6NdWgteIa3c13MpKeNJop6lWhtTFjSnDMjXYB7WuclX27sPVYKOe9SMbN6MWlVO_6J53DUkB3ghsIHqvPE5ufOaNueNf6VVY/s1600/arduino+pro+mini+conf+1.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="510" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgzHYSHmckTOd6pFZRQSKuqSU-Oc_MF-NSsG6A1QEXwdUS6NdWgteIa3c13MpKeNJop6lWhtTFjSnDMjXYB7WuclX27sPVYKOe9SMbN6MWlVO_6J53DUkB3ghsIHqvPE5ufOaNueNf6VVY/s640/arduino+pro+mini+conf+1.png" width="640" /></a></div>
<br />
Escolher o processador:<br />
<br />
Menu Ferramentas --> Processador --> ATMega328 (5V, 16MHz)<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg9c_ilFDI9gEisKIJuy7tvgE-nDUIiyTXaSX7J8OqLYwa_UUSlWdeknfzbUNl7yCFF-Pa2TY0_V8fnVfB6ELS_PQAkdrdx8rJjTemii2P7JDn-yFxkYHl-fifNMknGZgbz_9TYYjN-mP8/s1600/arduino+pro+mini+conf+2.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="554" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg9c_ilFDI9gEisKIJuy7tvgE-nDUIiyTXaSX7J8OqLYwa_UUSlWdeknfzbUNl7yCFF-Pa2TY0_V8fnVfB6ELS_PQAkdrdx8rJjTemii2P7JDn-yFxkYHl-fifNMknGZgbz_9TYYjN-mP8/s640/arduino+pro+mini+conf+2.png" width="640" /></a></div>
<br />
<br />
<b>4º Passo: Mudar o Programador para "Arduino as ISP"</b><br />
<br />
Menu Ferrramentas --> Programador --> Arduino as ISP<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg3DcXe0cdUUM1zLJCx2Y1XfIPvQqagH4iDPDh-731NlLxrISLj446ki_6vCd_G2pD2-1JiJYayqpiu9viGmsYnjshCNLpnJr7LrSdkfBK-nDSpKjxl7OISyBb9yaBsBZPhoQJLyrqWKqg/s1600/arduino+pro+mini+conf+3.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="552" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg3DcXe0cdUUM1zLJCx2Y1XfIPvQqagH4iDPDh-731NlLxrISLj446ki_6vCd_G2pD2-1JiJYayqpiu9viGmsYnjshCNLpnJr7LrSdkfBK-nDSpKjxl7OISyBb9yaBsBZPhoQJLyrqWKqg/s640/arduino+pro+mini+conf+3.png" width="640" /></a></div>
<br />
<br />
<b>5º Passo: Fazer Upload via Programador (Ctrl+Shift+U)</b><br />
<br />
Nesse ponto aqui, por falta de atenção tive alguns problemas, pois tentei fazer o upload via comando tradicional, clicando no botão. Até que percebi que o Correto é enviar através de uma outra função, que fica no Menu Arquivo:<br />
<br />
Com o Sketch que se deseja upar aberto<br />
vá até o Menu Arquivo --> Carregar Usando Programador<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg3LEWyfuMiLFEDOtAQ8-KqZV_UyJKarCNaJnfd8La5S-YwB6EkfDX3vO34Xxh10HXC1rjZ5BiCmrRyaZNiaO25vMgw8WVI6-19A9oGggthLyDSrxpCYk6knE2l7gEZc_SQuFNsU7qt818/s1600/arduino+pro+mini+conf+4.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="554" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg3LEWyfuMiLFEDOtAQ8-KqZV_UyJKarCNaJnfd8La5S-YwB6EkfDX3vO34Xxh10HXC1rjZ5BiCmrRyaZNiaO25vMgw8WVI6-19A9oGggthLyDSrxpCYk6knE2l7gEZc_SQuFNsU7qt818/s640/arduino+pro+mini+conf+4.png" width="640" /></a></div>
<br />
<br />
Feito Isso, o Programa deve ser enviado para o Pro Mini.<br />
<br />
<b>6º Passo: Voltar as configurações iniciais.</b><br />
<br />
Como esse procedimento exige muitas mudanças de configurações da IDE do Arduino, é recomendável que as configurações oginais sejam restauradas, para evitar da próxima vez que a IDE seja usada, não cause outros problemas, principalmente se for um computador utilizado por outros programadores. Deixe sempre do jeito que estava quando iniciou.<br />
<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
</div>
<br />
<br />Fabiano Arndthttp://www.blogger.com/profile/15268923442674744187noreply@blogger.com3