4 Ekim 2017 Çarşamba

Measure Period and Duty Cycle using CCP2 and TIMER1

We will use the same circuit to measure duty cycle together with the period.  Period is the time difference between two rising edges (t3-t1). Duty cycle is the difference between the first rising edge and next falling edge (t2-t1).


MCC: MCC settings are the same as previous example, 1MHz internal osc, 1:1 prescaler, CCP interrupt enabled for every rising edge.

XC8:   while(!CCP2IF) statement to capture the interrupt case  inside the main.c did not work (there are many examples of it in internet). Thus I put conditions inside ccp2.c ISR  such as
...
extern uint16_t  t1,t2,t3;
int c=0;

void CCP2_CallBack(uint16_t capturedValue)
{
    // Add your code here

       
    if ( c==2) {
      t3=capturedValue;  
      CCP2CON=0x00;   //second rising edge
      c=0;        
    } else if (c==1) {
      t2=capturedValue; // falling edge
      CCP2CON=0x00;
      CCP2CON=0x05;
      c=2;
    } else {    
      t1=capturedValue;  //first rising edge
      CCP2CON=0x00;
      CCP2CON=0x04;
      c=1;        
    }
   
}

transition from rising to falling edge did not work. Thus I disabled interrupt first and enabled with new state. t1,t2,t3 are defined as extern to use in main.c, c integer is local for toggling between cases.

statement inside main.c enables rising edge, waits for interrupt stop then calculated duty and period, prints on LCD

        CCP2CON=0x05;  
        while(CCP2CON!=0);
        d=t2-t1;
        if (d<0) d=d+65536;
        d=d*4.0/1000.0;
        f=t3-t1;
        if (f<0) f=f+65536;
        f=f*4.0/1000.0;
        
        sprintf(s1,"%3.3fms", f); 
        sprintf(s2,"%3.3fms", d); 

This code was able to measure between 1-262 ms with 1MHZ oscillator. Below 1ms yields multiple captured values and wrong results. We can increase the osc frequency (8MHZ or 16MHz) or use 16th rising edge etc for lower values. we can add timer1 overflow interrupt to measure values above 262ms, too. 


2 Ekim 2017 Pazartesi

Measure period using CCP2 and TIMER1

In this example, we will measure period of a pulse signal. In theory it is simple, measure the time difference between two rising/falling edges. I saw two approaches; first to do this within main.c, like in http://www.electronicwings.com/pic/pic18f4550-timer-capture. But it did not work for me specially in high frequencies=low periods. If you measure high frequency a lot of CCP interrupt occurs and it seems blocking the PIC.


while(1) { while(!(PIR1bits.CCP1IF)); /* Wait for interrupt flag which is generated when edge is detected*/ data1=CCPR1; /* Copy count of 1st edge detected*/ PIR1bits.CCP1IF=0; while(!(PIR1bits.CCP1IF)); /* Wait for interrupt flag which is generated when edge is detected*/ data2=CCPR1; /* Copy count of 2nd edge detected*/ PIR1bits.CCP1IF=0;


Thus, I wanted to capture only two rising edge, not more, at every 1 second. I
handled it within interrupt routine.

Proteus: Connect a pulse generator to the RC1/CCP2A. I opened the debugger to see
how many CCP event occurs.


MCC:  Add TIMER and CCP peripherals. Select rising edge. CCP2 interrupt automatically selected. 1MHZ internal osc was used. Use 1:1 prescaler at timer. no timer interrupt.





XC8:   added the code inside ISR. I used c=0, c=1 tokens to take only two samples, then disable the CCPCON=0x00;  interrupt. I enable within main.c again. Period is measured inside main.c. Be careful, you have to define t1 variable as extern inside ISR and take them to top at main.c  

It is working good between 500us-262ms (timer1 range without overflow). but again below 500us, more than two CCP interrupts occurs and measurement may give wrong results. I could not solve it yet. Any comments are welcome. obviously using higher freq oscillator will decrease this value.

------ccp2.c---------
#include <xc.h>
#include "ccp2.h"

extern double t1;
int c;
...

void CCP2_CaptureISR(void)
{
    CCP_PERIOD_REG_T module;

    // Clear the CCP2 interrupt flag
    PIR2bits.CCP2IF = 0;
    
    // Copy captured value.
    module.ccpr2l = CCPR2L;
    module.ccpr2h = CCPR2H;
    
    // Return 16bit captured value
    CCP2_CallBack(module.ccpr2_16Bit);
}

void CCP2_CallBack(uint16_t capturedValue)
{
    // Add your code here
    

    if ( c==1) {        
        t2=capturedValue;    //second  rising edge
        CCP2CON=0x00;    //disable after second rising edge
        c=0;        
    } else  {    
        t1=capturedValue;    //first rising edge
        c=1;
    }


    }


-----main.c-------

#include "mcc_generated_files/mcc.h"
#include "delays.h"
#include "myxlcd.h"
#include <stdio.h>

double  pr,t1,t2;
int c;
/*
                         Main application
 */
void main(void)
{
    // Initialize the device
    SYSTEM_Initialize();

    // Enable the Global Interrupts
    INTERRUPT_GlobalInterruptEnable();


    // Enable the Peripheral Interrupts
    INTERRUPT_PeripheralInterruptEnable();

   unsigned char s1[10], s2[10];

   
    OpenXLCD(FOUR_BIT & LINES_5X7);
    WriteCmdXLCD(DON&CURSOR_OFF&BLINK_OFF); 
    
    
    while (1)
    {
        // Add your application code


         CCP2CON=0x05;  //enable CCP interrupt, which will be disabled inside ccp2.c        
        while (CCP2CON ==0x05); // wait for the end of interrupt
        pr=t2-t1;   //time difference
        if (pr<0) pr=pr+65536;  //in case of timer1 overflow
        pr=pr*4.0/1000.0;
     
        sprintf(s1,"%3.3fms", pr);   
  

        while(BusyXLCD());
        SetDDRamAddr(0x01); // clear LCD        
        putrsXLCD("P=");putrsXLCD(s1);
        SetDDRamAddr(0x40); // goto second line
        while(BusyXLCD());
        putrsXLCD("D=");putrsXLCD(s2);
        __delay_ms(1000);
        
    }
}

I checked with measurement inside main.c using (8MHz clock, thus 0.5 multiplier) this works fine upto 10ms but gives double or wrong values below. I noticed that  while (PIR2bits.CCP2IF==0);  may not work well because interrupts flowing continuously at debugger which causes high CPU load.

   while (1)
    {
        // Add your application code


        while (PIR2bits.CCP2IF==0); //wait until interrupt
        t1=CCPR2H*256+CCPR2L;

        while (PIR2bits.CCP2IF==0);
        t2=CCPR2H * 256 + CCPR2L;
     
        pr=t2-t1;
        if (pr<0) pr=pr+65536;
        pr=pr*0.5/1000.0;

I also checked the algorithm at https://web.sonoma.edu/users/f/farahman/sonoma/courses/es310/lectures/chapter_11_timers.pdf but it did not work for me also. I read some comments that resetting timer for period measurement will not give accurate results, instead measure time difference between two CCP rising edge interrupt advised as a better approach


28 Eylül 2017 Perşembe

Write and Read to/from EEPROM

Proteus: We will use an LCD connected to B ports,


MCC: Just add MEMORY peripheral and left default settings. internal 1MHz osc, disable MCLR in pin manager. no interrupt.

XC8: we will write addresses starting from 0x01 . Read these addresses and show on LCD. That's so simple. 

   volatile unsigned char RAMArray[10];
    unsigned char i, s1[10];

    for(i=0; i<10; i++){
    DATAEE_WriteByte(0x01 + i, i*i);
    }
    
    OpenXLCD(FOUR_BIT & LINES_5X7);
    WriteCmdXLCD(DON&CURSOR_OFF&BLINK_OFF); 
    while (1)
    {
        // Add your application code
        
        for(i=0; i<10; i++){
        RAMArray[i] = DATAEE_ReadByte(0x01 + i);
        
        sprintf(s1,"%02d",RAMArray[i]);
        
        while(BusyXLCD());
        SetDDRamAddr(0x01); // clear LCD        
        putrsXLCD("Memory Example");
        SetDDRamAddr(0x40); // goto second line
        while(BusyXLCD());
        putrsXLCD("Data=");putrsXLCD(s1);
        
        __delay_ms(1000);
        }
    }
}

download the example

27 Eylül 2017 Çarşamba

SPI Example Using TC77

Proteus: SPI is another digital bus to communicate multiple devices. We will read temperature through TC77 using SPI bus. I connected oscilloscope and SPI debugger for diagnostic purposes. Connections  :
PIC RC3 SCL clock --> TC77 SCK clock
PIC RC4 SDI input data --> TC77 I/O
PIC RC0 output --> TC77 CS chip select

Equipment in SPI is selected by making chip select port to Low.



MCC: internal osc 1Mhz selected, MSSP with SPI master selected. input data sampled at End, clock polarity is idle to active, clock edge active to idle. SPI clock is selected at 15.625khz. We also selected  RC0 as output pin for chip select on SPI bus. Nothing is selected in interrupt tab.





XC8: dont touch interrupts, everything is disabled.  We will set Low RC0 to enable communication with TC77. Then, read 8bits, twice. disable TC77 by setting RC0 high. TC77 uses 13bits with 0.0625 resolution. Thus multiply 0.0625 with 13 bits. It may need different formula for negative temperatures.


    unsigned char s1[10];
    uint8_t  TempLSB, TempMSB           = 0;
    
    OpenXLCD(FOUR_BIT & LINES_5X7);
    WriteCmdXLCD(DON&CURSOR_OFF&BLINK_OFF); 

    while (1)
    {
        // Add your application code
        //SPI_Exchange8bit(0x00);
        
        IO_RC0_SetLow(); 
        TempMSB = SPI_Exchange8bit(0x00);
        TempLSB = SPI_Exchange8bit(0x00);
        IO_RC0_SetHigh(); 

        sprintf(s1, "%3.1f", ((TempMSB<<8|TempLSB)>>3)*0.0625);   
    

        while(BusyXLCD());
        SetDDRamAddr(0x01); // clear LCD        
        putrsXLCD("TC77 Example");
        SetDDRamAddr(0x40); // goto second line
        while(BusyXLCD());
        putrsXLCD("Temp=");putrsXLCD(s1);
        __delay_ms(1000);
    }
}


26 Eylül 2017 Salı

Setting DS1307

If we dont set DS1307 it will show computer clock where Proteus is running on. Instead we can set hour, minnutes,seconds etc any value on registers. Let's set everything to zero for example.




    unsigned char s1[10], s2[10];
    uint8_t readValue[7], writeValue[8]={0,0,0,0,0,0,0,0};
    I2C_MESSAGE_STATUS    I2C_status;
 
 
    I2C_MasterWrite( &writeValue, 7, 0b1101000, &I2C_status); //set pointer address zero and write 7 zero
    while (I2C_MESSAGE_PENDING  == I2C_status );

Time will start from 00:00:00 Similarly we can set only hours, minutes, seconds etc by adding some buttons to the circuit. If button pressed increase hours +1 mod 24 ...



 

Reading multiple TC74 on I2C

We can read/write multiple devices on I2C by addressing them. Let's read two TC74 with different addresses, 0x96 (A1 option) and 0x9A (A5 option).  we will unhide address to see on Proteus. What will be the 7-bit address on write/read commands
0x9A, 9=1001, A=1010, 3bit 101,  7bit= 0b1001101
0x96,  9=1001, 6=0110, 3bit 011, 7bit=0b1001011


we can modify the code for multiple TC74 as below:

    unsigned char s1[10], s2[10];
    uint8_t readValue[1], readValue2[1];
    I2C_MESSAGE_STATUS    I2C_status;
    
    OpenXLCD(FOUR_BIT & LINES_5X7);
    WriteCmdXLCD(DON&CURSOR_OFF&BLINK_OFF); 
    
    while (1)
    {
        // Add your application code
        
    I2C_MasterWrite( 0, 1, 0b1001101, &I2C_status);
    while (I2C_MESSAGE_PENDING  == I2C_status);    
    I2C_MasterRead( &readValue, 1, 0b1001101, &I2C_status);
    while (I2C_MESSAGE_PENDING  == I2C_status );

    sprintf(s1, "%02d", readValue[0]);   
    
    I2C_MasterWrite( 0, 1, 0b1001011, &I2C_status);
    while (I2C_MESSAGE_PENDING  == I2C_status);    
    I2C_MasterRead( &readValue2, 1, 0b1001011, &I2C_status);
    while (I2C_MESSAGE_PENDING  == I2C_status );
    
    sprintf(s2, "%02d", readValue2[0]);   
    

        while(BusyXLCD());
        SetDDRamAddr(0x01); // clear LCD        
        putrsXLCD("Temp1=");putrsXLCD(s1);
        SetDDRamAddr(0x40); // goto second line
        while(BusyXLCD());
        putrsXLCD("Temp2=");putrsXLCD(s2);
        __delay_ms(1000);

    }
}

I2C Debugger

I2C debugger of Proteus is a useful virtual instrument. You can follow the traffic on the BUS as sniffer or you send messages to devices on the bus. If we look at TC74 example, we see that there are two type of communication caused by write and read functions.

    I2C_MasterWrite( 0, 1, 0b1001101, &I2C_status) :                 S 9A A 00 A P
    I2C_MasterRead( &readValue, 1, 0b1001101, &I2C_status) : S 9B A 1A N P

Syntax of I2C is given at help file.

TC74 datasheet provides structure of write and read protocols. As you can see S 9A A 00 A P corresponds to a write sequence and S 9B A 1A N P corresponds to a receive byte. 1A is the temperature value.


We can monitor clock (blue) and data (yellow) also in oscilloscope 



Possible errors in I2C: 
  1. Pullup resistors are left at ANALOG, change them to DIGITAL
  2. Power supply must be VCC, dont put separate +5V to feed I2C bus
  3. Address may become wrong. In that case, equipment will not respond and you will receive 0x00 byte. for example, I changed the A5 address of TC74 to A1, look at I2C debugger will show only traffic coming from PIC like "S 9A N P" but there is no response from TC74.