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
(
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.
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.
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.
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.
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.
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.
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.
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.
Esse número deve ser inserido na textbox do programa, conforme figura 10.
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.
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.
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 |