Softwares desenvolvidos

Interface Gráfica

A interface gráfica foi feita em linguagem python. Para rodar o programa corretamente é necessário ter o Python 2.7 com os seguintes pacotes instalados:

  1. Matplotlib - pacote padrão para plotar gráficos.
  2. PySerial - pacote utilizado para comunicação serial entre python e arduino.
  3. numpy - pacote fundamental para computação científica com python.
  4. Tkinter - pacote para gerenciar a interface gráfica, um intepretador de TK.

Sistemas linux, como Ubuntu e Debian, costumam vir com python2.7 instalado como padrão. Caso o aluno não tenha python em seu computador basta instalá-lo através do gerenciador de pacotes de seu sistema, em ubuntu, por exemplo, digite sudo apt-get install python2.7.

Em windows aconselhamos instalar a distribuição *Python(x,y)*, pois nela já se encontram todos os pacotes necessários. Ao instalar o python(x,y) verifique se as bibliotecas necessárias estão selecionadas para instalação.

# encoding: utf-8
"""
@author Emilio Galera, Heitor de Bittencourt
@date Dezembro, 2016
Interface gráfica para o sistema EPR do LEF.
"""

from Tkinter import *
from tkFileDialog import askopenfilename, asksaveasfilename
from matplotlib.figure import Figure
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg, NavigationToolbar2TkAgg
import matplotlib.ticker as mtick
import serial
import serial.tools.list_ports
import time
import numpy as np
import struct
import random


global count_max
count_max = 500000

global stop_flag
stop_flag = False

# tempo entre aquisições (em milisegundos)
global delay
delay = 1

global number_of_points
number_of_points = 1

global mean
mean = 1

global x_axis
x_axis = []

global y_axis
y_axis = []

global b_axis
b_axis = []


# descobre qual a porta que o arduino esta conectado
def get_arduino_port():
    ports = list(serial.tools.list_ports.comports())
    for p in ports:
        if "Arduino" in p[1]:
            print("Arduino in", p[0])
            return p[0]
    print("Did not find arduino. Using \"/dev/ttyUSB0\" as fallback.")
    return "/dev/ttyUSB0"

#essa função se comunica com o arduino e obtem dados do campo B, rampa
#do registrador (tensão de referencia de 0 a 1 volt) e sinal do lock-in
def read_data():
    global count_max
    sem_dados = True
    count = 0
    x = 0.
    y = 0.
    b = 0.
    connection.write("B")
    #print("B")

    ehlo1   = connection.read(1)
    datalen = connection.read(1)
    data    = connection.read(int(datalen))
    ehlo2   = connection.read(1)
    #print(ehlo1, datalen, data, ehlo2)

    if ehlo1 == b'x' and ehlo2 == b'X':
        dados_x = float(data)
    else:
        dados_x = None

    ehlo1   = connection.read(1)
    datalen = connection.read(2)
    data    = connection.read(int(datalen))
    ehlo2   = connection.read(1)

    #print(ehlo1, datalen, data, ehlo2)
    if ehlo1 == b'y' and ehlo2 == b'Y':
        dados_y = float(data)
    else:
        dados_y = None

    ehlo1   = connection.read(1)
    datalen = connection.read(1)
    data    = connection.read(int(datalen))
    ehlo2   = connection.read(1)
    #print(ehlo1, datalen, data, ehlo2)

    if ehlo1 == b'b' and ehlo2 == b'B':
        dados_b = float(data)
    else:
        dados_b = None

    #print dados_x, dados_y
    #print type(dados_x), type(dados_y)
    return dados_x, dados_y, dados_b


# essa função coleta dados e plota os mesmos de maneira recursiva
def plot_received_data(collected_points):

    # se for o primeiro ponto, a função espera até que a rampa inicie
    # para iniciar a coleta.
    if collected_points == 0:
        from_AD_x, from_AD_y, from_AD_b = read_data()
        while True:
            aux, aux2, aux3 = read_data()
            if abs(from_AD_x - aux) > 0.0002:
                break

    global stop_flag

    if not stop_flag:
        from_AD_x, from_AD_y, from_AD_b = read_data()
        while from_AD_x == None or from_AD_y == None:
            from_AD_x, from_AD_y, from_AD_b = read_data()

        global x_axis, y_axis, b_axis
        try:
            # corta os ultimos pontos para não plotar a volta abrupta de
            # tensão da rampa.
            if abs(from_AD_x - x_axis[len(x_axis) - 1]) < .1:
                x_axis.append(from_AD_x)
                y_axis.append(from_AD_y)
                b_axis.append(from_AD_b)

                #graph.scatter(from_AD_x, from_AD_y, color="red")
            else:
                stop_flag = True
            #graph.set_xlim(min(x_axis) * .99, max(x_axis) * 1.01)
            #canvas.draw()

        except IndexError:
            if collected_points == 0:
                x_axis.append(from_AD_x)
                y_axis.append(from_AD_y)
                b_axis.append(from_AD_b)
            else:
                pass

        window.after(delay, plot_received_data, collected_points + 1)

    else:
        bt_on.config(state="normal")
        bt_off.config(state="disabled")
        print len(x_axis), len(b_axis)
        (b, b0), cov = np.polyfit(x_axis, b_axis, 1, cov = True)
        #print b, b0
        B_axis = []
        for x_iten in x_axis:
            B_axis.append(10000. * ((x_iten * b) + b0))
        #print B_axis
        try:
            graph.lines[0].remove()
        except:
            pass
        try:
            graph.clear()
        except:
            pass
        canvas.draw()
        graph.grid()
        graph.yaxis.set_major_formatter(mtick.FormatStrFormatter('%.2e'))
        graph.xaxis.set_major_formatter(mtick.FormatStrFormatter('%.2e'))
        graph.set_ylabel("Sinal (Volts)", size=18)
        graph.set_xlabel("B (Gauss)", size=18)
        graph.autoscale(True, "y", False)
        graph.plot(B_axis, y_axis, color="red", linestyle="solid", linewidth="2.5")
        graph.set_xlim(min(B_axis) * .99, max(B_axis) * 1.01)
        canvas.draw()

        b_axis = B_axis

        print "Fim da coleta"

# essa função inicia a leitura
#inicia os parâmetros de tempo e varredura antes da leitura
def start_reading():
    global mean

    graph.set_xlim(0, number_of_points)
    canvas.draw()

    tp = tempo.get()
    cp = campo.get()
    #print type(tp), tp
    #print type(cp), cp

    connection.write("T")
    connection.write(str(tp))
    time.sleep(0.1)
    echo1_tp = connection.read(1)
    echo2_tp = connection.read(len(str(tp)))
    echo3_tp = connection.read(1)
    if echo1_tp == b't' and echo3_tp == b'T':
        print str(echo2_tp)

    #time.sleep(0.01)

    connection.write("D")
    connection.write(str(cp))
    time.sleep(0.1)
    echo1_cp = connection.read(1)
    echo2_cp = connection.read(len(str(cp)))
    echo3_cp = connection.read(1)
    if echo1_cp == b'd' and echo3_cp == b'D':
        print str(echo2_cp)
    #time.sleep(0.01)


    #time.sleep(0.01)

    mean = '5'
    #print mean
    connection.write("A")
    time.sleep(0.01)
    connection.write(mean)
    time.sleep(0.1)

    try:
        graph.lines[0].remove()
    except IndexError:
        pass

    global x_axis, y_axis, b_axis
    x_axis = []
    y_axis = []
    b_axis = []

    global stop_flag
    stop_flag = False

    bt_on.config(state="disabled")
    bt_off.config(state="normal")
    connection.write("I")
    graph.set_xlabel(u"Tensäo Rampa (Volts)", size=18)
    graph.autoscale(True, "y", False)
    plot_received_data(0)

#força uma parada de leitura, é chamada pelo botão
def stop_reading():
    global stop_flag
    stop_flag = True
    connection.write("P");

#plota o gráfico de um arquivo txt
def plot_file():
    cores = ['b', 'g', 'r', 'c', 'm', 'y', 'k', 'g'] #no more white
    # recebe o nome do arquivo a ser lido
    file_name = askopenfilename()
    # Le o arquivo em uma linha e retorna uma matriz com os dados
    data = np.loadtxt(file_name)

    # separa a matriz de dados em vetores para X e Y
    x_axis = data[:, 0]
    y_axis = data[:, 1]
    # grafica
    graph.plot(x_axis, y_axis, "k-", lw="1.5", color = cores[random.randint(0, len(cores)-1)])
    graph.autoscale()
    # desenha
    canvas.draw()

#escreve os dados do ultimo gráfico plotado em um arguivo txt
def write_data():
    global mean
    global x_axis, y_axis

    # recebe o nome do arquivo a ser salvo
    file_name = asksaveasfilename()
    print file_name + "!!!!!"
    header = "Arquivo: " + file_name + "\n"
    header = header + "Numero de pontos: " + str(len(x_axis)) + "\n"
    header = header + "Numero de medias: " + str(mean)

    print header
    # salva os dados
    np.savetxt(file_name, np.transpose(
        [b_axis, y_axis]), delimiter='\t', header=header, comments='# ')

#função chamada quando o botão de limpar a tela for ativado
def clear_plot():
    global x_axis, y_axis
    x_axis = []
    y_axis = []
    # remove o gráfico feito
    graph.lines[0].remove()
    # reseta os limites do gráfico
    graph.set_xlim(0, 1)
    graph.set_ylim(-1, 1)
    # redesenha
    canvas.draw()

# função chamada ao fechar o programa
def on_closing():
    print("Adios")
    # manda o comando C pro arduino resetar
    connection.write("C")
    connection.close()
    window.destroy()

if __name__ == '__main__':
    connection = serial.Serial(get_arduino_port(), 115200, timeout = 2)
    time.sleep(1)
    print("Foi")

    window = Tk()
    window.minsize(width=900,height=600)
    window.title("Trambolhino - EPR - LEF")
    window.state("normal")

    #### divide a janela em áreas ###
    #### TÍTULO ####
    title_area = Frame(window)
    title_area.pack(side="top", fill="y")

    #### GRÁFICO ####
    graph_area = Frame(window)
    graph_area.pack(side="top", fill="both", expand=True)

    #### INTERAÇÃO COM USUÁRIO ####
    user_area = Frame(window)
    user_area.pack(side="bottom", fill="x")
    ###                           ###

    # TÍTULO
    title = Label(title_area, text="Trambolhino - Emilio Galera & Heitor de Bittencourt, 2016",
                  font="arial 14 bold")
    title.pack(side="top", fill="x", expand=True)


    # GRÁFICO

    fig = Figure()

    canvas = FigureCanvasTkAgg(fig, graph_area)
    canvas.draw()
    canvas.get_tk_widget().pack(side="bottom", fill="both", expand=True)

    graph = fig.add_subplot(1, 1, 1)
    graph.grid()
    graph.yaxis.set_major_formatter(mtick.FormatStrFormatter('%.2e'))
    graph.xaxis.set_major_formatter(mtick.FormatStrFormatter('%.2e'))
    graph.set_ylabel("Sinal (Volts)", size=18)
    graph.set_xlabel("B (Gauss)", size=18)
    graph.autoscale(True, "y", False)

    toolbar = NavigationToolbar2TkAgg(canvas, graph_area)
    toolbar.update()
    toolbar.pack(side="left")


    # USUÁRIO

    # Botões para iniciar e para a leitura, salvar um gráfico em txt
    #(apenas as coordenadas x e y), ler um arquivo txt e plotar o
    #gáfico e limpar a tela.

    user_buttons = Frame(user_area)
    user_buttons.pack(side="right", fill="y", expand=True)

    # grid 2x3 (duas colunas e três linhas)
    user_buttons.columnconfigure(0, weight=1)
    user_buttons.columnconfigure(1, weight=1)
    for i in range(0, 3):
        user_buttons.rowconfigure(i, weight=1)

    bt_on = Button(user_buttons, text="Ler conversor",
                   font="Arial 12 bold", width=10, command=start_reading)
    bt_on.grid(row=0, column=0, pady=3)

    bt_off = Button(user_buttons, text="Parar leitura", font="Arial 12 bold",
                    width=10, state="disabled", command=stop_reading)
    bt_off.grid(row=1, column=0, pady=3)

    bt_write = Button(user_buttons, text="Salvar",
                      font="Arial 12 bold", width=10, command=write_data)
    bt_write.grid(row=0, column=1, pady=3)

    bt_read = Button(user_buttons, text="Ler",
                     font="Arial 12 bold", width=10, command=plot_file)
    bt_read.grid(row=1, column=1, pady=3)

    bt_clear = Button(user_buttons, text="Limpar",
                      font="Arial 12 bold", width=10, command=clear_plot)
    bt_clear.grid(row=2, column=1, pady=3)


    #Radio buttons, para tempo e escala de varredura
    radio_buttons = Frame(user_area)
    radio_buttons.pack(side="left", fill="y", expand=True)

    radio_buttons.columnconfigure(0, weight=1)
    radio_buttons.columnconfigure(1, weight=1)

    for i in range(0, 5):
        radio_buttons.rowconfigure(i, weight=1)

    # Radio button para selecionar o tempo de varredura
    tempo = IntVar()
    titulo_tempo = Label(radio_buttons, text="Tempo", font="Arial 12 bold",width=10)
    titulo_tempo.grid(row = 0, column = 0)

    tempo30RB = Radiobutton(radio_buttons, text = "30 segundos", variable = tempo, value = '0')
    tempo30RB.grid(row = 1, column = 0, sticky = 'W')

    tempo60RB = Radiobutton(radio_buttons, text = "60 segundos", variable = tempo, value = '1')
    tempo60RB.grid(row = 2, column = 0, sticky = 'W')

    tempo3mRB = Radiobutton(radio_buttons, text = "3  minutos", variable = tempo, value = '2')
    tempo3mRB.grid(row = 3, column = 0, sticky = 'W')

    tempo5mRB = Radiobutton(radio_buttons, text = "5 minutos", variable = tempo, value = '3')
    tempo5mRB.grid(row = 4, column = 0, sticky = 'W')

    #Radio button para selecionar o delta B
    campo = IntVar()
    titulo_campo = Label(radio_buttons, text="Campo", font="Arial 12 bold",width=10)
    titulo_campo.grid(row = 0, column = 1)

    campo50G = Radiobutton(radio_buttons, text = "50 Gauss", variable = campo, value = '0')
    campo50G.grid(row = 1, column = 1, sticky = 'W')

    campo100G = Radiobutton(radio_buttons, text = "100 Gauss", variable = campo, value = '1')
    campo100G.grid(row = 2, column = 1, sticky = 'W')

    campo500G = Radiobutton(radio_buttons, text = "500 Gauss", variable = campo, value = '2')
    campo500G.grid(row = 3, column = 1, sticky = 'W')

    campo1000G = Radiobutton(radio_buttons, text = "1000 Gauss", variable = campo, value = '3')
    campo1000G.grid(row = 4, column = 1, sticky = 'W')

    window.protocol("WM_DELETE_WINDOW", on_closing)
    window.mainloop()

Observações importantes

O programa EPR-LEF.py foi testado nos computadores dos alunos, e no computador disponível no laboratório.

Os resultados obtidos nos computadores dos alunos foram satisfatórios, foram coletados pontos suficiente para observar todos os sinais necessários. O computador do laboratório não obteve pontos o suficientes para apresentar resultados aceitáveis: estava lento demais mesmo antes do programa ser rodado. Levantamos a hipótese de que o sistema operacional não era adequado, o computador estava usando Windows XP, formatamos a máquina e colocamos nela o sistema lubuntu, um sistema específico para máquinas com restrição de recursos. Nesse sistema o programa EPR-LEF.py obteve resultados melhores porém, ainda assim, não foi o suficiente para observar sinais claros e limpos.

Nas três máquinas testadas, os resultados foram substancialmente melhorados quando retiramos a função de desenhar o gráfico enquanto os dados são obtidos.

Instabilidades no gerador de rampa também dificultaram as medidas, o mesmo apresenta perda de linearidade no final da varredura a qual não conseguimos solucionar. A imagem Fig. 36 a seguir mostra este problema. Ligamos um osciloscópio digital nas saídas de tensão para a fonte do eletroímã (mostrado na curva amarela) e também na saída para registrador gráfico (em azul).

A curva azul deveria ser linear de zero a cerca de um volt, com duração de 300 segundos, mas vemos que rampa está muito instável: após cerca de 100 segundos, a tensão sobe abruptamente para o valor máximo, tendo duração de cerca de 140 segundos.

_images/rampa_osciloscopio.jpg

Fig. 36 Saídas do controlador de varredura vistas no osciloscópio. Em amarelo, vemos o sinal enviado para a fonte do magneto, em azul está a rampa a ser usada em registrador gráfico. Esse sistema apresenta um problema de instabilidade muito acentuado: a rampa (azul) era para ser linear, de zero a um volt, com duração de 300 segundos, mas após cerca de 100 segundos rampa atinge abruptamente o valor máximo.

Recomendamos fortemente aos alunos que irão realizar esta prática que utilizem seus próprios computadores para coleta de dados com o programa EPR-LEF.py

Firmware para Arduino

Para obter os dados de tensão da rampa e sinal do lock-in utilizamos um microcontrolador Arduino.

Para programá-lo utilizamos linguagem C++. O arduino da caixa já está carregado com o programa de aquisição de dados mas, caso seja necessário carregá-lo novamente são necessárias algumas bibliotecas adicionais para que o programa compile corretamente.

  1. Nanoshield_ADC.h - biblioteca para controlar o módulo ADC (convesor analógico digital).
  2. SoftReset.h - biblioteca utilizada para resetar o arduino através de software.
  3. SoftwareSerial.h - biblioteca utilizada para se comunicar com o software, python nesse caso.

Todas as bibliotecas se emcontram no projeto EPR-LEF na pasta Firmware/libs.

/**
 * @author Emilio Galera, Heitor de Bittencourt
 * @date Dezembro, 2016
 *
 * Firmware para Arduino fazer a aquisicao de dados do equipamento de EPR
 * do LEF.
 */

#include <Arduino.h>
#include <Wire.h>
#include <stdlib.h>
// external libraries
#include <Nanoshield_ADC.h>
#include <SoftReset.h>
#include <SoftwareSerial.h>
//#include <WString.h>

// canais do ADC para leitura do valor x e B
const int channel_x = 1;
const int channel_b = 2;

const int pinLed13 = 13;

// pinos que acionam start/stop do controle de varredura do campo
const int start_r = 2;
const int stop_r = 3;

// Pinos digitais para comunicacao com chave digital de selecao de tempo de
// varredura e variacao de campo magnetico
const int temp_A = 4;
const int temp_B = 5;
const int campo_A = 6;
const int campo_B = 7;


Nanoshield_ADC adc;
SoftwareSerial lockin(10, 11); // RX, TX


void setup()
{
	adc.begin();
	Serial.begin(115200);
	lockin.begin(9600);
	delay(2000);

	while (!Serial);

	pinMode(start_r, OUTPUT);
	pinMode(stop_r,OUTPUT);

	pinMode(temp_A, OUTPUT);
	pinMode(temp_B, OUTPUT);

	pinMode(campo_A, OUTPUT);
	pinMode(campo_B, OUTPUT);

	delay(1000);

	digitalWrite(start_r, LOW);

	digitalWrite(stop_r, HIGH);
	delay(7);
	digitalWrite(stop_r, LOW);

	digitalWrite(temp_A, LOW);
	digitalWrite(temp_B, LOW);

	digitalWrite(campo_A, LOW);
	digitalWrite(campo_A, LOW);

	pinMode(pinLed13, OUTPUT);

	// o lock-in envia um cabeçalho ao iniciar
	// o while abaixo le o mesmo e o descarta
	analogReference(DEFAULT);
	while (lockin.available() > 0)
		Serial.print(lockin.read());
	lockin.print("W0\r");

	//ganho utilizado para medir as voltagens com o ADC
	adc.setGain(GAIN_EIGHT);
}


void loop()
{
	int opcao = 100;
	int media = 1;
	int i;
	double x;
	double y;
	double b;
	char result[20];
	int merda;
	int tempo_aux = 0;
	int campo_aux = 0;
	char aux_y[11];
	int j;

	if (Serial.available() > 0) {
		opcao = Serial.read();

		switch(opcao) {
		case 'A':
			/*while(Serial.peek() < 0);
			media = Serial.parseInt();*/
			ret1:
				if (Serial.available() > 0)
					media = Serial.parseInt();
				else
					goto ret1;

			opcao = 100;
			//Serial.println(media);
			break;

		case 'B':
			digitalWrite(pinLed13, HIGH);
			x = 0;
			y = 0;
			b = 0;
			for(i = 0; i < media; i++){
				x += 2*adc.readVoltage(channel_x);
				b += adc.readVoltage(channel_b);
				lockin.print("q\r");
				j = 0;
				while(lockin.available() > 0 && j < 20){
					aux_y[j]= lockin.read();
					j++;
				}
				y += atof(aux_y);
			}
			x /= (double) media;
			y /= (double) media;
			b /= (double) media;

			dtostrf(x, 3, 6, result);
			Serial.write('x');
			Serial.print(strlen(result));
			Serial.print(result);
			Serial.write('X');

			dtostrf(y, 4, 10, result);
			Serial.write('y');
			Serial.print(strlen(result));
			Serial.print(result);
			Serial.write('Y');

			dtostrf(b, 3, 6, result);
			Serial.write('b');
			Serial.print(strlen(result));
			Serial.print(result);
			Serial.write('B');


			digitalWrite(pinLed13, LOW);
			opcao = 100;
			break;

		case 'C':
			opcao = 100;
			soft_restart();
			break;

		// reset lock-in
		case 'Z':
			opcao = 100;
			lockin.print("z\r");
			break;

		// inicia rampa
		case 'I':
			opcao = 100;
			digitalWrite(start_r, HIGH);
			delay(10);
			digitalWrite(start_r, LOW);
			break;

		// interrompe rampa
		case 'P':
			opcao = 100;
			digitalWrite(stop_r, HIGH);
			delay(10);
			digitalWrite(stop_r, LOW);
			break;

		// selecao de Tempo
		// 0 - 0.5 min
		// 1 - 1   min
		// 2 - 3   min
		// 3 - 5   min
		case 'T':
			opcao = 100;
			while(Serial.peek() < 0);
			tempo_aux = Serial.read();
			Serial.write('t');
			Serial.write(tempo_aux);
			Serial.write('T');

			switch(tempo_aux) {
			case '1':
				digitalWrite(temp_B, LOW);
				digitalWrite(temp_A, HIGH);
				media = 50;
				break;

			case '2':
				digitalWrite(temp_B, HIGH);
				digitalWrite(temp_A, LOW);
				media = 150;
				break;

			case '3':
				digitalWrite(temp_B, HIGH);
				digitalWrite(temp_A, HIGH);
				media = 250;
				break;
			case '0':
			default:
				digitalWrite(temp_B, LOW);
				digitalWrite(temp_A, LOW);
				media = 25;
				break;
			}
			break;

		// Selecao de delta B
		// 0 - 50   gauss
		// 1 - 100  gauss
		// 2 - 500  gauss
		// 3 - 1000 gauss
		case 'D':
			opcao = 100;
			while(Serial.peek() < 0);
			campo_aux = Serial.read();
			Serial.write('d');
			Serial.write(campo_aux);
			Serial.write('D');

			switch(campo_aux) {
			case '1':
				digitalWrite(campo_B, LOW);
				digitalWrite(campo_A, HIGH);
				break;

			case '2':
				digitalWrite(campo_B, HIGH);
				digitalWrite(campo_A, LOW);
				;

			case '3':
				digitalWrite(campo_B, HIGH);
				digitalWrite(campo_A, HIGH);
				break;
			case '0':
			default:
				digitalWrite(campo_B, LOW);
				digitalWrite(campo_A, LOW);
				break;
			}
			break;

		default:
			opcao = 100;
		}
	}
}