Un altro telecomando TV per smartphone Android

Sì, l’ho fatto ancora. In un mio precedente articolo descrivevo come costruire una interfaccia da collegare al telefonino per poterlo usare come telecomando per il TV. L’applicazione Android permetteva, in quel caso, di controllare un TV di modello Sony Bravia, che utilizza un protocollo a infrarossi di tipo NEC. Ora non ho più quel televisore ed ho un piccolo Telefunken, che però usa un protocollo a infrarossi di tipo RC5, che è completamente diverso. Allora ho scritto una nuova applicazione, mantenendo sempre la stessa interfaccia hardware. E’ da notare che la distanza di funzionamento di questa versione è inferiore alla precedente. Purtroppo, la frequenza generata col sistema descritto nella pagina citata a inizio articolo è sempre di 38.4 KHz che è molto vicina ai 38 KHz “standard” del protocollo NEC, ma purtroppo è un po’ distante dai 36 KHz canonici del protocollo Philips RC5. Comunque, funziona lo stesso, dato che il notch filter del ricevitore non è proprio così stretto in frequenza, ma ha una “campana” ampia a sufficienza per tollerare questo errore in frequenza… Per la corretta temporizzazione delle fasi di “pieno / vuoto” ho utilizzato i pattern che derivano da 11 bytes di valore 0x5B per il “pieno” (burst di frequenza) e 11 byte di valore 0x00 per il “vuoto” (pausa). Durante la trasmissione di 11 bytes di valore 0x00 si avranno comunque dei “glitches” di 8.68 uS attivi alti, perché questi sono gli “stop bits” della trasmissione che non posso eliminare. Per fortuna, i circuiti di ricezione presenti nei TV hanno una funzione di “filtro” che elimina questi disturbi. I filtri sono indispensabili perché alcuni tipi di illuminazione domestica (specie i tubi al neon) emettono disturbi nello spettro dell’infrarosso con una potenza notevole (provare per credere). Senza una “ripulita” del segnale, i telecomandi sarebbero poco efficienti. Comunque, i prossimi controlli remoti per TV saranno via radio, con i soliti 2.4GHz che ci stanno rendendo le case simili a forni a microonde… :)

Perché il tutto funzioni, è necessario che la versione Android sia in grado di gestire le periferiche USB, naturalmente. Nella figura sottostante potete vedere lo screenshot del programma in funzione.

Il programma in funzione: iRemUSB-RC5

Il programma in funzione: iRemUSB-RC5

La App iRemUSB-RC5.apk è scaricabile gratuitamente dalla mia pagina di Google Play.

Recupero dei miei vecchi siti web

Ho deciso di inserire nel blog alcune parti di miei vecchi siti web. Lo stile di impaginazione sarà differente perché in precedenza usavo solo un text editor per scrivere in html le mie pagine web, mentre ora uso WordPress. Comunque, credo che i contenuti siano più importanti della forma.

A mano a mano che aggiungerò materiale, modificherò il menù principale in modo da avere un collegamento diretto con le nuove pagine.

21 Agosto 2015: aggiunta sezione microcontrollore Zilog Z8
22 Agosto 2015: aggiunta sezione microcontrollore STMicroelectronics ST6

“Pedrone” Part #4 – drive it using web browser and WiFi

This is the last step. “Pedrone” is now complete. Please, remember that this is just a toy, you can’t use it to discover any sort of life on Mars ! I wrote a small Android application that acts like a mini web-server receiving commands from a standard web browser. I realized a demo video, capturing the screen while driving Pedrone with my web browser (SeaMonkey). Here it is:


pedrone3 di robotop

The Android application is relatively simple. It receives commands as “get” requests and, depending on the parameters, outputs the sound sequence to communicate with the microcontroller. Acting this way, the Pedrone executes the requested movement. After moving, the application takes a picture of the new situation (position) and gives the answer to the browser, that is still waiting. The process is a little bit slow, ’cause the full path for the command is: 1) you click a “button” on the navigation map on the browser 2) an internet request is sent to the mini webserver 3) the webserver executes the command and send a sound sequence to the microcontroller 4) the microcontroller decodes the command and runs the motors for half second (short move) or one second (long move) 5) the application takes a picture 6) the application sends back an answer to web browser, changing the name of the image to force the browser to reload (no-cache) 7) the browser sends a new “get” to download the updated image. A screenshot of the Android application is here:

Screenshot of the Android App Pedrone-Rx3 (click to enlarge)

Screenshot of the Android App Pedrone-Rx3 (click to enlarge)

If you want, you can freely download and install this App from the Google Play Store.

For the mechanical part, I used the Lego Technic (my “children” have given me when they left university) with addition of some small screws and strong glue to create the adapters from Servo-motors to wheels and from structure to Servo-motors. In the following picture you can see how it was realized.

Three different views of "Pedrone" (click to enlarge)

Three different views of “Pedrone” (click to enlarge)…

The microcontroller circuit is not changed, look at Part #2 for complete schematics. The firmware has some changes. You can download the C source and the compiled HEX packaged in a zip file from the link : pedrone3x.zip ; the password to unzip is : eficara. Remember to set the correct fuses configuration when burning the micro. You can read such configuration in the first lines of the C source file. Here is the full C source list :

////////////////////////////////////////////////////////////////////////////////
// FW_VERSION  150704 (yymmdd)
//
// FUSES CONFIGURATION: EXT=0xFF HIGH=0xCD LOW=0xCC LOCK=0xFF

// Compiler includes
// -----------------
#include <iom48.h>
#include <ina90.h>

// Application defines
// -------------------
#define BYTE    unsigned char
#define WORD    unsigned int
#define LONG    unsigned long

#define STOP    22      // Pulse width for motor STOP
#define CW      15      // Pulse width for motor run fast ClockWise
#define SCW     21      // Pulse width for motor run slow ClockWise
#define CCW     30      // Pulse width for motor run fast CounterClocWise
#define SCCW    23      // Pulse width for motor run slow CounterClocWise
#define MS1500  90      // Num. of timer ticks for 1.5 seconds (rounded)
#define MS500   30      // Num. of timer ticks for 0.5 seconds (rounded)
#define RIGHT   OCR0A   // Define RIGHT motor OutCompare
#define LEFT    OCR0B   // Define LEFT motor OutCompare

// I/O signals
// -----------
#define TEST    PORTB_Bit2  // Test output
#define LEDR    PORTD_Bit7  // RED led

// Global variables
// ----------------
volatile BYTE tdelay; // delay timer for last input capture 
volatile BYTE optime; // operation timer 16.384mS per step
volatile BYTE cap; // capure flag, set in irq, reset in main
BYTE tstart; // counter for "start" signal
BYTE t0; // counter for "0" signal
BYTE t1; // counter for "1" signal
BYTE mask; // mask for bits decoded from tones
BYTE cmd; // command received
WORD samples[2]; // last two samples from input capture
WORD diff; // difference accumulator

// Global read-only arrays
// -----------------------
flash BYTE author[] = "(c) E.Ficara 2015\r\n";

////////////////////////////////////////////////////////////////////////////////
// Debug
// -----
void DebugBurst(void) // burst on TEST pin (executed with irq disabled)
{
    for(GPIOR1 = 0; GPIOR1 < 100; ++GPIOR1)
        {
        TEST ^= 1;
        for(GPIOR0 = 0; GPIOR0 < 200; ++GPIOR0);
        }
}

void SetWatchdog(BYTE to) // change the watchdog timeout (irq must be disabled)
{
    __watchdog_reset();
    MCUSR &= ~(1<<WDRF); // Clear WDRF in MCUSR
    WDTCSR |= (1<<WDCE); // enable write
    WDTCSR = to; // 00x0.exxx set enable flag and new watchdog time
}

////////////////////////////////////////////////////////////////////////////////
// Interrupts
// ----------
#pragma vector = TIMER0_OVF_vect
__interrupt void TIMER0_OVF_i(void) // TMR0 Overflow (16.384 mS tick)
{  
    if(optime) // if operation timer
        --optime; // decrement timer
    if(tdelay) // if delay time after capture running
        {
        if(--tdelay == 0) // if time finished
            {
            tstart = 0; // reset "start" time counter
            t0 = 0; // reset "0" time counter
            t1 = 0; // reset "1" time counter
            samples[0] = 0; // clear first sample
            samples[1] = 0; // clear second sample
            }
        }
}

#pragma vector = TIMER1_CAPT_vect
__interrupt void TIMER1_CAPT_i(void) // TMR1 Input Capture
{  
    samples[0] = samples[1]; // shift first sample
    samples[1] = ICR1; // get actual Input Capure value (irq disabled 16bit rd)
    tdelay = 3; // restart delay time after last input capture (3*16.384mS)
    cap = 1; // set new capture flag
}

////////////////////////////////////////////////////////////////////////////////
// Main Program
// ------------
//
void DecodeTone(void) // decoding a tone
{
    if(samples[1] > samples[0]) // if last sample higher that previous
        diff = samples[1] - samples[0]; // difference of last 2 captures
    else // if last sample lower than the previous
        diff = samples[0] - samples[1]; // difference of last 2 captures

    if(diff >= (52-5) && diff <= (52+5)) // if F=1.2KHz (T=833uS 52*16uS)
        {
        if(++tstart >= 48) // increment tstart counter, if > 40mS (48*0.833mS)
            {
            LEDR = 0; // turn ON Red led
            __disable_interrupt(); // global interrupts disable
            optime = MS1500; // set operation time for command
            __enable_interrupt(); // global interrupts enable
            mask = 0x08; // init mask for tone bits
            cmd = 0; // reset incoming command
            tstart = 0; // reset counter
            }
        return; // exit here
        }

    if(optime == 0) // if operation time elapsed
        return; // exit here

    // the following code is executed only while optime != 0

    if(mask == 0) // if full 4 bit data received
        {
        switch(cmd) // select action to do
            {
            case 0x09: // MOT-Right Fast Forward (pedrone move left)
                RIGHT = CW;
                break;
            case 0x0A: // MOT-Right Slow Forward (pedrone move left)
                RIGHT = SCW;
                break;
            case 0x04: // (forward) MOT-both Fast Forward
                LEFT = CCW;
                RIGHT = CW; // left and right motor inverted !
                break;
            case 0x07: // MOT-both Slow Forward
                LEFT = SCCW;
                RIGHT = SCW; // left and right motor inverted !
                break;
            case 0x0F: // MOT-both Slow Backward
                LEFT = SCW;
                RIGHT = SCCW; // left and right motor inverted !
                break;
            case 0x0C: // MOT-both Fast Backward
                LEFT = CW;
                RIGHT = CCW; // left and right motor inverted !
                break;
            case 0x0D: // MOT-Left Slow Forward (pedrone move right)
                LEFT = SCCW;
                break;
            case 0x0E: // MOT-Left Fast Forward (pedrone move right)
                LEFT = CCW;
                break;
            default: // unknown command
                __disable_interrupt(); // global interrupts disable
                optime = 0; // operation time terminated
                __enable_interrupt(); // global interrupts enable
            }
        if(optime) // if valid command
            {
            if(cmd == 0x0A || cmd == 0x07 || cmd == 0x0F || cmd == 0x0D) // if move slow
                {
                __disable_interrupt(); // global interrupts disable
                optime = MS500; // reduce operation time for command
                __enable_interrupt(); // global interrupts enable
                }
            }
        return; // exit here
        }

    if(diff >= (39-4) && diff <= (39+4)) // if F=1.6KHz (T=625uS 39*16uS)
        {
        if(++t0 >= 64) // increment tstart counter, if > 40mS (64*0.625mS)
            {
            cmd |= mask; // '1' bit received
            mask >>= 1; // shift right mask
            t0 = 0; // reset counter
            }
        return; // exit here
        }

    if(diff >= (78-7) && diff <= (78+7)) // if F=800Hz (T=1250uS 78*16uS)
        {
        if(++t1 >= 32) // increment tstop counter, if > 40mS (32*1.6mS)
            {
            mask >>= 1; // shift right mask ('0' bit received)
            t1 = 0; // reset counter
            }
        }
}

void main(void)
{ 
    CLKPR = 0x80; // enable writing to CLKPR register
    CLKPR = 0; // clock frequency = Fosc/1 (4 MHz)
    
// Pin    10       9      19      18      17      16      15      14
//      --------------------------------------------------------------
// Port  PB.7    PB.6    PB.5    PB.4    PB.3    PB.2    PB.1    PB.0
// Func [XTAL2] [XTAL1]  [SCK]  [MISO]  [OC2A]  [OC1B]  [OC1A]  [ICP1]
// Func [TOSC2] [TOSC1]                 [MOSI]   [/SS]          [CLKO]
// I/O                                           test            FrqIn                  
    PORTB = 0xFF;   // 1111.1111
    DDRB = 0xFE;    // 1111.1110 (0=input)

// Pin             1      28      27      26      25      24      23
//      --------------------------------------------------------------
// Port          PC.6    PC.5    PC.4    PC.3    PC.2    PC.1    PC.0
// Func         [/RES]  [ADC5]  [ADC4]  [ADC3]  [ADC2]  [ADC1]  [ADC0]
// Func                  [SCL]   [SDA]
// I/O                                                                 
    PORTC = 0xFF;   // 1111.1111
    DDRC = 0xBF;    // 1011.1111 (0=input)

// Pin    13      12      11       6       5       4       3       2
//      --------------------------------------------------------------
// Port  PD.7    PD.6    PD.5    PD.4    PD.3    PD.2    PD.1    PD.0
// Func [AIN1]  [AIN0]   [T1]    [T0]   [INT1]  [INT0]   [TXD]   [RXD] 
// Func         [OC0A]  [OC0B]  [XCK]   [OC2B]
// I/O   LEDR    MOTL    MOTR                                             
    PORTD = 0xFF;   // 1111.1111
    DDRD = 0xFF;    // 1111.1111 (0=input)

//  Pin    7       8      20      21      22
//      -------------------------------------
//  Func [VCC]   [GND]  [AVCC]  [AREF]  [GND]  

    OCR0A = STOP; // set pulse duration (OCR0A*64 uS) every 16,384 mS
    OCR0B = STOP; // set pulse duration (OCR0B*64 uS) every 16,384 mS
    TCCR0A = 0xA3; // 1010.0011 TMR0 is Fast PWM mode, 8 bits
    TCCR0B = 0x04; // 0000.0100 TMR0 clock is IOclk/256 = 15625 Hz (64uS)
    TIMSK0 = (1<<TOIE0); // enable TMR0 Overflow interrupt

    TCCR1B = 0x83; // 1000.0011 activate IC Noise Canceler, IOclk/64 (16uS)
    TIMSK1 = (1<<ICIE1); // enable Input Capture interrupt

    GPIOR1 = MCUSR; // read MCU reset status in GPIO Register[1]
    MCUSR = 0; // clear flags (8 Watchdog, 4 Brownout, 2 External, 1 Power-on)

    // enable watchdog, timeout = 2 sec
    SetWatchdog((1<<WDE) | (1<<WDP2) | (1<<WDP1) | (1<<WDP0));
    if(GPIOR1 & 8) // if micro was been reset from watchdog
        DebugBurst(); // burst on TEST output

    __enable_interrupt(); // global interrupts enable
    for(;;) // main loop  
        {    
        __watchdog_reset(); // kick the watchdog
        if(optime == 0) // if operative time elapsed
            {
            LEDR = 1; // turn OFF Red led
            OCR0A = STOP; // MOT-Left Stop
            OCR0B = STOP; // MOT-Right Stop
            }
        if(cap) // if new capture flag set
            {
            __disable_interrupt(); // global interrupts enable
            cap = 0; // reset flag
            __enable_interrupt(); // global interrupts enable
            DecodeTone();
            }
        } 
}

Finally, note that you can drive the “Pedrone” on your local net using a PC / Tablet / Smartphone connected on the same WiFi subnet, but you can also control it from everywhere in the world if you set your modem / router with the following rules:
1) force the router to assign a fixed IP to the MAC address of the WiFi interface of the smartphone you use as “receiver”, es: assign local IP 192.168.1.6 to MAC address 11:22:33:AA:BB:CC; this way the “Pedrone” smartphone will have every time the same IP on your local network.
2) modify the router’s firewall rules to accept incoming connections to port 8080, redirecting the requests to the IP assigned to the “receiver” (192.168.1.6 in the example)
3) get a free account on some Dynamic Dns service, as no-ip or dyndns or others and set the relative username and password in the router’s Dynamic Dns settings.
After this, you can reach “Pedrone” from everywhere in the world (if there is an internet connection !) with a standard web browser, writing the address http://my-assigned-dynamic-ip.com:8080 in the navigation bar.

Historical notes:
The idea for this project was born in August 2009, when I was in France for a sabbatic time and my smartphone was equipped with WM6. I realized a circuit and a couple of applications to test the motor driving using DTMF tones. After that, I published a video on YouTube describing the general concept. After some months I removed the video ’cause Google acquired YouTube. I think that too much power in the hands of a single subject is very dangerous, but now that all the world is fulfilled by Android devices, it’s impossible to stop the “Masters of Universe”… Anyway, the original video is now stored on “DailyMotion”. If you want, take a look here:


dtmf_remote di robotop

“Pedrone” Part #3 – 16 commands using sound protocol

This is the third step. The previous version of the firmware was able to recognize three tones and the demo used a single tone for a unique operation. Now, I used a “protocol” to send multiple tones and create 16 possible commands. I used Audacity, a fantastic sound editor, to create the sound sequences. Here is an example:

The sound file for command 1011

The sound file for command 1011

A “command” is composed by a “start” tone (1200Hz) and four bits starting from the most significative. The “ones” are 1600Hz, while the “zeroes” are 800Hz. In the picture is represented the code 1011. A full “transmission” is always 500mS. Every sound has 50mS duration, followed by 50mS pause. For sound file output, I used the format “ogg” because is compatible with Android and iOS smartphones.

To test the firmware, I wrote a small application on Android smartphone. This is a screenshot of the application running:

Screenshot of Pedrone-Rx2 application

Screenshot of Pedrone-Rx2 application

The three buttons in the middle are just for compatibility with the old version of the firmware, while the cursors at the right and at the left are the controls for the motors. There are five positions for each motor : Fast Forward, Slow Forward, Stop, Slow Backward, Fast Backward. So, at this moment, I used only 10 of the 16 commands available. The App was not published on Play Store because is just for testing purposes, but if you want, you can download it and install from this link: Pedrone-rx2.apk .

The schematic of electronic circuit has not been modified, while the firmware has been improved. If you want to load the C source and the HEX file to burn the micro, both of them are packaged in a zip file that can be downloaded from this link : Pedrone3.zip (use password eficara to unzip) . Here is the full list of the C source file ; the hex comes from compilation with IAR V5.50.0. To burn the micro, use the fuses configuration that is described in the first lines of the source.

////////////////////////////////////////////////////////////////////////////////
// FW_VERSION  150627 (yymmdd)
//
// FUSES CONFIGURATION: EXT=0xFF HIGH=0xCD LOW=0xCC LOCK=0xFF

// Compiler includes
// -----------------
#include <iom48.h>
#include <ina90.h>

// Application defines
// -------------------
#define BYTE    unsigned char
#define WORD    unsigned int
#define LONG    unsigned long

#define STOP    22  // Pulse width for motor STOP
#define CW      15  // Pulse width for motor run fast ClockWise
#define SCW     21  // Pulse width for motor run slow ClockWise
#define CCW     30  // Pulse width for motor run fast CounterClocWise
#define SCCW    23  // Pulse width for motor run slow CounterClocWise
#define ONESEC  61  // num. of timer ticks for 1 second
#define TWOSEC  122 // num. of timer ticks for 2 seconds

// I/O signals
// -----------
#define TEST    PORTB_Bit2  // Test output
#define LEDR    PORTD_Bit7  // RED led

// Global variables
// ----------------
volatile BYTE tdelay; // delay timer for last input capture 
volatile BYTE optime; // operation timer 16.384mS per step
volatile BYTE cap; // capure flag, set in irq, reset in main
BYTE tstart; // counter for "start" signal
BYTE t0; // counter for "0" signal
BYTE t1; // counter for "1" signal
BYTE mask; // mask for bits decoded from tones
BYTE cmd; // command received
WORD samples[2]; // last two samples from input capture
WORD diff; // difference accumulator

// Global read-only arrays
// -----------------------
flash BYTE author[] = "(c) E.Ficara 2015\r\n";

////////////////////////////////////////////////////////////////////////////////
// Debug
// -----
void DebugBurst(void) // burst on TEST pin (executed with irq disabled)
{
    for(GPIOR1 = 0; GPIOR1 < 100; ++GPIOR1)
        {
        TEST ^= 1;
        for(GPIOR0 = 0; GPIOR0 < 200; ++GPIOR0);
        }
}

void SetWatchdog(BYTE to) // change the watchdog timeout (irq must be disabled)
{
    __watchdog_reset();
    MCUSR &= ~(1<<WDRF); // Clear WDRF in MCUSR
    WDTCSR |= (1<<WDCE); // enable write
    WDTCSR = to; // 00x0.exxx set enable flag and new watchdog time
}

////////////////////////////////////////////////////////////////////////////////
// Interrupts
// ----------
#pragma vector = TIMER0_OVF_vect
__interrupt void TIMER0_OVF_i(void) // TMR0 Overflow (16.384 mS tick)
{  
    if(optime) // if operation timer
        --optime; // decrement timer
    if(tdelay) // if delay time after capture running
        {
        if(--tdelay == 0) // if time finished
            {
            tstart = 0; // reset "start" time counter
            t0 = 0; // reset "0" time counter
            t1 = 0; // reset "1" time counter
            samples[0] = 0; // clear first sample
            samples[1] = 0; // clear second sample
            }
        }
}

#pragma vector = TIMER1_CAPT_vect
__interrupt void TIMER1_CAPT_i(void) // TMR1 Input Capture
{  
    samples[0] = samples[1]; // shift first sample
    samples[1] = ICR1; // get actual Input Capure value (irq disabled 16bit rd)
    tdelay = 3; // restart delay time after last input capture (3*16.384mS)
    cap = 1; // set new capture flag
}

////////////////////////////////////////////////////////////////////////////////
// Main Program
// ------------
//
void DecodeTone(void) // decoding a tone
{
    if(samples[1] > samples[0]) // if last sample higher that previous
        diff = samples[1] - samples[0]; // difference of last 2 captures
    else // if last sample lower than the previous
        diff = samples[0] - samples[1]; // difference of last 2 captures

    if(diff >= (52-5) && diff <= (52+5)) // if F=1.2KHz (T=833uS 52*16uS)
        {
        if(++tstart >= 48) // increment tstart counter, if > 40mS (48*0.833mS)
            {
            LEDR = 0; // turn ON Red led
            __disable_interrupt(); // global interrupts disable
            optime = ONESEC; // set operation time for command
            __enable_interrupt(); // global interrupts enable
            mask = 0x08; // init mask for tone bits
            cmd = 0; // reset incoming command
            tstart = 0; // reset counter
            }
        return; // exit here
        }

    if(optime == 0) // if operation time elapsed
        return; // exit here

    // the following code is executed only while optime != 0

    if(mask == 0) // if full 4 bit data received
        {
        switch(cmd) // select action to do
            {
            case 0x01: // MOT-Left Fast Forward
                OCR0A = CW;
                break;
            case 0x02: // MOT-Left Slow Forward
                OCR0A = SCW;
                break;
            case 0x03: // MOT-Left Stop
                OCR0A = STOP;
                break;
            case 0x05: // MOT-Left Slow Backward
                OCR0A = SCCW;
                break;
            case 0x06: // MOT-Left Fast Backward
                OCR0A = CCW;
                break;

            case 0x09: // MOT-Right Fast Forward
                OCR0B = CW;
                break;
            case 0x0A: // MOT-Right Slow Forward
                OCR0B = SCW;
                break;
            case 0x0B: // MOT-Right Stop
                OCR0B = STOP;
                break;
            case 0x0D: // MOT-Right Slow Backward
                OCR0B = SCCW;
                break;
            case 0x0E: // MOT-Right Fast Backward
                OCR0B = CCW;
                break;
            }
        __disable_interrupt(); // global interrupts disable
        optime = 0; // operation time terminated
        __enable_interrupt(); // global interrupts enable
        return; // exit here
        }

    if(diff >= (39-4) && diff <= (39+4)) // if F=1.6KHz (T=625uS 39*16uS)
        {
        if(++t0 >= 64) // increment tstart counter, if > 40mS (64*0.625mS)
            {
            cmd |= mask; // '1' bit received
            mask >>= 1; // shift right mask
            t0 = 0; // reset counter
            }
        return; // exit here
        }

    if(diff >= (78-7) && diff <= (78+7)) // if F=800Hz (T=1250uS 78*16uS)
        {
        if(++t1 >= 32) // increment tstop counter, if > 40mS (32*1.6mS)
            {
            mask >>= 1; // shift right mask ('0' bit received)
            t1 = 0; // reset counter
            }
        }
}

void main(void)
{ 
    CLKPR = 0x80; // enable writing to CLKPR register
    CLKPR = 0; // clock frequency = Fosc/1 (4 MHz)
    
// Pin    10       9      19      18      17      16      15      14
//      --------------------------------------------------------------
// Port  PB.7    PB.6    PB.5    PB.4    PB.3    PB.2    PB.1    PB.0
// Func [XTAL2] [XTAL1]  [SCK]  [MISO]  [OC2A]  [OC1B]  [OC1A]  [ICP1]
// Func [TOSC2] [TOSC1]                 [MOSI]   [/SS]          [CLKO]
// I/O                                           test            FrqIn                  
    PORTB = 0xFF;   // 1111.1111
    DDRB = 0xFE;    // 1111.1110 (0=input)

// Pin             1      28      27      26      25      24      23
//      --------------------------------------------------------------
// Port          PC.6    PC.5    PC.4    PC.3    PC.2    PC.1    PC.0
// Func         [/RES]  [ADC5]  [ADC4]  [ADC3]  [ADC2]  [ADC1]  [ADC0]
// Func                  [SCL]   [SDA]
// I/O                                                                 
    PORTC = 0xFF;   // 1111.1111
    DDRC = 0xBF;    // 1011.1111 (0=input)

// Pin    13      12      11       6       5       4       3       2
//      --------------------------------------------------------------
// Port  PD.7    PD.6    PD.5    PD.4    PD.3    PD.2    PD.1    PD.0
// Func [AIN1]  [AIN0]   [T1]    [T0]   [INT1]  [INT0]   [TXD]   [RXD] 
// Func         [OC0A]  [OC0B]  [XCK]   [OC2B]
// I/O   LEDR    MOTL    MOTR                                             
    PORTD = 0xFF;   // 1111.1111
    DDRD = 0xFF;    // 1111.1111 (0=input)

//  Pin    7       8      20      21      22
//      -------------------------------------
//  Func [VCC]   [GND]  [AVCC]  [AREF]  [GND]  

    OCR0A = STOP; // set pulse duration (OCR0A*64 uS) every 16,384 mS
    OCR0B = STOP; // set pulse duration (OCR0B*64 uS) every 16,384 mS
    TCCR0A = 0xA3; // 1010.0011 TMR0 is Fast PWM mode, 8 bits
    TCCR0B = 0x04; // 0000.0100 TMR0 clock is IOclk/256 = 15625 Hz (64uS)
    TIMSK0 = (1<<TOIE0); // enable TMR0 Overflow interrupt

    TCCR1B = 0x83; // 1000.0011 activate IC Noise Canceler, IOclk/64 (16uS)
    TIMSK1 = (1<<ICIE1); // enable Input Capture interrupt

    GPIOR1 = MCUSR; // read MCU reset status in GPIO Register[1]
    MCUSR = 0; // clear flags (8 Watchdog, 4 Brownout, 2 External, 1 Power-on)

    // enable watchdog, timeout = 2 sec
    SetWatchdog((1<<WDE) | (1<<WDP2) | (1<<WDP1) | (1<<WDP0));
    if(GPIOR1 & 8) // if micro was been reset from watchdog
        DebugBurst(); // burst on TEST output

    __enable_interrupt(); // global interrupts enable
    for(;;) // main loop  
        {    
        __watchdog_reset(); // kick the watchdog

        if(optime == 0) // if operative time elapsed
            LEDR = 1; // turn OFF Red led

        if(cap) // if new capture flag set
            {
            __disable_interrupt(); // global interrupts enable
            cap = 0; // reset flag
            __enable_interrupt(); // global interrupts enable
            DecodeTone();
            }
        } 
}

“Pedrone” Part #2 – connecting to smartphone

After the Part #1, with the Servo Motors control program, now I connected a smartphone to the circuit. The software on the phone (in the next future) will do all the Internet job, receiving commands and sending pictures. Anyway, a connection is needed to control the hardware (motors) for moving “Pedrone” via web commands. A very simple solution is to use the headphone jack and send some tones to activate the motors. As first option, I decided for one DTMF tone decoder, but after some considerations, like the absence of noise on direct connection, I decided to use single tones. The hardware is very simple (stereo version is shown, but you just need one channel) :

Particular of the schematic (click to enlarge)

Particular of the schematic (click to enlarge)

This circuit is a mixer of Left and Right audio channels coming from the headphones jack of the smartphone, followed by protection resistor and diodes to safely connect to the ICP1 pin of the micro ATmega48. The ICP1 is the input capture pin of Timer 1 (TMR1). I used such peripheral to measure the period of incoming frequency. There are three frequencies: the first is considered as “start” signal and the other two as “0” and “1”. The protocol isn’t still implemented (will follow soon). At this moment, the test program just recognizes the frequencies and does a programmed action for anyone of them. The frequencies are 800Hz, 1200Hz end 1600Hz. All the tones have 50 mS duration. The complete schematic (including the servo motors outputs) is in the following picture

The complete schematic (a PDF version is in the downloadable project's zip file) - click to enlarge

The complete schematic (a PDF version is in the downloadable project’s zip file) – click to enlarge

If you are interested in the C source list and in the hex file to burn into the micro, you can download both of them (plus the schematic PDF), from this link: Pedrone2.zip
(password for zipped file: eficara)

There is a small video to show the program running. The application for Android smartphone has not been published on Play Store, because it’s just for testing purposes, but if you want you can download the apk directly from this link: Pedrone-rx.apk.


Pedrone part 2 – connecting to smartphone di robotop

This is the C source list for the test program (compiled with IAR V5.50.0):

////////////////////////////////////////////////////////////////////////////////
// FW_VERSION  150617 (yymmdd)
//
// FUSES CONFIGURATION: EXT=0xFF HIGH=0xCD LOW=0xCC LOCK=0xFF

// Compiler includes
// -----------------
#include <iom48.h>
#include <ina90.h>

// Application defines
// -------------------
#define BYTE    unsigned char
#define WORD    unsigned int
#define LONG    unsigned long

#define STOP    22  // Pulse width for motor STOP
#define CW      15  // Pulse width for motor run ClockWise
#define CCW     30  // Pulse width for motor run CounterClocWise
#define ONESEC  61  // num. of timer ticks for 1 second
#define TWOSEC  122 // num. of timer ticks for 2 seconds

// I/O signals
// -----------
#define TEST    PORTB_Bit2  // Test output
#define LEDR    PORTD_Bit7  // RED led

// Global variables
// ----------------
volatile BYTE softt1; // software timer #1 (down counter) 16.384 mS step
volatile BYTE tdelay; // delay timer for last input capture 
volatile BYTE cap; // capure flag, set in irq, reset in main
BYTE tstart; // counter for "start" signal
BYTE t0; // counter for "0" signal
BYTE t1; // counter for "1" signal
WORD samples[2]; // last two samples from input capture
WORD diff; // difference accumulator

// Global read-only arrays
// -----------------------
flash BYTE author[] = "(c) E.Ficara 2015\r\n";

////////////////////////////////////////////////////////////////////////////////
// Debug
// -----
void DebugBurst(void) // burst on TEST pin (executed with irq disabled)
{
    for(GPIOR1 = 0; GPIOR1 < 100; ++GPIOR1)
        {
        TEST ^= 1;
        for(GPIOR0 = 0; GPIOR0 < 200; ++GPIOR0);
        }
}

void SetWatchdog(BYTE to) // change the watchdog timeout (irq must be disabled)
{
    __watchdog_reset();
    MCUSR &= ~(1<<WDRF); // Clear WDRF in MCUSR
    WDTCSR |= (1<<WDCE); // enable write
    WDTCSR = to; // 00x0.exxx set enable flag and new watchdog time
}

////////////////////////////////////////////////////////////////////////////////
// Interrupts
// ----------
#pragma vector = TIMER0_OVF_vect
__interrupt void TIMER0_OVF_i(void) // TMR0 Overflow (16.384 mS tick)
{  
    if(softt1) // if software timer enabled
        --softt1; // decrement time
    if(tdelay) // if delay time after capture running
        {
        if(--tdelay == 0) // if time finished
            {
            tstart = 0; // reset "start" time counter
            t0 = 0; // reset "0" time counter
            t1 = 0; // reset "1" time counter
            samples[0] = 0; // clear first sample
            samples[1] = 0; // clear second sample
            }
        }
}

#pragma vector = TIMER1_CAPT_vect
__interrupt void TIMER1_CAPT_i(void) // TMR1 Input Capture
{  
    samples[0] = samples[1]; // shift first sample
    samples[1] = ICR1; // get actual Input Capure value (irq disabled 16bit rd)
    tdelay = 5; // restart delay time after last input capture (5*16.384mS)
    cap = 1; // set new capture flag
}

////////////////////////////////////////////////////////////////////////////////
// Main Program
// ------------
//
void SetDelay(BYTE dt) // set delay time dt*16.384 mS
{
    __disable_interrupt(); // global interrupts disable
    softt1 = dt; // assign value to software timer #1
    __enable_interrupt(); // global interrupts enable
}

void DecodeTone(void) // decoding a tone
{
    if(samples[1] > samples[0]) // if last sample higher that previous
        diff = samples[1] - samples[0]; // difference of last 2 captures
    else // if last sample lower than the previous
        diff = samples[0] - samples[1]; // difference of last 2 captures

    if(diff >= (39-4) && diff <= (39+4)) // if F=1.6KHz (T=625uS 39*16uS)
        {
        if(++t0 >= (80-8)) // increment tstart counter, if > 80*0.625mS
            {
            LEDR = 1; // turn OFF Red led
            OCR0A = CW; // set pulse duration for ClockWise (MOT1)
            }
        return;
        }

    if(diff >= (52-5) && diff <= (52+5)) // if F=1.2KHz (T=833uS 52*16uS)
        {
        if(++tstart >= (60-6)) // increment tstart counter, if > 60*0.833mS
            {
            LEDR = 0; // turn ON Red led
            OCR0A = STOP; // set pulse duration for STOP
            OCR0B = STOP; // set pulse duration for STOP
            }
        return;
        }

    if(diff >= (78-7) && diff <= (78+7)) // if F=800Hz (T=1250uS 78*16uS)
        {
        if(++t1 >= (31-3)) // increment tstop counter, if > 31*1.6mS
            {
            LEDR = 1; // turn OFF Red led
            OCR0B = CCW; // set pulse duration for CounterClockWise (MOT2)
            }
        }
}

void main(void)
{ 
    CLKPR = 0x80; // enable writing to CLKPR register
    CLKPR = 0; // clock frequency = Fosc/1 (4 MHz)
    
// Pin    10       9      19      18      17      16      15      14
//      --------------------------------------------------------------
// Port  PB.7    PB.6    PB.5    PB.4    PB.3    PB.2    PB.1    PB.0
// Func [XTAL2] [XTAL1]  [SCK]  [MISO]  [OC2A]  [OC1B]  [OC1A]  [ICP1]
// Func [TOSC2] [TOSC1]                 [MOSI]   [/SS]          [CLKO]
// I/O                                           test            FrqIn                  
    PORTB = 0xFF;   // 1111.1111
    DDRB = 0xFE;    // 1111.1110 (0=input)

// Pin             1      28      27      26      25      24      23
//      --------------------------------------------------------------
// Port          PC.6    PC.5    PC.4    PC.3    PC.2    PC.1    PC.0
// Func         [/RES]  [ADC5]  [ADC4]  [ADC3]  [ADC2]  [ADC1]  [ADC0]
// Func                  [SCL]   [SDA]
// I/O                                                                 
    PORTC = 0xFF;   // 1111.1111
    DDRC = 0xBF;    // 1011.1111 (0=input)

// Pin    13      12      11       6       5       4       3       2
//      --------------------------------------------------------------
// Port  PD.7    PD.6    PD.5    PD.4    PD.3    PD.2    PD.1    PD.0
// Func [AIN1]  [AIN0]   [T1]    [T0]   [INT1]  [INT0]   [TXD]   [RXD] 
// Func         [OC0A]  [OC0B]  [XCK]   [OC2B]
// I/O   LEDR    MOT1    MOT2                                             
    PORTD = 0xFF;   // 1111.1111
    DDRD = 0xFF;    // 1111.1111 (0=input)

//  Pin    7       8      20      21      22
//      -------------------------------------
//  Func [VCC]   [GND]  [AVCC]  [AREF]  [GND]  

    OCR0A = STOP; // set pulse duration (OCR0A*64 uS) every 16,384 mS
    OCR0B = STOP; // set pulse duration (OCR0B*64 uS) every 16,384 mS
    TCCR0A = 0xA3; // 1010.0011 TMR0 is Fast PWM mode, 8 bits
    TCCR0B = 0x04; // 0000.0100 TMR0 clock is IOclk/256 = 15625 Hz (64uS)
    TIMSK0 = (1<<TOIE0); // enable TMR0 Overflow interrupt

    TCCR1B = 0x83; // 1000.0011 activate IC Noise Canceler, IOclk/64 (16uS)
    TIMSK1 = (1<<ICIE1); // enable Input Capture interrupt

    GPIOR1 = MCUSR; // read MCU reset status in GPIO Register[1]
    MCUSR = 0; // clear flags (8 Watchdog, 4 Brownout, 2 External, 1 Power-on)

    // enable watchdog, timeout = 2 sec
    SetWatchdog((1<<WDE) | (1<<WDP2) | (1<<WDP1) | (1<<WDP0));
    if(GPIOR1 & 8) // if micro was been reset from watchdog
        DebugBurst(); // burst on TEST output

    __enable_interrupt(); // global interrupts enable
    for(;;) // main loop  
        {    
        __watchdog_reset(); // kick the watchdog
        if(cap) // if new capture flag set
            {
            __disable_interrupt(); // global interrupts disable
            cap = 0; // reset flag
            __enable_interrupt(); // global interrupts enable
            DecodeTone();
            }
        } 
}

“Pedrone” Part #1 – Trimming stop point on modified servo

I am going to build my new “Pedrone” (Pedestrian Drone), a device that can be remotely controlled by smartphone, running at home. I did the same in 1998, now it’s time to make one better. In the picture below, you can see the old model. The internet server was set on the home PC and the data exchange with the rover was done by means of 866 MHz transceivers using half-duplex packets communication. The “camera” was a GameBoy gadget modified for the purpose, giving a fantastic 128*128 pixels gray scale image at every command received from the Internet.

My first Rover, built in 1998, controllable via a standard browser on the Internet.

My first Rover, built in 1998, controllable via a standard browser on the Internet  (click to enlarge)

For the new model, I decided to use a couple of cheap Servo-Motors, purchased on ebay at VERY low price. The servo motors have a good torque and so are very efficient. The second advantage of servo motors is that they just require a single microcontroller output to be driven in both directions. Obviously, a servo motor has to be modified, because its purpose is to move in the arc of 180° (someone more, some other less). I want a continue rotation to drive a wheel in both directions, plus a stop condition. I have written a small test program on Atmel ATmega48 microcontroller to generate a waveform to control the motor, then I have modified the servo substituting the feedback potentiometer with a couple of smd fixed resistors, both of the same value. Connecting the servo to the microcontroller, I had a problem: no one of the values that can be set in pulse width gave a real STOP condition. One value gave slow rotation clockwise, one other slow rotation counterclockwise. This problem can happen if the center tap of the two resistor is not exactly the median point, or because the pulse width is not exactly 1.5 mS or for the combination of the two things. Actually, my solution for controlling 2 motors with a single 8 bit timer (TMR0) using two output compare registers (OCR0A and OCR0B) gives me a step factor of 64 uS on a full range of 256 * 64 uS (the total repetition time is 16.384 mS, that’s inside the specs for servo-motors). The classical 1.5 mS center point can’t be set, cause 1500uS / 64uS is not an integer value (23.4375) and then we have to decide if the center point is 23 (*64uS = 1.472mS) or 24 (*64uS = 1.536mS). In any case the stop position depends upon the feedback supplied to the motor controller by the potentiometer (in our modified servo, the couple of SMD resistors with the same value).  To solve the problem, I decided to use an external multiturn trimmer (in place of the smd resistors) to trim the correct stop point. In the picture below you can see that I connected three wires on the pads where the potentiometer was originally soldered.

Servo circuit. Internal potentiometer has been disconnected

Servo circuit. Internal potentiometer has been disconnected (click to enlarge)

I modified the slot in the plastic cover to have enough space to go out with the 3 additional gray wires, then I soldered a multiturn trimmer to set the stop point. Note that the internal potentiometer was 5KOhm, but I used a 10KOhm trimmer without problems. Anyway, the best solution is to measure the resistance of the original potentiomenter and then buy a 10 turns linear trimmer of such value.

The 10 turn trimmer soldered on gray wires

The 10 turns trimmer soldered on grey wires (click to enlarge)

Finally, I used a biadhesive tape to fix the trimmer on the case of the servo-motor.

Trimmer fixed on the Servo-motor case

Trimmer fixed on the servo-motor case (click to enlarge)

Alternative version: using 2KOhm trimmer, a couple of resistors and a filtering capacitor makes easier setting the stop point.

Modified circuit (1KOhm resistors used, but 1.5K is better) - click to enlarge

Modified circuit (1KOhm resistors used, but 1.5K is better) – click to enlarge

Well, after connecting the servo to the microcontroller prototype board (using a default pulse width for stop position), I rotated the trimmer near its middle position, finding the exact point for motor stop. Actually, with my microcontroller’s test program, the stop point is at value 22, while the full speed in one direction is 15 or 30 in the other.

Driving two modified servos di robotop

My prototype board (the one visible in the video) has this schematic:

Schematic diagram of the prototype (click to enlarge)

Schematic diagram of the prototype (click to enlarge)

The C source file, the HEX object to burn the micro and the schematic in PDF format can be downloaded in the zip file EF190Pedrone.zip (when you unzip it, give password: eficara). Here is a quick look of the C source listing. It has been compiled with IAR V5.50.0.

////////////////////////////////////////////////////////////////////////////////
// FW_VERSION  150611 (yymmdd)
//
// FUSES CONFIGURATION: EXT=0xFF HIGH=0xCD LOW=0xCC LOCK=0xFF

// Compiler includes
// -----------------
#include <iom48.h>
#include <ina90.h>

// Application defines
// -------------------
#define BYTE    unsigned char
#define WORD    unsigned int
#define LONG    unsigned long

#define STOP    22 // Pulse width for motor STOP
#define CW      15 // Pulse width for motor run ClockWise
#define CCW     30 // Pulse width for motor run CounterClocWise
#define ONESEC  61 // num, of timer ticks for 1 second

// I/O signals
// -----------
#define TEST    PORTB_Bit2 // Test output

// Global variables
// ----------------
volatile BYTE softt1; // down counter 16.384 mS step
BYTE fase; // state machine fase

// Global read-only arrays
// -----------------------
flash BYTE author[] = "(c) E.Ficara 2015\r\n";

////////////////////////////////////////////////////////////////////////////////
// Debug
// -----
void DebugBurst(void) // burst on TEST pin (executed with irq disabled)
{
    for(GPIOR1 = 0; GPIOR1 < 100; ++GPIOR1)
        {
        TEST ^= 1;
        for(GPIOR0 = 0; GPIOR0 < 200; ++GPIOR0);
        }
}

void SetWatchdog(BYTE to) // change the watchdog timeout (irq must be disabled)
{
    __watchdog_reset();
    MCUSR &= ~(1<<WDRF); // Clear WDRF in MCUSR
    WDTCSR |= (1<<WDCE); // enable write
    WDTCSR = to; // 00x0.exxx set enable flag and new watchdog time
}

////////////////////////////////////////////////////////////////////////////////
// Interrupts
// ----------
#pragma vector = TIMER0_OVF_vect
__interrupt void TIMER0_OVF_i(void) // TMR0 Overflow (16.384 mS tick)
{  
    if(softt1) // if software timer enabled
        --softt1; // decrement time
}

////////////////////////////////////////////////////////////////////////////////
// Main Program
// ------------
//
void SetDelay(BYTE dt) // set delay time dt*16.384 mS
{
    __disable_interrupt(); // global interrupts disable
    softt1 = dt; // assign value to software timer #1
    __enable_interrupt(); // global interrupts enable
}

void main(void)
{ 
    CLKPR = 0x80; // enable writing to CLKPR register
    CLKPR = 0; // clock frequency = Fosc/1 (4 MHz)
    
// Pin    10       9      19      18      17      16      15      14
//      --------------------------------------------------------------
// Port  PB.7    PB.6    PB.5    PB.4    PB.3    PB.2    PB.1    PB.0
// Func [XTAL2] [XTAL1]  [SCK]  [MISO]  [OC2A]  [OC1B]  [OC1A]  [ICP1]
// Func [TOSC2] [TOSC1]                 [MOSI]   [/SS]          [CLKO]
// I/O                                                                 
    PORTB = 0xFF;   // 1111.1111
    DDRB = 0xFF;    // 1111.1111 (0=input)

// Pin             1      28      27      26      25      24      23
//      --------------------------------------------------------------
// Port          PC.6    PC.5    PC.4    PC.3    PC.2    PC.1    PC.0
// Func         [/RES]  [ADC5]  [ADC4]  [ADC3]  [ADC2]  [ADC1]  [ADC0]
// Func                  [SCL]   [SDA]
// I/O                                                                 
    PORTC = 0xFF;   // 1111.1111
    DDRC = 0xBF;    // 1011.1111 (0=input)

// Pin    13      12      11       6       5       4       3       2
//      --------------------------------------------------------------
// Port  PD.7    PD.6    PD.5    PD.4    PD.3    PD.2    PD.1    PD.0
// Func [AIN1]  [AIN0]   [T1]    [T0]   [INT1]  [INT0]   [TXD]   [RXD] 
// Func         [OC0A]  [OC0B]  [XCK]   [OC2B]
// I/O           MOT1    MOT2                                             
    PORTD = 0xFF;   // 1111.1111
    DDRD = 0xFF;    // 1111.1111 (0=input)

//  Pin    7       8      20      21      22
//      -------------------------------------
//  Func [VCC]   [GND]  [AVCC]  [AREF]  [GND]  

    OCR0A = STOP; // set pulse duration (OCR0A*64 uS) every 16,384 mS
    OCR0B = STOP; // set pulse duration (OCR0B*64 uS) every 16,384 mS
    TCCR0A = 0xA3; // 1010.0011 TMR0 is Fast PWM mode, 8 bits
    TCCR0B = 0x04; // 0000.0100 TMR0 clock is IOclk/256 = 15625 Hz
    TIMSK0 = (1<<TOIE0); // enable TMR0 Overflow interrupt

    GPIOR1 = MCUSR; // read MCU reset status in GPIO Register[1]
    MCUSR = 0; // clear flags (8 Watchdog, 4 Brownout, 2 External, 1 Power-on)

    // enable watchdog, timeout = 2 sec
    SetWatchdog((1<<WDE) | (1<<WDP2) | (1<<WDP1) | (1<<WDP0));
    if(GPIOR1 & 8) // if micro was been reset from watchdog
        DebugBurst(); // burst on TEST output

    __enable_interrupt(); // global interrupts enable
    for(;;) // main loop  
        {    
        switch(fase)
            {
            case 0:
                if(softt1 == 0)
                    {
                    OCR0A = CW; // set pulse duration for ClockWise
                    OCR0B = CCW; // set pulse duration for CounterClockWise
                    SetDelay(ONESEC); // set 1 sec time
                    fase = 1;
                    }
                break;
            case 1:
                if(softt1 == 0)
                    {
                    OCR0A = STOP; // set pulse duration for STOP
                    OCR0B = STOP; // set pulse duration for STOP
                    SetDelay(ONESEC); // set 1 sec time
                    fase = 2;
                    }
                break;
            case 2:
                if(softt1 == 0)
                    {
                    OCR0A = CCW; // set pulse duration for CounterClockWise
                    OCR0B = CW; // set pulse duration for ClockWise
                    SetDelay(ONESEC); // set 1 sec time
                    fase = 3;
                    }
                break;
            case 3:
                if(softt1 == 0)
                    {
                    OCR0A = STOP; // set pulse duration for STOP
                    OCR0B = STOP; // set pulse duration for STOP
                    SetDelay(ONESEC); // set 1 sec time
                    fase = 0;
                    }
                break;
            }
        __watchdog_reset(); // kick the watchdog
        } 
}

Constant Current 300mA Led drive with a battery charger

Li-ion battery chargers use the CC/CV algorithm. CC means Constant Current and CV is Constant Voltage. When a battery is under charge, the device supplies a constant current until a threshold voltage is reached (typical 4.2V), then applies a Constant Voltage. When the charging current drops below the 10% of the set value, the battery is disconnected (float). I tried to use a battery charger to light a white led at 300mA constant current. The trick is that the led (obviously) does not recharge, so the charger remains continuously in CC mode, with the current set as programmed.

The prototype working with a power white led

The prototype lights a power white led (click to enlarge)

I built the circuit on a proto board, using a self made adapter for the smd IC TP4056. Note that this IC has a metal pad on the bottom side, for thermal dissipation. I soldered a wire under the chip before placing it onto the adapter. The schematic is very simple:

Hand-made schematic. The IC accepts max 8V in input. I used 6.2V supply (click to enlarge)

Hand-made schematic. The IC accepts from 4 to 8V as input. I used a 6Volts battery for the test (click to enlarge)

Detail of the prototype board :

The prototype. Note the wire that comes out from the bottom side of the IC (click to enlarge)

The prototype. Note the wire that comes out from the bottom side of the IC and the metallic stripe used for thermal dissipation (click to enlarge)

Now it’s time to test. I used a trimmer for setting the value of charging current. For 300mA the value must be 4K Ohm. If you don’t want to use a trimmer, such non-standard value can be obtained paralleling two resistors: 10K and 6.8K (the result is 4.04K). The image below shows the current measured with a multimeter.

Led ON, driven with 300mA current (click to enlarge)

Led ON, driven with 300mA current (click to enlarge)

Note the 1N5819 diode at the charger output. This is important. The white led I used has a typical Forward Voltage of 3.42 Volts and if you connect it directly to the BAT signal of the IC (pin 5) the charge does not start. You will have a current that is the 10% of your settings, so about 30mA. The IC “starts” if there is a voltage near 3.7V ; the diode 1N5819 has about 0.35V voltage drop at 300mA, so 3.42V of led plus 0.35V of diode makes 3.77V and the circuit start “charging” at 300mA. I also tested one commercial board I purchased some time ago on ebay. I just modified the charge set resistor and added the diode at the output. Please note that if you set higher currents, the IC will be hot. I don’t recommend current values higher than 300mA, without any thermal dissipation. This IC is not made to be a led driver. For professional results, use one made for that purpose.

test with a commercial board (click to enlarge)

test with a commercial board (click to enlarge)

Here is a table for values of resistor to set a specific current and the position of such resistor on the circuit.

Change the circled resistor to modify the charging current (click to enlarge)

Change the circled resistor to modify the charging current (click to enlarge)

Single 3.7V Li-ion cell battery back-up for Raspberry Pi

 I have a small application continuously running on Raspberry Pi board. Sometimes, in my country, we have (short time) mains power failures. In such situation, my Raspberry board, powered by the wall adapter, turns off immediatly, without any attention for open files, transmissions in act, etcetera. This is really a problem. I decided to use some materials purchased in the past on ebay to realize a small power back-up unit, using a single Li-ion cell. First of all, you need for a battery charger circuit, to maintain the back-up battery fully charged. The battery is 3.7V typical, and 4.2V when fully charged, but the Raspberry Pi board wants 5V supply, so we need for a Step-Up circuit. As third element, we need for a switch that disconnects the load from battery when mains supply is present, ’cause the charger can’t see the real battery level if you connect a load while charging. I suggest to read this document released from Microchip: AN1149.

circuit-view

The prototype (click to enlarge)

The charger can be easily found on ebay searching for “1A Li-ion battery charger”. It’s based on well known chip named TP4056. In the picture there is a circle indicating Rchg. The component circled is the charge resistor. The value of such resistor sets the charging current. In the original circuit, the value of Rchg was 1K2 (smd mark 122) that sets 1A charge current. I removed such resistor and placed a new one with value 3K3 (smd mark 332). With this resistor, the charging current will be about 370 mA. I don’t want to charge the battery at higher rate for two reasons: the first is the capacity of the cell (I used a small one), the second is that the wall adapter MUST have enough current to drive the charger AND the Raspberry Pi board. In the next picture you can see three different situations:

Charge / discharge steps (click to enlarge)

Charge / discharge steps (click to enlarge)

Starting from the left, you can see the circuit connected to the wall adapter and the battery charging (red led ON), then the battery fully charged (blue led ON), then the wall adapter has been removed (both leds OFF). Note that the multimeter measures 5.13V in all cases.

The step-up module can be found with a search on ebay, looking for step-up regulator with variable voltage (mine is based on recent XL6009 chip). The XL6009 regulator is rated for Vin_min = 5V, but I tested that works with 3.5V. For best operative conditions, look for a step-up module that mounts an LM2577S-ADJ chip, that is rated for Vin_min = 3.5V. Note that you must trim the variable resistor to have 5V output. Please, connect a 1K resistor as load to the output when setting the Vout, ’cause there is a capacitor that remains charged when you turn the trimmer decreasing the output voltage. After setting, remove the load resistor.

The switch element cannot be purchased on ebay (sorry) and you must build it by yourself. I started from the circuit proposed in the Microchip’s application note and just modified something. For the P-channel mosfet, I used a free sample received from NXP: the PMV48XP. This SOT23 mosfet is very small (look at the picture “The SWITCH”), but it’s really powerful. I used two 1N5819 Schottky diodes (not one) to reduce the voltage drop and increase the current. The circuit is very simple:

The Switch (click to enlarge)

And finally, look at the next picture. The circuit is connected to my Raspberry Pi board. In the animation you can see that the board doesn’t turn off when the wall adapter is disconnected. The time of back-up depends from the capacity of the cell that you use. Remember that the current drawn from the battery (when wall adaptor is disconnected) is the nominal current of the Raspberry Pi board multiplied for the efficiency of the step-up in converting from Vbat  to 5V.

Conenctin and disconnecting the wall adapter (click to enlarge and animate)

Connecting and disconnecting the wall adapter (click to enlarge and animate)

Test report
Test conditions: battery fully charged, Raspberry Pi running Raspbian Wheezy and connected to HDMI monitor and to USB interface for RF keyboard and mouse. No other applications running, just the desktop and the default services. I disconnected the wall adapter exactly at 17:17 .
Test results: the battery discharged from 4.2V to 3.7V (about 20% of residual charge) at 18:18, so in about 1 hour.

Switch relays On/Off with WhatsApp messages

I recently installed the famous messaging application “WhatsApp” on my smartphone. After a while, I decided to create a device that can remotely switch two relays On or Off using messages sent through WhatsApp. Obviously, you must have two active accounts (and two smartphones) for remotely control the relays. In this description we call them the transmitter and the receiver. This device needs for a specific circuit (the relays board) and for a special Android application that works together with WhatsApp on receiver smartphone. Let’s start with the description of the circuit (the hardware), then the Android application (the software) will follow.

The hardware

The hardware is based on PIC12F635 microcontroller from Microchip. It’s a small 8 pin device. In the picture you can see the prototype, realized on a 50x70mm proto board. The smd micro has been placed on small adapter (the red one on the left).

The working prototype

The working prototype

The schematic is relatively simple. We have the micro, a DTMF tone decoder (MT8870), a couple of relays and a switching regulator from 12V to 5V. That’s all.

Schematic diagram; click to enlarge

Schematic diagram; click to enlarge

If you want a more readable copy, download the PDF file at this link. Please note the switching regulator module KIS-3R33S. I purchased a lot of (used) modules on ebay, at very low price. The problem is that the module is rated for 3.3V -3A max output, but I need for 5V out, so I modified the module removing a couple of components: one zener diode and one 51K resistor. It’s a very simple operation, please look at the picture:

The switchin regulator and the parts that must be removed.

The switching regulator and the parts that must be removed. (click to enlarge)

This switching regulator is needed only if you want to have an USB output that can recharge the smartphone that you use as receiver. In other cases, you can simply use a 5V linear regulator capable of 100-200 mA output current. A reduced (easy) schematic will be like this: (please, note that also ICSP section has been removed, that means you must program the micro off-board).

A reduced (easy) version of the schematic (click to enlarge)

A reduced (easy) version of the schematic (click to enlarge)

A pdf version of this schematic can be downloaded at this link.

The circuit detects a sequence of four DTMF tones. The first three tones are “the activation key” and are fixed to ‘1’, ‘3’, ‘7’, while the fourth tone is “the command” and can have the values: ‘2’ for R1-ON R2-OFF, ‘5’ for R1-OFF R2-ON, ‘6’ for R1-ON R2-ON and finally ‘8’ for R1-OFF R2-OFF. Once built up, the circuit can be tested with every device capable of playing MP3 files. The test files 1372.mp3, 1375.mp3,1376.mp3 and 1378.mp3 can be downloaded as a zip file from this link. Connect a stereo jack from the circuit to the player and play one at a time the 4 files. The relays will follow the combination presented as DTMF tones.

To make hardware work, you must program the PIC micro with the .hex file that can be downloaded from this link (updated version:150409 – changed red LED behaviour and implemented timeout after valid key received). The Configuration Bits for the PIC12F635 in this application is shown here:

The configuration word for PIC12F635

The configuration word for PIC12F635 (click to enlarge)

Using the ICD2 programmer under MPLAB, you can receive a warning like this:

ICD2 warning (click to enlarge)

ICD2 warning (click to enlarge)

On my prototype the device is correctly programmed if you click the “OK” button.

The software

The WhatsApp (I will use WA abbreviation from now on) protocol is proprietary and I don’t want to hack the received text messages; so… how to decode a command for relays activation ? I have seen on my smartphone there is a folder named WhatsApp/Media/WhatsApp Images. When you receive an image as attachment to a message sent via WA, a copy of that image is saved on this folder. So, my way to control the relays is simply to poll that directory to see if a new IMG file is present, then I load that file in an imagebox of my Android App and analyze the contents in order to decode the relays command; after that the image is renamed (next polling doesn’t find it again). This way to operate is non-intrusive and co-operative with WA application.

The transmitter doesn’t need any additional application; you just need to store the command images on a folder that’s visible for WA when you try to send an image as attachment ( I use the Downloads folder on main storage). The commands are four (all the combinations of two relays) and are small and simple images:

The images that will be used as attachments for sending commands

The images that will be used as attachments for sending commands

The lower part is for the user (human readable), while the upper part will be read by the Android application. Note that the left two bits are the complement of the right two, this is just to have a validity check while analyzing the image. You can download all the files zipped at this link. Finally, when you want to activate a relay on the receiver, simply send from transmitter a WA empty message with one of the previous command images as attachment.

The receiver is a little bit more complicated. You must download, first of all, a special ringtone, that is the “key” to assign the receiver to a specific transmitter. After downloading the ringtone from this link (right button mouse click to download the ringtone if your browser tries to play it directly), you must store the audio file on the receiver smartphone, in a folder that makes it visible under the phone’s ringtones. On my old A5000 smartphone (Android Version 2.2.1) I created a folder on the main storage (/sdcard/) named Media/audio/ringtones, and inside that folder I stored the new ringtone named 137.ogg; after this operation and after rebooting, the file appeared in the list of ringtones. When this new ringtone is in the list, you must assign it to the specific contact (or contacts) in your phonebook that is (are) authorized to play with relays; then you must set WA preferences to play the notification tone using the contact’s ringtone. To test this settings, send a WA message from transmitter to receiver and hear if the played notification tone is the one just assigned. After this, send a WA message from another phone (not authorized) to the receiver and verify that the notification tone is different (or absent).

Now it’s time to load and install the WhatRelay application from my page on Google Play Store. Once installed, at first run, the program asks for the working directory of WA. On my devices (both of them) this folder is on the main storage (/sdcard/) with this path: WhatsApp/Media/WhatsApp Images/ (take care of capital letters and last slash). Insert this path and accept. Please, note that all files IMG-xxxx.jpg already present on such folder will be renamed by the application in .IMG-xxxx.jpg (hidden), one every 5 + 2 seconds. If you want to preserve such files from renaming, move them to a new folder. In any case, every IMG-xxxx.jpg file present will be loaded and analyzed by the program, then renamed in .IMG-xxxx.jpg, so remove all such files before starting the App, or you will see them appear in the imagebox, be analyzed, then renamed at 5 + 2 seconds steps. The application sets the phone to stay always ON. Click the “quit” button to exit the application and restore the normal auto-turn-off time.

First run of WhatRelay App

First run of WhatRelay App

When the default path is set, the application starts, polling every 5 seconds the working directory to look if any IMG-xxxx.jpg file has been received. In the picture below, there is a screenshot of what happens when an IMG file is found. The image is copied in the small box at the right and after 2 seconds the program analyzes the picture to attribute a code. If the code is valid, a DTMF tone is played. In any case (valid image or not) the IMG-xxxx.jpg file will be renamed in .IMG-xxxx.jpg.

Program running: one IMG file has been received

Program running: one IMG file has been received

So, when WA receives a message, plays the notification tone that is the 137.ogg audio file, containing three DTMF tones that are the “key” to enable the circuit, then the WhatRelay App detects a new image, decodes it and plays the fourth DTMF tone (after less than 15 seconds) and the electronic circuits has received the key and the command, so can switch the relays. Job done.

Please, note that this is a release 0.1. This release will be revised many times. Look at this page or on Google Play Store to see if there is something new (and better). A special note regarding the volumes: remember that the circuit connects to the smartphone headphone plug, so put the volumes around 2/3 of the maximum and remove any notification that isn’t needed for the relays control. Do some tests without connecting the circuit, just to hear if all the tones are played with a good audio level. Use the local command buttons for other tests or for manual control of relays.

Spread your meteo sensor data over the internet

I published on the Italian Magazine for Electronics “CQ Elettronica” (issue N.485 September 2007) an article about my project based on Atmel ATtiny2313 microcontroller that converts the RF data stream (OOK modulation at 433 MHz) received from Oregon Scientific meteo sensor model THGR228NF (look at this link for more details).  This device was also revised by Hack-a-day in September 2007.

Recently, I created an Android App to display the received data on a tablet or a smartphone, using an USB-Serial interface to connect the receiver to the device. You can find the App on my page on Google Play Store and more informations at this link.

After the first version, I created a new one that uses a PIR sensor (Passive InfraRed) to swap the screen from a flip-clock to the meteo data screen when a person passes near the box. This is the prototype:

App in funzione su tablet 800x480 scala=1

Prototype running on 800 x 480 display 6.5″ Phablet

Well, after that I had the idea to publish my webcam on the Internet, superimposing over the picture (taken every 20 minutes) the meteo data received from the Oregon Scientific meteo sensor. This will enhance the picture with useful meteo data. Here is an example of what you can see clicking my webcam link :

webpic

click to reload actual view

As you can see, at the bottom of the image there are two information lines. Such lines are created by a PHP script that I stored on the server. The first line is the date and time of the picture, while the second is the date and time of the meteo informations (sent every 10 minutes), together with the temperature and humidity at the specific time.

The picture sent by the webcam hasn’t any information, being the camera a very simple device (look below how simple is..)

very home made webcam (click to enlarge)

very home made webcam (click to enlarge)

The bridge from the meteo receiver to the second line of picture is a new App written for an old 7″ tablet, running Android 2.2. This App does what the previous did, but every 10 minutes sends an HTTP GET request to a specific PHP script that I saved on the server. This GET request is formatted with a simple crypting and contains the temperature and humidity read from the sensor. The PHP script on the server receives the meteo data and creates a file with such infos. When the user clicks the link of my webcam, the PHP script loads the picture received from the webcam and the meteo data received from the tablet and creates on-the-fly the image you receive on your browser. Obviously, the tablet running the App connects to the Internet thru the home modem / router that acts as a WiFi hotspot.

I haven’t published this App on Google Play ’cause I decided to use, as communication port, the internal ttyS0 of the tablet. Such communication port was present in almost all the first models of tablets, but was used by the system for the “console” service. I modified the init.rc file of my rooted tablet (running Uberoid image) in order to disable the console service on that port, so I connected my RX-Met1 receiver directly to ttyS0 without any USB-Serial interface. This approach is too “specialistic” and therefore isn’t idoneous to the Play Store distribution. I just added a small video to show how I arranged the old tablet in a box with the receiver and the PIR sensor… (Dailymotion video)


MeteoRx22 di robotop

This way of sending sensor data over the Internet can be easily achieved with new low-cost modules, like ESP8266 that gives you the power of WiFi at very low price, with extreme easy of use.