Documente Academic
Documente Profesional
Documente Cultură
Contingut
• 1 Arduino
♦ 1.1 Códigos ejemplo
◊ 1.1.1 Pins E/S
⋅ 1.1.1.1 Salidas básicas Arduino
⋅ 1.1.1.2 Salidas básicas Arduino con ATmega328
⋅ 1.1.1.3 Entradas/Salidas básicas Arduino
⋅ 1.1.1.4 Entradas/Salidas básicas Arduino con ATmega328
◊ 1.1.2 I2C
⋅ 1.1.2.1 I2C: Dispositivo Maestro envía texto y pide respuesta a dos dispositivos esclavos
⋅ 1.1.2.2 I2C: Dispositivo Esclavo recibe texto y responde cuando el máster pide respuesta
◊ 1.1.3 SPI
⋅ 1.1.3.1 SPI: Maestro envía datos por SPI
⋅ 1.1.3.2 SPI: Esclavo recibe datos por SPI e interrupción (y AVR)
⋅ 1.1.3.3 SPI: Esclavo recibe datos por SPI e interrupción y responde un nuevo byte (y AVR)
◊ 1.1.4 Serie USART
⋅ 1.1.4.1 Serie: Modifica el valor de los pins 12 y 13 mediante puerto serie
⋅ 1.1.4.2 Serie: Recibe un carácter por encuesta (polling), incrementa su valor y lo envía
⋅ 1.1.4.3 Serie: (AVR) Recibe un carácter por encuesta (polling), incrementa su valor y lo envía
⋅ 1.1.4.4 Serie: (AVR) Recibe un carácter por interrupción, incrementa su valor y lo envía
⋅ 1.1.4.5 Serie: (AVR) Recibe string por el puerto serie, y lo vuelve a enviar añadiendo CRLF (por polling)
⋅ 1.1.4.6 Serie: (AVR) Recibe string por el puerto serie por interrupción, añade los caracteres a un buffer, y lee el
buffer por partes indicando el número de bytes a leer
◊ 1.1.5 TIMERS
⋅ 1.1.5.1 Timers: (AVR) Utiliza la interrupción del Timer 1 (16bits) para parpadear un Led cada 5 segundos
⋅ 1.1.5.2 Timers: (AVR) Utiliza la interrupción del Timer 2 (8bits) para parpadear un Led cada 1 segundo
◊ 1.1.6 PWM
⋅ 1.1.6.1 Fast PWM mediante analogWrite (Timer2)
⋅ 1.1.6.2 Fast PWM mediante registros ATMega328 (Timer2)
⋅ 1.1.6.3 Fast PWM mediante analogWrite (Timer0)
⋅ 1.1.6.4 Fast PWM mediante registros ATMega328 (Timer0)
⋅ 1.1.6.5 Fast PWM mediante registros ATMega328 (Timer1)
◊ 1.1.7 Contadores ATmega
⋅ 1.1.7.1 Activa un contador por registros. Cuenta hasta 65535 (0xFFFF)
⋅ 1.1.7.2 Activa un contador por registros. Cuenta hasta 15 (0x000F)
⋅ 1.1.7.3 Activa un contador por registros y activa interrupción al sobrepasar 65535 (0xFFFF)
⋅ 1.1.7.4 Activa un contador por registros y activa interrupción al sobrepasar el valor de OCR5A
◊ 1.1.8 Entrada Analógica ADC
⋅ 1.1.8.1 Lee el valor de una entrada analógica y parpadea LED (Arduino)
⋅ 1.1.8.2 Lee el valor de una entrada analógica y parpadea LED (Registros ATmega)
◊ 1.1.9 Guardar datos EEPROM
⋅ 1.1.9.1 Guardar nuevo valor de retardo de delay (Arduino)
◊ 1.1.10 Modo Sleep
⋅ 1.1.10.1 Intermitente de 1 segundo en modo Sleep e interrupción Timer 2 mediante instrucciones Arduino
⋅ 1.1.10.2 Intermitente de 1 segundo en modo Sleep e interrupción Timer 2 mediante AVR y ASM ATmega328
⋅ 1.1.10.3 Modo Sleep y despertar por interrupción 0
♦ 1.2 Funciones y ejemplos
◊ 1.2.1 Strings
⋅ 1.2.1.1 Separa String en dos partes separadas por un carácter
⋅ 1.2.1.2 Separa String en tres partes separadas por dos caracteres
⋅ 1.2.1.3 Separa busca un String dentro de otro String
⋅ 1.2.1.4 Devuelve el tamaño de un string de tipo char array
⋅ 1.2.1.5 Concatena varios char arrays
◊ 1.2.2 Serie
⋅ 1.2.2.1 Lee caracteres del puerto serie 0 hasta que encuentra el carácter indicado
⋅ 1.2.2.2 Lee la cantidad de caracteres indicados del puerto serie 0
⋅ 1.2.2.3 Escribe un string de array de chars por puerto serie 0
♦ 1.3 Hardware
◊ 1.3.1 LCD
⋅ 1.3.1.1 1602 (2x16)
◊ 1.3.2 Ultrasonidos
⋅ 1.3.2.1 HC-SR04
◊ 1.3.3 Distancia Infrarrojos
⋅ 1.3.3.1 Sharp GP2DY120X y similares
◊ 1.3.4 Configurar módulo Bluetooth HC-05 o similar con Arduino
⋅ 1.3.4.1 Módulo Bluetooth v.4 BLE HM-10
⋅ 1.3.4.2 Módulo Bluetooth v.4 BLE HC-10 / AT-09/ CC41A / MLT-BT05
◊ 1.3.5 Módulo WIFI ESP-01 basado en ESP8266
⋅ 1.3.5.1 Actualizar firmware ESP-01
♦ 1.4 Enlaces Arduino
Arduino
Datasheet ATMega328
Códigos ejemplo
Pins E/S
Salidas básicas Arduino
Corresponde al programa blink.ino del ejemplo en que se configura como salida el pin 13 de la placa Arduino, y se cambia el valor de la salida
alternativamente cada segundo. El pin 13 esta conectado a un LED.
void setup() {
pinMode(13,OUTPUT); // Configura el pin 13 como salida. Si no se configura no funcionaria como salida
}
void loop() {
digitalWrite(13, HIGH); // pone el led a 1
delay(1000); // espera un segundo
digitalWrite(13, LOW); // pone el led a 0
delay(1000); // espera un segundo
}
Equivalente al anterior pero modificando los registro del microcontrolador ATmega. (Registros en C:\Program Files
(x86)\Arduino\hardware\tools\avr\avr\include\avr\iom328p.h)
void setup() {
DDRB |= (1<<DDB5); // Pone a 1 el bit DDB5 del registro DDRB, ya que el pin 13 esta conectado a la salida PB5 del ATmega328 (PB7 en ATmega25
}
void loop() {
PORTB |= (1<<PORTB5); // Pone a uno el bit PORTB5 del registro PORTB que corresponde a la salida PB5
delay(1000); // espera un segundo
PORTB &= (0xFE<<PORTB5); // Pone a cero la salida PB5
delay(1000); // espera un segundo
}
Lee el estado del pin 7, y lo escribe en el pin 13 que esta conectado a un led.
int a;
void setup() {
pinMode(13,OUTPUT); //Configura pin 13 como salida
pinMode(7,INPUT); //Configura pin 7 como entrada
}
void loop() {
a=digitalRead(7); //Lee el pin 7
digitalWrite(13, a); //Escribe en el pin 13 la lectura del pin 7
}
I2C
I2C: Dispositivo Maestro envía texto y pide respuesta a dos dispositivos esclavos
//Placa Arduino UNO y NANO
//I2C SDA: AN4, SCL:AN5
#include <Wire.h>
String rep="";
void setup() {
Wire.begin();
Serial.begin(9600);
Serial.println("Inici Mestre");
}
void loop() {
Wire.beginTransmission(0b0100); // transmite al dispositivo esclavo 4
Wire.write("M4"); //Envia "M4" al dispositivo esclavo 4 la sin esperar respuesta
Wire.endTransmission(); // para transmisión (no necesario si se sigue transmitiendo al mismo dispositivo)
delay(1000);
rep="";
Wire.requestFrom(4,6); //Solicita 6 bytes al dispositivo esclavo 4
while (Wire.available()) // espera a recibir todos los caracteres
{
char c = Wire.read(); // recibe byte a byte como carácter
rep+=c;
}
Serial.println(rep);
delay(200);
Wire.beginTransmission(8); // transmite al dispositivo esclavo 8
Wire.write("M8"); //Envía "M8" al dispositivo esclavo 8 sin esperar respuesta
Wire.endTransmission(); // para transmisión
delay(1000);
rep="";
Wire.requestFrom(8,6); //Solicita 6 bytes al dispositivo esclavo 8
while (Wire.available()) // Mientra haya datos a recibir
{
char c = Wire.read(); // recibe un byte como carácter
rep+=c;
}
Serial.println(rep);
delay(200);
}
I2C: Dispositivo Esclavo recibe texto y responde cuando el máster pide respuesta
//Placa Arduino UNO y NANO
//I2C SDA: AN4, SCL:AN5
#include <Wire.h>
String rebut="";
int incrM4=0;
void setup() {
Wire.begin(4); // Configura dirección 4 como esclavo I2C
Wire.onReceive(recibeEvento); // configura el evento de recibir texto
Wire.onRequest(requestEvento); // configura el evento de pedir respuesta
Serial.begin(9600);
Serial.println("Inici Esclau 4");
}
void loop() {
// No hace nada en el loop(). Solo por eventos
}
void recibeEvento(int byterecibe)
{ // Lee información recibida del maestro sin enviar respuesta y comprueba si es "M4"
rebut="";
while (Wire.available()>=1)
{
char c=Wire.read();
rebut +=c;
}
if (rebut=="M4")
{
Serial.print(rebut);
Serial.print(" ");
Serial.println(incrM4);
incrM4++;
}
}
void requestEvento()
{ //Responde al requerimiento de respuesta del máster de 6 bytes
Wire.write("hello4");
}
SPI
SPI: Maestro envía datos por SPI
//Arduino Uno
//SPI: 10 (SS), 11 (MOSI), 12 (MISO), 13 (SCK).
#include <SPI.h>
void setup (void)
{
Serial.begin(115200);
digitalWrite(SS, HIGH);
SPI.begin ();
SPI.setClockDivider(SPI_CLOCK_DIV8);
}
void loop (void)
{
char c;
digitalWrite(SS, LOW); // Activa el esclavo SPI destino, SS es pin 10 en Arduino Uno
// Envia "Hola, mundo\n" por SPI, carácter a carácter
for (const char * p = "Hola, mundo!\n" ; c = *p; p++) {
SPI.transfer (c);
Serial.print(c);
}
digitalWrite(SS, HIGH); // Desactiva el esclavo SPI destino
delay (1000);
}
Aprovecha las librerias SPI de Arduino, pero utiliza instrucciones de AVR para modo esclavo
//Arduino Uno
//SPI: 10 (SS), 11 (MOSI), 12 (MISO), 13 (SCK).
#include <SPI.h>
char buf [100];
volatile byte pos;
volatile boolean fin_recepcion;
void setup (void)
{
Serial.begin (9600);
pinMode(SS,INPUT);//Configura pins del esclavo. SS corresponde al pin 10 en Uno
pinMode(MISO, OUTPUT);
//Las instrucciones básicas de Arduino no permiten modo esclavo
SPCR |= _BV(SPE); // Activa el modo esclavo SPI del Atmega328 de Arduino Uno.
pos = 0; // Prepara variables de la interrupción
fin_recepcion = false;
SPI.attachInterrupt(); // Asigna interrupción de recepción del SPI
}
void loop (void)
{
if (fin_recepcion)//Espera fin de recepción para enviar
{
buf [pos] = 0;
Serial.println (buf);
pos = 0;
fin_recepcion = false;
}
}
ISR (SPI_STC_vect)// Rutina de interrupción recepción SPI
{
byte c = SPDR; // lee el byte recibido en el SPI Data Register
if (pos < sizeof buf) // añade el byte al buffer si hay espacio
{
buf [pos++] = c;
if (c == '\n') // Al encontrar '\n' indica fin de recepción
fin_recepcion = true;
}
}
SPI: Esclavo recibe datos por SPI e interrupción y responde un nuevo byte (y AVR)
Aprovecha las librerias SPI de Arduino, pero utiliza instrucciones de AVR para modo esclavo
//Arduino Nano
//SPI: 10 (SS), 11 (MOSI), 12 (MISO), 13 (SCK).
#include "pins_arduino.h"
// what to do with incoming data
byte command = 0;
void setup (void)
{
pinMode(SS,INPUT);
// have to send on master in, *slave out*
pinMode(MISO, OUTPUT);
// turn on SPI in slave mode
SPCR |= _BV(SPE);
// turn on interrupts
SPCR |= _BV(SPIE);
} // end of setup
Serie USART
Serie: Modifica el valor de los pins 12 y 13 mediante puerto serie
La función serialEvent() no corresponde a la interrupción del puerto serie, sino que se ejecuta tras cada ciclo de loop()
//Al recibir el texto "led12\n" o "led13\n" cambia el valor de la salida del
//pin correspondiente (0-->1; 1-->0)
String inString = "";
boolean stringComplete=false;
boolean val13=LOW;
void setup() {
Serial.begin(9600);
pinMode (12,OUTPUT);
pinMode (13,OUTPUT);
}
void loop() {
if (stringComplete) {
inString.trim(); // Elimina el carácter '\n' del final y los caracteres no imprimibles
if (inString=="led12"){
digitalWrite(12,!digitalRead(12));
Serial.print("Led12:");
Serial.println(digitalRead(12));
}
if (inString=="led13"){
val13=!val13;
digitalWrite(13,val13);
Serial.print("Led13:");
Serial.println(val13);
}
inString = "";
stringComplete=false;
}
}
void serialEvent() {
while (Serial.available()) {
char inChar = (char)Serial.read();
inString += inChar;
if (inChar == '\n') {
stringComplete = true;
}
}
}
Serie: (AVR) Recibe un carácter por encuesta (polling), incrementa su valor y lo envía
Equivalente al anterior sin utilizar la librería "Serial" y configurando los registros del AVR. Este código utiliza un 20% de la memoria del código en que
se utiliza la librería.
#include <avr/io.h>
#define USART_BAUDRATE 9600
uint8_t TempData;
void setup() {
IniciaUSART0();
}
void loop() {
// Receive data
TempData = USART0RecibeByte();
// Increment received data
TempData++;
//Send back to terminal
USART0EnviaByte(TempData);
}
void IniciaUSART0()
{
// Configura velocidad baudios. F_CPU Arduino Uno = 16000000 (16MHz)
unsigned int UBRR_VALUE = (((F_CPU / (USART_BAUDRATE * 16UL))) - 1);
UBRR0H = (uint8_t)(UBRR_VALUE>>8);
UBRR0L = (uint8_t)UBRR_VALUE;
// UCSR0A= x0xxxx0x, UCSR0B=00011000, UCSR0C=00000110
// Configura la comunicación a 8 data bits (UCSZ02=0,UCSZ01=1, UCSZ00=1
// no parity, 1 stop bit, usart asíncrona
UCSR0A = 0;
UCSR0B = 0;
UCSR0C = 0;
UCSR0C |= (1<<UCSZ01)|(1<<UCSZ00);
// Habilita la transmisión y la recepción
UCSR0B |= (1<<RXEN0)|(1<<TXEN0);
}
void USART0EnviaByte(uint8_t Datou8)
{
// Espera a que se haya enviado el byte previo, comprobando la activación del bit
// de registro de datos vacío (UDRE0) del UCSR0A
while(!(UCSR0A&(1<<UDRE0))){};
// Al vaciarse, envia el byte
UDR0 = Datou8;
}
uint8_t USART0RecibeByte()
{
// Espera a que se haya recibido un byte en el registro de datos (UDR0), comprobando
// la activación del bit de recepción completa (RXC0) del UCSR0A
while(!(UCSR0A&(1<<RXC0))){};
// Devuelve la lectura del registro de datos
return UDR0;
}
Serie: (AVR) Recibe string por el puerto serie, y lo vuelve a enviar añadiendo CRLF (por polling)
Lee carácteres por el puerto serie hasta encontrar el carácter indicado o TIMEOUT, lo graba en un string tipo array de char y vuelve a enviarlo por el
puerto serie añadiendo los carácteres de fin de linea CRLF (\r\n)
#include <avr/io.h>
#define USART_BAUDRATE 9600
#define TIMEOUTS 5000
char dadarep[250];
char finlinea[3]={'\r','\n','\0'};
void setup() {
IniciaUSART0();
}
void loop() {
leeseriehasta(dadarep,';');
escribeserie(dadarep);
escribeserie(finlinea);
// Al no usar interrupciones, se podria mezclar el uso del puerto serie mediante los registros con las funciones Serial de Arduino
// Serial.print("Escribe nuevo string:");
}
void IniciaUSART0()
{
unsigned int UBRR_VALUE = (((F_CPU / (USART_BAUDRATE * 16UL))) - 1);
UBRR0H = (uint8_t)(UBRR_VALUE>>8);
UBRR0L = (uint8_t)UBRR_VALUE;
// UCSR0A= x0xxxx0x, UCSR0B=00011000, UCSR0C=00000110
UCSR0A = 0;
UCSR0B = 0;
UCSR0C = 0;
UCSR0C |= (1<<UCSZ01)|(1<<UCSZ00);
UCSR0B |= (1<<RXEN0)|(1<<TXEN0);
}
int leeseriehasta(char datolee[], char caracter){
unsigned long toutini;
int ind=0;
char leebyte;
toutini=millis();
do
{
if (UCSR0A&(1<<RXC0)){
leebyte=UDR0;
datolee[ind]=leebyte;
ind++;
}
} while(((millis()-toutini)<TIMEOUTS) & (leebyte!=caracter));
datolee[ind]='\0';
return ind;
}
int escribeserie(char datoescribe[]){
unsigned long toutini;
int ind=0;
toutini=millis();
char lee;
do
{
lee=datoescribe[ind];
if (lee!='\0'){
if (UCSR0A&(1<<UDRE0)){
UDR0=lee;
ind++;
}
}
} while (((millis()-toutini)<TIMEOUTS) & (lee!='\0'));
return ind;
}
Serie: (AVR) Recibe string por el puerto serie por interrupción, añade los caracteres a un buffer, y lee el buffer por partes indicando el
número de bytes a leer
Lee carácteres por el puerto serie por interrupcion y lee el buffer de 3 en 3 bytes (o menos). La cantidad de bytes de contiene el buffer se puede leer en
la variable indserbuf
#include <avr/io.h>
#define USART_BAUDRATE 9600
#define TIMEOUTS 5000
#define SERBUFSIZE 10 //Tamaño máximo del buffer
char serbuf[SERBUFSIZE + 1]; //Crea el buffer con un carácter más, para el carácter null ('\0') de final
int indserbuf = 0; //Indice del buffer. Corresponde al tamaño acual del buffer
char finlinea[3] = {'\r', '\n', '\0'}; // Array de fin de linea con CR LF
char lectbuf[20];
String a;
char ap[5];
int bt;
void setup() {
IniciaUSART0();
}
void loop() {
escribeserie("buffer_actual: ");
escribeserie(serbuf); // Escribe el contenido del buffer por el puerto serie
escribeserie(finlinea);
escribeserie("tamanyo_buffer: ");
a = String(indserbuf, DEC); //Convierte el tamaño del buffer a String
a.toCharArray(ap, 5); //Convierte el String con el tamaño del buffer a string de array de char
escribeserie(ap); //Escribe el valor del tamaño del buffer por el puerto serie
escribeserie(finlinea);
escribeserie("lectura_buffer: ");
bt = leeseriebuffer(lectbuf, 3); //Lee 3 bytes del buffer y los bytes leidos
escribeserie(lectbuf); //Escribe los carácteres leidos
escribeserie(finlinea);
escribeserie("tamanyo_leido: ");
a = String(bt, DEC); //Convierte el valor de los bytes leidos
a.toCharArray(ap, 5);
escribeserie(ap); //Escribe el valor de los bytes leidos
escribeserie(finlinea);
delay(2000);
}
int leeseriebuffer(char lectura[], int nbytes) {
UCSR0B |= (0 << RXCIE0)| (0 << RXCIE0); // Deshabilita la recepción de nuevos carácteres
int byteslee;
if (nbytes >= indserbuf) { // Si el valor de los bytes pedidos a leer es mayor o igual al tamaño
byteslee = indserbuf; // actual de buffer, devuelve todo el buffer
for (int i = 0; i <= indserbuf; i++) {
lectura[i] = serbuf[i];
}
indserbuf = 0;
serbuf[0] = {0}; //serbuf[0]='\0' // Borra el buffer completo
UCSR0B |= (1 << RXCIE0)| (1 << RXCIE0); // Habilita la recepción de nuevos carácteres
return byteslee;
}
if (nbytes < indserbuf) { // Si el valor de los bytes pedidos a leer es mayor o igual al tamaño
byteslee = nbytes; // actual de buffer, devuelve los carácteres solicitados
for (int i = 0; i < nbytes; i++) {
lectura[i] = serbuf[i];
}
for(int i=nbytes; i<=SERBUFSIZE; i++) { // Elimina los carácteres leidos y desplaza todos
serbuf[i-nbytes] = serbuf[i]; // los carácteres del array el mismo número de bytes leidos
}
indserbuf-=nbytes;
UCSR0B |= (1 << RXCIE0)| (1 << RXCIE0); // Habilita la recepción de nuevos carácteres
return nbytes;
}
UCSR0B |= (1 << RXCIE0)| (1 << RXCIE0); // Habilita la recepción de nuevos carácteres
return 0;
}
ISR(USART_RX_vect) { // Interrupción de recepción por puerto serie. USART Rx Complete UCSR0B=1xx1xxxx
if (indserbuf < SERBUFSIZE) { // Si el buffer no esta lleno, lee y añade el caracter recibido
while (!(UCSR0A & (1 << RXC0))) {};
serbuf[indserbuf] = UDR0;
indserbuf++;
serbuf[indserbuf] = '\0'; // Añade el fin de array de char al final del array
}
}
void IniciaUSART0()
{
// Configura velocidad baudios. F_CPU Arduino Uno = 16000000 (16MHz)
unsigned int UBRR_VALUE = (((F_CPU / (USART_BAUDRATE * 16UL))) - 1);
UBRR0H = (uint8_t)(UBRR_VALUE >> 8);
UBRR0L = (uint8_t)UBRR_VALUE;
// UCSR0A= x0xxxx0x, UCSR0B=10011000, UCSR0C=00000110
// Configura la comuniación a 8 data bits (UCSZ02=0,UCSZ01=1, UCSZ00=1
// no parity, 1 stop bit, usart asíncrona
UCSR0A = 0;
UCSR0B = 0;
UCSR0C = 0;
UCSR0C |= (1 << UCSZ01) | (1 << UCSZ00);
// Habilita la transmisión, la recepción y la interrupción de recepción
UCSR0B |= (1 << RXEN0) | (1 << TXEN0) | (1 << RXCIE0);
}
int escribeserie(char datoescribe[]) {
unsigned long toutini;
int ind = 0;
toutini = millis();
char lee;
do
{
lee = datoescribe[ind];
if (lee != '\0') {
if (UCSR0A & (1 << UDRE0)) {
UDR0 = lee;
ind++;
}
}
} while (((millis() - toutini) < TIMEOUTS) & (lee != '\0'));
return ind;
}
TIMERS
Timers: (AVR) Utiliza la interrupción del Timer 1 (16bits) para parpadear un Led cada 5 segundos
El Timer 1 es de 16 bits. Cuidado si utilizamos la libreria Servo de Arduino, ya que utiliza el Timer 1. Los timers 3, 4 y 5 del ATmega2560 funcionan de
forma similar al Timer 1
// Arduino Atmega328 interrupción timer1. www.engblaze.com
// Añadir librerias propias de microntroladores AVR como el Atmega328
// Valores de registros en: ...\Arduino\hardware\tools\avr\avr\include\avr\io.h
// y ...\Arduino\hardware\tools\avr\avr\include\avr\iom328p
// Limites teoricos tiempo para 16MHz:
// min. 62.5ns OCR1A=1, prescaler=1 (teorico); max. 4.194304s OCR1A=65535, prescaler=1024
#include <avr/io.h>
#include <avr/interrupt.h>
#define LEDPIN 13
int segundos;
void setup()
{
pinMode(LEDPIN, OUTPUT);
// Initialización Timer1 (16bits) (Cuidado, utilizado por libreria Arduino de servo)
cli(); // deshabilita interrupcions
TCCR1A = 0; // WGM11=0, WGM10=0, resto a 0
TCCR1B = 0; // Inicializamos el registro de control B
// Activa modo CTC(Clear Timer on Compare); WGM13=0,WGM12=1,WGM11=0, WGM10=0, para comparar con OCR1A
TCCR1B |= (1 << WGM12);
// CS12=1, CS11=0, CS10=1 para 1024 prescaler
TCCR1B |= (1 << CS10);
TCCR1B |= (1 << CS12);
// indica el valor del contador del timer para comparar y conseguir un timer de 1 segundo
// F=16MHz --> T=0.0000000625s= 625x10^-10
// resolución timer = prescaler * T =1024*625x10^-10 = 64x10-6
// tiempo = resolución timer * (valor contador timer + 1)
// valor contador timer = (tiempo/resolución timer)-1 = (1s/64x10-6)-1= 15624
OCR1A = 15624; //OCR1A del timer 1 es de 16 bits ==> valor máx. 65535
// Activa la interrupción para que se ejecute al comparar con el valor del registro OCR1A
TIMSK1 |= (1 << OCIE1A);
// Activa interrupciones
sei();
}
void loop()
{
// No hace nada ya que solo funciona la interrupción
}
ISR(TIMER1_COMPA_vect)
{
segundos++;
if (segundos == 5)
{
segundos = 0;
digitalWrite(LEDPIN, !digitalRead(LEDPIN));
}
}
Timers: (AVR) Utiliza la interrupción del Timer 2 (8bits) para parpadear un Led cada 1 segundo
Similar al timer1, pero el timer 2 es de 8 bits. El timer 2 es utilizado en la función tone() de Arduino. El timer 0, también de 8 bits, funciona igual que el
timer 2 pero las instrucciones delay(), millis(), micros() utilizan el timer 0 para su funcionamiento.
// Arduino Atmega328 interrupción timer2.
// Añadir librerias propias de microntroladores AVR como el Atmega328
// Valores de registros en: ...\Arduino\hardware\tools\avr\avr\include\avr\io.h
// y ...\Arduino\hardware\tools\avr\avr\include\avr\iom328p
// Limites teoricos timer 2 tiempo para 16MHz:
// min. 62.5ns OCR2A=1, prescaler=1 (teórico); max. 16.384ms OCR2A=255, prescaler=1024
#include <avr/io.h>
#include <avr/interrupt.h>
#define LEDPIN 13
int ms8;
void setup()
{
pinMode(LEDPIN, OUTPUT);
// Inicialización Timer1 (16bits) (Cuidado, utilizado por librería Arduino de servo)
cli(); // deshabilita interrupciones
TCCR2A = 0; // Inicializamos el registro de control A
TCCR2B = 0; // Inicializamos el registro de control B
// Activa modo CTC(Clear Timer on Compare); WGM22=0,WGM21=1,WGM20=0, para comparar con OCR2A
TCCR2A |= (1 << WGM21);
// CS22=1, CS21=1, CS20=1 para 1024 prescaler
TCCR2B |= (1 << CS20);
TCCR2B |= (1 << CS21);
TCCR2B |= (1 << CS22);
// indica el valor del contador del timer para comparar y conseguir un timer de 8ms (8ms*125=1s)
// F=16MHz --> T=0.0000000625s= 625x10^-10
// resolución timer = prescaler * T =1024*625x10^-10 = 64x10-6
// tiempo = resolución timer * (valor contador timer + 1)
// valor contador timer = (tiempo/resolución timer)-1 = (8ms/64x10-6)-1= 124
OCR2A = 124; //OCR2A del timer 2 es de 8 bits
// Activa la interrupción para que se ejecute al comparar con el valor del registro OCR2A
TIMSK2 |= (1 << OCIE2A);
// Activa interrupciones
sei();
}
void loop()
{
// No hace nada ya que solo se utiliza la interrupción
}
ISR(TIMER2_COMPA_vect)
{
ms8++;
if (ms8 == 125) //(8ms*125=1s)
{
ms8 = 0;
digitalWrite(LEDPIN, !digitalRead(LEDPIN));
}
}
PWM
Los Timers de Arduino tienen la posibilidad de trabajar como PWM. Cada timer en ATmega328 tiene 2 salidas PWM, y en ATmega2560 los timers 0 y 2
tienen 2 salidas PWM, y los timers 1,3,4 y 5 tienen 3 salidas PWM. Hay que tener en cuenta que si utilizamos el modo PWM, y queremos utilizar el
timer simultáneamente, la frecuencia del PWM dependerá del valor del timer configurado, además de otras limitaciones.
http://www.righto.com/2009/07/secrets-of-arduino-pwm.html
Como que el Timer0 lo utiliza Arduino para calcular el tiempo de las instrucciones de tiempo como delay(), utilizando las instrucciones de referencia de
Arduino no hay problema para utilizar las salidas PWM del timer 0, como son los pines 5 y 6, simultaneamente con las instrucciones como delay(),
millis(), etc...
void setup() {
pinMode(6,OUTPUT);
pinMode(13,OUTPUT);
analogWrite(6,127); //La frecuencia del PWM en el pin6 de Arduino Uno es de 980Hz
}
void loop() {
digitalWrite(13, HIGH);
delay(1000);
digitalWrite(13, LOW);
delay(1000);
}
Si utilizamos el PWM del Timer0, hay que tener en cuenta de que para que funcionen correctamente las instrucciones de tiempo de Arduino como
delay(), el Timer0 está configurado con un preescalado por defecto de 64, con lo que si modificamos el preescalado en el registro TCCR0B, el tiempo
de las instrucciones delay(), millis(),etc... quedará modificado.
//Utilizaremos la salida OC0A del Timer 0 (pin6)
void setup() {
pinMode(13,OUTPUT);
pinMode(6,OUTPUT); //Configura salida OC0A
// Deshabilita OC0B (COM0B1=0, COM0B0=0), configura OC0A modo no invertido (HIGH antes, LOW después comparación OCR0A)
// (COM0A1=1, COM0A0=0), Configura Fast PWM (TOP=0xFF=256) (WGM02=0,WGM01=1,WGM00=1)
TCCR0A = _BV(COM0A1) | _BV(WGM01) | _BV(WGM00);
// Configura prescaler 64 (CS02=0,CS01=1,CS00=1) --> Frecuencia PWM= 16MHZ/64/256=980Hz
TCCR0B = _BV(CS01)|_BV(CS20);
//Si modificaramos el preescaler a otro valor, por ejemplo 256 (CS02=1,CS01=0,CS00=0),
//la frecuencia disminuiria a 16MHZ/256/256=245Hz, con lo que delay(1000) en lugar de realizar
//un retardo de 1 segundo, realizaria un retardo de 4 segundos.(TCCR0B = _BV(CS02);)
//Valor OCR0A=((256*duty)/100)-1. Ej. Duty cycle=50%-->OCR0A=127
OCR0A=(uint8_t)(((256*50)/100)-1);
}
void loop() {
digitalWrite(13, HIGH);
delay(1000);
digitalWrite(13, LOW);
delay(1000);
}
El timer 1 es de 16 bits y tiene más modos de funcionamiento que los timers 0 y 2 de 8 bits.
//El Timer1 es de 16 bits, pero como Fast PWM puede trabajar con 8, 9 o 10 bits.
//Utilizaremos la salida OC1A del Timer 1 (pin9)
void setup() {
pinMode(9,OUTPUT); //Configura salida OC1A
// Deshabilita OC1B (COM1B1=0, COM1B0=0), configura OC1A modo no invertido (HIGH antes, LOW después comparación OCR0A)
// (COM1A1=1, COM1A0=0), Configura Fast PWM 10bits (TOP=0x03FF=1024) (WGM13=0,WGM12=1,WGM11=1,WGM10=1)
TCCR1A = _BV(COM1A1) | _BV(WGM11)| _BV(WGM10);
// Configura prescaler 64 (CS12=0,CS11=1,CS10=1) --> Frecuencia PWM= 16MHZ/64/1024=244Hz
TCCR1B = _BV(CS10) |_BV(CS11) | _BV(WGM12);
//Valor OCR1A=((1024*duty)/100)-1. Ej. Duty cycle=50%-->OCR1A=512
OCR1A=(uint16_t)((((uint16_t)1024*50)/100)-1);
}
void loop() {
}
Contadores ATmega
Arduino no dispone de funciones para utilizar los contadores, ya que al ser comunes con loos temporizadores, no se podrían utilizar instrucciones como
delay() o el PWM, ya que utilizan el temporizador 0. Además la placas Arduino no proporcionan acceso a todos los pines de contaje externo, con lo que
estamos limitados en el uso de contadores. Por ejemplo, la placa Arduino UNO, si que tiene acceso a la entrada de contador T0 del pin del ATmega328
PD4 que corresponde al pin de la placa 4, y a T1 del pin del ATmega328 PD5 que corresponde a la entrada 5 de la placa. Sin embargo, en la placa
Arduino Mega 2560 solo podemos acceder a T0 del pin del ATmega2560 PD7 de la entrada de la placa 38, y a T5 del pin del ATmega2560 PL2 de la
entrada de la placa 47. No hay acceso ni a T1-PD6, ni T3-PE6, ni T4-PH7.
Utilizaremos la placa Arduino Mega 2560 y el contador 5 de 16 bits, dejando libre el temporizador 0 para el uso de la instrucción delay(). La entrada T5
corresponde a la entrada 47 de la placa y al pin PL2. Es importante tener en cuenta que el contador es muy sensible a los rebotes ya que los cuenta,
con lo que es recomendable poner un circuito antirrebotes.
void setup() {
Serial.begin(9600);
// Configura como entrada PL2 - T5
DDRL &= ~(1<<DDL2); // Pone a 0 el bit DDL2 del registro DDRL, ya que T5 corresponde al pin 47 que esta conectado a la entrada PL2 en ATmega
PORTL |= (1 << PORTL2); // Pone a 1 el pin PL2 para activar pull-up y evitar cortocircuitos internos.
// WGM53=0, WGM52=0, WGM51=0, WGM50=0. Normal mode TOP 0xFFFF
TCCR5A = 0x0000; //WGM51=0, WGM50=0
// Activa el contador 5 con contaje por flanco de subida del pin externo T5 (PL2, entrada 47)
TCCR5B |= (1 << CS52) | (1 << CS51) | (1 << CS50); // WGM53=0, WGM52=0
}
void loop() {
Serial.println(TCNT5); //Envia por el puerto serie el valor del contador.
delay(1000);
}
La interrupción por sobrepasamiento TOI, se activa solo al sobrepasar del máximo 65535, con lo que no se puede activar limitando el valor de contaje a
OCRnA. En este ejemplo se configura para que salte la interrupción al sobrepasar 65535
void setup() {
Serial.begin(9600);
// Configura como entrada PL2 - T5
DDRL &= ~(1<<DDL2);
PORTL |= (1 << PORTL2);
// WGM53=0, WGM52=0, WGM51=0, WGM50=0. Normal mode TOP 0xFFFF
TCCR5A = 0x0000; //WGM51=0, WGM50=0
// Activa el contador 5 con contaje por flanco de subida del pin externo T5 (PL2, entrada 47)
TCCR5B |= (1 << CS52) | (1 << CS51) | (1 << CS50); // WGM53=0, WGM52=0
cli(); // Deshabilita interrupciones;
TIMSK5 |= (1 << TOIE5); // Habilita interrupción de Timer por sobrepasamiento
sei(); // Habilita interrupciones;
}
void loop() {
if (TCNT5<65520)
{
TCNT5=65520; //Para no esperar, ponemos a 65520 (0xFFF0) para solo contar 15 hasta sobrepasar
}
Serial.println(TCNT5); //Envia por el puerto serie el valor del contador.
delay(1000);
}
Activa un contador por registros y activa interrupción al sobrepasar el valor de OCR5A
La interrupción por comparación (Output Compare A Match Interrupt), se activa solo al sobrepasar el valor TOP indicado en el registro OCRnA. En este
ejemplo se configura para que salte la interrupción al sobrepasar de 15 (0x000F) el contador 5.
int sobre=0;
void setup() {
Serial.begin(9600);
DDRL &= ~(1<<DDL2);
PORTL |= (1 << PORTL2);
// WGM53=0, WGM52=1, WGM51=0, WGM50=0. CTC modo TOP OCR5A
TCCR5A = 0x0000; //WGM51=0, WMM50=0
// Activa el contador 5 con contaje por flanco de subida del pin externo T5 (PL2, entrada 47)
TCCR5B |= (1<<WGM52)|(1 << CS52) | (1 << CS51) | (1 << CS50); // WMGM53=0, WGM52=1
OCR5A=0x000F; // Valor de comparación
cli(); // Deshabilita interrupciones;
TIMSK5 |= (1 << OCIE5A); // Habilita interrupción de Timer por sobrepasamiento de OCR5A
sei(); // Habilita interrupciones;
TCNT5=0;
}
void loop() {
Serial.println(TCNT5);
delay(1000);
}
ISR (TIMER5_COMPA_vect) //Cuando sobrepasa OCR5A salta la interrupción
{
sobre++;
Serial.print("Ha sobrepasado: ");
Serial.print(sobre,DEC);
Serial.println(" veces");
}
Modo Sleep
El modo Sleep o modo de bajo consumo, es el modo que permite disminuir el consumo en dispositivos Arduino y así conseguir que las baterias duren
más tiempo. En este modo podemos apagar varios periféricos que no utilizemos, y enviamos a Arduino a "dormir" con lo que reducimos el consumo. En
función del modo Sleep configurado, existen varias formas de volver a despertar. En la siguiente tabla se muestra en cada modo, los relojes y
osciladores que se pueden mantener activos, y los métodos de despertar:
Los modos Sleep en función del consumo aproximado de más a menos son:
• SLEEP_MODE_IDLE: 15 mA (ahorra poco debido a que puede mantener muchas funciones de los periféricos)
• SLEEP_MODE_ADC: 6.5 mA
• SLEEP_MODE_PWR_SAVE: 1.62 mA
• SLEEP_MODE_EXT_STANDBY: 1.62 mA
• SLEEP_MODE_STANDBY : 0.84 mA
• SLEEP_MODE_PWR_DOWN : 0.84 mA
Las instrucciones que proporciona Arduino mediante la libreria sleep.h (#include <avr/sleep.h>) (C:\Program Files
(x86)\Arduino\hardware\tools\avr\avr\include\avr\sleep.h) son:
• set_sleep_mode(modo): esta funciona establece el modo de bajo consumo, el parámetro modo puede tomar el valor de los nombres de los
modos que hemos indicado anteriormente.
• sleep_enable(): habilita el sistema para ponerse en modo bajo consumo pero sin activarlo.
• sleep_cpu(): activa el modo de bajo consumo
• sleep_disable(): primera instrucción que debe aparecer cuando se vuelve del modo bajo consumo. deshabilita el modo bajo consumo.
• sleep_mode(): activa el modo bajo consumo pero incluye de una sola vez sleep_enable() + sleep_cpu() + sleep_disable(). En algunos casos
puede dar problemas y es mejor ejecutar las tres instrucciones de forma separada en lugar de sleep_mode().
Realiza intermitencias de 1 segundo en el led conectado al pin 13 (similar al Blink), pero utilizando el modo Sleep. Apaga todos los periferidos excepto
el Timer2, y activa el modo Sleep Power-Save. El Timer 2 despierta el microcontrolador cada 8ms y vuelve a dormir, excepto cada segundos (8ms*125)
en que cambia el estado del led y vuelve a dormir.
Excepto en la interrupción temporizada 2 que no la proporcionan las instrucciones básicas de Arduino, se intentan utilizar el máximo de instrucciones y
librerias propias de Arduino.
#include <avr/interrupt.h>
#include <avr/sleep.h>
#include <avr/power.h>
int ms8;
void setup() {
pinMode(13,OUTPUT);
configura_timer2_8ms();
ADCSRA=0; //Apagamos todo el ADC
//Apagamos todos los periféricos poniendo el bit correspondiente a 1 en PRR, excepto el Timer2
power_twi_disable(); //PRR|=(1<<PRTWI);
power_timer0_disable(); //PRR|=(1<<PRTIM0);
power_timer1_disable(); //PRR|=(1<<PRTIM1);
power_spi_disable(); //PRR|=(1<<PRSPI);
power_usart0_disable(); //PRR|=(1<<PRUSART0);
power_adc_disable(); //PRR|=(1<<PRADC);
power_timer2_enable(); //Activa Timer2
set_sleep_mode(SLEEP_MODE_PWR_SAVE); //Modo Sleep Power-Save (registro SMCR atmega SM2=0,SM1=1,SM0=1)
}
void loop() {
// Activa interrupciones
sei();
sleep_enable(); //Habilita modo Sleep SE=1
while (ms8<125) //Mientras ms8<125 no habrá pasado el tiempo, y estará durmiendo y despertandose.
{
sleep_cpu(); //Activa Sleep
//Tras volver del modo Sleep, continua desde aqui
sleep_disable();//Deshabilita modo Sleep SE=0
}
cli(); //Deshabilita interrupciones
ms8=0;
digitalWrite(13,!digitalRead(13));
}
ISR(TIMER2_COMPA_vect)
{
ms8++;
}
void configura_timer2_8ms()
{
TCCR2A = 0; // Inicializamos el registro de control A
TCCR2B = 0; // Inicializamos el registro de control B
// Activa modo CTC(Clear Timer on Compare); WGM22=0,WGM21=1,WGM20=0, para comparar con OCR2A
TCCR2A |= (1 << WGM21);
// CS22=1, CS21=1, CS20=1 para 1024 prescaler
TCCR2B |= (1 << CS20);
TCCR2B |= (1 << CS21);
TCCR2B |= (1 << CS22);
// indica el valor del contador del timer para comparar y conseguir un timer de 8ms (8ms*125=1s)
// F=16MHz --> T=0.0000000625s= 625x10^-10
// resolución timer = prescaler * T =1024*625x10^-10 = 64x10-6
// tiempo = resolución timer * (valor contador timer + 1)
// valor contador timer = (tiempo/resolución timer)-1 = (8ms/64x10-6)-1= 124
OCR2A = 124; //OCR2A del timer 2 es de 8 bits
// Activa la interrupción para que se ejecute al comparar con el valor del registro OCR2A
TIMSK2 |= (1 << OCIE2A);
}
Intermitente de 1 segundo en modo Sleep e interrupción Timer 2 mediante AVR y ASM ATmega328
Equivalente al anterior pero reduciendo al mínimo las instrucciones y librerias propias de Arduino, y utilizando instrucciones en ensamblador
directamente.
int ms8;
void setup() {
pinMode(13,OUTPUT);
configura_timer2_8ms();
ADCSRA=0; //Apagamos todo el ADC
//Apagamos todos los periféricos poniendo el bit correspondiente a 1 en PRR, excepto el Timer2
//PRR-> 7:PRTWI | 6:PRTIM2 | 5:PRTIM0 | 4:RESERV | 3:PRTIM1 | 2:PRSPI | 1:PRUSART0 | 0:PRADC
PRR=0;
PRR|=(1<<PRTWI);
PRR|=(1<<PRTIM0);
PRR|=(1<<PRTIM1);
PRR|=(1<<PRSPI);
PRR|=(1<<PRUSART0);
PRR|=(1<<PRADC);
//Modo Sleep Power-Save (registro SMCR atmega SM2=0,SM1=1,SM0=1)
SMCR=0;
SMCR|=(1<<SM1);
SMCR|=(1<<SM0);
}
void loop() {
__asm__ __volatile__ ("sei" ::: "memory"); //sei(); // Activa interrupciones
SMCR|=(1<<SE); //sleep_enable(); Habilita modo Sleep SE=1
while (ms8<125) //Mientras ms8<125 no habrá pasado el tiempo, y estará durmiendo y despertandose.
{
__asm__ __volatile__ ( "sleep" "\n\t" :: ); //sleep_cpu(); //Activa modo Sleep
//Tras volver del modo Sleep, continua desde aquí
SMCR &= 0xFE; //sleep_disable(); Deshabilita modo Sleep SE=0
}
__asm__ __volatile__ ("cli" ::: "memory");// cli(); //Deshabilita interrupciones
ms8=0;
digitalWrite(13,!digitalRead(13));
}
ISR(TIMER2_COMPA_vect)
{
ms8++;
}
void configura_timer2_8ms()
{
TCCR2A = 0; // Inicializamos el registro de control A
TCCR2B = 0; // Inicializamos el registro de control B
// Activa modo CTC(Clear Timer on Compare); WGM22=0,WGM21=1,WGM20=0, para comparar con OCR2A
TCCR2A |= (1 << WGM21);
// CS22=1, CS21=1, CS20=1 para 1024 prescaler
TCCR2B |= (1 << CS20);
TCCR2B |= (1 << CS21);
TCCR2B |= (1 << CS22);
// indica el valor del contador del timer para comparar y conseguir un timer de 8ms (8ms*125=1s)
// F=16MHz --> T=0.0000000625s= 625x10^-10
// resolución timer = prescaler * T =1024*625x10^-10 = 64x10-6
// tiempo = resolución timer * (valor contador timer + 1)
// valor contador timer = (tiempo/resolución timer)-1 = (8ms/64x10-6)-1= 124
OCR2A = 124; //OCR2A del timer 2 es de 8 bits
// Activa la interrupción para que se ejecute al comparar con el valor del registro OCR2A
TIMSK2 |= (1 << OCIE2A);
}
En este ejemplo, Arduino está dormido y cada vez que la entrada 2 (INT0) cambia de 0 a 1, se detecta medienta la interrupción 0 y tras 250ms el led
cambia de estado. La entrada 2 se configura como entrada con resistencia Pull-up, con lo que por defecto la entrada está a 1 y hay que poner la
entrada a 0 y quitar el 0 para que realice el cambio. El modo SLEEP_MODE_PWR_DOWN es el que menos energia consume y solo se puede
despertar mediante interrupción externa (INT0 o INT1) por cambio de nivel, o Two-wire Serial Interface (TWI).
#include <avr/sleep.h>
void setup() {
pinMode(2,INPUT_PULLUP);
pinMode(13,OUTPUT);
set_sleep_mode(SLEEP_MODE_PWR_DOWN);
attachInterrupt(0,rutinainterrupcion,HIGH);
}
void loop() {
delay(250);
digitalWrite(13,!digitalRead(13));
sleep_enable();
sleep_cpu();
}
void rutinainterrupcion(){
sleep_disable();
}
Funciones y ejemplos
Strings
Separa String en dos partes separadas por un carácter
int separaDos(String dato,char caracter,String &pres,String &posts){
//Busca el primer caracter en el dato y separa en dos el dato.
//Antes de el caracter lo devuelve en pres y despues del caracter en post
//Si no lo encuentra, devuelve -1, si lo encuentra devuelve la posición del caracter buscado
int i;
i=dato.indexOf(caracter);
if (i>-1){
pres=dato.substring(0,i);
posts=dato.substring(i+1);
}
return i;
}
//Uso: Separar en dos el dato "a10*20b"
// String inString = ""; String pre,post =""; int err;
// err=separaDos(inString,'*',pre,post);
// Quedaria: err=0, pre=a10, post=20b
Este ejemplo envia por el puerto serie unos numeros tipo float, convirtiendolos a char arrays de tamaño 7 con 3 decimales (eee.ddd) y con caracteres
(;) de separacion además del caracter inicial (:). Lo realiza enviando por separado cada caracter y número, y posteriormente los une en un solo char
array utilizando las funciones strcat() y strcpy(), y envia todo con una sola transmisión.
int width=7; int precision=3; //Datos configuracion conversion dtostrf()
float v=2.60 ; float ref=0.5; float u=12.893;
char refchar[8]; char uchar[8]; char vchar[8]; char unionchar[28];
void setup() {
Serial.begin(9600);
}
void loop() {
Serial.println("Sin unir:");
dtostrf(ref,width,precision,refchar);
dtostrf(u,width,precision,uchar);
dtostrf(v,width,precision,vchar);
Serial.print(":");Serial.print(refchar);
Serial.print(";");Serial.print(uchar);
Serial.print(";");Serial.println(vchar);
delay(1000);
Serial.println("Unidos:");
strcpy(unionchar,":");
strcat(unionchar,refchar);
strcat(unionchar,";");
strcat(unionchar,uchar);
strcat(unionchar,";");
strcat(unionchar,vchar);
Serial.println(unionchar);
delay(1000);
}
Serie
Lee caracteres del puerto serie 0 hasta que encuentra el carácter indicado
Para evitar bloqueos, en la variable TIMEOUTS se indica el tiempo máximo en milisegundos para leer del puerto.
// UCSR0A= x0xxxx0x, UCSR0B=00011000, UCSR0C=00000110
#define TIMEOUTS 5000
int leeseriehasta(char datolee[], char caracter){
unsigned long toutini;
int ind=0;
char leebyte;
toutini=millis(); //Tiempo inicial
do
{
if (UCSR0A&(1<<RXC0)){ // Si hay byte en el registro de entrada lee el byte y lo añade al array
leebyte=UDR0;
datolee[ind]=leebyte;
ind++;
}
} while(((millis()-toutini)<TIMEOUTS) & (leebyte!=caracter)); // Lee el puerto serie hasta que encuentre el carácter o pase el tiempo
datolee[ind]='\0'; // Añade el terminador nulo para indicar final de cadena de carácteres '\0'
return ind; // Devuelve el número de caracteres leídos, incluido el indicado
}
Para evitar bloqueos, en la variable TIMEOUTS se indica el tiempo máximo en milisegundos para leer del puerto
// UCSR0A= x0xxxx0x, UCSR0B=00011000, UCSR0C=00000110
#define TIMEOUTS 5000
int leeseriebytes(char datolee[], int bytes){
unsigned long toutini;
int ind=0;
char leebyte;
toutini=millis();
do
{
if (UCSR0A&(1<<RXC0)){
leebyte=UDR0;
datolee[ind]=leebyte;
ind++;
}
} while(((millis()-toutini)<TIMEOUTS) & (ind<bytes)); // Lee el puerto serie hasta leer la cantidad indicada de caracteres o pase el tiemp
datolee[ind]='\0';
return ind;
}
Para evitar bloqueos, en la variable TIMEOUTS se indica el tiempo máximo en milisegundos para escribir en el puerto
// UCSR0A= x0xxxx0x, UCSR0B=00011000, UCSR0C=00000110
#define TIMEOUTS 5000
int escribeserie(char datoescribe[]){
unsigned long toutini;
int ind=0;
toutini=millis();
char lee;
do
{
lee=datoescribe[ind];
if (lee!='\0'){ // Si el siguiente carácter no es el nulo de fin, envia el carácter
if (UCSR0A&(1<<UDRE0)){
UDR0=lee;
ind++;
}
}
} while (((millis()-toutini)<TIMEOUTS) & (lee!='\0')); // Escribe en el puerto serie hasta detectar el caracter nulo o pase el tiempo
return ind; //Devuelve el número de bytes enviados
}
Hardware
LCD
1602 (2x16)
Lee caracteres del puerto serie hasta encontrar el salto de linea ('\n') y lo envía al LCD. Si se reciben hasta 32 caracteres los muestra separados en dos
lineas de hasta 16 caracteres.
#include <LiquidCrystal.h>
//LCD RW:0v, Vo=0v
//LCD Pins--> RS:12, En:11, D4:5, D5:4, D6:3, D7:2
LiquidCrystal lcd(12, 11, 5, 4, 3, 2);
String inString = "";
boolean stringComplete=false;
String primer16="";
String segon16="";
void setup() {
lcd.begin (16,2);
lcd.print("Esperando texto");
Serial.begin(9600);
Serial.println("\n\nEscribe texto con un máximo de 32 carácteres:");
Serial.println();
}
void loop() {
if (stringComplete) {
Serial.print("Has escrito:");
Serial.println(inString);
inString.trim();
lcd.clear();
lcd.setCursor(0,0);
if (inString.length()<=32)
{
if(inString.length()<=16)
{
lcd.print(inString);
}
else
{
primer16=inString.substring(0,16);
segon16=inString.substring(16);
lcd.setCursor(0,0);
lcd.print(primer16);
lcd.setCursor(0,1);
lcd.print(segon16);
}
}
else
{
lcd.print("Demasiados caracteres");
}
inString = "";
stringComplete=false;
}
}
void serialEvent() {
while (Serial.available()) {
char inChar = (char)Serial.read();
inString += inChar;
if (inChar == '\n') {
stringComplete = true;
}
}
}
Ultrasonidos
HC-SR04
Uso del sensor de ultrasonidos HC-SR04. Para leer la distancia, se envia un pulso de 10us por el pin de trigger, tras los 10us el sensor envia 8 pulsos a
una frecuencia de 40kHz, y tras los pulsos se activa el pin de echo durante el tiempo que tarda en recibir el rebote de los pulsos enviados. El tiempo es
proporcional al doble de la distancia ya que el sonido ha de rebotar y la velocidad del sonido es de 343m/s.
#define HCSR04trigPin 13
#define HCSR04echoPin 12
void setup() {
Serial.begin (9600);
configuraHCSR04();
Serial.println("Sensor ultrasonidos HC-SR04 (2cm-400cm)");
}
void loop() {
long duracion;
float distancia;
duracion=leeHCSR04();
Serial.print("Duracion: ");
Serial.println(duracion);
//En este ejemplo la distancia correcta corresponde a: distancia=duración/50 --> distancia=duracion*0.02 que corresponde
//a un entorno con una velocidad del sonido de 400m/s.
//duración=250 -->5cm, 500-->10cm, 1000-->20cm, 20000-->4m
distancia= duracion*0.02;
if (distancia > 400 || distancia < 2){
Serial.println("Fuera de rango");
}
else {
Serial.print(distancia,4);
Serial.println(" cm");
}
delay(500);
}
void configuraHCSR04(){
pinMode(HCSR04trigPin, OUTPUT);
pinMode(HCSR04echoPin, INPUT);
}
long leeHCSR04(){
//Devuelve el tiempo de respuesta en us
//Distancia teorica corresponde a la mitad de la duración ya que corresponde al tiempo de ida y vuelta que es dos veces la distancia
//La velocidad del sonido es de 343 m/s (a 20 °C de temperatura, con 50 % de humedad y a nivel del mar y ello
//corresponde a 29.155us/cm --> distancia = (duracion/2) / 29.155 --> distancia=duracion/58.31;
//Tiempo de activación 2us+10us+200us=212us
//Distancia máxima 4m --> 20000us duracion
//Tiempo mínimo de adquisición 21ms para 4m, 1.3ms para 20cm, ...
digitalWrite(HCSR04trigPin, LOW);
delayMicroseconds(2);
digitalWrite(HCSR04trigPin, HIGH);
delayMicroseconds(10);
digitalWrite(HCSR04trigPin, LOW);
return (pulseIn(HCSR04echoPin, HIGH));
}
Distancia Infrarrojos
Sharp GP2DY120X y similares
Para la detección de distancia también disponemos de sensores de distancia ópticos, y los más utilizados son de la marca Sharp. Para detectar la
distancia utiliza un emisor y un receptor infrarrojos utilizando un método de triangulación qu consiste en medir el ángulo formado por el triángulo
emisor-objeto-receptor, donde el receptor es un PDS (Position Sensitive Detector) que detecta el punto de incidencia de la luz infrarroja recibida, el cual
depende del ángulo y a su vez de la distancia del objeto. Existen varios modelos, en función del rango de detección. Por ejemplo:
• GP2DY120X de 4 a 30cm
• GP2DY0A21 de 10 a 80cm
• GP2DY0A02 de 20 a 150cm
• GP2DY0A710 de 100 a 550cm
Para el uso con Arduino, utilizaremos una entrada analógica para convertir el voltaje a un valor digital y poder trabajar con él.
La salida del sensor Sharp no es lineal, sino que tiene forma de potencia negativa a partir del rango mínimo del sensor:
Realizamos una prueba con un GP2DY120X, adquiriendo muestras cada centímetro entre 0 y 26 cm (teóricamente 30cm, pero devolvía el mismo valor
entre 26 y 30cm), y adquirimos los siguientes valores:
Al no ser lineal, necesitamos linealizar el valor recibido del sensor, y así tener un valor en centímetros. Para ello tenemos dos posibilidades. Una
aproximación para la zona de mayor pendiente seria mediante la fórmula siguiente formula basada en la ecuación de una recta
(https://acroname.com/articles/linearizing-sharp-ranger-data):
Para una aproximación que abarque la mayoría del rango, utilizamos un modelo potencial del tipo:
Necesitamos calcular los parámetros de las fórmulas mediante un sistema de ecuaciones. Para ahorrarnos el tener de calcular, podemos utilizar un
programa matemático como el wxMaxima y utilizar la opción de resolución de sistemas algebraicos. Para el cálculo de los parámetros de la fórmula de
la división, en wxMaxima utilizaríamos:
algsys([6=(a/(376-b))+c, 15=a/(166-b)+c,10=a/(240-b)+c], [a,b,c]),numer;
En cambio para el modelo de potencia, hay que adaptar la formula mediante propiedades de logaritmos, donde:
En este caso el sistema devolvería los valores de y , con lo que para calcular a habría que calcular . En
wxMaxima utilizaríamos:
algsys([log(6)=al+b*log(376), log(15)=al+b*log(166)], [al,b]),numer;
a:exp(rhs(%[1][1]));
Calculados los parámetros, las formulas quedarían:
Tras adquirir los parámetros de las fórmulas, realizamos el cálculo para cada uno de los datos adquiridos, y comparamos las rectas. Podemos ver
como con las dos fórmulas el cálculo se aproxima al valor real, pero el cálculo mediante la división parece más preciso, seguramente a que se
necesitan 3 parámetro en lugar de 2 para el cálculo.
Si adquirimos directamente valores del sensor, aparecen unos picos de ruido que molestan para utilizar el valor en futuras aplicaciones.Por ello se
recomienda filtrar el ruido, por ejemplo mediante el cálculo del valor medio de las 10 últimos valores del sensor leídos. En la gráfica podemos ver en
azul el valor del sensor sin filtrar, y en rojo el valor filtrado.
En el siguiente ejemplo se muestra un ejemplo de la lectura del sensor aplicando dicho filtrado, y se muestra el valor instantáneo leído, el valor medio
calculado, y los cálculos de la distancia mediante las dos formulas:
int lecturas[10]={0,0,0,0,0,0,0,0,0,0};
int sensorPin = A0; // Entrada del sensor
int sensorValue = 0; // Valor instantáneo leído del sensor
int sensor=0; // Valor filtrado del sensor
float distancia=0; // Distancia calculada
void setup() {
Serial.begin(9600);
}
void loop() {
sensorValue = analogRead(sensorPin); // lectura del valor del sensor
Serial.print(sensorValue);
Serial.print(",");
sensor=filtra(sensorValue,lecturas); // Filtra mediante la media de los 10 últimos valores
Serial.print(sensor);
distancia=(float)(2580/(sensor-4.125))-0.9375; // Calcula distancia mediante división
Serial.print(",");
Serial.print(distancia);
distancia=(float)4615*pow(sensor,-1.1207); // Calcula distancia mediante la potencia
Serial.print(",");
Serial.println(distancia);
}
int filtra(int sensorValue, int vector[]){
int valormedio=0;
vector[9]=sensorValue; // Añade la última lectura del sensor al final del vector
for (int i=0;i<10;i=i+1){ // Calcula la media del vector
valormedio=valormedio+vector[i];
};
valormedio=valormedio/10;
// Desplaza el vector para después añadir la última lectura del sensor al final
for (int i=0;i<9;i=i+1){
vector[i]=vector[i+1];
};
return valormedio;
}
https://naylampmechatronics.com/blog/55_tutorial-sensor-de-distancia-sharp.html
Una vez hemos traspasado el código al Arduino, activaremos el modo AT2 del módulo HC-05. Para ello, teniendo desconectada la alimentación del
módulo, pulsamos el botón cercano al pin KEY o EN del módulo, y manteniendolo pulsado conectamos la alimentación del módulo. Al cabo de unos
segundos, el led del módulo comenzará a parpadear de forma lenta con pulsos de unos 3 segundos, y esto indicará que se encuentra en modo AT de
configuración. El botón conecta el pin 34 del módulo HC-05 a nivel alto (5 o 3.3v)
En caso de querer activar el modo AT1, la velocidad será la configurada en el módulo HC-05 (por defecto es 9600bauds), y simplemente hay que
pulsar, y mantener pulsado, el botón en cualquier momento y cambiará a modo AT1, aunque en este modo el led no modificará su velocidad de
parpadeo.
Una vez en modo AT, podemos escribir los diferentes comandos AT para leer o configurar parámetros del módulo. Todos los comandos finalizan con
\r\n, y en algún caso se puede añadir el interrogante al solicitar parámetros. Normalmente se puede enviar ?AT+VERSION?\r\n?, de la misma forma
que ?AT+VERSION\r\n?.
Algunos de los comandos que se pueden utilizar para leer los parámetros són:
(*)Algunos comandos necesitan que esté pulsado el botón para que devuelva algún tipo de resultado, como por ejemplo AT+NAME.
Referencias Bluetooth
http://wiki.pinguino.cc/index.php/SPP_Bluetooth_Modules
http://www.martyncurrey.com/arduino-with-hc-05-bluetooth-module-at-mode/#comment-4042.
http://www.instructables.com/id/AT-command-mode-of-HC-05-Bluetooth-module/?ALLSTEPSç
En el caso de Bluetooth v.4 LE (Low Energy), la compañia JNHuaMao Technology Company creó el módulo HM-10, pero otras compañias como
Bolutek han realizado una copia similar que se puede encontrar con nombres como CC41A, HC-10, MLT-BT05 o AT-09. En el siguiente enlace se
puede encontrar más información, así como el enlace a un sketch para Arduino que permite identificar automáticamente el tipo de módulo.
http://blog.yavilevich.com/2016/12/hm-10-or-cc41-a-module-automatic-arduino-ble-module-identification/
El módulo HM-10 se encuentra en modo AT por defecto excepto cuando esté conectado a algún dispositivo. Para poder enviar comandos AT, NO hay
que enviar ningún retorno de carro (CR) ni fin de línea (LF), sinó directamente el comando AT.
http://www.jnhuamao.cn/Bluetooth40_en.zip
http://www.ibeacon.com/what-is-ibeacon-a-guide-to-beacons/
* Por algún problema de seguridad, los navegadores detectan la web como sitio atacante.
Para consultar el estado de la mayoria de comandos AT, se añade un interrogante (?) tras el comando (ej.: AT+IBEA?). En la documentación se
encuentran todos los comandos, pero algunos de los comandos interesantes son:
• AT simplemente devuelve OK y nos informa de que el módulo está activo y a la espera de nuevos comandos
• AT+ADDR? devuelve la dirección MAC del módulo HM-10
• AT+VERR? devuelve la versión actual del firmware
• AT+RENEW revierte el módulo a la configuración de fábrica
• AT+RESET reinicializa el módulo
• AT+MODEx cambia el funcionamiento del módulo de cara al procesamiento de comandos AT
• AT+MODE0 (valor por defecto). Sólo acepta comandos AT vía la conexión serie (TXD/RXD) hasta que se conecta un dispositivo BLE central
al módulo
• AT+MODE2 Igual que el MODE0 pero,una vez establecida la conexión, se pueden enviar comandos AT desde el dispositivo BLE central. Por
ejemplo se puede cambiar el valor de uno de los pines GPIO del módulo HM-10 sin necesidad de un micro adicional.
• AT+UUID0xnnnn cambia el UUID del servicio utilizado por el módulo HM-10 para encapsular la conexión serie (el valor por defecto es
0xFFE1)
• AT+CHAR0xnnnn cambia el UUID de la característica utilizada por el módulo HM-10 para encapsular la conexión serie (el valor por defecto
es 0xFFE1)
• AT+PIOxy (x: 2-B ; y: 0-1) Permite activar el pin GPIO x del módulo HM-10 con el valor y (0 Off; 1 On). Por ejemplo para encender el GPIO 2
hay que enviar AT+PIO21
• AT+BEFCxyz define el valor de los pines GPIO 2 a B cuando el módulo es alimentado (valores permitidos 000 a 3FF). Por ejemplo para que
configurar a alto el GPIO2 nada más alimentar el módulo, hay que utilizar el comando AT+BEF200
• AT+NAMExxxxxxxxx permite cambiar el nombre del módulo
• AT+ROLEx (x: 0-1; 0 periférico; 1 - central; valor por defecto 0) permite definir el rol del módulo HM-10. En el caso de configurar el módulo
como un dispositivo central, se pueden utilizar los comandos AT+DISC?, AT+CON y AT+CONN para conectarlo a otro dispositivo periférico
• AT+IBEAx Activa o desactiva el modo iBeacon (0 Off; 1 On). Una vez configurado como iBeacon, el módulo deja de encender el led de la
placa y está dormido la mayorparte del tiempo con lo que no responde a los comandos AT Para despertar el módulo y poder mandarle
nuevos comandos AT, hay dos opciones: Enviarle una cadena larga (más de 80 bytes) de caracteres sin la cadena AT (repetir varias veces
hasta que recibamos de respuesta OK+WAKE; Conectar el pin PIO0/System Key (pin BRK en la placa breakout) a tierra/GND durante más
de un segundo.
• AT+ADCx? Query module address (4<x<B)
• AT+ADVI? Query/Set Advertising interval
• AT+ADTY? Query/Set Advertising Type
• AT+ANCS? Query/Set ANCS switch
• AT+ALLO? Query/Set whitelist switch ; Set whitelist mac address
• AT+ADx?? Query whitelist mac address (1<x<3)
• AT+BEFC? Query/Set Module pin output state, After power supplied
• AT+AFTC? Query/Set Module pin output state, After connection is established
• AT+BATC? Query/Set battery monitor switch
• AT+BATT? Query battery information (Android onLeScan() Data format is 0x02, 0x16, 0x00, 0xB0, [FLAG], [temperature], [ humidity],
[battery].)
• AT+BAUD? Query/Set baud rate (0-->9600)
• AT+COMI? Query/Set Minimum Link Layer connection interval
Más informacion: http://wiki.makespacemadrid.org/index.php?title=M%C3%B3dulo_HM-10
El módulo CC-41A se encuentra en modo AT por defecto. Para poder enviar comandos AT, hay que enviar los carácteres de retorno de carro y fin de
línea (CR&LF) tras el comando AT.
Existen varias versiones en función de la memoria: ESP-01 placa azul con 512Mb, ESP-01S placa negra con 1Mb, o placa negra con 8Mb.
- Si no se sabe que firmware incorpora el módulo, se recomienda actualizarlo a una versión que esté actualizada. Para ello se realiza la siguiente
conexión, en que se conecta el GPIO0 a GND para indicar que se actualizará el firmware:
Como el módulo trabaja a 3.3v, no es recomendable conectar directamente los pines a salidas a 5v, aunque no suele haber ningún problema si las
comunicaciones con el módulo no se realizan de forma continua.
- Una vez conectado, cargamos en Arduino IDE un Sketch vacio, como el que encontramos en: Archivos --> Ejemplos --> 01.Basics --> BareMinimum
void setup() {
// put your setup code here, to run once:
}
void loop() {
// put your main code here, to run repeatedly:
}
- Bajamos el siguiente archivo que contiene todo lo necesario para la actualización: ESP8266_flasher.zip.
- Ejecutamos el archivo esp8266_flasher.exe, pulsamos el botón BIN, y cargamos el firmware de actualización v0.9.2.4 AT Firmware-ESPFIX.bin, o
el que queramos en función de la versión. - Seleccionamos el puerto donde está conectado Arduino, y pulsamos download. Al final puede aparecer un
error, que podemos ignorar.
Ya tenemos actualizado el módulo ESP-01. Desconectamos el pin GPIO0 y ya podemos utilizar el módulo con Arduino:
Con el esquema anterior no podríamos utilizar la aplicación de monitor incluida en Arduino IDE, ya que utiliza los mismos pines del puerto serie.
Por ello se recomienda realizar las siguientes conexiones y utilizar la libreria SoftwareSerial, y poder utilizar el monitor de Arduino.
Debido a que la librería crea un puerto serie virtual por software, puede haber problemas en función de la velocidad de comunicación. Por ello se
recomienda cambiar la velocidad del módulo a un valor inferior o igual a 19600bps. Para cambiar la velocidad a 9600, por ejemplo, se enviaría al
módulo ESP-01 el comando:
AT+CIOBAUD=9600
Referencia: http://kio4.com/arduino/57modulowifi_2.htm
Enlaces Arduino
Enlaces a diferentes páginas sobre Arduino
https://www.arduino.cc/
http://portalarduino.com/index
http://garretlab.web.fc2.com/en/arduino/inside/index.html