sábado, 11 de julho de 2015

[PIC] Compilador, IDE, Configuration Bits e um Inicio...

Olá, 
Este irá ser o meu primeiro tutorial sobre PICs. A partir deste ponto irei assumir que você sabe o que é um PIC, tem algum conhecimento sobre os microcontroladores e uma noção de programação. Caso contrário, pode ser bem difícil entender o restante do assunto.

Como qualquer outro microcontrolador, os PICs devem ser programados em linguagem de máquina. Porém, com a evolução das arquiteturas (PICs cada vez mais "espaçosos" e rápidos) e da complexidade dos códigos, muitos compiladores começaram a aparecer a fim de trazer mais facilidade ao programador na hora de elaborar seu código. A maioria desses compiladores "traduzem" uma espécie de código em C em código Assembly para dai compilar em um arquivo ".hex" que deve ser gravado no PIC. Alguns exemplos desses compiladores são: CCS, mikroC, hi-tech e, finalmente, os compiladores XC que são fornecidos pela microchip e possuem uma versão gratuita. 

Para que seja possível iniciar a programação do PIC você deverá fazer o download da IDE da microchip, o MPLAB (no meu caso estou utilizando o MPLAB X v2.25) e, como o PIC utilizado é da série 18, o compilador XC8 também deverá ser instalado.
nota: também é extremamente necessário que você tenha o datasheet do seu PIC. No nosso caso, o datasheet do PIC 18F4550.

Após tudo instalado, abra o MPLAB X.

Figura 1: Tela Inicial do MPLAB X.

Clique em File >> New Project, seleciona a opção Standalone Project.

Figura 2: Configurando um Novo Projeto.

Na próxima janela, "Select Device", indique a família do PIC (PIC 18) e em device selecione o PIC18F4550. Clique em next.

Em Select Tool devemos definir qual o gravador que iremos utilizar, selecione e clique em next mais uma vez.

Agora o compilador deverá ser selecionado, como estamos utilizando o XC8, clique nele e prossiga. 


Figura 3: Selecionando o Compilador.

Na janela que segue, indique o nome do seu projeto e sua pasta.

Figura 4: Finalizando o Wizard do novo projeto.

No canto esquerdo, na aba "files", clique com o botão direito em cima do projeto em que você acabou de criar e crie um novo arquivo "C Source File".

Figura 5: Crie um novo arquivo "C source".

Na janela que aparece, indique um nome para seu arquivo. No meu caso eu nomeei de "main.c", clique em finish.

Figura 6: "main.c" deve surgir na pasta do seu projeto.

Agora devemos fazer algumas configurações de inicialização para o PIC. Isso pode ser feito utilizando o MPLAB X. 
Em Window >> PIC Memory Views >> clique em Configuration Bits.

Figura 7: Habilitar janela Configuration Bits.

Na janela de Configuration Bits você é capaz de gerar o código para inicialização do seu PIC de acordo com seu projeto.

No datasheet você pode encontrar mais informações sobre para que você configure seu oscilador (que produz um clock para o seu PIC). Nesse exemplo a opção escolhida é de utilizar os componentes internos do PIC para obter um clock de 8 MHz.

Figura 8: Configuração do Oscilador.

Para configurar utilizando a janela Configuration Bits basta selecionar as opções e clicar no botão Generate Source Code to Output.

Figura 9: Configuration Bits.

Para esse exemplo podemos desligar o WDT (watchdog timer), BOR (Brown out Voltage), LVP (Low Voltage Programming) e PBADEN (seta as portas B são pinos digitais). Pesquise no datasheet para obter mais informações sobre essas funções, elas podem ser de grande importância em projetos futuros.

Copie o código gerado para seu programa, esse código servirá como um cabeçalho para os seus projetos. Observe que nesse código existe um "include <xc.h>" esse comando insere a biblioteca xc.h responsável por incluir informações sobre o seu PIC.

Todo programa em C deve possuir uma, e somente uma, função main(); porém, ela muda de acordo com o compilador. No XC8, ela deve retorna um número inteiro, o valor 0 indica que a função retornou um valor válido (nenhum erro ocorreu). 

O código do programa para piscar o led deve ser similar ao mostrado abaixo.

-- nessa parte vem todos os includes e bits de configuração gerados pelo Configuration Bits --
int main(void){
    OSCCONbits.IRCF = 0b111; // de acordo com o datasheet, Fosc = 8 MHz
    TRISB = 0b00000000; // seta todas as portas B para serem saídas
    while(1){ // loop para rodar o programa "infinitamente"
        LATBbits.LATB0 = ~PORTBbits.RB0; // inverte a saida do pino RB0
        // delay de 0,25s
        _delay(100000); // (4/8.000.000)*100.000 = 0,05 seg
        _delay(100000);
        _delay(100000);
        _delay(100000);
        _delay(100000);
    }
    return 0;
}

Informações mais detalhadas sobre os registradores podem ser encontradas no datasheet
O cálculo do delay é relativamente simples. A função _delay(x); "descarta" uma quantidade X de ciclo de instrução do PIC, o tempo dessa instrução é dado por 4/Fosc (no nosso caso, 8MHz) e é assim que obtemos um atraso de 0,25s para visualizar o LED piscando.

Compile seu código e grave no seu PIC ou simule.

Figura 10: Botão para criar o arquivo .hex no MPLAB X. 

Figura 11: Simulação do projeto no ISIS "Proteus". 

Pronto! Seu PIC alterna o seu LED a cada período de tempo de aproximadamente 0,25s! Bom trabalho! 

Confira o código completo abaixo. 

-------------------------------------------
// PIC18F4550 Configuration Bit Settings

// 'C' source line config statements

#include <xc.h>

// #pragma config statements should precede project file includes.
// Use project enums instead of #define for ON and OFF.

// CONFIG1L
#pragma config PLLDIV = 1       // PLL Prescaler Selection bits (No prescale (4 MHz oscillator input drives PLL directly))
#pragma config CPUDIV = OSC1_PLL2// System Clock Postscaler Selection bits ([Primary Oscillator Src: /1][96 MHz PLL Src: /2])
#pragma config USBDIV = 1       // USB Clock Selection bit (used in Full-Speed USB mode only; UCFG:FSEN = 1) (USB clock source comes directly from the primary oscillator block with no postscale)

// CONFIG1H
#pragma config FOSC = INTOSCIO_EC// Oscillator Selection bits (Internal oscillator, port function on RA6, EC used by USB (INTIO))
#pragma config FCMEN = OFF      // Fail-Safe Clock Monitor Enable bit (Fail-Safe Clock Monitor disabled)
#pragma config IESO = OFF       // Internal/External Oscillator Switchover bit (Oscillator Switchover mode disabled)

// CONFIG2L
#pragma config PWRT = OFF       // Power-up Timer Enable bit (PWRT disabled)
#pragma config BOR = OFF        // Brown-out Reset Enable bits (Brown-out Reset disabled in hardware and software)
#pragma config BORV = 3         // Brown-out Reset Voltage bits (Minimum setting)
#pragma config VREGEN = OFF     // USB Voltage Regulator Enable bit (USB voltage regulator disabled)

// CONFIG2H
#pragma config WDT = OFF        // Watchdog Timer Enable bit (WDT disabled (control is placed on the SWDTEN bit))
#pragma config WDTPS = 32768    // Watchdog Timer Postscale Select bits (1:32768)

// CONFIG3H
#pragma config CCP2MX = ON      // CCP2 MUX bit (CCP2 input/output is multiplexed with RC1)
#pragma config PBADEN = OFF     // PORTB A/D Enable bit (PORTB<4:0> pins are configured as digital I/O on Reset)
#pragma config LPT1OSC = OFF    // Low-Power Timer 1 Oscillator Enable bit (Timer1 configured for higher power operation)
#pragma config MCLRE = ON       // MCLR Pin Enable bit (MCLR pin enabled; RE3 input pin disabled)

// CONFIG4L
#pragma config STVREN = ON      // Stack Full/Underflow Reset Enable bit (Stack full/underflow will cause Reset)
#pragma config LVP = OFF        // Single-Supply ICSP Enable bit (Single-Supply ICSP disabled)
#pragma config ICPRT = OFF      // Dedicated In-Circuit Debug/Programming Port (ICPORT) Enable bit (ICPORT disabled)
#pragma config XINST = OFF      // Extended Instruction Set Enable bit (Instruction set extension and Indexed Addressing mode disabled (Legacy mode))

// CONFIG5L
#pragma config CP0 = OFF        // Code Protection bit (Block 0 (000800-001FFFh) is not code-protected)
#pragma config CP1 = OFF        // Code Protection bit (Block 1 (002000-003FFFh) is not code-protected)
#pragma config CP2 = OFF        // Code Protection bit (Block 2 (004000-005FFFh) is not code-protected)
#pragma config CP3 = OFF        // Code Protection bit (Block 3 (006000-007FFFh) is not code-protected)

// CONFIG5H
#pragma config CPB = OFF        // Boot Block Code Protection bit (Boot block (000000-0007FFh) is not code-protected)
#pragma config CPD = OFF        // Data EEPROM Code Protection bit (Data EEPROM is not code-protected)

// CONFIG6L
#pragma config WRT0 = OFF       // Write Protection bit (Block 0 (000800-001FFFh) is not write-protected)
#pragma config WRT1 = OFF       // Write Protection bit (Block 1 (002000-003FFFh) is not write-protected)
#pragma config WRT2 = OFF       // Write Protection bit (Block 2 (004000-005FFFh) is not write-protected)
#pragma config WRT3 = OFF       // Write Protection bit (Block 3 (006000-007FFFh) is not write-protected)

// CONFIG6H
#pragma config WRTC = OFF       // Configuration Register Write Protection bit (Configuration registers (300000-3000FFh) are not write-protected)
#pragma config WRTB = OFF       // Boot Block Write Protection bit (Boot block (000000-0007FFh) is not write-protected)
#pragma config WRTD = OFF       // Data EEPROM Write Protection bit (Data EEPROM is not write-protected)

// CONFIG7L
#pragma config EBTR0 = OFF      // Table Read Protection bit (Block 0 (000800-001FFFh) is not protected from table reads executed in other blocks)
#pragma config EBTR1 = OFF      // Table Read Protection bit (Block 1 (002000-003FFFh) is not protected from table reads executed in other blocks)
#pragma config EBTR2 = OFF      // Table Read Protection bit (Block 2 (004000-005FFFh) is not protected from table reads executed in other blocks)
#pragma config EBTR3 = OFF      // Table Read Protection bit (Block 3 (006000-007FFFh) is not protected from table reads executed in other blocks)

// CONFIG7H
#pragma config EBTRB = OFF      // Boot Block Table Read Protection bit (Boot block (000000-0007FFh) is not protected from table reads executed in other blocks)

int main(void){
    OSCCONbits.IRCF = 0b111; // de acordo com o datasheet, Fosc = 8 MHz
    TRISB = 0b00000000; // seta todas as portas B para serem saídas
    while(1){ // loop para rodar o programa "infinitamente"
        LATBbits.LATB0 = ~PORTBbits.RB0; // inverte a saida do pino RB0
        // delay de 0,25s
        _delay(100000); // (4/8.000.000)*100.000 = 0,05 seg
        _delay(100000);
        _delay(100000);
        _delay(100000);
        _delay(100000);
    }
    return 0;
}
-------------------------------------------

sábado, 19 de julho de 2014

[FPGA] Tutorial 4 - LEDs pulsantes em VHDL

Olá pessoal,
Estamos aqui mais uma vez para mostrar um outro pequeno projeto feito com a DE0 Board, os LEDs pulsantes. Esse efeito consiste em aumentar a intensidade do brilho de cada LED da placa gradativamente e, ao atingir o brilho máximo, diminuir. Se você não conhece e/ou não entendeu, é só checar o vídeo abaixo.



A primeira pergunta que deve surgir na sua cabeça para criar esse efeito provavelmente é: como controlar a intensidade do brilho de um LED usando apenas lógica digital, aonde não existe meio termo (ligado/desligado, zero ou um)?

Primeiramente, para responder essa pergunta temos que arremeter a um efeito chamado persistência da visão. Esse efeito faz com que luz capturada (vista) pelo olho humano fique "presa" na retina dos nossos olhos um pouco mais de tempo. Caso esteja interessado em ler mais sobre isso, o wikipédia explica.

OK, entendi... a luz fica na retina um pouco mais de tempo, mas e dai? 
Bom, graças a isso, podemos controlar a intensidade dos LEDs desligando e ligando eles em diferentes "velocidades". Por exemplo, se em 20 mili-segundos eu deixo todos os LEDs ligados, o brilho deles nesse período de tempo é constante e máximo. Se dentro dos próximos 20 mili-segundos eu consigo desligar por 10 mili-segundos e ligar pelos restantes 10 mili-segundos a impressão para quem vê é de que o brilho foi atenuado. 

É basicamente isso, ligar e desligar em diferentes tempos de forma que tenhamos uma "média" como resultado para o brilho. Em eletrônica essa técnica é conhecida como PWM (do inglês, Pulse-Width Modulation), não entrarei em detalhes sobre PWM porque não interfere muito no resultado e levaria muuuito tempo para cobrir todo o assunto, mas se você deseja saber mais, basta pesquisar.


O projeto.

Crie um novo projeto no Quartus II (no caso, v. 13.1) e complete com todas as informações necessárias. 

Nota: qualquer dificuldade em como criar um projeto, pin assignments ou qualquer outra coisa relacionada a criação do projeto, recorra aos tutoriais antigos.

Figura 1: Criando um novo projeto.

Crie 3 novos arquivos VHDL. Um chamado fading_led.vhd, outro fading_freq.vhd e fading_pwm.vhd.

Como foi dito, estaremos usando três arquivos VHDL, cada arquivo terá uma função: o arquivo _freq.vhd irá ser o divisor de frequência e o _pwm.vhd fará o controle do liga/desliga dos leds. Esses arquivos devem ser conectados e para que isso seja possível, o uso do COMPONENT é indispensável. O component diz como as portas são usadas e conectadas. 
O component é declarado dentro do architecture e inicializados pelo PORT MAP, como estaremos vendo a seguir.

fading_freq.vhd

-- inicialização das bibliotecas
LIBRARY ieee;
USE ieee.std_logic_1164.all;
USE ieee.numeric_std.all;
-- criacao da entidade fading_freq
ENTITY fading_freq IS
-- contador para div. de freq. aprox. 8 miliseg.
GENERIC(TEMPO: INTEGER := 400000);
-- entradas/saidas
PORT(
-- clock de entrada
CLK: IN STD_LOGIC;
-- indica a porcentagem de tempo q fica ligado
CONT: OUT INTEGER RANGE 0 TO 100
);
END fading_freq;

ARCHITECTURE behavioral OF fading_freq IS
SIGNAL temporal: INTEGER RANGE 0 TO TEMPO;
-- 0 = fade in | 1 = fade out
SIGNAL fade_inout: STD_LOGIC := '0'; 
-- valor de saida de CONT
SIGNAL cnt: INTEGER RANGE 0 TO 100;
BEGIN
CONT <= cnt;
PROCESS(CLK)
BEGIN
-- a cada borda de subida do clock
IF rising_edge(CLK) THEN
-- se o TEMPO for alcançado
IF (temporal = TEMPO) THEN
temporal <= 0;
-- se cnt = 99%
IF (cnt = 99) THEN
-- executa fade out
fade_inout <= '1';
-- se cnt = 1%
ELSIF (cnt = 1) THEN
-- executa fade in
fade_inout <= '0';
END IF;
-- fade in, incrementa tempo ligado
-- fade out, decrementa.
CASE fade_inout IS
WHEN '0' => cnt <= cnt + 1;
WHEN '1' => cnt <= cnt - 1;
END CASE;
-- continua contando TEMPO
ELSE
temporal <= temporal + 1;
END IF;
END IF;
END PROCESS;
END behavioral;

________________________

fading_pwm.vhd

LIBRARY ieee;
USE ieee.std_logic_1164.all;
USE ieee.numeric_std.all;

ENTITY fading_pwm IS
-- entradas e saidas
PORT(
-- clock, 
CLK: IN STD_LOGIC;
-- valor do contador vindo do divisor de freq
CONTADOR: IN INTEGER RANGE 0 TO 100;
-- saida para os LEDs
PWM_SAIDA: OUT STD_LOGIC_VECTOR (9 downto 0)
);
END fading_pwm;

ARCHITECTURE behavioral OF fading_pwm IS
-- contador pwm para comparar com contador do divisor
SIGNAL pwm_cont: INTEGER RANGE 0 TO 100;
-- ligar/desligar os LEDs
SIGNAL pwm_signal: STD_LOGIC := '0';
BEGIN
PROCESS(CLK)
BEGIN
-- na borda de subida do clock
IF rising_edge(CLK) THEN
-- se o contado chegou no máximo
IF (pwm_cont = 99) THEN
-- zera
pwm_cont <= 0;
-- senão, conta...
ELSE
pwm_cont <= pwm_cont + 1;
END IF;
END IF;
-- se o contador pwm for menor que o contador do divisor
IF(pwm_cont < CONTADOR) THEN
-- liga o led
pwm_signal <= '1';
-- senao, desliga
ELSE 
pwm_signal <= '0';
END IF;
-- sao 10 LEDs com o valor de pwm_signal
PWM_SAIDA <= (OTHERS => pwm_signal);
END PROCESS;
END behavioral;
________________________

fading_led.vhd

LIBRARY ieee;
USE ieee.std_logic_1164.all;
USE ieee.numeric_std.all;
-- entity fading_led
ENTITY fading_led IS
PORT(
-- LEDs da placa são saidas
LEDG: OUT STD_LOGIC_VECTOR (9 downto 0);
-- e o clock da placa, entrada.
CLOCK_50:IN STD_LOGIC
);
END fading_led;

-- aqui serão declaradas e inicializados os components
ARCHITECTURE behavioral OF fading_led IS
-- o component para o primeiro entity, _freq
-- eh igual ao entity fading_freq
COMPONENT fading_freq IS
PORT(
CLK: IN STD_LOGIC;
CONT: OUT INTEGER RANGE 0 TO 100
);
END COMPONENT;
-- o component para o segundo entity, _pwm
-- eh igual ao entity fading_pwm
COMPONENT fading_pwm IS
PORT(
CLK: IN STD_LOGIC;
CONTADOR: IN INTEGER RANGE 0 TO 100;
PWM_SAIDA: OUT STD_LOGIC_VECTOR (9 downto 0)
);
END COMPONENT;
-- inicializa um sinal para o contador passar
-- de fading_freq para fading_pwm
SIGNAL cnt: INTEGER RANGE 0 TO 100;
BEGIN
-- inicialização dos components usando PORT MAP
-- cnt passa por fading_freq e fading_pwm
fading_freq_i: fading_freq PORT MAP(CLOCK_50, cnt);
fading_pwm_i: fading_pwm PORT MAP(CLOCK_50, cnt, LEDG);
END behavioral;

________________________

O pin assignment é bem simples, utilizaremos apenas os LEDs verdes como saída e o clock da placa como entrada (chamados de LEDG e CLOCK_50).


Se você deseja mudar o tempo, basta brincar com a constante TEMPO criada no arquivo fading_freq.vhd. Para calcular o valor é simples. 
A placa possui um clock de 50Mhz, então, se você deseja 60Hz.


TEMPO = 50 000 000/ 60 
TEMPO ~ 833 333.

Assim você terá um tempo de 1/60 segundos. Ou seja, 16.67ms.
Também é recomendável estudar um pouco sobre PWM, essa técnica de modulação é muito importante para controle dos mais imagináveis e inimagináveis procedimentos. Realmente muito importante, bem relevante. 

É basicamente isso, até a próxima :D





segunda-feira, 30 de junho de 2014

[FPGA] Tutorial 3 - Shift Registers em VHDL

Olá pessoal,
Nesse tutorial a gente vai falar sobre shift registers, o que são? Para que serve? E ainda montar um projeto com um resultado muito interessante, o resultado vocês podem ver no vídeo abaixo.


- O que é um shift registers?

Shift registers, como o próprio nome propõe, é um circuito digital que possui dados que são armazenados e shifted, ou seja, "substituidos" como se saltassem a cada clock, juntos. 
Eu recomendo ao leitor dar uma olhada na página do wikipedia sobre shift registers e conferir todas as imagens e entedê-las.

Esse tipo de circuito é bem útil e usado para os vários propósitos, e em projetos maiores ele pode ser conveniente.


- O nosso projeto

Como de costume, abra o Quartus (aqui estamos utilizando a v. 13.1) e crie um novo projeto. 


Figura 1: Criando um novo projeto.

Qualquer maior dificuldades nesse passo, consulte o tutorial 01. Em File > New > VHDL File, crie um arquivo VHDL em branco.


Figura 2: Criando um novo arquivo VHDL.

Agora é só descrever o nosso hardware.

-- declara ao compilador a biblioteca usada
LIBRARY ieee;
USE ieee.std_logic_1164.all;
USE ieee.numeric_std.all;
                                              
-- entidade shift_register           
ENTITY shift_register IS             
-- listagem de entradas e saidas 
PORT(                                      
LEDG: OUT STD_LOGIC_VECTOR (9 downto 0);
SW:         IN STD_LOGIC_VECTOR (9 downto 0);
CLOCK_50:                 IN STD_LOGIC                                
);                           
END shift_register;   
                             
-- descrever o comportamento do circuito
ARCHITECTURE behavioral OF shift_register IS
-- contador para o tempo (aprx. um quarto de segundo)
SIGNAL temporal: INTEGER RANGE 0 TO 12500000 := 0; 
-- inicialmente nenhum LED é ligado.
SIGNAL vetor_temp: STD_LOGIC_VECTOR (9 downto 0) := (OTHERS => '0');
-- indice para deslocar o vetor
SIGNAL i: INTEGER RANGE 0 TO 9 := 0;
BEGIN
-- LEDs possuirao o valor de vetor_temp
LEDG <= vetor_temp;
-- CLOCK_50 na lista de sensibilidade do process
PROCESS (CLOCK_50)
BEGIN
-- na borda de subida do clock
IF rising_edge(CLOCK_50) THEN
-- caso tenha percorrido 1/4 segundo
IF(temporal = 12500000) THEN
-- reseta a variavel
temporal <= 0;
-- desloca os valores
vetor_temp(9 downto 1) <= vetor_temp (8 downto 0);
-- transfere valor da chave compassadamente para o vetor_temp(0)           
vetor_temp(0) <= SW(9-i);
-- reseta o indice quando chegar no limite
IF (i = 9) THEN
i <= 0;
ELSE            
i <= i + 1;
END IF;        
ELSE                    
-- senao, continua contando (tempo correndo...)
temporal <= temporal + 1;
END IF;
END IF;
END PROCESS;
END behavioral;

Antes de compilar, importamos (ou declaramos) os pinos da placa. Para isso, Assignments -> Import Assignments.


Figura 3: Declarando os pinos da placa.

E finalmente, Processing> Start Compilation

Nota: qualquer dificuldade em algum desses passos, leia os tutoriais anteriores.


- Existem outras maneiras de fazê-lo?

Claro que sim. Assim como existem várias maneiras de descrever um objeto, existem várias maneiras de descrever um shift register em VHDL. Talvez uma possível solução seria utilizando o for loop e executar a mudança dos valores dentro do loop... seria um ótimo exercício tentar uma outra aproximação para arranjar o shift register.

É isso pessoal, e não esqueçam de ler a página do wikipédia :-) .