25 Ekim 2017 Çarşamba

Generating Sinewave using PWM

I found two references for this project Roman Black's 1kHz generator pages and Microchip app note AN1523. Both ideas are the same to create a PWM pulse train such that pulse width will be proportional to sinewave amplitude. A filter required to create the sine wave. I used Roman Black's simple filter instead of AN1523 Salen Key Filter.

I have 40 samples on a single 1kHz=1000us sinewave. so  sample period will be 1000us/40=25us. 25us will be TMR2 period.

Circuit works fine and creates a sinewave but the period was 6ms I used Roman Black table by changing TMR2 period to 20us  and 50 samples, but period was 9ms for this case. I checked 50usx20 samples and noticed that same pulse repeats 3 times then changes to new duty cycle. It means that during ISR processing (changing CCPR2L value) 3 to 6 cycle passes before changing the duty cycle. Maybe we can avoid this using higher freq external oscillator. 25MHz external clock decreased the period to 4ms.

Thus I decreased the frequency to 100Hz and 20 samples. It makes 500us timer2 period. I can reach to this range by using 1:16 prescaler. 20 sample values are the same as for 50usx20 table. Note that table values are related to number of samples and duty range you want to work. Finally, I modified the filter RC values to have 100Hz=1/(2*pi*R*C)


Proteus: PIC24k20 PWM output RC1 is connected to the filter.



1Khz was not achivable with this setup but 100Hz was ok with some ripples. We can play with the filter to reduce ripples.





MCC: Internal 16MHz oscillator was used. CCP2 is selected for PWM operation. CCP2 uses TMR2. Timer2 period is adjusted to 25us. 1:1 pre and post scalers are selected. we want to use PWM between 10-90% duty cycle. 




XC8: First we will create a sinevalue array for 40 samples. I have prepared using an excel file. I added +1 to have positive sinewave amplitude:

1+sin(2.pi.f.t) 
f=1000hz  
t=0,25us,50us...975us 


we can check maximum value of CCPR2L by setting duty cycle 100% and press GENERATE, check pwm2.c CCPRL value. For our example it was 99. Thus we can use the table as it is. I changed duty cycle by setting CCPR2L. Another method is to use PWM2_LoadDutyValue() function but I noticed that it created some spikes probably due to processing of another function inside ISR. We have to finish our job inside ISR as quick as possible to catch next interrupt otherwise we may miss some cycles.   I also checked with AN1523 sine table which has different numbers and it works fine, too. 

Roman Black changes duty cycle inside main.c within a while loop. This did not work for me. Thus I put this function inside timer2 ISR tmr2.c  



void TMR2_DefaultInterruptHandler(void){
    // add your TMR2 interrupt custom code
    // or set custom function using TMR2_SetInterruptHandler()

     const unsigned char sinevalue[20] = {
        50,62,74,82,88,90,88,82,74,62,50,38,26,18,12,10,12,18,26,38
    };
   
    static unsigned char pwmstep=0;   

    CCPR2L=sinevalue[pwmstep];
    pwmstep=(pwmstep+1) % 20;
       
}

download the example

23 Ekim 2017 Pazartesi

IRIGB Time Code Display

Years ago, I have designed an IRIGB decoder with PIC16F88  using CCS compiler. I updated the code for XC8.

Proteus: First stage is a comparator to remove sine waves below 1.2V (two reference diodes). Second stage is a "missing pulse detector" to create train of pulses with 2ms, 5ms and 8ms DC level shift modulated (DCLS) IRIGB code. Once we have pulses we can measure pulse width using a PIC18F24K20 CCP2 and TIMER1.


MCC: 8MHz internal oscillator. CCP2 rising edge and interrupt. Timer1 works with CCP2,  no need for timer interrupt. 



XC8: inside ccp2.c we can add our code to measure pulse width. transition from rising to falling works only if I disable with CCP2CON=0x00; ccp_delta is pulse width. 

main.c has a switch statement counts hours, minutes and seconds according to pulse widths in IRIGB format.


-------ccp2.c--------------

void CCP2_CallBack(uint16_t capturedValue)
{
    // Add your code here
static int t1_rising_edge;  //define static to keep the value

if(CCP2CON==0x05)
  {
    t1_rising_edge = capturedValue;
    CCP2CON=0x00;
    CCP2CON=0x04;
   
  }
else
  {
   ccp_delta = capturedValue - t1_rising_edge;
   got_pulse_width = 1;
   CCP2CON=0x00;
   CCP2CON = 0x05;   
  }
    
}


download the example hex file and irigb.wav to feed the circuit. make 3volt amplitude.

17 Ekim 2017 Salı

Introduction to the C Programming Language

I found presentations of Masters 2013 conference at here and followed Introduction to the C Programming Language Class and LAB Manual prepared for XC16. Very useful reference. Collected and modified source codes of LAB subjects into a single XC8 project file. You can follow below subjects by commenting out related part inside main.c


  • LAB01: variables
  • LAB02: constants
  • LAB03: printf()
  • LAB04: operators
  • LAB05: if
  • LAB06: switch
  • LAB07: loops
  • LAB08: functions
  • LAB09: header files
  • LAB10: array
  • LAB11: pointer
  • LAB12: pointer arrays
  • LAB13: function pointers
  • LAB15: structure
  • LAB16: bitfields



16 Ekim 2017 Pazartesi

Learning C using UART1 Output Window

We can practice C code programming using UART1 output window of MPLAB.  http://microchipdeveloper.com/xc8:console-printing proposes the way to do it. I simplified this using MCC.

MCC: Start a new project for PIC18F24K20. add EUSART peripheral. enable transmit. redirect STDIO to USART.  that's all, 1MHz internal osc selected.

Project Properties-> Simulator -> Enable UART1 IO

these settings will be enough to redirect stdio to UART1 window. press GENERATE to create mcc files and write your first code inside main.c

printf("Hello World!\n");

press the debug button. it will run simulation, open UART1 Output and will print Hello World!

use cmd-L to clear the screen for the next run.









13 Ekim 2017 Cuma

Temperature Logger

We will combine thermometer and SD card examples to create a temperature logger.

Proteus: SD card is connected to SPI pins and we will use RA0 for ADC to read temperature from LM35.


MCC: Add MSSP and ADC peripherals. no interrupts. Select RA0 at pin manager for ADC. 







XC8:  add headers of SD card as in previous example.  First read the value convert  and save the result "t" string. Writing to SD card is the same as in SD card example. Just be careful on number of bytes, it must be same to the length of string "Measurement 000:000.0\r\n" which is 23 bytes, otherwise you will get garbage characters in your "temp_log.txt" file.  We use the same 2Mbytes image file "sdcard.ima" that we created in SD card example. "temp_log.txt" will be created if you run the simulation in Proteus. Dont forget to select "sdcard.ima" file in SD card properties.







#include "mcc_generated_files/mcc.h"
#include <stdio.h>
#include "ff.h"


FATFS FatFs; /* FatFs work area needed for each volume */
FIL Fil;  /* File object needed for each open file */


...


    char t[20];
    uint16_t cV = 0,i=0;
    float  tm;

    while (1)
    {
        // Add your application code
        i++;
        ADC_StartConversion(channel_AN0);
        while(!ADC_IsConversionDone());
        cV = ADC_GetConversionResult();
        tm=cV*500.0/65536;
        sprintf(t, "Measurement %3d:%3.1f \r\n", i,tm);
        
    UINT bw;

    if (f_mount(&FatFs, "", 1) == FR_OK) { /* Mount SD */

    if (f_open(&Fil, "temp_log.txt", FA_OPEN_ALWAYS | FA_READ | FA_WRITE) == FR_OK) { /* Open or create a file */

    if ((Fil.fsize != 0) && (f_lseek(&Fil, Fil.fsize) != FR_OK))  goto endSD; /* Jump to the end of the file */

        f_write(&Fil, t, 23, &bw); /* Write data to the file */
           
     endSD: f_close(&Fil);        /* Close the file */
    }
    }
    
    

    __delay_ms(1000);
    }
}

download the example

Adjustable Clock DS1307

We learned how to read and set DS1307 clock in our previous examples. We will develop an adjustable clock in this example. There are three buttons SET, UP, DOWN as you see on commercial digital clocks. SET button will select and blink  hour and minutes sequentially.  UP,DOWN buttons will increment or decrement hours or minutes.

Proteus: DS1307 with a crystal. LCD is connected to PORTB. RA0, RA1,RA2 are used for setting buttons.


MCC: nothing special. RA0,RA1,RA2 is set for input. select MSSP, I2C master, 1Khz. 1Mhz internal oscillator.





XC8: Enable global and peripheral interrupts. First set clock to 03:02:01 we will not use day, month year but I set them, too. read DS1307 and print to LCD. In case of SET button pressed, set seconds to zero and check UP/DOWN buttons. If up/down buttons are pressed change hour/minutes. Second SET button will switch to minutes and third SET button will set the clock.

I converted DS1307 BCD values to decimal for math operations and turned back to BCD again for register setting.  

    
    unsigned char s1[10], s2[10];
    uint8_t hr[2]={1,0}, readValue[7], writeValue[8]={0,1,2,3,4,5,6,7};
    uint8_t c,ho,mi,se,da,mo,ye;
    I2C_MESSAGE_STATUS    I2C_status;
    
    I2C_MasterWrite( &writeValue, 8, 0b1101000, &I2C_status); //sets pointer address and write data
    while (I2C_MESSAGE_PENDING  == I2C_status ); 

    
    OpenXLCD(FOUR_BIT & LINES_5X7);
    WriteCmdXLCD(DON&CURSOR_OFF&BLINK_OFF); 
    

    while (1)
    {
        // Add your application code
        
    I2C_MasterWrite( 0, 1, 0b1101000, &I2C_status); //set pointer address to zero
    while (I2C_MESSAGE_PENDING  == I2C_status);
    I2C_MasterRead( &readValue, 7, 0b1101000, &I2C_status); // read 7 bytes starting from zero
    while (I2C_MESSAGE_PENDING  == I2C_status );

    sprintf(s1, "%02X:%02X:%02X", readValue[2],readValue[1],readValue[0]);             
        
    SetDDRamAddr(0x01); // clear LCD          
    SetDDRamAddr(0x00); // first line 
    putrsXLCD(s1);        
    SetDDRamAddr(0x40); // goto second line
    putrsXLCD(s2);
    
    writeValue[0]=0;  //pointer address of zero 
    writeValue[1]=readValue[0]; //sec
    writeValue[2]=readValue[1]; //min
    writeValue[3]=readValue[2]; //hour
    writeValue[4]=readValue[3]; //weekday
    writeValue[5]=readValue[4]; //day
    writeValue[6]=readValue[5]; //month
    writeValue[7]=readValue[6]; //year
        
    ho=readValue[2]; mi=readValue[1]; se=readValue[0];
    da=readValue[4]; mo=readValue[5]; ye=readValue[6];
    
    c=0;
    if (IO_RA0_GetValue()==1) { //set pressed
    c=(c+1) % 3;   // set value 0=no-set,1=hour,2=minutes
    
    while (c==1){ //set hours    

        
    writeValue[1]=0; //set seconds to zero
    
    ho=(ho>>4)*10+(ho&0x0F); //bcd2dec
    if (IO_RA1_GetValue()==1) ho=(ho+1) % 24; //up
    if (IO_RA2_GetValue()==1) ho=(ho+23) % 24; //down
    ho=((ho/10)<<4)+(ho%10); //dec2bcd
    writeValue[3]=ho;
    
    sprintf(s1, "%02X:%02X:%02X", ho,mi,0);             
    SetDDRamAddr(0x01); // clear LCD          
    SetDDRamAddr(0x00); // first line 
    putrsXLCD(s1);        
    __delay_ms(100);
    
    sprintf(s1, "  :%02X:%02X",mi,0);             
    SetDDRamAddr(0x01); // clear LCD          
    SetDDRamAddr(0x00); // first line 
    putrsXLCD(s1);        
    __delay_ms(100);
    
    
    if (IO_RA0_GetValue()==1) { //set pressed
        c=(c+1) % 3;
        I2C_MasterWrite( &writeValue, 8, 0b1101000, &I2C_status); //sets pointer address and write data
        while (I2C_MESSAGE_PENDING  == I2C_status ); 
    }
    }

    while (c==2){ //set minutes       
    mi=(mi>>4)*10+(mi&0x0F); //bcd2dec
    if (IO_RA1_GetValue()==1) mi=(mi+1) % 60; //up
    if (IO_RA2_GetValue()==1) mi=(mi+59) % 60; //down
    mi=((mi/10)<<4)+(mi%10); //dec2bcd
    writeValue[2]=mi; 
    
    sprintf(s1, "%02X:%02X:%02X", ho,mi,0);             
    SetDDRamAddr(0x01); // clear LCD          
    SetDDRamAddr(0x00); // first line 
    putrsXLCD(s1);        
    __delay_ms(100);
    
    sprintf(s1, "%02X:  :%02X",ho,0);             
    SetDDRamAddr(0x01); // clear LCD          
    SetDDRamAddr(0x00); // first line 
    putrsXLCD(s1);        
    __delay_ms(100);
    
   if (IO_RA0_GetValue()==1){ 
        c=(c+1) % 3;
        I2C_MasterWrite( &writeValue, 8, 0b1101000, &I2C_status); //sets pointer address and write data
        while (I2C_MESSAGE_PENDING  == I2C_status ); 

    }

    }    
    }
        
        __delay_ms(1000);
    }
    



9 Ekim 2017 Pazartesi

SD Card Example

We will create a text file in an SD card and write some text inside. Microchip MDD legacy library did not work for XC8 as expected. It is for XC16 and XC32. I tried to modify header files but I gave up. I followed the example at http://www.studentcompanion.net/en/interfacing-sd-card-with-pic-microcontroller-xc8/  which is using  Chan's FatFs file system. The example was for 18F45K22, my old proteus was not supporting this PIC thus I used PIC18F45K20. It needs a little modification but working well.

Proteus: PIC18F45K20 connected to an SD card using RC3, RC4, RC5 ports and RC0 is for ChipSelect CS.  To create image file for SD card I used winimage. Select custom format, FAT16, 4096 sectors for 2MByte. Save the image give a name "sdcard2M.ima" and select this image in Proteus. If you run the simulation. "test.txt" will be created inside the image. Stop the simulation and open the image file again. You will see the "test.txt file",  we extract and look inside.






MCC: 1MHz internal oscillator as usual. select MSSP peripheral for SPI Master. No interrupts. Rename RC0 as SD_CS; this is important and used in header files. 





XC8: There are some modifications at diskio.h
#define _SD_SPI 0  // we are using PIC18F45K20 and this has one SPI module

#else

#define sd_init() SPI_Initialize()
#define sd_open() SPI_Initialize()  //SPI_Open() does not exist, thus change it to SPI_Initialize()
#define sd_tx(d) SPI_Exchange8bit(d)
#define sd_rx() SPI_Exchange8bit(0xFF)

there are no changes at other files.  main.c is as below, creates "test.txt" file and writes "Hello world! ..." sentence to this file.

#include "mcc_generated_files/mcc.h"
#include "ff.h"

FATFS FatFs; /* FatFs work area needed for each volume */
FIL Fil; /* File object needed for each open file */


/*
                         Main application
 */
void main(void)
{
    // Initialize the device
    SYSTEM_Initialize();
    
        
    UINT bw;

    if (f_mount(&FatFs, "", 1) == FR_OK) { /* Mount SD */

if (f_open(&Fil, "test.txt", FA_OPEN_ALWAYS | FA_READ | FA_WRITE) == FR_OK) { /* Open or create a file */

if ((Fil.fsize != 0) && (f_lseek(&Fil, Fil.fsize) != FR_OK)) goto endSD; /* Jump to the end of the file */

f_write(&Fil, "Hello world! This is text message written to sd card\r\n", 54, &bw); /* Write data to the file */
           
endSD: f_close(&Fil); /* Close the file */
}
}


     

    while (1)
    {
        // Add your application code
    }
}