Este artigo mostra os passos para realizar uma comunicação de dados em um microcontrolador PIC 18F4550 pela interface USB, bem como a construção de uma Interface. Os compiladores utilizados foram o CCS-PICC e Microsoft Vistual Studio Express C#. Neste, utilizaremos o device USB como CDC, com um driver padrão do compilador, onde a comunicação USB é “convertida virtualmente” em uma porta serial COM, expandindo as possibilidades de interfaceamento com programas de alto nível.

 

Eng. Rahul Martim Juliato
 (Este endereço de email está sendo protegido de spambots. Você precisa do JavaScript ativado para vê-lo.)

 

Circuito Proposto

Iniciaremos analisando o circuito de comunicação proposto. Para esse artigo, foi escolhido trabalhar um exemplo envolvendo uma entrada digital (DI), uma saída digital(DO), uma entrada analógica(AI) e uma saída analógica(AO), cobrindo a maioria das necessidades de técnicos, hobistas e engenheiros.

Apesar da real complexidade dos padrões definidos para portas USB, esse artigo foca na construção prática, sem quaisquer conhecimentos prévios sobre o protocolo USB. Contudo, quando trabalhamos com microcontroladores, é recomendável estar em dia com os conhecimentos sobre eletrônica digital e analógica, bem como programação, nesse caso C para o firmware (programa embarcado) e C# para o programa de alto nível.

Para auxiliar a análise em funcionamento, foi inserido também um display LCD não obrigatório, mas que facilita e deixa a busca por soluções mais amigável.

O circuito proposto é exemplificado pela figura 1.

Os microcontroladores PIC da Microchip, trabalham com 5Vcc, com funções de entrada digital e saída digital, entradas analógicas com conversores A/D, porém não possuem saídas analógicas. Mas isso não é problema, visto que há módulos PWM embutidos, e basta passar este sinal por um circuito integrador para se obter níveis de tensão analógicos.

 

Figura 1a – Circuito Proposto.
Figura 1b – Circuito Proposto
Figura 1 – Circuito Proposto.

 

  

 

Foi utilizado um cristal de 8 MHz, bem como capacitores de 22 pF no circuito de geração de clock. As I/Os estão conectadas conforme a tabela 1.

Tabela 1 – I/Os utilizadas.

Pino Função
RB0 Entrada Digital (DI)
RB1 Saída Digital (DO)
RA0 Entrada Analógica (AI)
RC2 Saída Analógica (AO) – PWM
RD0 – RD7 Dados do display LCD
RE1 – RE2 Controle do display LCD
RC4 – RC5 Dados da porta USB

 

Firmware (Programa Embarcado)

O próximo passo consiste na criação de um software, capaz de fazer a leitura e escrita dos estados das portas digitais e enalógicas e enviar e receber informações pela porta USB, para esse projeto foi utilizado o compilador PCH 16bits da CCS.

Nesse passo, foi criado um “protocolo” de comunicação, ou seja, foi definida a formatação da mensagem à ser enviada e recebida pelo programa de alto nível que estará rodando no microcomputador.

Existem diversas formas de realizar essa etapa, assim como existem diversas configurações possíveis para conversão analógica/digital e PWM, definimos alguns padrões de funcionamento antes de iniciar a construção do Firmware, são eles:

- O microcontrolador somente trabalhará com níveis TTL, ou seja, 5V no máximo em cada porta analógica ou digital;

- O módulo CCP1, será utilizado na configuração PWM e com frequência fixa e ciclo ativo variável entre 0 e 1023, sendo que 0 representa 0% e 1023 representa 100%;

- A conversão analógica será feita somente na porta RA0 com conversão de 10 bits, assim 0V será lido como 0 e 5V será lido como 1023.

A mensagem enviada pelo microcontrolador é montada a partir de dos valores lidos da entrada digital e da entrada analógica em um pacote de 6 bits, conforme o quadro seguinte:

 

MSB D1 D2 D3 D4 D5 D6 LSB
Digital01 Digital02 Analógico01 Analógico02 Analógico03 Analógico04

 

Dessa forma, se a entrada digital estivesse no estado 01 e a entrada analógica em 1000, teríamos o quadro preenchido da seguinte forma:

MSB D1 D2 D3 D4 D5 D6 LSB
0 1 1 0 0 0

 

Já a leitura de mensagens enviadas ao microcontrolador é realizada em dois vetores, conforme quadros seguintes:

recebemsg1[0] recebemsg1[1] recebemsg1[2] recebemsg1[3]
A Digital01 Digital02 0x00
recebemsg2[0] recebemsg2[1] recebemsg2[2] recebemsg2[3] recebemsg2[4]
Analógico01 Analógico02 Analógico03 Analógico04 0x00

 

A posição recebemsg1[0] deve ser a letra identificadora A para que o restante da informação se encaixe nas devidas posições, essa verificação é realizada no programa. Se tivermos a condição digital 00 e a condição analógica 994, teremos os vetores preenchidos conforme sequência mostrada nos quadros à seguir:

recebemsg1[0] recebemsg1[1] recebemsg1[2] recebemsg1[3]
A 0 0 0x00
recebemsg2[0] recebemsg2[1] recebemsg2[2] recebemsg2[3] recebemsg2[4]
0 9 9 4 0x00

 

O programa então segue a lógica demonstrada no fluxograma da figura 2.

A razão para a separação em dois vetores, reside na dificuldade em se manipular “strings” no microcontrolador, repare que isso não foi necessário na mensagem enviada pelo PIC, que será tratada em alto nível no computador.

A figura 2 mostra o fluxograma do programa no microcontrolador.

 

Figura 2 – Fluxograma do firmware
Figura 2 – Fluxograma do firmware

 

O programa completo e comentado, é apresentado à seguir. Seu estudo é de extrema importância para a compreensão do funcionamento da parte de alto nível, aqui está somente o programa principal, sendo que bibliotecas não padrão podem ser encontradas no pacote do programa.

/*
Programa: Firmware da Comunicação USB e Interface em C#
Autor: Rahul Martim Juliato
Compilador: PICC-CCS
Data: 20.12.2012
*/
#include < 18F4550 . h >//Biblioteca do PIC Utilizado
#device ADC = 10 //Seta conversão A/D em 10 Bits
#fuses HSPLL , MCLR , NOWDT , NOPROTECT , NOLVP , NODEBUG , USBDIV , PLL2 , CPUDIV1 , VREGEN , NOPBADEN //Fuses do PIC
#use delay ( clock = 48000000 )

 

#include < stdlib . h >
#include "usb_desc_cdc.h" //Biblioteca de descrição do device
#include "usb_cdc.h" //Biblioteca de conversão usb->UART
#include "lcd_Artigo_USB.c" //Biblioteca de manipulação do display LCD
#define DI pin_b0 //Definições das funções e pinos
#define DO pin_b1
#define AI pin_a0
#define AO pin_c2
int16 valor_ai = 0 ; //Variáveis globais
int32 valor_ao = 0 ;
int16 valor_do = 0 ;
short int valor_di = 0 ;
char recebemsg1 [] = { 0 , 0 , 0 , 0 };
char recebemsg2 [] = { 0 , 0 , 0 , 0 , 0 };
void main ( ){
//Configurações do conversor A/D
setup_adc_ports ( AN0 | VSS_VDD ); //Liga modo analógico somente na AN0
setup_adc ( ADC_CLOCK_INTERNAL ); //Usa o clock interno como ref. de conv.
//Configurações do módulo PWM
SETUP_TIMER_2 ( t2_div_by_4 , 248 , 1 );
SETUP_CCP1 ( CCP_PWM_HALF_BRIDGE | CCP_PWM_H_L );
set_pwm1_duty ( 0 );
setup_ccp1 ( CCP_PWM );

 

output_b ( 0x00 );
//Inicializa o display LCD e escreve mensagens
Lcd_ini ();
delay_ms ( 250 );
Lcd_escreve ( '\f' );
delay_ms ( 250 );
lcd_pos_xy ( 1 , 1 );
printf ( lcd_escreve , "Comunicacao USB! " );
lcd_pos_xy ( 1 , 2 );
printf ( lcd_escreve , "Rahul M Juliato " );
//Inicializa o módulo USB
usb_cdc_init ();
usb_init ();
DELAY_MS ( 1000 );
//Aguarda a conexão do cabo e Programa
while (! usb_cdc_connected ()){
lcd_pos_xy ( 1 , 1 );
printf ( lcd_escreve , "Ligue o cabo e " );
lcd_pos_xy ( 1 , 2 );
printf ( lcd_escreve , "conecte pela IHM" );
delay_ms ( 10 );
}
usb_task ();
//Aqui começa o programa após a conexão USB ser bem sucedida
while ( TRUE ){
valor_di =! input ( DI ); //Lê o valor da DI
set_adc_channel ( 0 ); //Lê o valor da AI
valor_ai = read_adc ();

 

if ( usb_enumerated ()) {
//Envia os valores lidos pela USB
printf ( usb_cdc_putc , "\r%02u%04Lu\n" , valor_di , valor_ai );
delay_ms ( 100 );

}

 

if ( usb_cdc_kbhit ()){
//Recebe primeira parte da msg
recebemsg1 [ 0 ] = usb_cdc_getc ();
recebemsg1 [ 1 ] = usb_cdc_getc ();
recebemsg1 [ 2 ] = usb_cdc_getc ();
//Recebe segunda parte da msg
recebemsg2 [ 0 ] = usb_cdc_getc ();
recebemsg2 [ 1 ] = usb_cdc_getc ();
recebemsg2 [ 2 ] = usb_cdc_getc ();
recebemsg2 [ 3 ] = usb_cdc_getc ();
recebemsg2 [ 4 ] = 0x00 ;
//Caso a primeira msg comece com o identificador A
if ( recebemsg1 [ 0 ]== 'A' ){
//monta o valor da DO
valor_do =(( recebemsg1 [ 1 ]- 48 )* 10 +( recebemsg1 [ 2 ]- 48 ));
//monta o valor da AO
valor_ao = atol ( recebemsg2 );
}
}
//Atualiza estado das Saídas
output_bit ( pin_b1 , valor_do );
set_pwm1_duty ( valor_ao );
//Atualiza o display LCD com os valores
lcd_pos_xy ( 1 , 1 );
printf ( lcd_escreve , "DI:%02u AI:%04Lu " , valor_di , valor_ai );
lcd_pos_xy ( 1 , 2 );
printf ( lcd_escreve , "DO:%02Lu AO:%04Lu " , valor_do , valor_ao );
}
}

 

 
 

Interface em C#

Esse projeto incorpora uma interface homem máquina (IHM), construída em C# e rodando em um computador, para isso, foi utilizado o Microsoft Visual Studio Express .
Para criar um novo projeto em C#, basta clicar em File->New Project..., escolhendo como padrão “Windows Form” e preenchendo as informações nome de projeto e diretório para gravação, conforme demonstrado nas figuras 3 e 4.
Figura 3 – Criando um novo projeto
Figura 3 – Criando um novo projeto

 

 

 

Figura 4 – Escolhendo Windows Form Application
Figura 4 – Escolhendo Windows Form Application

 

Após esses passos, um formulário em branco é criado, conforme figura 5 e para inserir objetos ao formulário é necessário que o menu toolboxes esteja ativo, conforme mostra a figura 6.

  

Figura 5 – Formulário em branco
Figura 5 – Formulário em branco

 

 

 

Figura 6 – Menu para selecionar visualização das Toolboxes
Figura 6 – Menu para selecionar visualização das Toolboxes

 

O leiaute foi montado utilizando objetos do menu toolboxes à esquerda. É importante se familiarizar com o ambiente Visual Studio, pois se deve acostumar com os nomes dos botões e objetos em formulários, bem como se familiarizar com a janela de propriedades de cada um desses botões. Assim foi criado o seguinte formulário.

 

Figura 7 – Programa de Testes
Figura 7 – Programa de Testes

 

Programação em alto nível envolve muitas configurações de pastas de projeto, propriedades de objetos, bibliotecas e classes, para que seja possível criar apenas uma “janelinha”. Assim, para que esse estudo se torne efetivo é necessário ter o projeto completo em mãos, uma vez que o programa ocuparia muitas páginas. Caso você não tenha recebido o projeto para Visual Studio com esse artigo, me envie um e-mail.

Graficamente, também foram inseridos alguns objetos, conforme figura 8.

 

Figura 8 – Serial e Timers.
Figura 8 – Serial e Timers.

 

Alguns passos de maior importância devem ser estudados com um pouco mais de profundidade. Iniciaremos pela função utilizada ao evento “ON_CLICK” do botão Conectar.

A conexão é feita através do bloco serialPort1, uma vez que nosso hardware faz a comunicação em USB e o driver realiza a conversão dos dados para uma porta serial comum COM.

O código à seguir, verifica se o nome da porta à ser utilizada foi escrita no textbox, caso não, envia uma mensagem, caso sim, imprime uma mensagem e tenta fazer a conexão pelo comando Open(), caso não seja possível, imprime uma mensagem.

 

privatevoid button1_Click(object sender, EventArgs e)

{

 

if (textBox1.Text.Trim().Length == 0)

{

MessageBox.Show("Digite o nome de uma Porta. Ex.: COM11.",

"Importante!",

MessageBoxButtons.OK,

MessageBoxIcon.Exclamation,

MessageBoxDefaultButton.Button1);

}

 

else

{

serialPort1.PortName = textBox1.Text;

 

try

{

 

MessageBox.Show("Esse processo pode levar algum tempo!\n Aguarde!\n",

"Importante!",

MessageBoxButtons.OK,

MessageBoxIcon.Exclamation,

MessageBoxDefaultButton.Button1);

 

serialPort1.Open();

 

}

 

catch (Exception)

{

MessageBox.Show("Essa porta não é válida!\n Tente outra!",

"Importante!",

MessageBoxButtons.OK,

MessageBoxIcon.Exclamation,

MessageBoxDefaultButton.Button1);

}

}

 

}

 

Outra parte primordial é o timer1, esse timer roda a cada 100ms e está ativo assim que o programa inicia. Esse timer realiza os processos de comunicação e escrita. Lê a porta USB através do comando ReadLine(), caso haja dados e o tamanho da string for 6 (conforme construída a mensagem de envio pelo microcontrolador), a informação referente à DI é enviada ao checkbox e a informação da AI enviada ao Scroll Bar.

Em seguida a rotina monta a string mensagem para ser enviada com as informações da checkbox e scollbar referentes à DO e AO e envia através do comando Write(). É importante enviar a quantidade exata de caracteres e no programa do microcontrolador receber exatamente a mesma quantidade.

 

privatevoid timer1_Tick(object sender, EventArgs e)

{

 

string valor_ao=null;

string dados = null;

 

int dadoss = 0;

 

 

timer1.Enabled = false;

 

 

if (serialPort1.IsOpen)

{

try

{

//LEITURA

dados = serialPort1.ReadLine();

 

 

 

if (dados != null)

{

dados = dados.Trim();

 

if (dados.Length == 6)

{

 

//Pega a informação da DI, a partir de 0 os 2 primeiros digitos

//converte para Inteiro e seta a checkbox.

 

progressBar2.Value= Convert.ToInt16(dados.Substring(0, 2));

 

//Pega a informação analógica da AI, a partir de 2 os 4 primeiros digitos

//converte para inteiro

dadoss = Convert.ToInt16(dados.Substring(2, 4));

 

//Testa se o valor está nos limites máximo e mínimo do progressbar.

 

if ((dadoss <= progressBar1.Maximum) && (dadoss >= progressBar1.Minimum))

progressBar1.Value = dadoss;

}

}

 

 

 

//ESCRITA

 

char[] mensagem = { 'A', '0', '0', '0', '0', '0', '0' };

 

 

//Verifica estado da checkbox e envia à char mensagem

if(checkBox2.Checked==true) mensagem[2]='1' ; else mensagem[2]='0';

 

//Converte o valor para a AO em String Formatada de 3 digitos

//passa pra char e encaixa a char temporária no array de mensagem

valor_ao = String.Format("{0:0000}", hScrollBar2.Value);

char[] temp = valor_ao.ToCharArray(0,4);

 

 

mensagem[3] = temp[0];

mensagem[4] = temp[1];

mensagem[5] = temp[2];

mensagem[6] = temp[3];

 

 

 

try {

//Envia mensagem e descarta buffer

serialPort1.Write(mensagem,0, mensagem.Length);

serialPort1.DiscardOutBuffer();

}

catch (TimeoutException) { }

 

 

}

catch (TimeoutException) { }

}

 

 

 

timer1.Enabled = true;

}

 

O último bloco analisado em detalhes é o timer2, com apenas uma função, limpar os buffers de recebimento de 1 em 1 segundo.

privatevoid timer2_Tick(object sender, EventArgs e)

{

if (serialPort1.IsOpen)

{

serialPort1.DiscardInBuffer();

serialPort1.DiscardOutBuffer();

}

}

 

É importante também definir os limites dos scrollbars, como estamos trabalhando com 10 bits em todas as entradas analógicas os limites devem estar entre 0 e 1023.

 

Em funcionamento

Com o microcontrolador gravado e o programa em C# compilado, deve-se conectar o hardware ao PC.

 

Nesse momento, o Windows tentará encontrar o driver, conforme a figura 8.

 

col0133 0008a
Figura 8 – Windows procurando o Driver.

 

Mesmo que o Windows acuse ter encontrado o driver, é necessário que ele seja instalado manualmente, o procedimento para isso é o comum do Windows. No painel de controle deve-se adicionar um novo hardware e pedir para escolher o driver de um local especifico, apontando a pasta dos drivers fornecidos pelo fabricante do compilador, geralmente no caminho: C:\Program Files (x86)\PICC\Drivers\NT,2000,XP,VISTA,7\.

Após a correta instalação do driver será possível verificar na lista de dispositivos do Windows uma nova porta COM, conforme mostra a figura 9.

 

col0133 0009a
Figura 9 – Porta COM17

 

Esse número deve ser inserido na textbox do programa, conforme figura 10.

 

DO acionada pelo Computador
Figura10 – Porta de comunicação

 

A partir desse momento, ao clicar em Conectar, estamos em conexão com o PIC. As figuras seguintes ilustram o comportamento do circuito e IHM.

 

DO acionada pelo Computador
DO acionada pelo Computador
DI e AI acionadas no microcontrolador mostrando resultado no Computador.
DO acionada pelo Computador

 

 

DI e AI acionadas no microcontrolador mostrando resultado no Computador.
DI e AI acionadas no microcontrolador mostrando resultado no Computador.
col0133 0011a
DI e AI acionadas no microcontrolador mostrando resultado no Computador.

 

 

AO acionada pelo computador controlando velocidade de um cooler.
AO acionada pelo computador controlando velocidade de um cooler.
col0133 0012a
AO acionada pelo computador controlando velocidade de um cooler.

 

 

 

Conclusões

Esses procedimentos concluem a implementação de uma comunicação simples USB de um microcontrolador PIC 18 à um PC com interfaceamento. Vale afirmar que em aplicações não didáticas, muitas precauções e muito mais programação é necessária, para evitar situações de travamento e melhorar a interface com o usuário.

Esse artigo surgiu em meio à criação de um projeto real, uma estação de supervisão meteorológica. A figura 14 mostra a interface projetada para esse fim.

 

AO acionada pelo computador controlando velocidade de um cooler.
Figura 14 – Projeto ESUME – Estação de Supervisão Meteorológica

 

Referências e Agradecimentos

Muito suor, muita dor de cabeça e superação fazem parte de qualquer trabalho bem feito. E como em qualquer trabalho a colaboração é sempre bem vinda!

Poucas são as referências neste tema, e este trabalho foi fruto de intensa pesquisa de exemplos de comunicação do compilador CCS, excelente compilador com uma documentação invejável.

Meus agradecimentos aos amigos Rodolfo Carlos Blumel, Afonso Celso Martini e Francisco Fambrini.

 

Atualizações Datas
Versão 1.0 16.11.2014
Versão Original 21.12.2012