1-wire LEGO LED stripe

This post is part 2 of 3 of  Rede de controlo 1-wire

E agora um primeira experiência com múltiplos DS2413 numa mesma microLAN…

Cada dispositivo 1-wire tem um ID distinto gravado em fábrica:

$sudo ls /mnt/1wire/
3A.4C2B13000000  81.086D33000000  settings    structure  uncached
3A.B0E216000000  bus.1          statistics  system

Vemos que temos 3 dispositivos de 2 classes diferentes.  A classe 81 é a do Master Controller (o adaptador USB DS9490R) e a classe  3A é a do DS2413. Temos portanto dois dispositivos DS2413 na rede, cada um com o seu ID:

  • 4C2B13000000
  • B0E216000000

Na forma como o owfs lida com 1-wire o par “Classe.ID” funciona como um endereço. É possível definir alias mas funcionam apenas localmente (isto é: se movermos a microLAN do meu Ubuntu para o LEGO ev3dev tẽm de ser redefinidos) e na minha primeira experiência com alias perdi o acesso aos dispositivos por isso deixo para outra altura.

Sendo da mesma classe, estes dois dispositivos partilham os mesmos atributos (PIO.A, PIO.B e PIO.ALL por exemplo). Se tiver um LED em cada PIO destes devices podemos por exemplo acender todos os LEDs percorrendo todos os dispositivos da classe 3A e escrevendo “1,1” na subpasta PIO.ALL.

O programa abaixo acende cada LED em sequência, primeiro num sentido depois no outro:

import sys, traceback
from time import sleep
from subprocess import call

# B0E216000000/PIO.A = primeiro LED
# B0E216000000/PIO.B = segundo LED
# 4C2B13000000/PIO.A = terceiro LED
# 4C2B13000000/PIO.B = quarto LED

def main():
    try:

# apagar tudo ao comecar

    call("echo 0,0 > /mnt/1wire/3A.B0E216000000/PIO.ALL", shell=True);
    call("echo 0,0 > /mnt/1wire/3A.4C2B13000000/PIO.ALL", shell=True);

        while True:
        # apaga tudo
        call("echo 0,0 > /mnt/1wire/3A.B0E216000000/PIO.ALL", shell=True);
        call("echo 0,0 > /mnt/1wire/3A.4C2B13000000/PIO.ALL", shell=True);

                call("echo 1 > /mnt/1wire/3A.B0E216000000/PIO.A", shell=True); # LED1
                sleep(0.25);

        # apaga tudo
        call("echo 0,0 > /mnt/1wire/3A.B0E216000000/PIO.ALL", shell=True);
        call("echo 0,0 > /mnt/1wire/3A.4C2B13000000/PIO.ALL", shell=True);

                call("echo 1 > /mnt/1wire/3A.B0E216000000/PIO.B", shell=True); # LED2
                sleep(0.25);

        # apaga tudo
        call("echo 0,0 > /mnt/1wire/3A.B0E216000000/PIO.ALL", shell=True);
        call("echo 0,0 > /mnt/1wire/3A.4C2B13000000/PIO.ALL", shell=True);

        call("echo 1 > /mnt/1wire/3A.4C2B13000000/PIO.A", shell=True); # LED3
                sleep(0.25);

        # apaga tudo
        call("echo 0,0 > /mnt/1wire/3A.B0E216000000/PIO.ALL", shell=True);
        call("echo 0,0 > /mnt/1wire/3A.4C2B13000000/PIO.ALL", shell=True);

        call("echo 1 > /mnt/1wire/3A.4C2B13000000/PIO.B", shell=True); # LED4
                sleep(0.25);

        # apaga tudo
        call("echo 0,0 > /mnt/1wire/3A.B0E216000000/PIO.ALL", shell=True);
        call("echo 0,0 > /mnt/1wire/3A.4C2B13000000/PIO.ALL", shell=True);

        call("echo 1 > /mnt/1wire/3A.4C2B13000000/PIO.A", shell=True); # LED3
                sleep(0.25);

        # apaga tudo
        call("echo 0,0 > /mnt/1wire/3A.B0E216000000/PIO.ALL", shell=True);
        call("echo 0,0 > /mnt/1wire/3A.4C2B13000000/PIO.ALL", shell=True);

                call("echo 1 > /mnt/1wire/3A.B0E216000000/PIO.B", shell=True); # LED2
                sleep(0.25);


    except (KeyboardInterrupt, SystemExit):
        print "Exiting...";
    except Exception:
        traceback.print_exc(file=sys.stdout);

# apagar tudo antes de sair

    call("echo 0,0 > /mnt/1wire/3A.B0E216000000/PIO.ALL", shell=True);
    call("echo 0,0 > /mnt/1wire/3A.4C2B13000000/PIO.ALL", shell=True);
    sys.exit(0);

if __name__ == "__main__":
    main()

 

E o programa abaixo acende as luzes aleatoriamente, mais próprio para um concerto ou uma discoteca (com tempo hei-de estudar a forma de usar as rotinas PyAudio para medir a intensidade de uma música e controlar as luzes ao ritmo desta embora duvide que o EV3 tenha CPU para tal).

 

 

import sys, traceback
from time import sleep
from subprocess import call
from random import randrange

# B0E216000000/PIO.A = primeiro LED
# B0E216000000/PIO.B = segundo LED
# 4C2B13000000/PIO.A = terceiro LED
# 4C2B13000000/PIO.B = quarto LED


def main():
    try:

# apagar tudo ao comecar

    call("echo 0,0 > /mnt/1wire/3A.B0E216000000/PIO.ALL", shell=True);
    call("echo 0,0 > /mnt/1wire/3A.4C2B13000000/PIO.ALL", shell=True);

        while True:
        # apaga tudo
        call("echo 0,0 > /mnt/1wire/3A.B0E216000000/PIO.ALL", shell=True);
        call("echo 0,0 > /mnt/1wire/3A.4C2B13000000/PIO.ALL", shell=True);

        if (randrange(2) == 1):
                    call("echo 1 > /mnt/1wire/3A.B0E216000000/PIO.A", shell=True); # LED1

        if (randrange(2) == 1):
                    call("echo 1 > /mnt/1wire/3A.B0E216000000/PIO.B", shell=True); # LED2

        if (randrange(2) == 1):
            call("echo 1 > /mnt/1wire/3A.4C2B13000000/PIO.A", shell=True); # LED3

        if (randrange(2) == 1):
            call("echo 1 > /mnt/1wire/3A.4C2B13000000/PIO.B", shell=True); # LED4
                
                sleep(0.25);


    except (KeyboardInterrupt, SystemExit):
        print "Exiting...";
    except Exception:
        traceback.print_exc(file=sys.stdout);

# apagar tudo antes de sair

    call("echo 0,0 > /mnt/1wire/3A.B0E216000000/PIO.ALL", shell=True);
    call("echo 0,0 > /mnt/1wire/3A.4C2B13000000/PIO.ALL", shell=True);
    sys.exit(0);

if __name__ == "__main__":
    main()

 

1-wire LED

This post is part 1 of 3 of  Rede de controlo 1-wire

Estou a reproduzir em LEGO um concerto dos Xutos&Pontapés. A posição dos elementos da banda em palco vai ser controlada por um controlador (ainda não decidi se um Raspberry Pi ou um LEGO EV3) e quero poder controlar também as luzes dos projectores:

Se as luzes acendessem todas em simultâneo as ligações eléctricas seriam simples e seriam necessários passar apenas 2 cabos (positivo e negativo, comuns a todos os LEDs) pela estrutura por cima do palco. Mas eu quero um controlo individual dos projectores e pelo menos 10 deles. Isso significa pelo menos 11 cabos (10 positivos e 1 negativo comum) e apesar de ser possível (consigo passar 4 ou 5 cabos de jumper por entre um furo Technic por isso bem arrumados os cabos até nem dariam muito nas vistas) obriga-me a ter pelo menos 10 portas digitais para o controlo (o que também é possível com circuitos adicionais mas não me cativa). E se quiser aumentar o número de projectores a coisa começa a complicar-se.

Comecei logo a pensar num bus de controlo e pendurar micro-circuitos de controlo ao longo do bus. O ideal mesmo seriam LEDs I2C mas não encontrei nada suficientemente pequeno ou funcional por isso optei por uma microLAN 1-wire.

O termo “1-wire” é enganador: são necessários pelo menos 2 cabos num bus 1-wire (dados e massa) e na maioria das aplicações práticas será necessário um terceiro para alimentar os dispositivos (a norma prevê um modo “parasita” em que os dispositivos retiram alimentação da ligação prevista para dados mas isso apenas se aplica a dispositivos de muito baixo consumo como sensores de temperatura). Mesmo assim 3 cabos é muito bom para controlar quantos LEDs quiser (se decidir mais 2 ou 3 projectores não preciso passar mais 2 ou 3 cabos nem desencantar mais 2 ou 3 portas digitais no controlador).

A Maxim fabrica o DS2413, um circuito extremamente reduzido que implementa 2 portas digitais bidireccionais. É um chip SMD mas a Adafruit fornece um kit com o chip já montado numa placa de circuito impresso sendo só necessário soldar 4 headers e eventualmente uma resistência (tudo fornecido com o kit) para podermos utilizar na nossa microLAN.

Uhmmm… qual microLAN?

É possível implementar uma microLAN com o Raspberry Pi como master utilizando uma porta GPIO e uma resistência de pull-up. Mas é algo específico para o Raspberry, prefiro algo que possa replicar no meu PC ou no LEGO EV3. Por isso encomendei à RS Online um Maxim DS9490R que é um dispositivo USB que funciona como master controller numa microLAN e é suportado pelo Linux.

No meu portátil (Ubuntu 14.04) foi só ligar:

$ dmesg
(...)
[18011.890734] usb 2-1.3: new full-speed USB device number 7 using ehci-pci
[18011.983566] usb 2-1.3: New USB device found, idVendor=04fa, idProduct=2490
[18011.983576] usb 2-1.3: New USB device strings: Mfr=0, Product=0, SerialNumber=0
[18012.021996] Driver for 1-wire Dallas network protocol.
[18012.026144] usbcore: registered new interface driver DS9490R

Não fui ver que módulos foram carregados na altura mas penso que foram ‘ds2490’ e ‘wire’ (existe também um módulo ‘ds9490r’ mas não está presente no meu Ubuntu).

Após detectar uma microLAN o kernel cria uma pasta ‘/sys/bus/w1’ para dispositivos 1-wire:

$ ls /sys/bus/w1/devices
 81-000000336d08  w1_bus_master1

“81-” identifica o DS9490

A ligação do DS2413 à microLAN é muito simples: o DS9490R tem uma ficha RJ12, idêntica às RJ11 dos telefones mas com 6 contactos. Felizmente dos 6 contactos apenas os 2 centrais são necessários pelo que podemos usar um cabo normal de telefone só com dois condutores:

  • 3 – OW (OW 1-Wire Data)
  • 4 – GND_OW (1-Wire Return)
Wire D

Apesar da datasheet da MAXIM indicar o pinout achei esta foto menos dada a confusões:

Sheepwalk Electronics: Pinout DS9490R

A Sheepwalk Electronics vende alguns produtos 1-wire (inclusive o DS9490R) e publica no seu site alguma informação útil relacionada com 1-wire.

Depois de soldar o header à placa da Adafruit foi só fazer as ligações (com cabos de jumper ou crocodilos, o que houver à mão):

1wire-led

Com 3 baterias AA NiMH (cerca de 3.8V) e um LED branco de alto brilho (cerca de 3.2V) temos 0.6V na resistência por isso se esta for de 1KΩ o consumo do LED será de 0.6 mA de corrente, bastante abaixo dos 20 mA tolerados por cada porta e suficiente para um teste (no palco LEGO irei puxar mais por cada LED, pelo menos 10 mA cada e alimentação será de outro tipo).

NOTA: com o kit da Adafruit vem uma resistência de 4.7KΩ para pull-up da linha ‘1-Wire Data’. Deveria ligar esta resistência ao pino 1 da ficha RJ12 (+5V vindos do bus USB do host onde estiver ligado o controlador, neste caso o PC) mas não me agradou muito a ideia e como suspeito que internamente o controlador já preveja isso optei deliberadamente por não usar o pull-up… aparentemente sem consequências.

Pouco depois de ligar o cabo telefónico ao controlador USB o nosso circuito é imediatamente reconhecido:

 [  706.356777] 0x81: count=17, status: 01 00 20 40 05 04 04 00 20 53 00 00 00 01 00 00 a5
 [  706.356800]
 [  706.356808]                                   enable flag:        1
 [  706.356812]                                  1-wire speed:        0
 [  706.356816]                        strong pullup duration:       20
 [  706.356819]                    programming pulse duration:       40
 [  706.356822]                    pulldown slew rate control:        5
 [  706.356825]                              write-1 low time:        4
 [  706.356829]      data sample offset/write-0 recovery time:        4
 [  706.356832]                      reserved (test register):        0
 [  706.356835]                           device status flags:       20
 [  706.356839]                  communication command byte 1:       53
 [  706.356842]                  communication command byte 2:        0
 [  706.356845]           communication command buffer status:        0
 [  706.356848]              1-wire data output buffer status:        0
 [  706.356852]               1-wire data input buffer status:        1
 [  706.356855]                                      reserved:        0
 [  706.356858]                                      reserved:        0
 [  706.356861]                             new device detect:       a5
 [  707.897674] w1_master_driver w1_bus_master1: Family 3a for 3a.00000012fdf4.44 is not registered.

Infelizmente diz que a famíla 3A não está registrada – o suporte nativo do kernel a dispositivos 1-wire não se extende [ainda?] ao DS2413. É pena, assim não temos nada útil dentro de ‘/sys/bus/w1/devices/3a-00000012fdf4’. Mas tudo bem, já esperava isso, existe o projecto owfs dedicado especificamente ao 1-wire:

sudo apt-get install owfs
Reading package lists... Done
Building dependency tree       
Reading state information... Done
The following extra packages will be installed:
  libow-2.8-15 owfs-common owfs-fuse owftpd owhttpd owserver
Suggested packages:
  owfs-doc
The following NEW packages will be installed:
  libow-2.8-15 owfs owfs-common owfs-fuse owftpd owhttpd owserver
0 upgraded, 7 newly installed, 0 to remove and 0 not upgraded.
Need to get 296 kB of archives.
After this operation, 1,364 kB of additional disk space will be used.
Do you want to continue? [Y/n]

Após a instalação descobrimos que temos 3 serviços a correr:

  • owserver
  • owhttpd
  • owftpd

Embora não me pareçam necessários, nesta fase vou deixá-los a correr. Descobrimos também que o owfs impede o kernel de carregar os drivers que referi acima de modo a não ocorrerem conflitos:

$ cat /etc/modprobe.d/libow-2.8-15.conf
blacklist ds9490r
blacklist ds2490
blacklist wire

Para aceder à microLAN usamos o comando ‘owfs’ mas primeiro é necessário definir um mountpoint onde o owfs vai criar uma estrutura de pastas virtuais que mapeiam cada dispositivo (escolhi ‘/mnt/1wire’):

sudo mkdir /mnt/1wire
sudo owfs -u -m /mnt/1wire

Para confirmar que funciona:

$ sudo ls /mnt/1wire
81.086D33000000 bus.1 settings statistics structure system uncached

temos 1 dispositivo na microLAN (o master controller DS9490R). Vamos agora ligar o DS2413 à microLAN – passadas algumas dezenas de segundos temos:

$ sudo ls /mnt/1wire
3A.F4FD12000000  bus.1       statistics  system
81.086D33000000  settings  structure   uncached

agora já temos algo útil dentro do device ‘3A.F4FD12000000’:

$ sudo ls /mnt/1wire/3A.F4FD12000000
address  family   PIO.A    PIO.BYTE   r_locator   sensed.B
alias     id      PIO.ALL  r_address  sensed.A      sensed.BYTE
crc8     locator  PIO.B    r_id       sensed.ALL  type

Ligando um LED a PIO.A podemos acendê-lo:

$ sudo sh -c  "echo 1 > /mnt/1wire/3A.F4FD12000000/PIO.A"

e voltar a apagá-lo:

$ sudo sh -c  "echo 0 > /mnt/1wire/3A.F4FD12000000/PIO.A"

(os comandos são muito feios porque o owfs tem de correr com previlégios de root e eu não sou root no meu Ubuntu; em Raspberry Pi ou ev3dev bastaria:

echo 1 > /mnt/1wire/3A.F4FD12000000/PIO.A
echo 0 > /mnt/1wire/3A.F4FD12000000/PIO.A

)

E agora um script python para piscar o LED a cada meio segundo:

import sys, traceback
from time import sleep
from subprocess import call

def main():
    try:
        while True:
                call("echo 1 > /mnt/1wire/3A.F4FD12000000/PIO.A", shell=True);
                sleep(0.5);
                call("echo 0 > /mnt/1wire/3A.F4FD12000000/PIO.A", shell=True);
                sleep(0.5);

    except (KeyboardInterrupt, SystemExit):
        print "Exiting...";
    except Exception:
        traceback.print_exc(file=sys.stdout);
    call("echo 0 > /mnt/1wire/3A.F4FD12000000/PIO.A", shell=True);
    sys.exit(0);

if __name__ == "__main__":
    main()

Os meus agradecimentos à PTRobotics por incluírem o Adafruit DS2413 na sua lista de produtos a meu pedido.