|
|
Este artigo está disponível em: English Castellano Deutsch Francais Nederlands Portugues Russian Turkce Arabic |
por Katja and Guido Socher Sobre o autor: A Katja é a editora alemã da LinuxFocus. Ela gosta do Tux, filme & fotografia e do mar. A sua página pessoal pode ser encontrada aqui. O Guido é um fã do Linux de há longa data e gosta do Linux porque é desenhado por pessoas honestas e abertas. Isto é uma das raz�es porque se chama código aberto. A sua página pessoal está em linuxfocus.org/~guido. Conteúdo: |
Abstrato:
Neste artigo construímos um pequeno robot de seis pernas, andante, o qual
depois controlamos com um PC com Linux através da interface da porta
paralela.
Outros dispositivos podem também ser controlados de modo semelhante com a
porta paralela.
Os Robots sempre nos fascinaram e ficámos ambos excitados quando encontrámos um livro acerca de robots, há algum tempo atrás, o qual incluía o kit para construir um robot um pequeno insecto como um robot chamado Stiquito. O Stiquito é um tanto ao quanto especial porque não tem motor mas ando porque as pernas são arames com nitinol e deste modo anda totalmente silenciosamente como um insecto real. Mas quando o tivemos de construir, notámos que por não haver fricção com o chão onde andava o seu movimento era muito lento. Felizmente que o livro também incluía algumas descriç�es para outros desenhos de robots que nos inspiraram a construir o robot que aqui pode ler.
Para construir o robot necessitamos do seguinte material:
Fig 1: Placa de circuito impresso |
Fig 2: Alicate de Ponta Curva |
Para o corpo precisa, primeiro, das três placas de circuito impresso,
uma de 6x6 buracos e duas com 6x7 buracos bem como 4cm de tubo de bronze
com 2 mm de diâmetro juntamente com 3.7 cm de cabo de música.
Fig 4: a coluna vertebral e o powerbus
Corte o tubo de bronze em pedaços de 8, 17.5 e 8mm como mostrado na figura.
Consegue fazê-lo deslizando o tubo sobre a faca de cozinha afiada e
curvando-o depois. O tubo quebrar-se-à onde fez o corte com a faca. É
importante que o tubo no meio seja ligeiramente mais longo que a placa de
6x6 buracos. Corte cerca de 3.7cm de cabo de música. O tamanho final deve
ser à volta de 3 mm mais comprido que os 3 tubos juntos. Coloque o cabo de
música dentro dos três tubos.
O tubo no meio deve ser capaz de rodar enquanto que os outros dois são
soldados ao fio de música.
Fig 5: soldar a placa à coluna vertebral
O tubo do meio é agora soldado à placa de circuito com 6x6 buracos.
Acautele-se para que possa rodar. Os outros dois tubos são soldados às
outras duas placas de circuitos impressos.
Agora pegue na pequena placa de 2x7 buracos. Deve ficar em pé e a meio do
tubo de bronze. A placa de circuito deve ser cortada com um pequeno fio ou
com o cortador. Solde-a a meio do tubo de bronze e a meio da placa de
circuito, como mostrado na figura:
Fig 6: adicionando a pequena placa de circuito
Lixe 1mm do tubo de bronze e corte-o em várias peças de 4mm de largura.
Rode o tubo ao longo da faca de cozinha e vá dobrando-o. Precisa de 16
"curvas" mas faça mais algumas.
Como é necessário muito arredondamento você, antes de avançar, deve testar
com um pouco de nitinol: Ponha o fim do cabo de nitinol dentro dos tubos de
bronze finos (1mm de diâmetro externo) e depois aperto o tubo de bronze com
o alicate de ponta curva. A isto chama-se frisar. Tome cuidado para comprar
um bom alicate pois a força para apertar o tubo de bronze é muita força.
Pode lixar as pontas do cabo de nitinol para obter uma boa conexão
eléctrica.
Agora instalamos o cabo de nitinol que é preciso para mover as pernas
para cima e para baixo.
Fig 7: "a ponte"
Coloque o cabo de nitinol como se quisesse construir uma ponte. Comece por
um lado. Aí porá o cabo de nitinol no último buraco que é possível à
esquerda e no lado direito. Você faz um nó no cabo de nitinol (no sentido
de assegurar uma melhor connecção) e p�e um friso sobre o mesmo (usando o
tubo de bronze de 4mm) e torça-o de maneira a que o cabo de nitinol fique
apertado e para que possa atravessar o segundo buraco a contar do buraco do
lado esquerdo, em cima apontado e depois atravessar o último buraco à
esquerda e do lado direito. No topo é feito um nó e posto um friso e
apertado bem (ver Fig. 7). O cabo de nitinol não deve estar muito apertado.
Se o pressionar com o dedo devia mexer-se cerca de 2 a 4mm. Se não está
muito apertado, ou muito solto o robot, mais tarde não se vai mover da
forma mais correcta. Solde os frisos à placa.
Faça o mesmo do outro lado.
Antes de continuar experimente-o para ver se funciona. Utilize uma pilha de
1.5V AA e ligue-a a um dos cabos de nitinol. Quando o cabo se contraí a
parte do meio do corpo deve rodar cerca de 10 a 20 graus. Tome precaução
para não ligar a pilha mais do que 1 segundo. Pode danificar o cabo por
sobre-aquecimento.
Fig 8: A forma do cabo
Para as pernas corte três longas partes de cabo de música com 10 cm. Em
cada uma das partes dobre o cabo 1.5cm em cada lado. Depois solde cada uma
das patas à parte inferior do robot. Procure que as mesmas fiquem
paralelas.
Fig 9, 10: pernas no robot
Agora deve pôr o nitinol nas 6 pernas.
Fig 11: adicionando os dispositivos eléctricos
Ponha o cabo de nitinol na parte de trás através de um dos tubos e num
buraco da placa de circuito impresso. A distância para o cabo de música é
de 3 buracos. Mantenha-o esticado (veja a figura em cima)
Prenda o cabo de nitinol numa ponta do cabo de música. Mantendo-o esticado.
Agora vem a parte mais difícil. Segure o robot com uma prensa, fixe-o e
curve as pernas. O cabo de música tem um comportamento oposto ao cabo de
nitinol. Para isto trabalhar o cabo de nitinol não pode ter nenhuma folga.
O cabo de música deve ser posto num dos buracos da placa de circuito em
direcção ao nitinol e depois a parte "frisada" deve ser soldada à
perna.
Fig 12: o nitinol e o cabo de música no mesmo nível
Certifique-se que o cabo de música e o nitinol estão ao mesmo nível. As
pernas não se devem mexer para cima ou para baixo quando o nitinol se
contraí. As pernas devem mover-se na direcção de atrás.
Faça o mesmo com as outras cinco pernas.
As pernas e o cabo de música juntamente com o tubo de bronze no meio do
robot actuam como um power bus e portanto deve estar conectados
electricamente entre si. Mas, contudo a parte do meio tem mais liberdade
porque pode rodar e assim não possui uma boa conexão, improvisámos
utilizando 3 cm de cabo de cobre envernizado com 0.1mm de diâmetro e
envolvemos o tubo de bronze para obter um bobine. Retire o tubo de bronze e
depois solde esta bobine no meio das pernas do meio e a um dos pares de
pernas exteriores. A forma de bobine do cabo garante maior
flexibilidade.
Quando o robot estiver pronto pode soldar diversos cabos de cobre envernizado com 0.1 mm de diâmetro e 0.5 m de comprimento (ou mais comprido se o desejar) aos frisos da placa e solde os frisos do corpo à placa de circuito. Precisamos de 9 cabos, 6 para as pernas, 2 para cima/baixo e um para o powerbus comum. Pode soldar as outras partes finais dos cabos a um conector pequeno o qual pode depois encaixar num pequeno encaixe do circuito de condução.
O nosso insecto é desenhado para andar num modo trípode. Este modo
significa que 3 pernas tocam no chão (duas de um lado e uma do outro)
enquanto que as outras 3 estão levantadas no ar. Quando o robot anda as 3
pernas que tocam no chão movem-se numa direcção enquanto que as pernas que
estão no ar movem-se no sentido oposto.
Fig 13: O modo de andar
Este placa de circuito permite-nos utilizar o PC para controlar os
dispositivos eléctricos no robot e ligá-los à porta paralela.
Quando desenvolvemos o nosso programa de computador, testámos em primeiro
com os LEDs e só ligámos à placa de controlo do robot quando os LEDs
mostraram um modo de andar correcto.
Você devia fazer o mesmo para experimentar o programa.
O robot é um tanto ao quanto faminto. Precisa de aplicar uma corrente de
200 a 250 mA sobre o nitinol para que se contraia. Os cabos de nitinol de
3cm de comprimento têm cerca de 7 Ohms. Inicie sempre o software antes de
ligar a energia ao circuito de condução porque todos os pinos de dados são
desligados pelo software para prevenir o estrago dos cabos de nitinol.
Como a bios do computador randomiza os valores dos pinos de dados, alguns
podem estar activos e danificar o nitinol se a corrente passar por mais do
que 1 segundo. O tempo para o nitinol arrefecer deve ser de 1.5 vezes o
tempo que se aqueceu.
O diagrama de circuito:
Fig 14: o diagrama de circuito
Como pode ver pelo diagrama acima utilizámos uma fonte de energia
eléctrica estável. Para assegurar uma alimentação boa e estável e para
proteger a porta paralela. Como fonte de alimentação externa pode conectar
qualquer fonte de alimentação DC entre 6 e 24 V. O 7805 é um regulador de
voltagem standard. A única coisa a prestar atenção é onde são colocados os
condensadores (470uF e 0.1uF), pois se estiverem muito perto do regulador
de voltagem 7805, este chip pode começar a oscilar o que pode ser a sua
destruição.
Este desenho repete-se 8 vezes. Um para cada perna e 2 para os movimentos
de cima e baixo do robot. Utilizámos um pequeno transístor NPN Darlington
porque o nosso robot precisa de imensa corrente. O BC875 ou BC618 pode
comutar cerca de 500mA. A resistência de 47K conectada à entrada garante
um circuito aberto (e.g. o computador não está ligado) é sempre equivalente
a "off". O nível de voltagem na porta paralela é de cerca de 4V para o "on"
e abaixo de 1V para o estado "off". O transístor trabalha somente como um
comutador. As resistências de 15 Ohm limitam a corrente e protegem quer as
pernas quer o robot e o transístor. Os LEDs mostram o estado (on ou
off).
Em baixo tendes figuras do circuito. Os LEDs vermelhos (os que estão em
paralelo com os dispositivos eléctricos do robot) são difíceis de ver pelo
que usámos LEDs vermelhos transparentes. Construímos as resistências de 15
Ohm com cabo de constantan, pois tínhamos imenso cabo deste. É mais barato
comprar resistências de 2W feitas.
A porta paralela foi desenhada para servir de porte de saída de um
computador pessoal e para ligar a uma impressora. Algumas portas paralelas
são quer saída quer entrada. Aqui utilizámos só a porta para output. Num
artigo posterior conectaremos sensores ao robot e então utilizaremos as
linhas de entrada. Apesar de existirem 25 pinos para a porta paralela, só
utilizaremos nove. Oito das linhas são usadas como saída de dados e só uma
linha é que serve de terra.
O layout dos pinos para a porta paralela é como se segue:
25 PIN D-SUB FEMALE at the PC. Pin Name Dir Description 1 STROBE [-->] Strobe 2 D0 [-->] Data Bit 0 3 D1 [-->] Data Bit 1 4 D2 [-->] Data Bit 2 5 D3 [-->] Data Bit 3 6 D4 [-->] Data Bit 4 7 D5 [-->] Data Bit 5 8 D6 [-->] Data Bit 6 9 D7 [-->] Data Bit 7 10 ACK [<--] Acknowledge 11 BUSY [<--] Busy 12 PE [<--] Paper End 13 SEL [<--] Select 14 AUTOFD [-->] Autofeed 15 ERROR [<--] Error 16 INIT [-->] Initialize 17 SELIN [-->] Select In 18 GND [---] Signal Ground 19 GND [---] Signal Ground 20 GND [---] Signal Ground 21 GND [---] Signal Ground 22 GND [---] Signal Ground 23 GND [---] Signal Ground 24 GND [---] Signal Ground 25 GND [---] Signal GroundVocê conecta o circuito de condução ao pino 18 (GND) e aos pinos de dados (2-9).
==== pprobi.c ===== /* vim: set sw=8 ts=8 si : */ /* * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License. * See http://www.gnu.org/copyleft/ for details. * * Written by Katja Socher <[email protected]> * and Guido Socher <[email protected]> * */ #include <stdio.h> #include <unistd.h> #include <stdlib.h> #include <stdarg.h> #include <string.h> #include <math.h> #include <signal.h> #include "robi.h" /* ----------- */ static int opt_r=0; static int fd=0; /* ----------- */ /* ----------- */ void help() { printf("pprobi -- control software for a walking robot\n\ USAGE: pprobi [-h] [parport-device]\n\ \n\ OPTIONS:\n\ -h this help\n\ -r reset the parallel port data pins (all zero) and exit\n\ \n\ The default device is /dev/parport0 \n\ "); #ifdef VERINFO puts(VERINFO); #endif exit(0); } /* Signal handler: all off then exit */ void offandexit(int code) { robi_setdata(fd,0); set_terminal(0); exit(0); } /* ----------- */ int main(int argc, char **argv) { int state,bpat,alternate; char *dev; /* The following things are used for getopt: */ int ch; extern char *optarg; extern int optind; extern int opterr; opterr = 0; while ((ch = (char)getopt(argc, argv, "hr")) != -1) { switch (ch) { case 'h': help(); /*no break, help does not return */ case 'r': opt_r=1; break; case '?': fprintf(stderr, "serialtemp ERROR: No such option. -h for help.\n"); exit(1); /*no default action for case */ } } if (argc-optind < 1){ /* less than one argument */ dev="/dev/parport0"; }else{ /* the user has provided one argument */ dev=argv[optind]; } fd=robi_claim(dev); /* robi_claim has its own error checking */ /* catch signals INT and TERM and switch off all data lines before * terminating */ signal(SIGINT, offandexit); signal(SIGTERM, offandexit); /* initialize parpprt data lines to zero: */ robi_setdata(fd,0); set_terminal(1); /* set_terminal has its own error handling */ state=0; alternate=0; if (opt_r){ offandexit(1); } while(1){ ch=getchoice(); if (ch!=0) state=ch; if (ch == ' '){ printf("Stop\n"); robi_setdata(fd,0); usleep(500*1000); } if (ch == 'q'|| ch == 'x'){ printf("Quit\n"); break; } if (state=='l'){ /*right */ printf("walking right\n"); walkright(fd); } if (state=='h'){ /*left */ printf("walking left\n"); walkleft(fd); } if (state=='j'){ printf("walking back\n"); walkback(fd); } if (state=='k'){ if (alternate){ printf("walking straight on a\n"); walkstraight_a(fd); }else{ printf("walking straight on b\n"); walkstraight_b(fd); } alternate=(alternate +1) %2; } } /* we get here if q was typed */ set_terminal(0); return (0); } ==== robi.c ===== /* vim: set sw=8 ts=8 si : */ /* * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License. * See http://www.gnu.org/copyleft/ for details. * * Written by Katja Socher <[email protected]> * and Guido Socher <[email protected]> * */ #include <stdio.h> #include <stdlib.h> #include <stdarg.h> #include <sys/types.h> #include <sys/time.h> #include <fcntl.h> #include <unistd.h> #include <signal.h> #include <linux/ppdev.h> #include <sys/ioctl.h> #include <termios.h> #include "robi.h" /* like printf but exit the program */ static int die(const char *fmt, ...) { va_list ap; va_start(ap, fmt); vprintf(fmt, ap); va_end(ap); exit(1); } /* get one character from stdin * Returns non zero if char was read otherwise zero * The arrow keys are mapped as follows: * <- = h * -> = l * v = j * ^ = k */ int getchoice() { int c; char s[20]; if (fgets(s,20,stdin)){ c=s[0]; switch (c){ case 0x1b: /* ESC */ if (s[1] == 0x5b){ /* arrow keys are pressed */ switch (s[2]){ case 0x41: /*up arrow*/ c='k'; break; case 0x42: /*down arrow*/ c='j'; break; case 0x44: /*l arrow*/ c='h'; break; case 0x43: /*r arrow*/ c='l'; break; default: c=0; } }else{ c=0; } break; case ' ': case 'h': case 'j': case 'k': case 'l': case 'q': case 'x': break; default: c=0; } return(c); } return(0); } /* Set the Terminal to Non Canonical mode with echo off * or reset the terminal. * USAGE: set_terminal(1) for canonical */ int set_terminal(int canonical) { static struct termios originalsettings; struct termios newsettings; static int origok=0; /* set if originalsettings valid */ if (canonical){ /* save original settings and set canonical mode*/ tcgetattr(fileno(stdin),&originalsettings); newsettings=originalsettings; newsettings.c_lflag &= ~ICANON; newsettings.c_lflag &= ~ECHO; newsettings.c_cc[VMIN]=0; /* do not block */ newsettings.c_cc[VTIME]=1; /* 100 ms */ if (tcsetattr(fileno(stdin),TCSANOW,&newsettings) !=0){ die("ERROR: could not set terminal attributes on stdin\n"); } origok=1; }else{ if (origok){ /* restore settings */ tcsetattr(fileno(stdin),TCSANOW,&originalsettings); } } return(0); } /* open /dev/parportX device and claim it. * USAGE: fd=robi_claim("/dev/parport0"); * The return value is a file descriptor used by other * functions such as robi_setdata */ int robi_claim(char *dev) { int fd,i; fd = open(dev, O_RDWR ); if (fd < 0) { die("ERROR: cannot open device %s\n",dev); } i=0; /* we need exclusive rights as we do not set the control lines*/ /*ioctl(fd, PPEXCL, &i)&& die("ERROR: request for exclusive rights failed\n");*/ ioctl(fd, PPCLAIM, &i)&&die("ERROR: could not claim parport\n"); return(fd); } /* Walk left */ int walkleft(int fd) { /* first B legs to ground */ robi_setdata(fd,LEGBD); usleep(400 *1000); /* all A legs 1 step */ robi_setdata(fd, LEGB1 | LEGB3 ); usleep(1100 *1000); /* first A legs to ground, cool B*/ robi_setdata(fd,LEGAD); usleep(400 *1000); robi_setdata(fd,0); usleep(1000 *1000); return(0); } /* Walk right */ int walkright(int fd) { /* first A legs to ground */ robi_setdata(fd,LEGAD); usleep(500 *1000); robi_setdata(fd, LEGA3 | LEGAD); usleep(300 *1000); /* all A legs 1 step */ robi_setdata(fd, LEGA1 | LEGA3 ); usleep(1100 *1000); /* first B legs to ground, cool A*/ robi_setdata(fd,LEGBD); usleep(400 *1000); robi_setdata(fd,0); usleep(1000 *1000); return(0); } /* Walk with all 3 legs 1 step forward */ int walkstraight_a(int fd) { /* first A legs to ground */ robi_setdata(fd,LEGAD); usleep(800 *1000); /* all A legs 1 step */ robi_setdata(fd, LEGA1 | LEGA2 | LEGA3 ); usleep(1000 *1000); /* first B legs to ground, cool A*/ robi_setdata(fd,LEGBD); usleep(500 *1000); robi_setdata(fd,0); usleep(1200 *1000); return(0); } /* Walk with all 3 legs 1 step forward */ int walkstraight_b(int fd) { /* first B legs to ground */ robi_setdata(fd,LEGBD); usleep(400 *1000); /* all B legs 1 step */ robi_setdata(fd,LEGB1 | LEGB2 | LEGB3); usleep(1000 *1000); /* A down and cool */ robi_setdata(fd,LEGAD); usleep(800 *1000); robi_setdata(fd,0); usleep(1200 *1000); return(0); } /* Walk with all 6 legs 1 step back */ int walkback(int fd) { /* first A legs to ground */ robi_setdata(fd,LEGAD); usleep(800 *1000); /* all B legs 1 step in the air*/ robi_setdata(fd, LEGB1 | LEGB2 | LEGB3 ); usleep(500 *1000); /* first B legs to ground, cool A*/ robi_setdata(fd,LEGBD); usleep(500 *1000); /* all A legs 1 step in the air*/ robi_setdata(fd,LEGA1 | LEGA2 | LEGA3); usleep(500 *1000); /* A down and cool */ robi_setdata(fd,LEGAD); usleep(800 *1000); robi_setdata(fd,0); usleep(1000 *1000); return(0); } /*---------*/ /* Write a bit pattern to the data lines * USAGE: rc=robi_setdata(fd,bitpat); * The return value is 0 on success. */ int robi_setdata(int fd,unsigned char bitpat) { int rc; rc=ioctl(fd, PPWDATA, &bitpat); return(rc); } ==== robi.h ===== /* vim: set sw=8 ts=8 si et: */ #ifndef H_ROBI #define H_ROBI 1 #define VERINFO "version 0.2" /* the first thing you need to do: */ extern int robi_claim(char *dev); /* write a bit pattern to the data lines of the parallel port: */ extern int robi_setdata(int fd,unsigned char bitpat); /* input and terminal functions */ extern int set_terminal(int canonical); extern int getchoice(); extern int walkstraight_a(int fd); extern int walkstraight_b(int fd); extern int walkback(int fd); extern int walkleft(int fd); extern int walkright(int fd); /* data pins to legs: * A1------=------B1 * = * = * B2------=------A2 * = * = * A3------=------B3 * * * Pin to set A-legs to ground= AD * Pin to set B-legs to ground= BD * * parallel port leg name * ------------------------- * data 0 A1 * data 1 A2 * data 2 A3 * data 3 AD * data 4 B1 * data 5 B2 * data 6 B3 * data 7 BD */ #define LEGA1 1 #define LEGA2 2 #define LEGA3 4 #define LEGAD 8 #define LEGB1 16 #define LEGB2 32 #define LEGB3 64 #define LEGBD 128 #endif
O software usa interface de programação ppdev, a partir do Kernel 2.4.x (Necessita do Kernel 2.3.x ou 2.4.x Kernel. O Programa não trabalhará com Kernels mais velhos). Isto é uma interface limpa e conveniente para programar drivers de utilizador para a porta paralela. Nos kernels mais velhos seria necessário escrever um módulo do Kernel ou utilizar um método triste que só permitia ao root executar o programa. A interface ppdev utiliza o ficheiro de dispositivo /dev/parport0 e ajustando o dono e as permiss�es do ficheiro pode controlar quem pode utilizar esta interface da porta paralela.
Para compilar o módulo ppdev no kernel precisa de compilar o módulo PARPORT juntamente com o dispositivo PPDEV. O que deve dar o seguinte ficheiro de .config:
# # Parallel port support # CONFIG_PARPORT=m CONFIG_PARPORT_PC=m CONFIG_PARPORT_PC_FIFO=y # CONFIG_PARPORT_PC_SUPERIO is not set # CONFIG_PARPORT_AMIGA is not set # CONFIG_PARPORT_MFC3 is not set # CONFIG_PARPORT_ATARI is not set # CONFIG_PARPORT_SUNBPP is not set CONFIG_PARPORT_OTHER=y CONFIG_PARPORT_1284=y # # Character devices # CONFIG_PPDEV=m #
O programa, primeiro, inicializa a porta paralela com o comando ioctl
PPCLAIM, Depois activa o terminal no modo canónico. Isto é para obter a
entrada directamente do teclado sem que o utilizador esteja sempre a premir
return após cada entrada. De seguida entra num ciclo à espera por uma
entrada do utilizador deixando o robot andar consoante o comando. Se não
fizer nada o programa continua com o último comando (e.g. continua a andar
a direito).
O comando ioctl(fd, PPWDATA, &bitpat); é usado para configurar
as linhas de dados com um determinado padrão de bit dado.
Os pinos do seu robot precisam de estar conectados às linhas de saída do circuito de condução como se segue:
Pernas: A1------=------B1 = = B2------=------A2 = = A3------=------B3 Pin to set A-legs to ground= AD Pin to set B-legs to ground= BD Correspondências às linhas de output e ao circuito de condução: data 0 A1 data 1 A2 data 2 A3 data 3 AD data 4 B1 data 5 B2 data 6 B3 data 7 BDO Data 0 é a saída do circuito de condução que conecta à porta paralela no pino 2 (D0).
Esperamos que se divirta a construir o robot. Fale-nos acerca do seu robot, especialmente se o seu for construído com um desenho diferente!
|
Páginas Web mantidas pelo time de Editores LinuxFocus
© Katja and Guido Socher, FDL LinuxFocus.org Clique aqui para reportar uma falha ou para enviar um comentário para LinuxFocus |
Informação sobre tradução:
|
2001-08-22, generated by lfparser version 2.17