16. Aquisição de dados de um espectrofotômetro - I

Agora vamos entender como fazer a aquisição automática das leituras de Aborbância ou Transmitância transmitidos pela porta serial RS232 de um espectrofotômetro de absorção, neste caso o espectrofotômetro Femto (mod. 600S)

No entanto os conceitos que vamos mostrar se aplicam a qualquer equipamento com saída de dados no formato ASCII pela porta serial.

O Femto 600S é um espectrofotômetro na faixa do visível (325 a 1100 nm) com saída serial RS232 com taxa de transferência de 2Hz (envia o valor do display pela porta serial a cada 0,5 s). Uma taxa de transferência satisfatória para o uso em sistemas de análise em fluxo.

16.1. Cabo para Comunicação Serial

Pinagem do cabo serial DB9:

  PC                 Femto
  2---------------------2
  3---------------------3
  5---------------------5
  7                     7
  |                     |
  8                     8

Atenção

O pino 7 está conectado com o pino 8 ("jampeado") em cada lado do conector DB9 (Ver Pinagem RS232)

Figura 108. Cabo montado

Cabo montado


16.2. Configurando o Equipamento

Este equipamento, assim como muitos outros, precisa ser configurado pelo teclado para enviar continuamente os dados do visor pela saída serial seguindo os seguintes passos (segundo o manual do equipamento):

  1. Tecle "Fn" + "Cal"

  2. O visor irá exibir a mensagem "Configuração Saída Serial"

  3. Tecle "Entrar"

  4. Use as teclas de rolagem para selecionar a opção "Contínua"

    A opção "Comando" envia os dados apenas quando pressionada a tecla "Entrar"

16.3. Arquivos de Dispositivos

As portas seriais recebem um nome dependendo do sistema.

No Linux os dispositivos físicos precisam estar associados a um arquivo, os quais ficam localizados no diretório /dev, como ttyS0, ttyS1 ...

Nota

Mas se você estiver usando um conversor USB-Serial o dispositivo serial recebe um nome do tipo ttyUSB0, ttyUSB1 ... ou ttyACM0, ttyACM1 ...

No Windows as portas seriais são nomeadas como "COM1", "COM2" ...

Observe que no Linux a primeira porta serial é numerada a partir de "0" e no Windows a partir de "1".

Figura 109. Nomes dos dispositivos físicos

Nomes dos dispositivos físicos


Para manipular um arquivo é preciso abrir um canal de acesso até ele. Na Tcl isso é feito com o comando open.

16.4. O comando open

Para abrir um arquivo: open [nome_do_dispositivo]

No caso da primeira porta serial /dev/ttyS0 (Linux):

open /dev/ttyS0

E no Windows:

open com1:

Mas ao abrir um canal de comunicação com o comando open é preciso especificar o modo de acesso, ou seja, se o arquivo será aberto apenas para leitura "r", escrita "w" ou leitura e escrita "r+". (Existem outros modos de acesso, ver Entrada e saída em arquivos e portas)

O Femto 600S opera no modo unidirecional, ou seja, não aceita comandos pela porta serial mas apenas envia dados de saída e portanto podemos abrir a porta apenas para leitura "r" ou, por comodidade, deixar sempre no modo leitura e escrita "r+".

open /dev/ttyS0 r+

16.5. O comando set

O canal aberto pelo comando open recebe um nome gerado "aleatoriamente" pela Tcl. Para facilitar a manipulação deste canal, associamos o nome do canal aberto a uma variável de fácil identificação com o comando set:

set nome_do_canal [ open nome_do_arquivo modo_acesso ]

set porta [ open /dev/ttyS0 r+ ]

A Tcl vai substituir o que estiver dentro do colchetes pelo resultado do comando open, que neste caso é o nome do canal, e vai atribuir o nome do canal à variável porta. Para escrever dados em arquivos usamos o comando puts e para ler dados de um arquivo usamos o comando gets.

Atenção

Estamos considerando que a primeira porta serial está disponível, caso contrário será necessário selecionar as portas seguintes.

No Linux, lembrar também de mudar as permissões para permitir leitura e escrita pela porta serial com o comando chmod.

16.6. O comando fconfigure

Para receber os dados corretamente, é necessário configurar a porta serial com os parâmetros de comunicação definidos pelo fabricante, ou seja:

9600 bps, 7 bits de dados, paridade par (e) e 2 bits de parada.

Atenção

Para recapitular esse conceito releia "Padrões de Transmissão"

Por isso acrescentamos ao nosso programa a linha:

fconfigure $porta -mode 9600,e,7,2

E o código fica:

set porta [ open /dev/ttyS0 r+ ]

fconfigure $porta -mode 9600,e,7,2

16.7. O comando gets

O comando gets "avança" uma linha do canal, cujo nome está armazenado na variável porta, e retorna o seu conteúdo

gets $porta

Para armazenar esse conteúdo em uma variável usamos o comando set e nome da variável (dados) que vai armazenar os dados recebidos pela porta serial.

set dados [gets $porta]

E o código fica:

set porta [ open /dev/ttyS0 r+ ]

fconfigure $porta -mode 9600,e,7,2

set dados [gets $porta]

Para exibir no terminal o dado recebido basta incluir o comando puts (put string).

puts $dados

16.8. O comando while

Do jeito que está, o programa vai abrir a porta, ler o dado disponível, exibir no terminal e encerrar. Mas queremos que ele fique lendo continuamente os dados enviados pelo espectrofotômetro e por isso vamos colocar os comandos de leitura com o gets dentro de um loop while. (Mais detalhes em www.souzamonteiro.com)

set porta [ open /dev/ttyS0 r+ ]
fconfigure $porta -mode 9600,e,7,2

while { 1 } {
set dados [gets $porta]
puts $dados
}

16.9. O comando eof

Se o envio de dados for interrompido, o comando gets $porta ficaria esperando indefinidamente o envio de dados.

Para evitar isso usamos o comando eof (end of file - fim de arquivo), o qual retorna verdadeiro (diferente de 0) caso o final de um canal tenha sido atingido.

while { ! [ eof $porta ] } {
      set dados [gets $porta]
      puts $dados}

Lembre-se que o while executa uma operação enquanto estiver recebendo um valor verdadeiro e o eof retorna falso até o fechamento do canal. Por isso incluímos o sinal de exclamação (!) que é um operador de negação. Sem este truque o nosso loop iria parar logo na primeira linha.

Atenção

Enquanto NÃO(!) for falso = Enquanto for verdadeiro

16.10. O comando fileevent

Um canal na Tcl, por padrão, vai ficar bloqueado se você tentar ler mais dados do que existem no canal, e gerar uma mensagem de erro se você tentar ler ou escrever dados em um canal que já tenha sido fechado.

Para evitar isso, existe o comando fileevent que executa um comando (definido por você) quando um canal puder ser lido ou escrito. Ele recebe o nome de um comando (definido por você) e um parâmetro que informa em qual situação o comando deve ser executado: se quando o canal puder ser lido (readable) ou escrito (writable).

Estrutura geral do comando fileevent para executar o meu_procedimento quando meu_canal puder ser lido:

fileevent$meu_canal readable [ meu_procedimento ]

Os colchetes promovem a substituição de comando, portanto na linha acima o comando "meu_procedimento" seria executado antes do comando fileevent impedindo o correto funcionamento do comando fileevent.

Para contornar esse problema, usamos o comando list dentro dos colchetes, que além de controlar o sincronismo dos comandos organiza o comando meu_procedimento e os respectivos argumentos. (Ver detalhes em Substituição de Comandos)

fileevent $meu_canal readable [ list meu_procedimento ]

E o nosso programa fica:

set porta [ open /dev/ttyS0 r+ ]
fconfigure $porta -mode 9600,e,7,2

while { ! [ eof $porta ] } {
      set dados [gets $porta]
      puts $dados}

fileevent $porta readable [list ler_porta $porta]

16.11. Criando o Procedimento ler_porta

Para recordar veja a seção "Criando seus procedimentos"

Definimos que o procedimento ler_porta seria executado sempre que o canal porta estivesse legível:

fileevent $porta readable [list ler_porta $porta]

Agora precisamos criar o procedimento ler_porta:

proc ler_porta { canal } {            ;# Testa se já chegou no fim
    if { [eof $canal ] } {            ;# Se "é o fim", envia mensagem
        puts stderr "Fechando $canal" ;# Fecha o canal
        catch { close $canal }        ;# Sai do procedimento
        return                           
    }
    set dados [gets $canal]           ;# Mas se ainda não "é o fim",
    puts $dados                                  então lê uma linha do canal e 
}                                                coloca na variável dados.  

Como está o script femto_600s.tcl:

#Abre a porta e configura os parâmetros de comunicação
set porta [ open /dev/ttyS0 r+ ]
fconfigure $porta -mode 9600,e,7,2

#Associa o recebimento de dados à execução de ler_porta

fileevent $porta readable [list ler_porta $porta]

#Definição do procedimento ler_porta com o argumento canal

proc ler_porta { canal } {

    if { [eof $canal ] } {
        puts stderr "Fechando $canal"
        catch { close $canal }
        return
    }
    set dados [gets $canal]
    puts $dados
}

Linhas iniciadas por "#" são comentários incluídos no programa e não são executadas.

Este programa apenas recebe os dados enviados pela porta serial, ttyS0 ou com1 (conforme o sistema operacional), e exibe no terminal (saída padrão).

16.12. Criando um script executável

Para rodar o programa podemos digitar linha por linha em um interpretador interativo ou, usando um editor de texto puro (ou plano), criar um arquivo com a extensão "tcl" para executar o programa toda vez que for chamado.

Para isso devemos incluir nas primeiras linhas do arquivo as seguintes linhas:

#!/bin/sh
#A próxima linha reinicia usando o wish \
    exec wish "$0" "$@"

E em seguida podemos salvar o arquivo com o nome femto600s.tcl, para ficar fácil de lembrar.

No Linux é necessário tornar o arquivo executável:

chmod +x femto600s.tcl

E o script femto600s.tcl poderá ser executado a partir da linha de comando como qualquer outro programa.

16.13. A biblioteca gráfica Tk

Combinada com o toolkit Tk (pronuncia-se tikei) a Tcl pode ser usada para gerar programas gráficos sem muitos códigos.

O toolkit (Kit de Ferramentas/Utilitários) Tk age como um complemento da Tcl, permitindo que você gere Widgets (elementos gráficos) facilmente criando uma interface gráfica com o usuário.

Figura 110. Exemplo do Widget "label"

Exemplo do Widget "label"


Para usar o Tk é importante entender quatro aspectos básicos:

  • Interpretador

  • Hierarquia

  • Eventos

  • Posicionamento dos widgets na tela (interface)

16.13.1. Interpretador

Um script que use apenas códigos Tcl pode ser executado com um interpretador tcl ou tclsh, dependendo da distribuição. Para usar Tk usamos outro interpretador, chamado "wish". Por simplicidade já incluímos a chamada ao interpretador wish na primeira linha do nosso script. Se lembra?

#!/bin/sh
#A próxima linha reinicia usando o wish \
    exec wish "$0" "$@"
         ^^^^

16.13.2. Hierarquia

Em Tk existe uma ordem para criar os objetos gráficos. O primeiro objeto gráfico é representado pelo caractere ponto ".".

Esse primeiro objeto gráfico é a sua janela principal, ou janela "pai", dentro da qual são colocados todos ou outros objetos "filhos". Por exemplo, ao colocar um botão em sua janela, ele poderá ser chamado: .botao, mas se você criar primeiro um frame dentro de sua janela e colocar um botão dentro do frame, poderia usar: .frame.botao

Criando um botão chamado botao:

#!/bin/sh
#A próxima linha reinicia usando o wish \
   exec wish "$0" "$@"
button .botao -text "Sair do programa" -command { exit }
pack .botao

Figura 111. Widget "botão" criado com o programa acima:

Widget "botão" criado com o programa acima:


As três primeiras linhas especificam qual interpretador deve ser usado. A quarta linha cria um botão, isso é feito com o comando button. Em seguida é definido um nome para o botão, repare no "." antes do nome ".botao". Em seguida definimos um texto que vai ser exibido no botão usando a opção "text".

16.13.3. Eventos

São ações que o usuário provoca no programa. Pode ser ao clicar um botão, pressionar uma tecla etc. Existem muitos eventos possíveis.

Com a opção command definimos o comando que será executado quando o usuário clicar o botão. Exit é um comando da Tcl que encerra um script.

16.13.4. Posicionamento dos widgets na tela (interface)

Em Tk, existem três desses gerenciadores, cada qual posicionando os widgets de forma diferenciada. A última linha do script do botão:

pack .botao

é uma instrução que diz: "use o gerenciador pack para posicionar o botão .botao na tela". Não adianta criar os elementos, botões, textos etc, e não usar um gerenciador desses. Os outros dois Gerenciadores de Posicionamento são o place e o grid. (Mais detalhes em www.souzamonteiro.com)

16.14. Criando a Interface Gráfica

Agora vamos criar uma interface gráfica para o programa femto_600s.tcl com widgets para as seguintes funções:

  1. Iniciar o recebimento de dados pela porta serial

  2. Exibir os dados recebidos em uma janela de texto

  3. Gravar o conteúdo da janela de texto em um arquivo

  4. Encerrar o programa

Figura 112. Projeto para a interface gráfica do programa

Projeto para a interface gráfica do programa


16.15. Incluindo os botões

#Cria o frame bodões, onde serão exibidos os botões
frame .botoes

#Exibe o frame
pack .botoes -side bottom -fill x

#Cria os botões
button .botoes.iniciar -text "Iniciar Leitura"
button .botoes.parar -text "Parar Leitura"
button .botoes.gravar -text "Gravar Arquivo"
button .botoes.sair -text "Sair" -command {exit}

#Exibe os botões
pack .botoes.iniciar .botoes.parar .botoes.gravar .botoes.sair \
-side left -expand yes -fill both

Figura 113. Frame contendo os botões

Frame contendo os botões


Como está o nosso script até aqui:

#!/bin/sh
#A próxima linha reinicia usando o wish \
   exec wish "$0" "$@"

#Abre a porta e configura os parâmetros de comunicação

set porta [ open /dev/ttyS0 r+ ]
fconfigure $porta -mode 9600,e,7,2

#Associa o recebimento de dados à execução de ler_porta

fileevent $porta readable [list ler_porta $porta]

#Definição do procedimento ler_porta com o argumento canal

proc ler_porta { canal } {

    if { [eof $canal ] } {
        puts stderr "Fechando $canal"
        catch { close $canal }
        return
    }
    set dados [gets $canal]
    puts $dados
}

#Cria o frame bodões, onde serão exibidos os botões
frame .botoes

#Exibe o frame
pack .botoes -side bottom -fill x

#Cria os botões
button .botoes.iniciar -text "Inciar Leitura"
button .botoes.parar -text "Parar Leitura"
button .botoes.gravar -text "Gravar Arquivo"
button .botoes.sair -text "Sair" -command {exit}

#Exibe os botões
pack .botoes.iniciar .botoes.parar .botoes.gravar .botoes.sair \
-side left -expand yes -fill both

16.16. Incluindo um text (texto) e o scrollbar (barra de rolagem)

frame .tela
pack .tela
text .tela.dados -relief sunken -yscrollcommand {.rolagem set} -height 30

As linhas acima criam o widget do tipo text chamado .tela.dados, do tipo texto dentro do frame .tela onde serão exibidos os resultados de leitura. A opção "relief" define o tipo de relevo do widget. Os valores possíveis são: flat, groove, raised, ridgen e sunken.

Para conhecer as diferenças, basta testar as diferentes opções de relevo e escolher a que você preferir.

A opção "yscrollcommand" define qual o nome da barra de rolagem (scrollbar) vertical que será usada para "rolar" a tela do texto no sentido vertical. A opção -height apenas define a altura da janela de texto.

O comando scrollbar cria uma barra de rolagem dentro do frame ".tela" chamada rolagem que permitirá visualizar (rolar) todo o texto

scrollbar .tela.rolagem -command {.tela yview}

E novamento usamos o gerenciador pack para exibir os elementos "tela" e "rolagem"

pack .tela.dados -side left -fill y 
pack .tela.rolagem -expand yes -fill both

Agora o programa exibe a seguinte tela:

Figura 114. Tela do programa femto600s.tcl incluindo os botões de controle e a texto para exibição/edição dos resultados.

Tela do programa femto600s.tcl incluindo os botões de controle e a texto para exibição/edição dos resultados.


16.17. Entendendo melhor a relação entre o text e o scrollbar

Observe a estrutura geral para associar um text com um scrollbar:

text .meu_texto -height 20 -yscrollcommand { .minha_barra_de_rolagem set }
scrollbar .minha_barra_de_rolagem -command { .meu_texto yview }
pack .meu_text -side left
pack .minha_barra_de_rolagem -fill both -expand yes

O comando set dentro da opção "yscrollcommand" serve para passar para a barra de rolagem a área visível atual do widget que está associado a ela.

A opção "command" do widget scrollbar qual widget ele deve "rolar" e em que sentido, yview (vertical) ou xview (horizontal).

A opção "side" especifica de qual lado um widget ficará em relação ao seu widget mestre. Os valores possíveis para "side" são left (esquerda), right (direita), bottom (abaixo) ou top (acima).

A opção "expand" pode ser yes/no ou false/true e especifica se o widget deverá ocupar, ou não, todo o espaço disponível, enquanto que a opção "fill" define como o widget será "esticado" para ocupar o espaço disponível.

16.18. Entendendo melhor as opções "side", "expand" e "fill"

Imagens falam mais que palavras, por isso observe os efeitos dessas opções no comando pack usado para exibir os botões de controle.

Usando o comando pack apenas:

pack .botoes.iniciar .botoes.parar .botoes.gravar .botoes.sair

Figura 115. Comando pack sem opção.

Comando pack sem opção.


Agora incluindo a opçao "side":

pack .botoes.iniciar .botoes.parar .botoes.gravar .botoes.sair -side left

Figura 116. Comando pack com a opção side.

Comando pack com a opção side.


Incluindo a opção "expand"

pack .botoes.iniciar .botoes.parar .botoes.gravar .botoes.sair -side left -expand yes

Figura 117. Comando pack com as opções side e expand.

Comando pack com as opções side e expand.


E finalment com a opção "fill" both (ou x)

pack .botoes.iniciar .botoes.parar .botoes.gravar .botoes.sair -side left -expand yes -fill both

Figura 118. Comando pack com as opções side, expand e fill.

Comando pack com as opções side, expand e fill.


E como está o script/programa femto600s.tcl:

#!/bin/sh
#A próxima linha reinicia usando o wish \
   exec wish "$0" "$@"

#Abre a porta e configura os parâmetros de comunicação

set porta [ open /dev/ttyS0 r+ ]
fconfigure $porta -mode 9600,e,7,2

#Associa o recebimento de dados à execução de ler_porta

fileevent $porta readable [list ler_porta $porta]

#Definição do procedimento ler_porta com o argumento canal

proc ler_porta { canal } {

    if { [eof $canal ] } {
        puts stderr "Fechando $canal"
        catch { close $canal }
        return
    }
    set dados [gets $canal]
    puts $dados
}

#Cria o frame bodões, onde serão exibidos os botões
frame .botoes

#Exibe o frame
pack .botoes -side bottom -fill x

#Cria os botões
button .botoes.iniciar -text "Inciar Leitura"
button .botoes.parar -text "Parar Leitura"
button .botoes.gravar -text "Gravar Arquivo"
button .botoes.sair -text "Sair" -command {exit}

#Exibe os botões
pack .botoes.iniciar .botoes.parar .botoes.gravar .botoes.sair -side left -expand yes -fill both


frame .tela
pack .tela

text .tela.dados -relief sunken -yscrollcommand {.tela.rolagem set} -bg white -height 30

scrollbar .tela.rolagem -command {.tela yview}

pack .tela.dados -side left -fill y 
pack .tela.rolagem -expand yes -fill both

16.19. Associando Eventos aos Botões

Até o momente o único botão com um evento associado é o botãoSair com o comando exit associado.

Vamos associar a rotina de abertura da porta serial ao botão Iniciar Leitura

Até o momento os comandos de abertura da porta serial, são executados uma única vez, quando o programa é aberto, ou seja não temos controle sobre o processo de abertura da porta serial.

Por isso vamos colocar essa sequência de comandos dentro de um procedimento que vamos chamar abrir_porta e associar a execução dessa rotina ao botão Iniciar Leitura.

Criando o procedimento abrir_porta

proc abrir_porta { } {
global porta

set porta [ open /dev/ttyS0 r+ ]

fconfigure $porta -mode 9600,e,7,2

fileevent $porta readable [list ler_porta $porta]

}

E incluir a opção -command na linha button .botoes.iniciar -text "Iniciar Leitura" -command {abrir_porta}

Figura 119. Botão Iniciar Leitura associado ao comando abrir_porta.

Botão Iniciar Leitura associado ao comando abrir_porta.


Para interromper a leitura de dados, vamos incluir a opção command na linha de criação do botão Parar Leitura incluindo o comando close $porta : button .botoes.parar -text "Parar Leitura" -command { close $porta }.

Figura 120. Botão Parar Leitura associado ao comando fechar_porta.

Botão Parar Leitura associado ao comando fechar_porta.


16.20. Escopo de Variáveis - Variáveis Locais e Globais

Vamos definir a variável porta como uma variável global mas antes vamos entender o conceito de Escopo de Variáveis.

O que é escopo?

Em linguagens de programação, escopo se refere ao alcance que uma variável possui, ou seja, de onde a variável pode ser acessada, trata da visibilidade de uma variável.

O escopo pode ser Global, Local ou variável de um Namespace.

Podemos ter variáveis cujos valores podem ser lidos e alterados por todo o programa (global). E variáveis que estão confinadas em determinados blocos de código e só podem ser manipuladas por esse bloco de código (local).

Figura 121. A variável "A" é uma variável Global pois pode ser acessada em qualquer parte do programa enquanto que as variáveis "B" e "C" são variáveis Locais e podem ser manipuladas apenas dentro dos respectivos procedimentos (1 e 2)

A variável "A" é uma variável Global pois pode ser acessada em qualquer parte do programa enquanto que as variáveis "B" e "C" são variáveis Locais e podem ser manipuladas apenas dentro dos respectivos procedimentos (1 e 2)


Por exemplo a variável porta está definida apenas dentro do procedimento abrir_porta e poderá ser manipulada apenas dentro deste proc. Ao tentar executar o procedimento fechar_porta $porta, a Tcl vai gerar um erro dizendo que a variável porta não existe. Ou seja a variável porta é uma variável local, definida apenas dentro de abrir_porta. Para contornar esse problema precisamos declarar a variável porta como uma variável global, tornando-a visível e disponível para todo o programa.

16.21. Gravando os Resultados

Agora só falta criar o código para salvar o conteúdo da janela de texto em um arquivo .txt, de tal forma que seja possível abrir o arquivo de dados em um planilha para construir gráficos etc.

Vamos chamar esse procedimento de gravar_arquivo

proc gravar_arquivo {} {
    
    set dados [.tela.dados get 1.0 {end -1c}]
    set tipo_arquivo {
	{"Arquivos de Texto"    { .txt } }
	{"Todos os arquivos" * }
    }
    set nome_arquivo [tk_getSaveFile -filetypes $tipo_arquivo -initialdir pwd]
    if {[catch {set id_arquivo [open $nome_arquivo w]} result] != 0} {
	    return
	}
    puts -nonewline $id_arquivo $dados
    close $id_arquivo
}

A linha set dados [.tela.dados get 1.0 {end -1c}] copia o conteúdo da janela de texto (.tela.dados) na variável dados

O comando set tipo_arquivo { {"Arquivos Texto" { .txt .TXT } } {"Todos os arquivos" * } } define os tipos de extensão de arquivos que vão aparecer na opção "tipo de Arquivo" na janela (widget) para escolha do nome do arquivo.

O comando tk_getSaveFile é um comando interno do Tk que facilita a tarefa de salvar arquivos abrindo uma janela de diálogo para o usuário selecionar o nome do arquivo.

Figura 122. Janela aberta pelo comando tk_getSaveFile para o usuário escolher o nome do arquivo que irá armazenanar as leituras.

Janela aberta pelo comando tk_getSaveFile para o usuário escolher o nome do arquivo que irá armazenanar as leituras.


O comando tk_getSaveFile retorna o caminho completo do arquivo selecionado pelo usuário o qual é armazenado na variável nome_arquivo.

A opção filetypes especifica os tipos de extensão de arquivos, e a opção initialdir define o diretório inicial que deve ser aberto, neste caso o diretório local pwd present work directory.

Na linha seguinte o comando set id_arquivo [open $nome_arquivo w] abre um canal para um arquivo com o nome selecionado pelo usuário atribui o nome do canal à variável id_arquivo.

O comando catch foi usado para interceptar um erro caso o usuário feche a janela sem selecionar um arquivo (variável nome_arquivo vazia) evitando a interrupção do programa.

O comando catch executa o script, e se este contiver um erro, retorna um valor diferente de zero o qual é armazenado na variável result.

Caso o retorno de catch seja diferente de zero, o comando if executa o retorno da função.

O comando puts -nonewline $id_arquivo $dados grava todos os dados da janela de texto para o arquivo.

E finalmente o comando close $id_arquivo fecha o canal.

Agora só falta associar a chamada desta rotina com o botão "Gravar Arquivo" incluindo a opção command na linha button .botoes.gravar -text "Gravar Arquivo" -command { gravar_arquivo }.

Como está o script femto_600s.tcl:

#!/bin/sh
#A próxima linha reinicia usando o wish \
	exec wish "$0" "$@"

global porta

frame .botoes

pack .botoes -side bottom -fill x

button .botoes.iniciar -text "Iniciar Leitura"  -command { abrir_porta }

button .botoes.parar -text "Parar Leitura" -command { close $porta }

button .botoes.gravar -text "Gravar Arquivo" -command { gravar_arquivo }

button .botoes.sair -text "  Sair  " -command "exit"

pack  .botoes.iniciar .botoes.parar .botoes.gravar .botoes.sair \
-side left -expand yes -fill both
frame .tela

pack .tela -side top  -fill x

text .tela.dados -relief sunken  -yscrollcommand  {.tela.rolagem set} \
-background white -width 60

scrollbar .tela.rolagem  -command {.tela.dados yview}

pack .tela.dados -side left -fill y

pack .tela.rolagem -expand yes -fill both

proc abrir_porta { } {

    global t0
    global porta    
    set porta [ open /dev/ttyS0 r+ ]

    fileevent $porta readable [list ler_porta $porta]

    .tela.dados insert end "\nLeituras\n\n"
}

proc ler_porta { canal } {
    
    if { [eof $canal ] } {
	puts stderr "Fechando $canal"
	catch { close $canal }
	return }

    set dados [gets $canal]

    .tela.dados insert end "$dados\n"

    .tela.dados see end
}

proc gravar_arquivo { } {

    set dados [.tela.dados get 1.0 {end -1c}]

    set tipo_arquivo {
	{"Arquivos Texto" { .txt .TXT } }
	{"Todos os arquivos" * }
    }

    set nome_arquivo [tk_getSaveFile -filetypes $tipo_arquivo  \
-initialdir pwd  -defaultextension .txt]

    set id_arquivo [open $nome_arquivo w]

    puts -nonewline $id_arquivo $dados

    close $id_arquivo
}

Do jeito que está, o programa vai registrar as leituras do equipamento, Transmitância (%T) ou Absorbância (Abs).

Figura 123. Exibição das leituras do espectrofotômetro.

Exibição das leituras do espectrofotômetro.


16.22. Ajustes Finais

Agora imagine que você queira registrar também o tempo de cada leitura para poder produzir gráficos Abs X tempo. Neste caso vamos criar uma variável que vai armazenar o instante inicial da aquisição e os tempos de cada leitura.

Dentro do procedimento abrir_porta vamos declarar a variável global t0 e vamos atribuir a esta variável o tempo do sistema em milisegundos com o comando set t0 [clock clicks -milliseconds].

O comando clock permite manipular datas e tempo decorrido entre eventos.

No procedimento ler_porta vamos declarar a variável global t0 e calcular o tempo atual em milisegundos e armazenar na variável tn com o comando: set tn [clock clicks -milliseconds].

Em seguida calculamos o tempo (segundos) decorrido desde o início das leituras (t0) com o comando: set tn [expr ($tn - $t0)/1000.0].

E finalmente vamos trocar o sinal decimal (.) por (,) para que as planilhas de cálculo (Excel ou Calc) identifique como valores numéricos.

Dentro do procedimento ler_porta usamos o comando regsub que substitui as ocorrências de uma expressão regular por um texto substituto informado para o comando.

 set tn [regsub {\.} $tn \, ]
 set dados [regsub {\.} $dados {,} ]

O programa todo ficou assim:

#!/bin/sh
#A próxima linha reinicia usando o wish \
exec wish "$0" "$@"

global porta

frame .botoes

pack .botoes -side bottom -fill x

button .botoes.iniciar -text "Iniciar Leitura"  -command { abrir_porta }

button .botoes.parar -text "Parar Leitura" -command { close $porta }

button .botoes.gravar -text "Gravar Arquivo" -command { gravar_arquivo }

button .botoes.sair -text "  Sair  " -command "exit"

pack  .botoes.iniciar .botoes.parar .botoes.gravar .botoes.sair \
    -side left -expand yes -fill both

frame .tela

pack .tela -side top  -fill x

text .tela.dados -relief sunken  -yscrollcommand  {.tela.rolagem set} \
    -background white -width 60
scrollbar .tela.rolagem  -command {.tela.dados yview}

pack .tela.dados -side left -fill y

pack .tela.rolagem -expand yes -fill both

  
################################################################
#Procedimento abrir_porta
################################################################

proc abrir_porta { } {
    
    global t0
    global porta

    set porta [ open /dev/ttyS0 r+ ]
   
    fconfigure $porta -mode 9600,e,7,2
    
    fileevent $porta readable [list ler_porta $porta]
    
    set t0 [clock clicks -milliseconds]
    
    .tela.dados insert end "\nTempo(ms) Leituras\n\n"
}

################################################################
#Procedimento ler_porta
################################################################

proc ler_porta { canal } {
    
    global t0    
    
#Comando para calcular o tempo atual em milisegundos
    set tn [clock clicks -milliseconds]
    
#Comando para calcular o tempo decorrido desde t0 em segundos
    set tn [expr ($tn - $t0)/1000.0]
    
    if { [eof $canal ] } {
	    puts stderr "Fechando $canal"
	    catch { close $canal }
	    return }
    set dados [gets $canal]
	
#Comando regsub usado para substituir as ocorrências de . por , 
#É possível usar \\. e \\, ou {\.} e {,} ou {,}
    set tn [regsub {\.} $tn \, ]

    set dados [regsub {\.} $dados {,} ]
	
    .tela.dados insert end " $tn $dados\n"

    .tela.dados see end
}


################################################################
#Procedimento gravar_arquivo
################################################################

proc gravar_arquivo { } {

    set dados [.tela.dados get 1.0 {end -1c}]

    set tipo_arquivo {
	{"Arquivos Texto" { .txt .TXT } }
	{"Todos os arquivos" * }
    }

    set nome_arquivo [tk_getSaveFile -filetypes $tipo_arquivo  \
-initialdir pwd  -defaultextension .txt]

    if {[catch {set id_arquivo [open $nome_arquivo w]} result] != 0} {
	    return
	}
    
    puts -nonewline $id_arquivo $dados
    
    close $id_arquivo
}

E no ambiente Windows?

Esse programa foi feito e testado em ambiente Linux, mas para usar com o Windows bastaria mudar o nome do dispositivo serial que você estiver usando.

Supondo que o dispositivo está na primeira porta serial, basta substituir "/dev/ttyS0" no Linux por "com1:" no Windows.

Então a linha:set porta [ open /dev/ttyS0 r+ ] ficaria set porta [ open com1: r+ ]

Prezado Amigo ou Prezada Amiga.

Tentamos mostrar, de forma didática, como usar a linguagem Tcl/Tk para desenvolver uma interface gráfica para aquisição de dados pela porta serial.

Espero que essas informações sejam úteis para um melhor entendimento da estrutura e funcionamento dos espectrofotômetros e para aqueles que queiram dar alguns passos no desenvolvimento de ferramentas simples, e úteis, para a aquisição automática de dados produzidos por esses instrumentos.

Este tipo de interface serve para a aquisição de qualquer instrumento que envie os dados (no formato ASCII) pela porta serial, ajustando apenas os dados para configuração da porta serial!

16.23. Links interessantes:

Artigos sobre Espectroscopia no Instituto de Química da UNICAMP

Lei de Beer - Universidade Sheffield Hallam

Espectrofotômetros - Site da Universidade de Virginia

Comunicação serial

Livro sobre Programação com Tcl/Tk