lunes, 30 de diciembre de 2013

Nivelando superficies con un acelerómetro

Cómo funciona un acelerómetro

Un acelerómetro, como su nombre indica, mide la aceleración a la que está sometido. Si lo movemos, nos medirá no sólo la aceleración, sino hacia dónde acelera. Para ello nuestro acelerómetro tiene una entrada para cada dimensión del espacio y nos devolverá los tres componentes del vector aceleración. Pero no solo es capaz de medir la aceleración, sino que en reposo mide la componente de la gravedad que soporta cada uno de sus ejes. Si lo conectamos y lo ponemos sobre una mesa en su posición normal veremos cómo las entradas de los ejes x e y tienen más o menos el mismo valor, pero la del eje z no. Esto es porque está perpendicular al centro de la tierra y por lo tanto la atracción gravitatoria recae enteramente sobre el eje z.



Si lo inclinamos un poco z disminuirá al repartirse la gravedad entre los tres ejes.



Así pues, la superficie sobre la que se apoye el acelerómetro estará nivelada cuando el eje perpendicular a ella soporte toda la fuerza de la gravedad si estamos nivelando en horizontal y ninguna fuerza si estamos nivelando en vertical. Un detalle importante es que el acelerómetro puede detectar la gravedad pero no la orientación, es decir, que si lo rotamos alrededor de uno de los ejes no se enterará. Para detectar el giro necesitaríamos un giróscopo . Este detalle es importante a la hora de mostrar en la pantalla LCD la inclinación que tiene el acelerómetro, ya que nos obliga a orientar correctamente los ejes para realizar el nivelado de superficies verticales.

El acelerómetro

Para el circuito vamos a usar un acelerómetro GY-61. Como podemos ver en la imagen tiene dibujados los ejes para que podamos orientarlo. Para que el sketch funcione correctamente lo tenemos que orientar de forma que el eje Z quede perpendicular a la placa de prototipo. El acelerómetro funciona a 3.3V, por lo que tenemos que ir con cuidado de no conectarlo al pin de 5V de Arduino sino al 3.3V ya que lo podemos quemar.



La pantalla LCD

Para mostrar la orientación del acelerómetro vamos a usar una pantalla Nokia LCD 5110. Supuestamente es la que llevan varios modelos de móviles Nokia, así que, al menos en teoría, los podríamos desmontar y reutilizar la pantalla. En mi caso la he comprado, ya que tampoco tienen un precio excesivo. Para programarla usaremos la biblioteca de arduino desarrollada por Adafruit. La podemos descargar aquí. También tenemos que descargar la biblioteca GFX también desarrollada por Adafruit.
La pantalla LCD también funciona a 3.3V y por lo tanto también tendremos que conectarla al pin 3.3V de arduino. Además tendremos que usar resistencias para bajar el voltaje que le llega desde los pines digitales, ya que su voltaje de salida es 5V. En la conexión del acelerómtro no las necesitamos porque únicamente vamos a leer, pero en los pines del LCD vamos a escribir y por lo tanto les tenemos que enviar 3.3V en lugar de 5V, que es lo que sale de los pines del arduino. El esquema de conexión es el siguiente:

  
El pin LED del LCD es el que se encarga de iluminar la pantalla. Lo hemos conectado a corriente para que esté siempre iluminada. Si quisiéramos encenderla mediante programación tendríamos que conectarla a uno de los pines digitales de arduino y encenderla o apagarla con la función analogWrite poniendo el pin a HIGH o LOW. El resto de pines digitales son los estándares del protocolo SPI que es el que usa la biblioteca de Adafruit. Un detalle importante es que conectamos el pin VCC y LED directamente a corrienta porque todo el circuito está funcionando a 3.3V. Si conectamos el LCD a un circuito cuyos componentes funcionen a 5V deberemos ponerles una resistencia de 10kΏ o correremos el riesgo de quemar el LCD. Aquí lo tenemos montado en la placa de prototipo



El sketch

El sketch va a dibujar en el LCD dos círculos, uno señalará el punto en el que la superficie está nivelada y el otro se moverá conforme vayamos inclinando el acelerómetro. La superficie estará nivelada cuando el segundo círculo esté dentro del primero.
Antes de poder usar el acelerómetro tenemos que calibrarlo para averiguar los valores para 1g, 0g y -1g. Para ello lo colocaremos sobre una superficie horizontal y anotaremos los valores para el eje Z, que será 1g, y el valor para X o Y que serán 0g. Para averiguar el -1g pondremos el acelerómetro al revés y lo rotamos hacia ambos lados. El menor valor que tome será -1g. Las constantes que guardan estos valores son:

const int negativegValue=270;
const int zerogValue=350;
const int onegValue=424;

El código fuente completo:

#include <Adafruit_GFX.h>
#include <Adafruit_PCD8544.h>

const int xPin = 0;
const int yPin = 1;
const int zPin = 2;

const byte SCLKpin = 13;
const byte DNpin = 11;
const byte DCpin = 12; 
const byte RESETpin = 10;

const byte ballRadius=5;
const byte errorMargin=1;
int xReadOld=0;
int yReadOld=0;
int zReadOld=0;

const int negativegValue=270;
const int zerogValue=350;
const int onegValue=424;
char contrast=57;
char buffer[10];

Adafruit_PCD8544 lcd(SCLKpin,DNpin,DCpin,RESETpin);

void setup(){
  Serial.begin(115200); 
  lcd.begin();
  lcd.setContrast(contrast);
}


void loop(){

  int xRead = analogRead(xPin);
  int yRead = analogRead(yPin);
  int zRead = analogRead(zPin);
  int currentX;
  int currentY;
  /* Elegimos la orientacion que estamos nivelando por el valor del eje z que siempre sera perpendicular a la superficie
  Cambiaremos segun su valor este mas cerca de cero o de 1g*/
  if(zRead<(zerogValue+((onegValue-zerogValue)/2))){
    //Orientacion vertical
    //currentX=lcd.width()/2;
    currentX=map(zerogValue,negativegValue,onegValue,0,lcd.width());
    currentY=map(zRead,negativegValue,onegValue,0,lcd.height());
  }
  else{
    currentX=map(xRead,negativegValue,onegValue,0,lcd.width());
    currentY=map(yRead,negativegValue,onegValue,0,lcd.height());
  }
  
  if(abs(zRead-zReadOld)>=errorMargin){
  
    lcd.clearDisplay();
    
    drawBall(currentX,currentY,5);
    
    xReadOld=xRead;
    yReadOld=yRead;
    zReadOld=zRead;
  }
  
  
  Serial.print("xRead: ");
  Serial.print(xRead);
  Serial.print(" | yRead: ");
  Serial.print(yRead);
  Serial.print(" | zRead: ");
  Serial.println(zRead);

  

  delay(100);
}

void drawBall(int x,int y,int radius){
  
  for(int i=0;i<radius;i++){
    lcd.drawCircle(x,y,i,BLACK);
  }
lcd.drawCircle((int)map(zerogValue,negativegValue,onegValue,0,lcd.width()),(int)map(zerogValue,negativegValue,onegValue,0,lcd.height()),radius+2,BLACK);
  lcd.display();
}

Básicamente lo que hace el sketch es leer los valores de los tres ejes del acelerómetro y redibujar la pantalla cuando el eje Z se ha movido más de un margen de error indicado en la variable errorMargin. La orientación de la superficie a nivelar lo decidimos en función del valor del eje Z, si está más cerca de 1g asumiremos que estamos nivelando una superficie horizontal y si está más cerca de 0g asumiremos que nivelamos una superficie vertical. Para las superficies horizontales la bola se moverá en cualquier dirección del plano XY y para las verticales sólo se moverá arriba y abajo. Para que funcione correctamente con las superficies verticales tenemos que situar el acelerómetro con la flecha del eje Y apuntando hacia el centro de la Tierra. Aquí tenemos un vídeo mostrando su funcionamiento


lunes, 4 de noviembre de 2013

El algoritmo de la compra del billete de tren

No es el título de un capítulo de The big bang theory, sino el dilema al que me enfrento al comprar un billete de tren. LLamadme raro, pero yo necesito un algoritmo para comprar mi abono mensual de renfe para ir a trabajar. Y no es que vuelva loca a la amable taquillera de mi estación como haría  Sheldon Cooper, por mucho que cierta persona se empeñe en que a veces le recuerdo a él, entre otras cosas porque soy más del estilo de Roy Trenneman. La razón es que el abono de renfe incluye un viaje de ida y vuelta al día durante un mes. Al cabo de un mes te caduca el abono independientemente del número de viajes que hayas hecho. Es que yo no trabajo todos los días, señor directivo de renfe, alego. Todo lo que empieza por es que es una excusa, excepto esqueleto y esquema, aduce el señor directivo. ¿Y esqueje?
He aquí nuestro problema, la abolición de la esclavitud. Si trabajásemos todos los días no tendríamos que andar contando cuántos días laborables incluye nuestro abono, aunque tal como está evolucionando la legislación laboral es probable que en un futuro no necesitemos hacerlo. La idea es minimizar el precio diario del billete retrasando la compra del abono para incluir el mayor número de días laborables con la dificultad añadida de que los días que la retrasemos también tenemos que ir a trabajar y por lo tanto también debemos pagar el billete sencillo que es mucho más caro. Explicándolo de una forma sencilla, tenemos que averiguar cuántos billetes sencillos compensan la inclusión de unos días más en el abono. Para ello nos podemos basar en dos criterios, aunque ambos suelen coincidir: el precio por día laborable y el precio por día real. El primero se calcularía dividiendo el número de días laborables que hemos ido a trabajar por el precio que hemos pagado. El segundo son los días que han pasado (incluidos festivos y vacaciones) desde que empezamos a contar dividido por el coste de los billetes. Para ello he desarrollado un script en Ruby. El resultado del script es el cálculo del precio si comprásemos el abono durante varios días teniendo en cuenta los billetes sencillos que hemos comprado previamente. Como hoy tocaba comprar el billete, ejecuto el script y este es el resultado:

./abono.rb --initial_date 20131104 --simple_ticket_price 5.70 --subscription_price 77.90 --months 1 --no-sunday --no-saturday --calculate_days 30

Condiciones:
Fecha inicial: 2013-11-04
Fecha final: 2013-12-03
Días de cálculo: 30
Precio del billete sencillo: 5.7
Precio del abono: 77.9
Trabaja los lunes: true
Trabaja los martes: true
Trabaja los miércoles: true
Trabaja los jueves: true
Trabaja los viernes: true
Trabaja los sábados: false
Trabaja los domingos: false
**2013-11-04: TOTAL PAGADO: 77.9. 22 DíAS EFECTIVOS. DíAS SIN BONO: 0. PRECIO POR DíA LABORABLE: 3.54. DíAS REALES: 30. PRECIO POR DíA REAL: 2.6
2013-11-05: Total pagado: 83.6. 22 días efectivos. Días sin bono: 1. Precio por día laborable: 3.63. Días reales: 31. Precio por día real: 2.7
2013-11-06: Total pagado: 89.3. 22 días efectivos. Días sin bono: 2. Precio por día laborable: 3.72. Días reales: 32. Precio por día real: 2.79
2013-11-07: Total pagado: 95.0. 22 días efectivos. Días sin bono: 3. Precio por día laborable: 3.8. Días reales: 33. Precio por día real: 2.88
2013-11-08: Total pagado: 100.7. 21 días efectivos. Días sin bono: 4. Precio por día laborable: 4.03. Días reales: 34. Precio por día real: 2.96
--2013-11-09: Total pagado: 106.4. 20 días efectivos. Días sin bono: 5. Precio por día laborable: 4.26. Días reales: 35. Precio por día real: 3.04
--2013-11-10: Total pagado: 106.4. 21 días efectivos. Días sin bono: 5. Precio por día laborable: 4.09. Días reales: 36. Precio por día real: 2.96
2013-11-11: Total pagado: 106.4. 22 días efectivos. Días sin bono: 5. Precio por día laborable: 3.94. Días reales: 37. Precio por día real: 2.88
2013-11-12: Total pagado: 112.1. 22 días efectivos. Días sin bono: 6. Precio por día laborable: 4.0. Días reales: 38. Precio por día real: 2.95
2013-11-13: Total pagado: 117.8. 22 días efectivos. Días sin bono: 7. Precio por día laborable: 4.06. Días reales: 39. Precio por día real: 3.02
2013-11-14: Total pagado: 123.5. 22 días efectivos. Días sin bono: 8. Precio por día laborable: 4.12. Días reales: 40. Precio por día real: 3.09
2013-11-15: Total pagado: 129.2. 21 días efectivos. Días sin bono: 9. Precio por día laborable: 4.31. Días reales: 41. Precio por día real: 3.15
--2013-11-16: Total pagado: 134.9. 20 días efectivos. Días sin bono: 10. Precio por día laborable: 4.5. Días reales: 42. Precio por día real: 3.21
--2013-11-17: Total pagado: 134.9. 21 días efectivos. Días sin bono: 10. Precio por día laborable: 4.35. Días reales: 43. Precio por día real: 3.14
2013-11-18: Total pagado: 134.9. 22 días efectivos. Días sin bono: 10. Precio por día laborable: 4.22. Días reales: 44. Precio por día real: 3.07
2013-11-19: Total pagado: 140.6. 22 días efectivos. Días sin bono: 11. Precio por día laborable: 4.26. Días reales: 45. Precio por día real: 3.12
2013-11-20: Total pagado: 146.3. 22 días efectivos. Días sin bono: 12. Precio por día laborable: 4.3. Días reales: 46. Precio por día real: 3.18
2013-11-21: Total pagado: 152.0. 22 días efectivos. Días sin bono: 13. Precio por día laborable: 4.34. Días reales: 47. Precio por día real: 3.23
2013-11-22: Total pagado: 157.7. 21 días efectivos. Días sin bono: 14. Precio por día laborable: 4.51. Días reales: 48. Precio por día real: 3.29
--2013-11-23: Total pagado: 163.4. 20 días efectivos. Días sin bono: 15. Precio por día laborable: 4.67. Días reales: 49. Precio por día real: 3.33
--2013-11-24: Total pagado: 163.4. 21 días efectivos. Días sin bono: 15. Precio por día laborable: 4.54. Días reales: 50. Precio por día real: 3.27
2013-11-25: Total pagado: 163.4. 22 días efectivos. Días sin bono: 15. Precio por día laborable: 4.42. Días reales: 51. Precio por día real: 3.2
2013-11-26: Total pagado: 169.1. 22 días efectivos. Días sin bono: 16. Precio por día laborable: 4.45. Días reales: 52. Precio por día real: 3.25
2013-11-27: Total pagado: 174.8. 22 días efectivos. Días sin bono: 17. Precio por día laborable: 4.48. Días reales: 53. Precio por día real: 3.3
2013-11-28: Total pagado: 180.5. 22 días efectivos. Días sin bono: 18. Precio por día laborable: 4.51. Días reales: 54. Precio por día real: 3.34
2013-11-29: Total pagado: 186.2. 21 días efectivos. Días sin bono: 19. Precio por día laborable: 4.65. Días reales: 55. Precio por día real: 3.39
--2013-11-30: Total pagado: 191.9. 20 días efectivos. Días sin bono: 20. Precio por día laborable: 4.8. Días reales: 56. Precio por día real: 3.43
--2013-12-01: Total pagado: 191.9. 21 días efectivos. Días sin bono: 20. Precio por día laborable: 4.68. Días reales: 57. Precio por día real: 3.37
2013-12-02: Total pagado: 191.9. 22 días efectivos. Días sin bono: 20. Precio por día laborable: 4.57. Días reales: 58. Precio por día real: 3.31
2013-12-03: Total pagado: 197.6. 22 días efectivos. Días sin bono: 21. Precio por día laborable: 4.6. Días reales: 59. Precio por día real: 3.35
2013-12-04: Total pagado: 203.3. 22 días efectivos. Días sin bono: 22. Precio por día laborable: 4.62. Días reales: 60. Precio por día real: 3.39


El script devuelve los días festivos precedidos de "--", el mejor precio por día laborable precedido de "**" y el mejor precio por día real precedido de "$$". Como noviembre es un mes sin festivos ni puentes nos conviene comprar nuestro abono el siguiente día laborable después de su caducidad. El problema es el siguiente abono que incluye varios días festivos y vacaciones.

./abono.rb --initial_date 20131204 --simple_ticket_price 5.70 --subscription_price 77.90 --months 1 --calculate_days 30 --no-sunday --no-saturday --holiday 20131225 --holiday 20140101 --holiday_start 20131215 --holiday_end 20131225
Condiciones:
Fecha inicial: 2013-12-04
Fecha final: 2014-01-03
Días de cálculo: 30
Precio del billete sencillo: 5.7
Precio del abono: 77.9
Trabaja los lunes: true
Trabaja los martes: true
Trabaja los miércoles: true
Trabaja los jueves: true
Trabaja los viernes: true
Trabaja los sábados: false
Trabaja los domingos: false
Festivo: 2013-12-25
Festivo: 2014-01-01
Vacaciones: 2013-12-15 a 2013-12-25
2013-12-04: Total pagado: 77.9. 14 días efectivos. Días sin bono: 0. Precio por día laborable: 5.56. Días reales: 31. Precio por día real: 2.51
2013-12-05: Total pagado: 83.6. 13 días efectivos. Días sin bono: 1. Precio por día laborable: 5.97. Días reales: 32. Precio por día real: 2.61
2013-12-06: Total pagado: 89.3. 12 días efectivos. Días sin bono: 2. Precio por día laborable: 6.38. Días reales: 33. Precio por día real: 2.71
--2013-12-07: Total pagado: 95.0. 12 días efectivos. Días sin bono: 3. Precio por día laborable: 6.33. Días reales: 34. Precio por día real: 2.79
--2013-12-08: Total pagado: 95.0. 13 días efectivos. Días sin bono: 3. Precio por día laborable: 5.94. Días reales: 35. Precio por día real: 2.71
2013-12-09: Total pagado: 95.0. 14 días efectivos. Días sin bono: 3. Precio por día laborable: 5.59. Días reales: 36. Precio por día real: 2.64
2013-12-10: Total pagado: 100.7. 14 días efectivos. Días sin bono: 4. Precio por día laborable: 5.59. Días reales: 37. Precio por día real: 2.72
2013-12-11: Total pagado: 106.4. 14 días efectivos. Días sin bono: 5. Precio por día laborable: 5.6. Días reales: 38. Precio por día real: 2.8
2013-12-12: Total pagado: 112.1. 13 días efectivos. Días sin bono: 6. Precio por día laborable: 5.9. Días reales: 39. Precio por día real: 2.87
2013-12-13: Total pagado: 117.8. 12 días efectivos. Días sin bono: 7. Precio por día laborable: 6.2. Días reales: 40. Precio por día real: 2.95
--2013-12-14: Total pagado: 123.5. 12 días efectivos. Días sin bono: 8. Precio por día laborable: 6.18. Días reales: 41. Precio por día real: 3.01
--2013-12-15: Total pagado: 123.5. 13 días efectivos. Días sin bono: 8. Precio por día laborable: 5.88. Días reales: 42. Precio por día real: 2.94
--2013-12-16: Total pagado: 123.5. 14 días efectivos. Días sin bono: 8. Precio por día laborable: 5.61. Días reales: 43. Precio por día real: 2.87
--2013-12-17: Total pagado: 123.5. 15 días efectivos. Días sin bono: 8. Precio por día laborable: 5.37. Días reales: 44. Precio por día real: 2.81
--2013-12-18: Total pagado: 123.5. 16 días efectivos. Días sin bono: 8. Precio por día laborable: 5.15. Días reales: 45. Precio por día real: 2.74
--2013-12-19: Total pagado: 123.5. 16 días efectivos. Días sin bono: 8. Precio por día laborable: 5.15. Días reales: 46. Precio por día real: 2.68
--2013-12-20: Total pagado: 123.5. 16 días efectivos. Días sin bono: 8. Precio por día laborable: 5.15. Días reales: 47. Precio por día real: 2.63
--2013-12-21: Total pagado: 123.5. 17 días efectivos. Días sin bono: 8. Precio por día laborable: 4.94. Días reales: 48. Precio por día real: 2.57
--2013-12-22: Total pagado: 123.5. 18 días efectivos. Días sin bono: 8. Precio por día laborable: 4.75. Días reales: 49. Precio por día real: 2.52
--2013-12-23: Total pagado: 123.5. 19 días efectivos. Días sin bono: 8. Precio por día laborable: 4.57. Días reales: 50. Precio por día real: 2.47
--2013-12-24: Total pagado: 123.5. 20 días efectivos. Días sin bono: 8. Precio por día laborable: 4.41. Días reales: 51. Precio por día real: 2.42
--2013-12-25: Total pagado: 123.5. 21 días efectivos. Días sin bono: 8. Precio por día laborable: 4.26. Días reales: 52. Precio por día real: 2.38
$$2013-12-26: TOTAL PAGADO: 123.5. 21 DíAS EFECTIVOS. DíAS SIN BONO: 8. PRECIO POR DíA LABORABLE: 4.26. DíAS REALES: 53. PRECIO POR DíA REAL: 2.33
2013-12-27: Total pagado: 129.2. 20 días efectivos. Días sin bono: 9. Precio por día laborable: 4.46. Días reales: 54. Precio por día real: 2.39
--2013-12-28: Total pagado: 134.9. 20 días efectivos. Días sin bono: 10. Precio por día laborable: 4.5. Días reales: 55. Precio por día real: 2.45
--2013-12-29: Total pagado: 134.9. 21 días efectivos. Días sin bono: 10. Precio por día laborable: 4.35. Días reales: 56. Precio por día real: 2.41
**2013-12-30: TOTAL PAGADO: 134.9. 22 DíAS EFECTIVOS. DíAS SIN BONO: 10. PRECIO POR DíA LABORABLE: 4.22. DíAS REALES: 57. PRECIO POR DíA REAL: 2.37
2013-12-31: Total pagado: 140.6. 22 días efectivos. Días sin bono: 11. Precio por día laborable: 4.26. Días reales: 58. Precio por día real: 2.42
--2014-01-01: Total pagado: 146.3. 22 días efectivos. Días sin bono: 12. Precio por día laborable: 4.3. Días reales: 59. Precio por día real: 2.48
2014-01-02: Total pagado: 146.3. 22 días efectivos. Días sin bono: 12. Precio por día laborable: 4.3. Días reales: 60. Precio por día real: 2.44
2014-01-03: Total pagado: 152.0. 21 días efectivos. Días sin bono: 13. Precio por día laborable: 4.47. Días reales: 61. Precio por día real: 2.49

El resultado es algo sorprendente, ya que si elegimos bien el día de compra nos puede salir más barato el transporte cuando hay festivos y vacaciones, ya que cubrimos el mayor número de días al menor precio. El código fuente del script es este:

#!/usr/bin/ruby
#encoding: utf-8

require 'optparse'
require 'date'
#require 'colorize'

def working_day(f,holidays,week_days,holiday_periods)
holiday=false
holidays.each do |h|
if h==f
holiday=true
end
end
holiday_periods.each do |k,v|
if f>=k && f<=v
holiday=true
end
end
return !holiday && week_days[f.wday]
end

initial_date=Date.today
simple_ticket_price=5.4
subscription_price=77.8
week_days=Array.new(7,true)
holidays=Array.new
holiday_periods=Hash.new
final_date=initial_date>>1
calculate_days=10
holiday_period_start=Date.new

opt_parser = OptionParser.new do |opts|

opts.banner="Uso abono.rb [options]"
opts.separator ""
opts.on("--initial_date Fecha inicial", String , :required, "Fecha de inicio del bono") do |switch|
initial_date=Date.iso8601(switch)
final_date=(initial_date>>1)-1
end
opts.on("--simple_ticket_price Precio", Float , :required, "Precio del billete sencillo") do |switch|
simple_ticket_price=switch
end
opts.on("--subscription_price Precio", Float , :required, "Precio del abono") do |switch|
subscription_price=switch
end
opts.on("--months Duración", Integer , :required, "Duración del abono en meses") do |switch|
final_date=(initial_date>>switch)-1
end
opts.on("--days Duración", Integer , :required, "Duración del abono en días") do |switch|
final_date=initial_date+switch-1
end
opts.on("--years Duración", Integer , :required, "Duración del abono en años") do |switch|
final_date=initial_date.next_year(switch)-1
end
opts.on("--calculate_days Duración", Integer , :required, "Días de cálculo") do |switch|
calculate_days=switch
end
opts.on("--no-sunday", :none, "No trabaja los domingos") do |switch|
week_days[0]=false
end
opts.on("--no-monday", :none, "No trabaja los lunes") do |switch|
week_days[1]=false
end
opts.on("--no-tuesday", :none, "No trabaja los martes") do |switch|
week_days[2]=false
end
opts.on("--no-wednesday", :none, "No trabaja los miércoles") do |switch|
week_days[3]=false
end
opts.on("--no-thursday", :none, "No trabaja los jueves") do |switch|
week_days[4]=false
end
opts.on("--no-friday", :none, "No trabaja los viernes") do |switch|
week_days[5]=false
end
opts.on("--no-saturday", :none, "No trabaja los sábados") do |switch|
week_days[6]=false
end
opts.on("--holiday Fecha", String, :required, "Fecha de un día festivo") do |switch|
holidays.push Date.iso8601(switch)
end
opts.on("--holiday_start Fecha", String, :required, "Fecha de inicio de las vacaciones") do |switch|
holiday_period_start=Date.iso8601(switch)
end
opts.on("--holiday_end Fecha", String, :required, "Fecha de final de las vacaciones") do |switch|
holiday_periods[holiday_period_start]=Date.iso8601(switch)
end
end

opt_parser.parse!
puts "Condiciones: "
puts "Fecha inicial: #{initial_date}"
puts "Fecha final: #{final_date}"
puts "Días de cálculo: #{calculate_days}"
puts "Precio del billete sencillo: #{simple_ticket_price}"
puts "Precio del abono: #{subscription_price}"
puts "Trabaja los lunes: #{week_days[1]}"
puts "Trabaja los martes: #{week_days[2]}"
puts "Trabaja los miércoles: #{week_days[3]}"
puts "Trabaja los jueves: #{week_days[4]}"
puts "Trabaja los viernes: #{week_days[5]}"
puts "Trabaja los sábados: #{week_days[6]}"
puts "Trabaja los domingos: #{week_days[0]}"
holidays.each do |holiday|
puts "Festivo: #{holiday}"
end
holiday_periods.each do |k,v|
puts "Vacaciones: #{k} a #{v}"
end

result=Hash.new
min_price=1.0/0.0 #Sí amigos, ruby sabe que 1/0 es infinito
min_real_price=1.0/0.0

min_price_string=String.new
min_real_price_string=String.new

0.upto(calculate_days) do |i|
effective_days_count=0
no_subscription_days_count=0
global_price=subscription_price
if i>0
(initial_date..initial_date+(i-1)).each do |f|
if working_day(f,holidays,week_days,holiday_periods)
global_price+=simple_ticket_price
no_subscription_days_count+=1
end
end
end
(initial_date+i..final_date+i).each do |f|
if working_day(f,holidays,week_days,holiday_periods)
effective_days_count+=1
end
end
price_per_day=(global_price/(effective_days_count+no_subscription_days_count)).round(2)
real_price_per_day=(global_price/((final_date+i)-initial_date)).round(2)
if min_price>price_per_day
min_price=price_per_day
min_price_string="#{initial_date+i}: Total pagado: #{global_price.round(2)}. #{effective_days_count} días efectivos. Días sin bono: #{no_subscription_days_count}. Precio por día laborable: #{price_per_day}. Días reales: #{((final_date+i)-initial_date).to_i()+1}. Precio por día real: #{(global_price/(((final_date+i)-initial_date)+1)).round(2)}"
end
if min_real_price>real_price_per_day
min_real_price=real_price_per_day
min_real_price_string="#{initial_date+i}: Total pagado: #{global_price.round(2)}. #{effective_days_count} días efectivos. Días sin bono: #{no_subscription_days_count}. Precio por día laborable: #{price_per_day}. Días reales: #{((final_date+i)-initial_date).to_i()+1}. Precio por día real: #{(global_price/(((final_date+i)-initial_date)+1)).round(2)}"
end
result["#{initial_date+i}: Total pagado: #{global_price.round(2)}. #{effective_days_count} días efectivos. Días sin bono: #{no_subscription_days_count}. Precio por día laborable: #{price_per_day}. Días reales: #{((final_date+i)-initial_date).to_i()+1}. Precio por día real: #{(global_price/(((final_date+i)-initial_date)+1)).round(2)}"]=price_per_day
end

result.each do |k,v|
if k==min_price_string
puts "**#{k.upcase}"
elsif k==min_real_price_string
puts "$$#{k.upcase}"
elsif !working_day(Date.iso8601(k[0,10]),holidays,week_days,holiday_periods)
puts "--#{k}"
else
puts k
end
end


Las opciones del script son las siguientes:

--initial_date Fecha desde la que queremos calcular la compra
--simple_ticket_price Precio del billete sencillo

--subscription_price Precio del abono mensual
--days Duración del abono en días

--months Duración del abono en meses

--years Duración del abono en años

--calculate_days Número de días que queremos calcular

--no-sunday No trabaja los domingos

--no-monday No trabaja los lunes

--no-tuesday No trabaja los martes

--no-wednesday No trabaja los miércoles

--no-thursday No trabaja los jueves

--no-friday No trabaja los viernes

--no-saturday No trabaja los sábados

--holiday Fecha de un día festivo

--holiday_start Fecha de inicio de vacaciones

--holiday_end Fecha de fin de vacaciones

miércoles, 16 de octubre de 2013

Construcción de una caja musical con arduino V

Vamos a concluir la construcción de la caja musical con la soldadura y el montaje.En las imágenes vemos todos los componentes soldados al shield




Y el conjunto completo con el altavoz y el botón de encendido




El montaje ya dentro de la caja



Para el encendido y apagado del microcontrolador hemos usado un botón en la parte posterior de la caja, conectado a una pila por un extremo y al microcontrolador por el otro. El botón deja pasar la corriente cuando está pulsado, de forma que al abrir la tapa lo pulsa y cierra el circuito.



Y aquí tenemos la caja acabada y funcionando




Artículos relacionados

martes, 10 de septiembre de 2013

Construcción de una caja musical con arduino IV

El sketch que hemos creado hasta ahora reproduce canciones indefinidamente sin interrumpirlas en ningún momento. En este artículo vamos a añadir el mecanismo para que la música se interrumpa cuando se cierra la caja y vuelva a sonar cuando se abre. Existen varias formas de hacerlo, pero en este artículo me voy a centrar en dos.

Utilizando una fotorresistencia

Una fotorresistencia funciona igual que el potenciómetro del artículo anterior, pero la resistencia varía con la cantidad de luz que detecta. La idea es que al cerrar la caja la fotorresistencia detecte la variación de luz al abrir y cerrar la caja para interrumpir o reanudar la música. En la foto podemos ver una fotorresistencia y una resistencia


El fotorresistor está conectado en serie con una resistencia de 10 KΩ. El valor devuelto por el fotoresistor lo leeremos por el pin analógico 2. El esquema es el siguiente



Y por último el montaje del prototipo


En el siguiente vídeo podemos ver el prototipo en funcionamiento.





El sketch

En el sketch hemos definido un umbral mínimo de luz por debajo del cual la música se interrumpirá. Cuando la luz vuelva a estar por encima del valor mínimo se volverá a elegir una nueva canción y se reproducirá. 

#include <SPI.h>
#include <Adafruit_VS1053.h>
#include <SD.h>

#define MP3_XCS 6   // CS Registros
#define MP3_XDCS 7  // CS Datos (BSYNC)
#define MP3_DREQ 2  // Interrupción chip disponible
#define MP3_RESET 5 // Reset activo bajo
#define VOLUME_PIN A1
#define PHOTO_PIN A2
#define MIN_VOLUME 70

#define MIN_LIGHT_LEVEL 900


bool isSdOpened=false;
File root;
File entry;
long fileCount;


unsigned long totalBytes;
int currentVolume=MIN_VOLUME-map(analogRead(VOLUME_PIN),0,1023,0,MIN_VOLUME);
int oldVolume=currentVolume;
int currentLightLevel=analogRead(PHOTO_PIN);
bool active=currentLightLevel>=MIN_LIGHT_LEVEL;

Adafruit_VS1053_FilePlayer musicPlayer = Adafruit_VS1053_FilePlayer(MP3_RESET, MP3_XCS, MP3_XDCS, MP3_DREQ, 10);

void setup(){
  pinMode(10,OUTPUT);
  Serial.begin(115200);

  musicPlayer.begin();
  
  while(!isSdOpened){
    isSdOpened=SD.begin(10);
  
    Serial.print("SD.begin ");
    Serial.println(isSdOpened);
  }

   musicPlayer.useInterrupt(VS1053_FILEPLAYER_PIN_INT); 
   musicPlayer.setVolume(currentVolume,currentVolume);
  
  root=SD.open("/");
  root.rewindDirectory();
  fileCount=0;
  entry=root.openNextFile();
  while(entry){
    fileCount++;
    Serial.print(entry.name());
    Serial.print("\t");
    if(entry.isDirectory()){
      Serial.println("Directorio");
    }
    else{
      Serial.println(entry.size(), DEC);
    }
    entry.close();
    entry=root.openNextFile();
  }
  root.rewindDirectory();
  Serial.print("Numero de ficheros: ");
  Serial.println(fileCount);
  randomSeed(analogRead(0));
  
  Serial.print("Volumen: ");
  Serial.println(currentVolume);
  Serial.println("Reset finalizado");
  
}


void loop(){
  
  currentLightLevel=analogRead(PHOTO_PIN);
  
  delay(1000);
  
  if(currentLightLevel>=MIN_LIGHT_LEVEL){
    active=true;
    if(!musicPlayer.playingMusic){
     
      
      Serial.print("currentLightLevel: ");
      Serial.print(currentLightLevel);
      Serial.print(" MIN_LIGHT_LEVEL: ");
      Serial.println(MIN_LIGHT_LEVEL);
     root.rewindDirectory();
  
     int randNumber = random(1,fileCount+1);
  
     Serial.print("Numero aleatorio: ");
     Serial.println(randNumber);
  
     for(int i=0;i<randNumber;i++){
       entry=root.openNextFile();
       entry.close();
    
      }
      Serial.print("Fichero: ");
      Serial.println(entry.name());
      
      musicPlayer.startPlayingFile(entry.name());
    }
    else{
      delay(500);
      currentVolume=MIN_VOLUME-map(analogRead(VOLUME_PIN),0,1023,0,MIN_VOLUME);
      if(currentVolume!=oldVolume){
        musicPlayer.setVolume(currentVolume,currentVolume);
        oldVolume=currentVolume;
        Serial.print("Volumen: ");
        Serial.println(currentVolume);
     }
   }
  }
  else if(active){
    Serial.print("currentLightLevel: ");
      Serial.print(currentLightLevel);
      Serial.print(" MIN_LIGHT_LEVEL: ");
      Serial.println(MIN_LIGHT_LEVEL);
      active=false;
      asm volatile ("  jmp 0");
      
     
    }
  }

Comentarios al código fuente

Hemos definido dos nuevas variables, PHOTO_PIN y MIN_LIGHT_LEVEL para definir el pin al que hemos conectado el fotorresistor y el umbral mínimo de luz para reproducir una canción. En la función loop() verificamos si la luz detectada por el fotoresistor es mayor que el umbral mínimo y si es mayor ejecutamos el mismo código que en el sketch anterior. En caso contrario comprobamos la variable active que determina si al leer un valor por debajo del umbral mínimo de luz la música estaba activa, lo cual significa que acabamos de bajar del umbral y por lo tanto hay que interrumpir la música. La forma más fácil de interrumpir la música es reiniciar el programa con la instrucción asm volatile ("  jmp 0").

Utilizando una pila

El problema del enfoque anterior es que el microcontrolador necesita estar siempre activo, es decir, consumiendo energía para poder detectar los cambios de luz. Esto nos lleva al problema de cómo proveer de energía a nuestra caja. El microcontrolador Arduino Uno puede alimentarse por USB, con un adaptador de corriente (según la documentación necesita entre 7 y 12 Voltios, pero puede soportar hasta 20) y a través del pin VIN. Las posibles opciones son:

  • Cable USB. Es poco práctico tener la caja siempre conectada a un ordenador en marcha.
  • Adaptador de corriente. Es una buena opción si vamos a tener la caja siempre en el mismo sitio, pero hay que tener en cuenta que siempre que la enchufemos estará consumiendo energía a menos que el adaptador tenga un interruptor que habría que conectar antes de abrir la caja.
  • Pilas. Se puede conectar uno de los varios adaptadores que existen para alimentarlo mediante pilas. Los hay que se conectan a la entrada DC y también que se conectan al pin VIN. Dado que el microcontrolador estará siempre consumiendo energía es poco práctico a menos que añadamos un un interruptor, o quitar la pila cuando no la usemos para que no la consuma.
  • Panel solar. El panel solar sólo estará alimentando en microcontrolador cuando reciba luz. Esta sería la solución ideal si siempre fuésemos a tener suficiente luz, ya que nos permite proveer de energía sin cables y si la ponemos en el interior de la caja solo recibirá luz cuando se abra. La ventaja adicional es que no tenemos que modificar el sketch que hemos programado en el artículo anterior.
Para terminar el diseño de la caja me he decantado por alimentarlo con la pila, que sólo alimentará el circuito se accione un pequeño interruptor que pondremos en la tapa de la caja de forma que se accione cuando la tapa se abra o se cierre. Conectaremos el pulsador de forma que cuando se presione abra el circuito, y lo cierre cuando se libere, es decir, lo presionará la tapa cuando se cierre la caja y se liberará cuando se abra. Aquí tenemos el interruptor:


Lo vamos a conectar en la entrada de corriente del circuito, que será una pila de 9 voltios


La ventaja de este montaje es que no hay que modificar el sketch del artículo anterior para que funcione y además sólo consumirá energía cuando reproduzca la música. Este es pues el diseño definitivo de nuestra caja. En el próximo artículo soldaremos los componentes a un shield para conectarlo al microcontrolador y atornillarlo a la caja.

Artículos relacionados

viernes, 30 de agosto de 2013

Construcción de una caja musical con arduino III

En este artículo vamos a añadir un potenciómetro a nuestro prototipo para ajustar el volumen del decodificador MP3. Un potenciómetro es un resistor variable que normalmente posee algún tipo de control que nos permite ajustar su resistencia y por lo tanto el voltaje que leeremos por unos de los pins analógicos del microcontrolador. En la foto vemos dos tipos de potenciómetros. En uno el ajuste de la resistencia se hace mediante una rueda que se puede girar fácilmente los dedos. En el otro se hace mediante un pequeño tornillo. Obviamente para nuestra caja usaremos el primero, ya que no queremos que el usuario tenga que ajustar el volumen con un destornillador ;-)



El potenciómetro tiene tres pines, dos de ellos van a corriente y tierra, y el tercero va conectado a uno de los pines analógicos del microcontrolador. El esquema de conexión es el siguiente


Y aquí está montado en la placa de prototipo


El sketch

El código fuente modificado para que al girar la rueda del potenciómetro se ajuste el volumen es el siguiente:

#include <SPI.h>
#include <Adafruit_VS1053.h>
#include <SD.h>

#define MP3_XCS 6   // CS Registros
#define MP3_XDCS 7  // CS Datos (BSYNC)
#define MP3_DREQ 2  // Interrupción chip disponible
#define MP3_RESET 5 // Reset activo bajo
#define VOLUME_PIN A1
#define MIN_VOLUME 70


bool isSdOpened=false;
File root;
File entry;
long fileCount;
unsigned long totalBytes;
int currentVolume=MIN_VOLUME-map(analogRead(VOLUME_PIN),0,1023,0,MIN_VOLUME);
int oldVolume=currentVolume;

Adafruit_VS1053_FilePlayer musicPlayer = Adafruit_VS1053_FilePlayer(MP3_RESET, MP3_XCS, MP3_XDCS, MP3_DREQ, 10);


void setup(){
  pinMode(10,OUTPUT);
  Serial.begin(115200);

  musicPlayer.begin();

  while(!isSdOpened){
    isSdOpened=SD.begin(10);

    Serial.print("SD.begin ");
    Serial.println(isSdOpened);
  }

   musicPlayer.useInterrupt(VS1053_FILEPLAYER_PIN_INT);
   musicPlayer.setVolume(currentVolume,currentVolume);

  root=SD.open("/");
  root.rewindDirectory();
  fileCount=0;
  entry=root.openNextFile();
  while(entry){
    fileCount++;
    Serial.print(entry.name());
    Serial.print("\t");
    if(entry.isDirectory()){
      Serial.println("Directorio");
    }
    else{
      Serial.println(entry.size(), DEC);
    }
    entry.close();
    entry=root.openNextFile();
  }
  root.rewindDirectory();
  Serial.print("Numero de ficheros: ");
  Serial.println(fileCount);
  randomSeed(analogRead(0));

  Serial.print("Volumen: ");
  Serial.println(currentVolume);

}


void loop(){

  if (! musicPlayer.playingMusic) {
    musicPlayer.softReset();
    delay(1000);
    root.rewindDirectory();

    int randNumber = random(1,fileCount+1);

    Serial.print("Numero aleatorio: ");
    Serial.println(randNumber);

    for(int i=0;i<randNumber;i++){
      entry=root.openNextFile();
      entry.close();
 
    }
    Serial.print("Fichero: ");
    Serial.println(entry.name());
    musicPlayer.startPlayingFile(entry.name());
  }
  else{
    currentVolume=MIN_VOLUME-map(analogRead(VOLUME_PIN),0,1023,0,MIN_VOLUME);
    if(currentVolume!=oldVolume){
      musicPlayer.setVolume(currentVolume,currentVolume);
      oldVolume=currentVolume;
      Serial.print("Volumen: ");
      Serial.println(currentVolume);
    }
  }
}

Comentarios al código fuente

En la instrucción currentVolume=MIN_VOLUME-map(analogRead(VOLUME_PIN),0,1023,0,MIN_VOLUME) leemos el valor de la entrada analógica y lo adaptamos al rango de valores que vamos a pasarle a la funcion setVolume(). La función analogRead(VOLUME_PIN) devuelve un valor entre 0 y 1023, pero a setVolume() le vamos a pasar un valor entre 0 y 70. La función map() devuelve un valor extrapolado de un rango a otro. En nuestro caso le pasamos un valor entre 0 y 1023 y devuelve su valor proporcional en el rango 0 a 70. Y por último restamos el valor devuelto por map() a 70 porque cuanto menor es el valor que le pasamos a setVolume() mayor es el volumen de la canción, es decir, el valor cero es el máximo volumen.
En la función loop(), cuando no se está reproduciendo la canción leemos el valor del potenciómetro y si ha cambiado ajustamos el valor con setVolume().

Con esto ya tenemos las funcionalidades básicas de nuestra caja. En la próxima entrada veremos cómo hacer que la música se reproduzca o pare cuando se abre o cierra la caja.

Artículos relacionados

 

sábado, 24 de agosto de 2013

Construcción de una caja musical con arduino II

Seguimos con la construcción de la caja musical que empezamos en Construcción de una caja musical con arduino I. Esta vez vamos a conectar el decodificador MP3 para que lea el fichero de la tarjeta SD que hemos seleccionado aleatoriamente y lo reproduzca. Para ello vamos a usar un módulo decodificador de MP3 basado en el chip VS1003/VS1053.




El esquema es el siguiente:



Y aquí lo tenemos montado en la placa de prototipo:



Como hemos visto en el esquema, el lector de tarjetas y el decodificador MP3 comparten algunas conexiones. Esto es consecuencia del uso del protocolo SPI  para la comunicación del microcontrolador con ambos dispositivos. De esta forma necesitamos menos pines para conectar varios dispositivos. Básicamente la idea es que haya un dispositivo master (el microcontrolador) y varios dispositivos esclavos (el lector de tarjetas y el decodificador MP3). El controlador elige con qué dispositivo se comunica desactivando los demás y usando las vías de comunicación comunes a todas ellas. El decodificador MP3 usa además otros cuatro pines además de los de corriente (5V y GND). La entrada XCS conectada al pin digital 6 del microcontrolador es la de Chip Select, que ha de ponerse a LOW para seleccionar el dispositivo. La entrada XDCS conectada al pin digital 7 es la de Data Chip Select, que ha de ponerse a LOW para enviar datos. La entrada DREQ conectada al pin digital 2 es la de Data Request, que estará a HIGH cuando el buffer de datos está vacío y por lo tanto podemos enviarle más. Y por último la entrada XRST conectada al pin digital 5 es la de reset, para reiniciar el dispositivo. Como veremos en el sketch, todo esto no debe preocuparnos ya que vamos a usar una biblioteca que nos permite obviar todos los detalles del protocolo SPI.

El sketch

Vamos a modificar el sketch del primer artículo de la serie para enviar el fichero seleccionado de la tarjeta al decodificador MP3. Para ello vamos a usar la biblioteca Adafruit VS1053 que se puede descargar aquí, ya que no viene por defecto en el IDE de arduino. Para instalarla hay que copiar los dos ficheros, Adafruit_VS1053.h y Adafruit_VS1053.cpp en una carpeta que crearemos donde están el resto de bibliotecas de nuestro IDE. O bien los podemos importar desde cualquier otra carpeta con la opción Sketch/Importa biblioteca.../Añadir del propio IDE de arduino. El código fuente del sketch es el siguiente:

#include <SPI.h>
#include <Adafruit_VS1053.h>
#include <SD.h>

#define MP3_XCS 6   // CS Registros
#define MP3_XDCS 7  // CS Datos (BSYNC)
#define MP3_DREQ 2  // Interrupción chip disponible
#define MP3_RESET 5 // Reset activo bajo


bool isSdOpened=false;
File root;
File entry;
long fileCount;
unsigned long totalBytes;

Adafruit_VS1053_FilePlayer musicPlayer = Adafruit_VS1053_FilePlayer(MP3_RESET, MP3_XCS, MP3_XDCS, MP3_DREQ, 10);


void setup(){
  pinMode(10,OUTPUT);
  Serial.begin(115200);

  musicPlayer.begin();

  while(!isSdOpened){
    isSdOpened=SD.begin(10);

    Serial.print("SD.begin ");
    Serial.println(isSdOpened);
  }

  musicPlayer.setVolume(10,10);

   musicPlayer.useInterrupt(VS1053_FILEPLAYER_PIN_INT);

  root=SD.open("/");
  root.rewindDirectory();
  fileCount=0;
  entry=root.openNextFile();
  while(entry){
    fileCount++;
    Serial.print(entry.name());
    Serial.print("\t");
    if(entry.isDirectory()){
      Serial.println("Directorio");
    }
    else{
      Serial.println(entry.size(), DEC);
    }
    entry.close();
    entry=root.openNextFile();
  }
  root.rewindDirectory();
  Serial.print("Numero de ficheros: ");
  Serial.println(fileCount);
  randomSeed(analogRead(0));

}


void loop(){

  if (! musicPlayer.playingMusic) {
    musicPlayer.softReset();
    delay(1000);
    root.rewindDirectory();

    int randNumber = random(1,fileCount+1);

    Serial.print("Numero aleatorio: ");
    Serial.println(randNumber);

    for(int i=0;i<randNumber;i++){
      entry=root.openNextFile();
      entry.close();
 
    }
    Serial.print("Fichero: ");
    Serial.println(entry.name());
    musicPlayer.startPlayingFile(entry.name());
  }

}

Comentarios al código fuente

La primera línea que hemos añadido es #include <Adafruit_VS1053.h> para poder usar las funciones de acceso al decodificador MP3. En la instrucción Adafruit_VS1053_FilePlayer musicPlayer = Adafruit_VS1053_FilePlayer(MP3_RESET, MP3_XCS, MP3_XDCS, MP3_DREQ, 10) estamos inicializando el decodificador MP3 con los valores de los pines a los que lo hemos conectado. Y finalmente musicPlayer.startPlayingFile(entry.name()) reproduce el fichero. Hemos añadido el if (! musicPlayer.playingMusic) porque la función de reproducción devuelve el control al programa inmediatamente, lo cual es importante si queremos hacer más cosas mientras se reproduce la canción como es nuestro caso. Pero esto ya lo veremos en la tercera entrega donde conectaremos un potenciómetro para controlar el volumen al que se reproducirá la canción.

Artículos relacionados

miércoles, 14 de agosto de 2013

Construcción de una caja musical con arduino I

Empiezo el blog con una serie de artículos en los que construiré una caja musical que al abrirla reproduzca un fichero mp3 aleatorio almacenado en una tarjeta SD. Para ello añadiremos la circuitería necesaria a una caja de madera.
Vamos a empezar conectando un módulo lector de tarjetas SD a un microcontrolador Arduino uno. Nuestro módulo lector de tarjetas es un YL-30.



La conexión de los pines al microcontrolador es el siguiente:

Pin CS (Chip Select) al pin digital 10
Pin MOSI (Master Out Slave In) al pin digital 11
Pin MISO (Master In Slave Out) al pin digital 12
Pin SCK (System Clock) al pin digital 13
Pin +5V al pin 5V
Pin GND al pin GND


Es importante no cambiar la conexión de los pines ya que en el sketch vamos a usar la biblioteca SD de arduino, que a su vez usa la SPI,para acceder al sistema de ficheros de la tarjeta. Esta biblioteca usa estos pines para comunicarse con el módulo YL-30.

Y aquí lo tenemos montado en la placa de prototipo



El sketch

Una vez montado nuestro prototipo tenemos que crear un sketch que generará un número aleatorio y seleccionará el fichero de la tarjeta que corresponda al número generado. Para ello usaremos el entorno de programación de arduino.

#include <SD.h>

bool isSdOpened;
File root;
File entry;
long fileCount;


void setup(){
  pinMode(10,OUTPUT);
  isSdOpened=SD.begin(10);
  Serial.begin(9600);
  Serial.print("SD.begin ");
  Serial.println(isSdOpened);
  root=SD.open("/");
  root.rewindDirectory();
  fileCount=0;
  entry=root.openNextFile();
  while(entry){
    fileCount++;
    Serial.print(entry.name());
    Serial.print("\t");
    if(entry.isDirectory()){
      Serial.println("Directorio");
    }
    else{
      Serial.println(entry.size(), DEC);
    }
    entry.close();
    entry=root.openNextFile();
  }
  root.rewindDirectory();
  Serial.print("Numero de ficheros: ");
  Serial.println(fileCount);
  randomSeed(analogRead(0));

}

void loop(){
  root.rewindDirectory();

  int randNumber = random(1,fileCount+1);

  for(int i=0;i<randNumber;i++){
    entry=root.openNextFile();
    entry.close();
 
  }
  Serial.print("Numero aleatorio: ");
  Serial.println(randNumber);
  Serial.print("Fichero: ");
  Serial.println(entry.name());
  delay(10000);
}

Más información sobre el lenguaje de programación de arduino aquí. Está basado en C y básicamente lo que hace es ejecutar una vez la función setup y repetir indefinidamente la función loop.

Comentarios al código fuente

La primera instrucción de la función setup pinMode(10,OUTPUT)  pone el pin al que hemos conectado el pin CS del módulo SD en modo salida. Sin esta instrucción las demás funciones de acceso a los ficheros de la tarjeta no funcionarán.
La siguiente instrucción isSdOpened=SD.begin(10) inicializa el módulo lector para que podamos usarlo. Por defecto usa el pin 10 del microcontrolador, así que si lo hemos montado tal como indica el esquema la podemos pasar sin parámetros.
Siguiendo con la función setup, una vez inicializado el módulo abrimos el directorio raiz con la instrucción root=SD.open("/"). La siguiente instrucción root.rewindDirectory() lo que hace es volver al primer fichero del directorio y en teoría no es necesaria cuando acabas de abrir un directorio, pero parece ser que hay un bug que provoca errores si no se rebobina el directorio al abrirlo. Una vez abierto el directorio, lo recorremos en el bucle while para contar el número de ficheros y poder sacar así el número aleatorio dentro del rango correcto.
Un detalle muy importante a tener en cuenta es que los experimentos se hacen con gaseosa, es decir, no uses la tarjeta SD de tu cámara cuyas fotos aún no has pasado al ordenador para hacer las pruebas porque lo puedes lamentar amargamente. Y lo digo por las instrucciones entry=root.openNextFile() y entry.close(), que hacen lo que indica su nombre, abrir y cerrar ficheros. El peligro, que he tenido ocasión de comprobar, es que una operación de apertura sin su correspondiente cierre puede corromper el fichero.
En la instrucción randomSeed(analogRead(0)) inicializa el generador aleatorio de números para que la función random() no genere siempre la misma secuencia de números aleatorios. El uso de la función analogRead(0) como parámetro de randomSeed() es porque necesitamos inicializar el generador de números aleatorios con un número diferente cada vez. En un ordenador lo haríamos pasando como parámetro a randomSeed() la hora del sistema, pero nuestro microcontrolador no tiene reloj así que lo que hacemos es leer de un pin que no está conectado a nada cuyo valor es imprevisible. El problema es que la función analogRead() devuelve un valor entre 0 y 1023, con lo cual es bastante probable que con el uso se repitan secuencias de números, pero como tampoco vamos a generar claves de cifrado no creo que suponga mucho inconveniente.
En la función loop() lo que hacemos es generar un número aleatorio entre 1 y el número de ficheros con int randNumber = random(1,fileCount+1) y accedemos a él con el bucle for.
La salida del sketch por el puerto serie es la siguiente:

SD.begin 1
01-BOB~1.MP3 3368960
02-BOB~1.MP3 3897344
03-BOB~1.MP3 4413505
04-BOB~1.MP3 6598656
05-BOB~1.MP3 7415808
06-BOB~1.MP3 5928960
07-BOB~1.MP3 3074048
08-BOB~1.MP3 4009984
09-BOB~1.MP3 3710976
10-BOB~1.MP3 3268608
11-BOB~1.MP3 3045376
12-BOB~1.MP3 5967872
13-BOB~1.MP3 6866944
14-BOB~1.MP3 4851712
15-BOB~1.MP3 6514688
16-BOB~1.MP3 7540736
17-BOB~1.MP3 3913728
18-BOB~1.MP3 7213056
Numero de ficheros: 18
Numero aleatorio: 17
Fichero: 17-BOB~1.MP3
Numero aleatorio: 8
Fichero: 08-BOB~1.MP3
Numero aleatorio: 16
Fichero: 16-BOB~1.MP3
Numero aleatorio: 8
Fichero: 08-BOB~1.MP3
Numero aleatorio: 1
Fichero: 01-BOB~1.MP3
Numero aleatorio: 10
Fichero: 10-BOB~1.MP3
Numero aleatorio: 5
Fichero: 05-BOB~1.MP3
Numero aleatorio: 8
Fichero: 08-BOB~1.MP3
Numero aleatorio: 7
Fichero: 07-BOB~1.MP3
Numero aleatorio: 12
Fichero: 12-BOB~1.MP3
Numero aleatorio: 14
Fichero: 14-BOB~1.MP3

Los más viejos del lugar habrán reconocido la estructura 8+3 de los ficheros que se usaba en windows 95 y 98. La biblioteca SD de arduino sólo soporta los sistemas de ficheros FAT16 y FAT32, pero para el caso que nos ocupa es más que suficiente.

En la próxima entrega conectaremos el módulo reproductor de MP3 al prototipo que hemos creado y modificaremos el sketch para que reproduzca el fichero que hemos seleccionado aleatoriamente.