Construimos un smartwatch
(Nada fácil)
Nos hemos enfrentado a uno de nuestros mayores retos, construir un smartwatch. Ha sido un proyecto realmente complicado porque los relojes inteligentes tienen un tamaño muy pequeño, así que la primera pregunta es, ¿cómo hacen las empresas como Apple para meter tantos componentes electrónicos en tan poco espacio?
Resumen del proyecto smartwatch
Para poder conectar tantos componentes en tan poco espacio hemos diseño y mandado a fabricar una PCB. En esta primera versión, la propia PCB tiene dos agujeros para poner la correa del smartwatch. El «cerebro» del proyecto es el microcontrolador ESP8266-12F. Podemos programarlo con el IDE de Arduino y es capaz de conectarse a Internet.
Para alimentar al microcontrolador y al resto de componentes hemos utilizado una batería de litio de 250 mAh. Como este tipo de batería es muy delicada, la PCB cuenta con un chip TP4056 para cargarla. Además como el microcontrolador trabaja con un máximo de 3,6V y la batería proporciona un voltaje de entre 4,1 y 3,7V, también hemos añadido un regulador de voltaje.
En cuanto a la pantalla, hemos utilizado una pantalla OLED de 0,96 pulgadas y 128×64 píxeles. Como no es táctil, también hemos agregado 3 botones para poder controlar el smartwatch.
Lista de materiales
- PCB del smartwatch
- Microcontrolador ESP8266-12F
- Pantalla OLED 0,96″ de 128×64 píxeles
- Chip cargador de baterías TP4056
- Chip regulador de voltaje de 3,3V SOT-23-5
- Interruptor PCM12SMTR
- Micro USB Molex 47346-0001
- 3 x Botón PTS525 SM15 SMTR2 LFS
- Zumbador Piezoeléctrico Murata PKMCS0909E4000-R1
- 2 x Condensador cerámico 0805 de 1uF
- 3 x Condensador cerámico 0805 de 10uF
- 2 x LED 0805
- 2 x Botón PTS810 SJM 250 SMTR LFS
- Batería de Litio de 250 mAh
- 4 x Resistencias 10K 0805
- 2 x Resistencias 1K 0805
- 1 x Resistencia 1,2K 0805
- Adaptador USB a TTL FT232RL
Esquema eléctrico del smartwatch

Por si tienes curiosidad de saber como es el esquema eléctrico del smartwatch aquí te lo dejamos. Aún así ya sabes que no lo necesitas porque te hemos proporcionado la carpeta Gerber para que puedas encargar tu PCB directamente.
Para hacer las conexiones hemos seguido el datasheet de cada componente. Si encuentras cualquier error, te agradeceríamos que te pongas en contacto con nosotros para comentárnoslo.
Código del smartwatch
El smartwatch es un proyecto que podríamos desarrollar durante años. Por ahora nos hemos centrado en el hardware. Sin embargo, hemos desarrollado 3 programas diferentes para testear que los diferentes componentes del reloj funcionan. El primer programa hace que se encienda el LED integrado de la ESP8266-12F al pulsar uno de los botones de control del smartwatch.
int led = 2;
int button = 14;
void setup() {
// put your setup code here, to run once:
pinMode(led, OUTPUT);
pinMode(button, INPUT_PULLUP);
}
void loop() {
// put your main code here, to run repeatedly:
if(digitalRead(button)){
digitalWrite(led, HIGH);
}
else{
digitalWrite(led, LOW);
}
delay(100);
}
El segundo programa es un código de ejemplo de la librería Adafruit_SSD1306.h. Nos sirve para probar si la pantalla OLED funciona correctamente. Para poder utilizar este código y el siguiente primero deberás instalar las librerías utilizadas.
#include
#include
#include
#include
#define SCREEN_WIDTH 128 // OLED display width, in pixels
#define SCREEN_HEIGHT 64 // OLED display height, in pixels
// Declaration for an SSD1306 display connected to I2C (SDA, SCL pins)
// The pins for I2C are defined by the Wire-library.
// On an arduino UNO: A4(SDA), A5(SCL)
// On an arduino MEGA 2560: 20(SDA), 21(SCL)
// On an arduino LEONARDO: 2(SDA), 3(SCL), ...
#define OLED_RESET -1 // Reset pin # (or -1 if sharing Arduino reset pin)
#define SCREEN_ADDRESS 0x3C ///< See datasheet for Address; 0x3D for 128x64, 0x3C for 128x32
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);
#define NUMFLAKES 10 // Number of snowflakes in the animation example
#define LOGO_HEIGHT 16
#define LOGO_WIDTH 16
static const unsigned char PROGMEM logo_bmp[] =
{ 0b00000000, 0b11000000,
0b00000001, 0b11000000,
0b00000001, 0b11000000,
0b00000011, 0b11100000,
0b11110011, 0b11100000,
0b11111110, 0b11111000,
0b01111110, 0b11111111,
0b00110011, 0b10011111,
0b00011111, 0b11111100,
0b00001101, 0b01110000,
0b00011011, 0b10100000,
0b00111111, 0b11100000,
0b00111111, 0b11110000,
0b01111100, 0b11110000,
0b01110000, 0b01110000,
0b00000000, 0b00110000 };
void setup() {
Serial.begin(9600);
//pinMode(2, INPUT_PULLUP);
// SSD1306_SWITCHCAPVCC = generate display voltage from 3.3V internally
if(!display.begin(SSD1306_SWITCHCAPVCC, SCREEN_ADDRESS)) {
Serial.println(F("SSD1306 allocation failed"));
for(;;); // Don't proceed, loop forever
}
// Show initial display buffer contents on the screen --
// the library initializes this with an Adafruit splash screen.
display.display();
delay(2000); // Pause for 2 seconds
// Clear the buffer
display.clearDisplay();
// Draw a single pixel in white
display.drawPixel(10, 10, SSD1306_WHITE);
// Show the display buffer on the screen. You MUST call display() after
// drawing commands to make them visible on screen!
display.display();
delay(2000);
// display.display() is NOT necessary after every single drawing command,
// unless that's what you want...rather, you can batch up a bunch of
// drawing operations and then update the screen all at once by calling
// display.display(). These examples demonstrate both approaches...
testdrawline(); // Draw many lines
testdrawrect(); // Draw rectangles (outlines)
testfillrect(); // Draw rectangles (filled)
testdrawcircle(); // Draw circles (outlines)
testfillcircle(); // Draw circles (filled)
testdrawroundrect(); // Draw rounded rectangles (outlines)
testfillroundrect(); // Draw rounded rectangles (filled)
testdrawtriangle(); // Draw triangles (outlines)
testfilltriangle(); // Draw triangles (filled)
testdrawchar(); // Draw characters of the default font
testdrawstyles(); // Draw 'stylized' characters
testscrolltext(); // Draw scrolling text
testdrawbitmap(); // Draw a small bitmap image
// Invert and restore display, pausing in-between
display.invertDisplay(true);
delay(1000);
display.invertDisplay(false);
delay(1000);
testanimate(logo_bmp, LOGO_WIDTH, LOGO_HEIGHT); // Animate bitmaps
}
void loop() {
}
void testdrawline() {
int16_t i;
display.clearDisplay(); // Clear display buffer
for(i=0; i=0; i-=4) {
display.drawLine(0, display.height()-1, display.width()-1, i, SSD1306_WHITE);
display.display();
delay(1);
}
delay(250);
display.clearDisplay();
for(i=display.width()-1; i>=0; i-=4) {
display.drawLine(display.width()-1, display.height()-1, i, 0, SSD1306_WHITE);
display.display();
delay(1);
}
for(i=display.height()-1; i>=0; i-=4) {
display.drawLine(display.width()-1, display.height()-1, 0, i, SSD1306_WHITE);
display.display();
delay(1);
}
delay(250);
display.clearDisplay();
for(i=0; i0; i-=3) {
// The INVERSE color is used so circles alternate white/black
display.fillCircle(display.width() / 2, display.height() / 2, i, SSD1306_INVERSE);
display.display(); // Update screen with each newly-drawn circle
delay(1);
}
delay(2000);
}
void testdrawroundrect(void) {
display.clearDisplay();
for(int16_t i=0; i0; i-=5) {
// The INVERSE color is used so triangles alternate white/black
display.fillTriangle(
display.width()/2 , display.height()/2-i,
display.width()/2-i, display.height()/2+i,
display.width()/2+i, display.height()/2+i, SSD1306_INVERSE);
display.display();
delay(1);
}
delay(2000);
}
void testdrawchar(void) {
display.clearDisplay();
display.setTextSize(1); // Normal 1:1 pixel scale
display.setTextColor(SSD1306_WHITE); // Draw white text
display.setCursor(0, 0); // Start at top-left corner
display.cp437(true); // Use full 256 char 'Code Page 437' font
// Not all the characters will fit on the display. This is normal.
// Library will draw what it can and the rest will be clipped.
for(int16_t i=0; i<256; i++) {
if(i == '\n') display.write(' ');
else display.write(i);
}
display.display();
delay(2000);
}
void testdrawstyles(void) {
display.clearDisplay();
display.setTextSize(1); // Normal 1:1 pixel scale
display.setTextColor(SSD1306_WHITE); // Draw white text
display.setCursor(0,0); // Start at top-left corner
display.println(F("Hello, world!"));
display.setTextColor(SSD1306_BLACK, SSD1306_WHITE); // Draw 'inverse' text
display.println(3.141592);
display.setTextSize(2); // Draw 2X-scale text
display.setTextColor(SSD1306_WHITE);
display.print(F("0x")); display.println(0xDEADBEEF, HEX);
display.display();
delay(2000);
}
void testscrolltext(void) {
display.clearDisplay();
display.setTextSize(2); // Draw 2X-scale text
display.setTextColor(SSD1306_WHITE);
display.setCursor(10, 0);
display.println(F("scroll"));
display.display(); // Show initial text
delay(100);
// Scroll in various directions, pausing in-between:
display.startscrollright(0x00, 0x0F);
delay(2000);
display.stopscroll();
delay(1000);
display.startscrollleft(0x00, 0x0F);
delay(2000);
display.stopscroll();
delay(1000);
display.startscrolldiagright(0x00, 0x07);
delay(2000);
display.startscrolldiagleft(0x00, 0x07);
delay(2000);
display.stopscroll();
delay(1000);
}
void testdrawbitmap(void) {
display.clearDisplay();
display.drawBitmap(
(display.width() - LOGO_WIDTH ) / 2,
(display.height() - LOGO_HEIGHT) / 2,
logo_bmp, LOGO_WIDTH, LOGO_HEIGHT, 1);
display.display();
delay(1000);
}
#define XPOS 0 // Indexes into the 'icons' array in function below
#define YPOS 1
#define DELTAY 2
void testanimate(const uint8_t *bitmap, uint8_t w, uint8_t h) {
int8_t f, icons[NUMFLAKES][3];
// Initialize 'snowflake' positions
for(f=0; f< NUMFLAKES; f++) {
icons[f][XPOS] = random(1 - LOGO_WIDTH, display.width());
icons[f][YPOS] = -LOGO_HEIGHT;
icons[f][DELTAY] = random(1, 6);
Serial.print(F("x: "));
Serial.print(icons[f][XPOS], DEC);
Serial.print(F(" y: "));
Serial.print(icons[f][YPOS], DEC);
Serial.print(F(" dy: "));
Serial.println(icons[f][DELTAY], DEC);
}
for(;;) { // Loop forever...
display.clearDisplay(); // Clear the display buffer
// Draw each snowflake:
for(f=0; f< NUMFLAKES; f++) {
display.drawBitmap(icons[f][XPOS], icons[f][YPOS], bitmap, w, h, SSD1306_WHITE);
}
display.display(); // Show the display buffer on the screen
delay(200); // Pause for 1/10 second
// Then update coordinates of each flake...
for(f=0; f< NUMFLAKES; f++) {
icons[f][YPOS] += icons[f][DELTAY];
// If snowflake is off the bottom of the screen...
if (icons[f][YPOS] >= display.height()) {
// Reinitialize to a random position, just off the top
icons[f][XPOS] = random(1 - LOGO_WIDTH, display.width());
icons[f][YPOS] = -LOGO_HEIGHT;
icons[f][DELTAY] = random(1, 6);
}
}
}
}
El último código es la primera versión del programa final del smartwatch. Con el nos conectamos a una base de datos creada en Firbase, de la cual recibimos diferente tipo de información, como por ejemplo la hora y si alguien nos está llamando al móvil o no. Hay ciertas variables que están vacías porque guardan información personal, como por ejemplo el nombre y la contraseña del Wifi o los datos de acceso a la base da datos de Firebase.
#include
#include
#include
#include
#include
#include
//Wifi y Firebase
#define FIREBASE_HOST "" //host name of the firebase
#define FIREBASE_AUTH "" //token for u to connect this particular with the firebase
#define WIFI_SSID ""
#define WIFI_PASSWORD ""
//Pantalla OLED
#define SCREEN_WIDTH 128 // OLED display width, in pixels
#define SCREEN_HEIGHT 64 // OLED display height, in pixels
#define OLED_RESET -1 // Reset pin # (or -1 if sharing Arduino reset pin)
#define SCREEN_ADDRESS 0x3C ///< See datasheet for Address; 0x3D for 128x64, 0x3C for 128x32
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);
void setup() {
// put your setup code here, to run once:
Serial.begin(9600);
pinMode(button1, INPUT_PULLUP);
pinMode(button2, INPUT_PULLUP);
connectToWifi();
Firebase.begin(FIREBASE_HOST, FIREBASE_AUTH);
// SSD1306_SWITCHCAPVCC = generate display voltage from 3.3V internally
if(!display.begin(SSD1306_SWITCHCAPVCC, SCREEN_ADDRESS)) {
Serial.println(F("SSD1306 allocation failed"));
for(;;); // Don't proceed, loop forever
}
}
void loop() {
// put your main code here, to run repeatedly:
if(WiFi.status() != WL_CONNECTED){
connectToWifi();
}
String shour = Firebase.getString("Hora");
String sminute = Firebase.getString("Minuto");
String scall = Firebase.getString("Llamada");
int hour = shour.toInt();
int minute = sminute.toInt();
int call = scall.toInt();
display.clearDisplay();
if(call != 0){
incomingCall();
display.display();
}
else if(date==false){
drawTheTime(hour, minute);
}
else if(date==true){
drawDate();
}
display.display();
delay(100);
}
void connectToWifi(){
WiFi.begin(WIFI_SSID, WIFI_PASSWORD);
Serial.print("connecting");
while (WiFi.status() != WL_CONNECTED) {
Serial.print(".");
delay(500);
}
Serial.println();
Serial.print("connected: ");
Serial.println(WiFi.localIP());
}
void drawTheTime(int hour, int minute){
display.setTextSize(4);
display.setTextColor(SSD1306_WHITE); // Draw white text
display.setCursor(5,20);
if(hour<10) display.print("0");
display.print(hour);
display.print(":");
if(minute<10) display.print("0");
display.print(minute);
}
void incomingCall(){
display.setTextSize(2);
display.setTextColor(SSD1306_WHITE); // Draw white text
display.setCursor(26, 10);
display.print("Llamada");
display.setCursor(19, 30);
display.print("entrante");
}
void drawDate(){
display.setTextSize(3);
display.setTextColor(SSD1306_WHITE); // Draw white text
display.setCursor(0,20);
display.print(20);
display.print("/");
display.print(03);
display.print("/");
display.print(03);
}