Mostrar mensagens com a etiqueta MPLAB XC8/hi-tech. Mostrar todas as mensagens
Mostrar mensagens com a etiqueta MPLAB XC8/hi-tech. Mostrar todas as mensagens

Eis que se chega à ultima parte, relacionada com o hardware construído - construção do funcionamento global.
A imagem seguinte mostra um pequeno diagrama de blocos com as principais tarefas realizadas pelo PIC12F1822 e que serão brevemente explicadas.
O MCU aguarda que pela USART seja recebido o comando para realizar e enviar uma nova leitura de peso, medido através da célula de carga.
Assim que esse pedido chega e é confirmado como tal, são realizadas 16 leituras da ADC, com a sua referência programada para 5V, posteriormente calculando a sua média. O objectivo, como já descrito na PART3, é determinar qual a melhor referência para a ADC com o intuito de melhorar também a sua resolução. As configurações do periférico são novamente modificadas para corresponder à referencia escolhida.
A função mostrada em seguida, não só aplica a referencia como realiza a medição da ADC e retorna esse valor.
unsigned int ADC_ref_read(unsigned char ref)
{
   
switch (ref)
    {
       
case 0:
            FVRCONbits.FVREN    =   1;      //enable reference
            FVRCONbits.ADFVR    =   0b01;   //ADC FVR Peripheral output is 1x (1.024V)
            while(!FVRCONbits.FVRRDY);      //Fixed Voltage Reference output is ready for use
            ADCON1bits.ADPREF   =   0b11;   //V+ = FVR
            break;
        case 1:

            FVRCONbits.FVREN    =   1;      //enable reference
            FVRCONbits.ADFVR    =   0b10;   //ADC FVR Peripheral output is 2x (2.048V)
            while(!FVRCONbits.FVRRDY);      //Fixed Voltage Reference output is ready for use
            ADCON1bits.ADPREF   =   0b11;   //V+ = FVR
            break;
        case 2:

            FVRCONbits.FVREN    =   1;      //enable reference
            FVRCONbits.ADFVR    =   0b11;   //ADC FVR Peripheral output is 4x (4.096V)
            while(!FVRCONbits.FVRRDY);      //Fixed Voltage Reference output is ready for use
            ADCON1bits.ADPREF   =   0b11;   //V+ = FVR
            break;
        case 3:

            FVRCONbits.FVREN    =   0;      //disable reference
            ADCON1bits.ADPREF   =   0b00;   //V+=vcc
            break;
    }
        _delay(40);       //Wait the required acquisition time(2)  - 5uS
        
        ADCON0bits.GO_nDONE = 1;        //Start conversion by setting the GO/DONE bit.
        while(ADCON0bits.GO_nDONE);
    return (ADRESH<<8)+ADRESL;
}
Ultrapassada esta parte, são realizadas 31 medições com uma frequência de amostragem de 10KHz (uma amostra a cada 100uS). Estas são utilizadas para criar um filtro FIR de 30ª ordem com uma frequência de corte de 10Hz. Esta etapa é muito importante para reduzir ao máximo possíveis erros de leitura provocados por ruído ou qualquer outro relacionado. Tudo sobre filtros FIR, incluindo o cálculos dos coeficientes para a multiplicação, pode ser encontrado AQUI.
A particularidade utilizada por mim, nesta parte, consiste na multiplicação dos coeficientes por uma constante que os torne a todos números inteiros de 8 bits, para facilitar os cálculos pelo MCU.
No código acima, o vector x[] armazena os valores resultantes da medição da ADC, o vector h[] contém os coeficientes do filtro (todos multiplicados por 2^12, para os tornar inteiros de 8bits), a variável "filtro" corresponde ao valor de medição obtido pelo filtro e por ultimo a variável "cod" volta a dividir o valor calculado e resultante do filtro por 2^12 de modo a obter um valor na mesma relação que a ADC.
Posteriormente estes dados são organizados em 2 bytes, junto com um checksum de 4bits e enviado pela USART para o computador. Lá são recebidos, "descompactados" convertidos em valores de força (peso) que podem ser mostrados à plateia.

O artigo seguinte deverá conter algumas fotos do circuito construído assim como uma breve explicação da sua utilização.

Meio engasgado, mas já foi bastante tarde e o meu raciocínio já não estava pelo melhor...

Não vou colocar aqui a biblioteca porque não faria sentido, mas poderei facultar a quem pedir via e-mail, para um melhor controlo.
Fica só um exemplo do main() que corresponde ao que está a ser feito no video.
#include <xc.h>
#include "Daviduino.h"
#include "LCD.h"

void main()
{
    char texto[8]={'C','o','n','t','a','g','e','m'};
    char i;
    unsigned int teste;

    initOSCILLATOR();
    initPORT();

    delayms(1000);

    lcd_init();

    while(1)
    {

    lcd_pos(1,5);

    for(i=0;i<8;i++)
    {
        lcd_dat(texto[i]);
    }

    lcd_pos(2,1);
    lcd_dat(62);        //>

        for (teste=0 ; teste<=65534 ; teste++)
        {
            lcd_pos(2,5);
            lcd_write_num(teste);

            delayms(250);
        }

        lcd_cmd(0x01);       //clear LCD
        __delay_ms(2);
    }

} 

Em baixo deixo o código já organizado para fazer e chamar um caractere especial (como um smile).
Estes displays dispõem de uns endereços na sua memória para alojar caracteres especiais. O problema aqui é que esses endereços estão numa memória RAM e quando há uma falha de energia, têm de ser "feitos" novamente.
Os caracteres podem ser feitos com base numa matriz de 5x8 pontos (sendo que a ultima linha deve ser deixada em branco). No caso do código que aqui apresento essa matriz é definida como elementos do vector "caracter[]". A função "chamar_especial()" apenas acessa o endereço onde foi previamente escrito o caracter. Aqui de futuro, não agora, ainda devo fazer alterações de modo a que consiga mudar facilmente o endereço do novo caracter especial.

Rotina para fazer e chamar ao display o caracter especial:
void especial()
{
    char endereco;
    char caracter[8]={0b00000,      //Matriz 5x8
                                     0b01010,
                                     0b01010,
                                     0b00000,
                                     0b10001,
                                     0b01110,
                                     0b00000,
                                     0b00000};


    PORTB = 0b11000000;         //DDRAM
    __delay_us(50);
    PORTBbits.RB7=0;
    __delay_us(50);
    PORTB = 0b11000000;
    __delay_us(50);
    PORTBbits.RB7=0;
    __delay_us(50);

    for(endereco=0;endereco<8;endereco++)
    {
        PORTB = 0b10001000;
        __delay_us(50);
        PORTBbits.RB7=0;
        __delay_us(50);
        PORTB = ((endereco<<1) & 0b1110) + 0b10000000;
        __delay_us(50);
        PORTBbits.RB7=0;
        __delay_us(50);

        PORTB = ((caracter[endereco]>>3) & 0b10) + 0b11000000;
        __delay_us(50);
        PORTBbits.RB7=0;
        __delay_us(50);
        PORTB = ((caracter[endereco]<<1) & 0b11110) + 0b11000000;
        __delay_us(50);
        PORTBbits.RB7=0;
        __delay_us(50);
    }

    chamar_especial();
}

Rotina para chamar ao display o caracter especial do endereço em que foi gravado:
void chamar_especial()
{
    PORTB = 0b11000000;     //DDRAM do crac especial
    __delay_us(50);
    PORTBbits.RB7=0;
    __delay_us(50);
    PORTB = 0b11000000;
    __delay_us(50);
    PORTBbits.RB7=0;
    __delay_us(100);
}

Como disse no artigo anterior, a solução acabaria por fazer a mina própria biblioteca, assim este artigo é sobre o estado em que se encontra a mesma.
Consegui fazer com que os primeiros caracteres aparecessem no ecrã.
Daí dei um jeito no código "em bruto", para ficar mais flexível e fácil de usar no futuro.
Basicamente o que estou a fazer é a inicialização do LCD, a colocação do cursor num endereço e a escrita de caracteres com incremento automático do endereço do cursor.
Código para utilização do LCD com comunicação a 4 bits.

Rotina de configuração do LCD:
void config_lcd()
{
    // E   RS  RW  D7  D6  D5  D4
    // RB7 RB6 RB5 RB4 RB3 RB2 RB1

    char i;
    char config[7]={
                    0b10000100, //1ª config - 4bits
                    0b10000100, //4bits; 2linhas; 5x8pontos + E
                    0b10010000, //4bits; 2linhas; 5x8pontos + E
                    0b10000000, //E + display ON; mostra cursor; cursor pisca
                    0b10011110, //E + display ON; mostra cursor; cursor pisca
                    0b10000000, //E + cursor incrementa; sem display shift
                    0b10001100, //E + cursor incrementa; sem display shift
                  };

    for(i=0;i<7;i++)
    {
        PORTB = config[i];
        __delay_us(50);
        PORTBbits.RB7=0;
        __delay_us(50);
    }  
}

Rotina de posição do cursor:
void cursor_lcd_pos(char posicao)
{
    /*
     * 00h -> linha 1
     * 40h -> linha 2
     */

    PORTB = ((posicao>>3) & 0b1110) + 0b10010000;
    __delay_us(50);
    PORTBbits.RB7=0;
    __delay_us(50);
    PORTB = ((posicao<<1) & 0b11110) + 0b10000000;
    __delay_us(50);
    PORTBbits.RB7=0;
}

Rotina de escrita no display:
void escrever_lcd(char texto[])
{
    char length, i;

    length=strlen(texto);

    for(i=0;i<length;i++)
    {
        PORTB = ((texto[i]>>3) & 0b11110) + 0b11000000;
        __delay_us(50);
        PORTBbits.RB7=0;
        __delay_us(50);
        PORTB = ((texto[i]<<1) & 0b11110) + 0b11000000;
        __delay_us(50);
        PORTBbits.RB7=0;
        __delay_us(50);
    }
}

Exemplo de código main();
void main()
{
    PIC_init();

    config_lcd();
    cursor_lcd_pos(3);
    escrever_lcd("Ola mundo ");

}

Confirma-se... após uma discução no fórum da microchip, que a melhor opção é fazer a minha própria biblioteca ou funções para comunicar com o módulo LCD.
Este compilador C pode não trazer uma grande quantidade de bibliotecas funcionais (pois leva-me a crer que muitas foram copiadas do hi-tech), mas os Application Notes da microchip são fantásticos.
No entanto, continuei a ter algumas dificuldades em comunicar com o display e só após algumas horas de leitura do datasheet da Hitachi é que comecei a entender o conceito aqui usado.
É realmente muito trabalhoso e demorado comunicar com um LCD sem recorrer a bibliotecas já feitas, mas o desafio era esse mesmo. De realçar que existe o modo de comunicação por 8 bits + E + RS + RW, mas obviamente optei pelo método de 4bits + E + RS + RW, pois desta forma ocupo apenas 7 pinos para ligar ao módulo.
O bom é que com a minha biblioteca "vai fazer" coisas que normalmente não se fazem (pelo menos as que conhecia do CCS)...
O facto de estar a fazer todo o código de raiz faz com que o meu conhecimento acerca do mesmo seja bem maior, assim como um conhecimento mais detalhado dos LCD. Isto vai permitir-me fazer caracteres especiais, símbolos, marcas, etc...
Em futuros projectos, o uso destes LCD's que embora baratos, poderá levar um toque mais personalizado à sua aplicação.
Primeiros caracteres no display começaram a surgir. Nos próximos dias irei postar mais evoluções, até completar uma biblioteca "minima" que me permita iniciar o display, fazer todo o processo de escrita "automaticamente" e fazer a alteração do endereço do cursor .

Até agora, desde que passei a usar o MPLAB XC8, que tenho feito sempre as minhas bibliotecas de raiz (excepto claro as dos MCU) ou  pelo menos todas as funções de raiz.
Este fim de semana apetece-me ligar um PIC16F88 a um LCD 16x2, mas sinceramente não queria escrever tudo de novo, pois o XC8 possui a biblioteca "xlcd.h" para estes casos.
Vamos ver o que consigo fazer ou se vou acabar por fazer todo o código como de costume.
Começa mais uma noite de "auto aprendizagem" :D.

Link para o artigo de Agosto, altura em que comprei 2 LCD:

Este é um pequeno programa para escrever e ler endereços da EEPROM de um PIC12F675.
O compilador é o MPLAB XC8, que já possui umas macros para fazer isto... mas optei por ser eu mesmo a fazê-las.
A descrição é simples, em escrita ele vai tirando 255 ao valor a aguardar e vai incrementando o número do endereço. Quando termina guarda o número do último endereço no endereço zero.
Aquando da leitura, é lido o endereço zero, que me diz qual é o ultimo endereço a ler e após isso são somados todos os valores (guardados em cada endereço) até lá.
#include <xc.h>

__CONFIG(FOSC_INTRCIO & WDTE_OFF & PWRTE_ON & MCLRE_OFF & BOREN_OFF & CP_OFF & CPD_OFF);


#define _XTAL_FREQ 4000000


EEPROM_escrita(unsigned int num)
{
signed char i;

i=1;
while(num>255)
{
num=num-255;

//escrita na EEPROM
PIR1bits.EEIF=0;
EEADR=i;
EEDATA=255;
EECON1bits.WREN=1;
EECON2=0x55;
EECON2=0xAA;
EECON1bits.WR=1; //clear por hardware
while(!PIR1bits.EEIF);

i++;
}

//escrita na EEPROM
PIR1bits.EEIF=0;
EEADR=i;
EEDATA=num;
EECON1bits.WREN=1;
EECON2=0x55;
EECON2=0xAA;
EECON1bits.WR=1; //clear por hardware
while(!PIR1bits.EEIF);

//escrita na EEPROM
PIR1bits.EEIF=0;
EEADR=0;
EEDATA=i;
EECON1bits.WREN=1;
EECON2=0x55;
EECON2=0xAA;
EECON1bits.WR=1; //clear por hardware
while(!PIR1bits.EEIF);
}

unsigned int EEPROM_leitura(void)
{
unsigned char i;
unsigned int num;

num=0;

EEADR=0;
EECON1bits.RD=1;
i=EEDATA; //numero de endereços

while(i>0)
{
EEADR=i;
EECON1bits.RD=1;
num+=EEDATA;
i --;
}

return num;
}

void main()
{
unsigned int tempo;

GPIO=0b000000;
CMCON=0b00000000;
TRISIO=0b00001000;

//ADC
ANSEL=0b00000000;


EEPROM_escrita(1000);

tempo=EEPROM_leitura();

__delay_ms(100);


while(1);

}
Diagrama de blocos do PIC12F675.

Eis aqui mais um achado na Internet.
Um projecto interessante, simples e muito aplicado por hobbistas agora com a chegada no Natal.
Trata-se de um LED que tenta simular uma vela, recorrendo a um pequeno MCU (neste caso um 12F675) para gerar o PWM aleatório com uma duração igualmente aleatória.
O efeito é bastante semelhante ao de uma vela. O código teve de sofrer algumas alterações, em relação ao original, mas funcionou bastante bem quando o testei. Digo isto porque não estando no meu super-mega laboratório caseiro, apenas consegui um LED branco de HB para testar e fiquei muito satisfeito.

Para mais Info e ver o código original com os comentários, este é o LINK.

#include <htc.h>

__CONFIG (FOSC_INTRCIO & WDTE_OFF & PWRTE_ON & MCLRE_OFF & BOREN_OFF & CP_OFF & CPD_OFF);

unsigned int t1value=65535-(100+99*1)+1; //inicial 1%

void interrupt isr(void)
{
if(T0IF)
{
TMR0=217;
TMR1H=(t1value>>8)&0xFF;
TMR1L=(t1value&0xFF);
GPIO2=1;
TMR1ON=1;
T0IF=0;
}
if(TMR1IF)
{
TMR1ON=0;
GPIO2=0;
TMR1IF=0;
}
}

char getrandombit(void)
{
static unsigned short shiftregister=0x9500;
unsigned short value;
char output;

value=(shiftregister&0x0200)>>9 ^ (shiftregister&0x0400)>>10;
shiftregister <<= 1;
shiftregister |= value;
output=(value ^ 1) & 0x01;
return output;
}

void main(void)
{
char i=30;
int pause;
GIE=0;
PEIE=0;

TRISIO=0x3B;

ANSEL=0;

OPTION_REG=0xD7;

TMR0=217;
T0IF=0;
T0IE=1;

T1CON=0x00;
TMR1H=(t1value>>8) & 0xFF;
TMR1L=(t1value&0xFF);
TMR1IF=0;
TMR1IE=1;

PEIE=1;
GIE=1;

while(1)
{
if(getrandombit())
i+=10;
else
i-=10;

if(i<0)  i=0;
if(i>60) i=60;

t1value=65535-(100+99*i)+1;

for(pause=0;pause<1000;pause++);
}
}


Este programa vai apenas produzir 10 pulsos de 256us*200 no pino RA2 e criar um pulso no pino RA3 com o período de 256us*256.

O programa vai contar 200 incrementos do TIMER0 e troca o estado do pino RA2. A frequência de clock é 4MHz, mas como são precisos 4 pulsos para fazer uma instrução, a frequência de instrução é 1MHz.
O registo OPTION_REG foi definido para um percaller de 1:256 no Timer0, quer dizer que é preciso 256us para cada incremento do timer e 65.536ms para o overflow do mesmo.


Após isso, configurei o registo INTCON, para "desmascarar" todas as interrupções (global), para activar a interrupção do TIMER0 e ainda para activar a flag de interrupção do TIMER0. Esta última é muito importante para que seja chamado o programa da interrupção.


Uma das coisas que também não estava habituado era de que todas as interrupções entrarem no "mesmo programa". O "void interrupt blabla()" é chamado sempre que ocorrer uma interrupção e dentro dele tem de ser feita a distinção de qual foi a origem da interrupção.
Como foi activada a flag de overflow do Timer0, no incio de "void interrupt INT" faz-se a seguinte verificação:

void interrupt INT(void)
{
   if (TMR0IE && TMR0IF)
   {
         bla bla bla.....
   }
}

A "pergunta" que é feita pelo "if (TMR0IE && TMR0IF)" é se ocorreu a interrupção (aparecimento da flag) e se a interrupção estava activa (definida pelo programador).
Verificando estas duas condições, entra no programa e faz o que tem a fazer...

IMPORTANTE: Não esquecer de no final limpar a flag da interrupção para que se houver outra, não haver enganos, ou seja, se for gerada uma interrupção por uma alteração no porto B, não ser executado o programa da interrupção do timer, por exemplo.


O código:

// PIC16F88 Configuration Bit Settings

#include <xc.h>

__CONFIG(FOSC_INTOSCIO & WDTE_OFF & PWRTE_OFF & MCLRE_OFF & BOREN_OFF & LVP_OFF & CPD_OFF & WRT_OFF & CCPMX_RB0 & CP_OFF);
__CONFIG(FCMEN_OFF & IESO_OFF);


#define _XTAL_FREQ 4000000


char a;

void interrupt INT(void)
{
   if (TMR0IE && TMR0IF)
   {
       if(a<10)
       {
          RA3=1;
       }
       else
       {
           RA3=0;
           INTCONbits.GIE=0;
           INTCONbits.TMR0IE=0;
       }
       a++;
       INTCONbits.TMR0IF=0;
   }
}

void main()
{
        char conta;

//***** Portos
                 TRISA = 0b00000000;
                 TRISB = 0b00000000;
                 PORTA = 0;
                 PORTB = 0;
           
//***** Oscilador
                 OSCCONbits.IRCF = 0b110;
                 OSCCONbits.OSTS = 1;
                 OSCCONbits.IOFS = 1;
                 OSCCONbits.SCS  = 0b00;

//***** TIMER0
             OPTION_REGbits.PS  =0b111;
             OPTION_REGbits.PSA =0;
             OPTION_REGbits.T0SE=0;
             OPTION_REGbits.T0CS=0;


//****  Led a piscar com leitura de Timer0
            __delay_ms(1000);
            conta=0;
            while(conta<10)
            {
                TMR0=0;
                RA2=1;
                while(TMR0<200);
                RA2=0;
                TMR0=0;
                while(TMR0<200);
                conta++;
            }

//****  Led a piscar com leitura de Timer0 + interrupção
            INTCONbits.GIE=1;
            INTCONbits.TMR0IE=1;
            INTCONbits.TMR0IF=0;        //interrupção não ocorreu ainda
            a=1;
}

Saída de RA2 e RA3