# Coding a PIC

Status
Not open for further replies.

#### Logjkos

##### New Member
Hi

(Apologise in advance for the long post)

I am after some help please, we have been given a task to program a PIC "to find the time duration for an automatic vacuum cleaning process".

The automatic vacuum cleaning process is intitiated in some way, either through a timer or manually started and left to run, the process continues until completion at which point the time taken for this process to be completed is displayed on a 7 segment display.

We are given the following assumptions,

That the time recorded should be reasonably accurate, 10ths of a second should be sufficient for this.
That the process will not take longer than 10 minutes, I am not sure how relevant this for either being able to count up or down.
This piece of program is to be called by the main program when required.

We have been told that the code will need three parts:
an initialisation section
an interrrupt section
a delay loop itself

We have been told that the code does not actually need to function but we do need to have some code.
We have also been told that we should be able to find modules of code necessary from the Microchip website and simply copy and paste these to create our own program.

The only problem that I have with this is that I will not learn very much if anything at all.

The code that I have managed to create so far is:
For the delay loop, using the internal 31KHz oscillator:

"
Delay loop

Mov lw 0xff

Mov 0x78

Mov wf count

Move wf count 1

Loop 0

Dec fsz count 60

Goto loop 1

Return

Loop 1

Dec fsz count 1

Goto loop 1

Reset

Goto loop0

End
"

It is help with the initialisation and interrupt parts of the program that I need, as well as constructive comments on the above piece of 'code'.

I know that Jon Wilder created a basic template for the PIC16F887, located at this link:
http://www.electro-tech-online.com/...-i-o-setup-template-for-16f887-in-asm.120899/

That contains both an interrupt handler and an intialisation routine.
There is obviously a lot more code in there than what I require.

I could simply take both of these sections of code and add it to what I have created above but I was wondering if someone could possibly offer some advice on the code I have created above for a start.

I would also like to add that prior to this course I have no relevant programming experience, a small amount in C many many years ago is all.

Thanks in advance for any help

Logjkos

#### Logjkos

##### New Member
Hi Ian

Thank you for the reply, I shall have a look through the website.

Regards

#### Logjkos

##### New Member
Hi

I understand that some instrucrtions take 2 cycles, others one, so if I wanted my delay to last 1/10 of a second I wuold need to change increase the count on the first loop to 300?
Then:
The section labelled delay: 3 cycles
Loop 0: 1200 cycles
Loop 1: 1800 cycles

With a 31 KHz clock:
The above would work out at a little under 0.1 of a second.

#### jpanhalt

##### Well-Known Member
The code fragment you post is a bit hard to follow. For example, this code
Code:
Loop1
Decfsz      count1
Goto        Loop1
Reset
Goto        Loop0     ; won't get here
will never get to the instruction "goto loop0." Are you in fact using the 16F887? Does it have a "reset" instruction?

Maybe as you resvise the code, posting the complete revision will help avoid confusion. Also, please use code
tags, e.g., [code=ASM] insert code here[/code]

For your delays, you might find this code calculator, which uses nested loops, helpful: http://www.piclist.com/techref/piclist/codegen/delay.htm

John

#### Logjkos

##### New Member
John

Thank you for your reply, the problem is that I have absolutely no knowledge of programming in assembly, we had very little guidance from the three different lecturers that we had in the year.

So from the link you have given me, for a 31 KHz clock, I need to use the code:

; Delay = 0.1 seconds
; Clock frequency = 0.031 MHz

; Actual delay = 0.1 seconds = 775 cycles
; Error = 1.46692693834e-014 %

cblock
d1
endc

Delay
;769 cycles
movlw 0x00
movwf d1
Delay_0
decfsz d1, f
goto Delay_0

;2 cycles
goto \$+1

;4 cycles (including call)
return

How would I organise this with respect to initialisation and interrupt code?

As for the choice of PIC, due to problems we have had during the academic year this has not been defined in the assignemnt given.

Regards
(edited for typo)

#### Les Jones

##### Well-Known Member
Hi Logjkos,
As You are required to use interrupts I would suggest using a 16 bit counter if the chosen PIC has one (If not an 8 bit counter and it's prescaler.) You could use this to count CPU cycles and interrupt every o.1 seconds. As these counters only count up you would have to pre load them with the overflow value (65536 decimal.) minus the number of cycles you need to count. When the counter overflows it would cause an interrupt. The interupt service routine (ISR) Would just increment a counter that would count in 0.1 seconds. At the start of the event you would set this counter to zero. At the end of the event you would copy the contents of this counter to give you the time.You could be running code to display the current time value without having the CPU counting cycles.

Les.

Last edited:

#### Colin

##### Active Member
Why not use the internal 4MHz osc?

#### Logjkos

##### New Member
Hi Colin

I guess the only advantage of using the 4MHz clock is more accuracy with counting the actual time elapsed?
This is a genuine question as I have no knowledge on this subject at all.

Hi Les

The PIC we had to use was initially specified, however in the actual assignment we have been given there is no mention of the PIC we have to use.
Therefore using a PIC with a 16 bit counter is possible, it is just a matter of how to implement this and I have no clue as to how to do this.

#### Les Jones

##### Well-Known Member
Hi Logjkos,
Here is some code I wrote several years ago for a tachometer. You will be able to see the sort of thing that is required ti initialize a 16 bit counter module and see the sort of code required in the iterrupt routine to generate interrupts at the time period. (0.8616 ms in this case with a 20 Mhz clock) The interrupt routine in this case first has to decide if it is an interrupt on change or a timer interrupt. (You will probably only have to deal with timer interrupts) The interrupt routine starts at location "Interrupt" and the part of the routine that deals with the timer (Counter) is at loaction "Timer_Int"

Code:
;Rev counter LED display
;For Sig X3 Mill

;   Spindle gear on mill 34 teeth
;   To get 60 pulses (Revs per second to revs per minute) need gate time of 60/34 = 1.7647058823529411764705882352941 seconds gate time.
;
; Using port B interrupt on change we get 2 interrupts per tooth so now need gate time of 60/68 =0.88235294117647058823529411764706 seconds gate time.
; Version 00

;Gate_Count divides by 256 , multiplexing divides by 4 so total division here is 1024
;0.88235294117647058823529411764706/1024 = 861.67279411764705882352941176471 us

;Using 20 Mhz xtal system clock is 5 Mhz   (200ns)

;861.67279411764705882352941176471 / 0.2 = 4308.3639705882352941176470588235
;Using 4308 will be accurate enough. (4308 * 0.2 us = 861.6 us)

;PIC16f628A, set for 20.00MHz XTAL, WDT OFF, POR on
;Instruction time at 20MHZ is 0.2 us

list P=16f628
#include "p16f628a.inc"
;   __config _RC_OSC & _WDT_OFF & _PWRTE_ON
__config _HS_OSC & _WDT_OFF & _PWRTE_ON & _LVP_OFF

;written in MASM

;Preset    = 65536 - 4308 = 61228               (4308 * 0.2 us = 861.6 us)
;But 7 CPU cycles required to reload timer so value to load timer with will be 61235
Preset       EQU   d'61235'

delay1       EQU 0x20        ;Used in delay routine
delay2       EQU 0x21        ;Used in delay routine
delay3       EQU 0x22        ;Used in delay routine
Counter_Low   EQU 0x23   ;Counts incoming pulses           (In BCD )
Counter_High   EQU 0x24   ;Counts incoming pulses
Count_Low   EQU 0x25   ;Latched count to be displayed
Count_High   EQU 0x26   ;Latched count to be displayed
Dig_Count   EQU 0x27   ;Current digit being displayed  (Only bits 0 & 1 used)
Gate_Count   EQU 0x28   ;Final divide by 256 to give 0.88235 seconds gate time

LED_Port   EQU PORTA
Dig_Port   EQU PORTB

;==========================================================================
;
;I/O Pin useage

;   RA0       Output   BCD to 7447 IC LSB      (Pin 17)
;   RA1       Output   BCD to 7447 IC           (Pin 18)
;   RA2       Output   BCD to 7447 IC            (Pin 1)
;   RA3       Output   BCD to 7447 IC MSB   (Pin 2)
;   RA4/TOCK1   Input   Not yet used       (Pin 3)

;
;   RB0/INT       Output     Digit drive bit 4 LSB            (Pin 6)   Ative low
;   RB1       Output     Digit drive bit 3            (Pin 7)   Ative low
;   RB2       Output     Digit drive bit 2            (Pin 8)   Ative low
;   RB3       Output     Digit drive bit 1 MSB            (Pin 9)   Ative low

;   RB4       Input     Pulse input            (Pin 10)
;   RB5       Input     Not used           (Pin 11)
;   RB6       Input     Not used            (Pin 12)
;   RB7       Input        Not used           (Pin 13)
;

; define reset and interrupt vector start addresses

org   0         ; start at address 0000h
goto   INIT       ; normal service routines from Reset vector
org     4       ; interrupt vector 0004h, start interrupt routine here
goto   Interupt

; **********************************************************************************************

;   Initialize
;
ORG 0x0005         ;Start of program memory

DE      "V01 22/04/08"
;
INIT    BCF INTCON,GIE          ;turn off global interrupts
BTFSC INTCON,GIE    ;Confirm global interrupts off
goto INIT

movlw    0x07           ; activate PORTA for PC16F628 as digital
movwf   CMCON

BSF    STATUS,5   ;Select register bank 1
movlw   B'11110000'
movwf   TRISB         ;Configure port B0-B3 as outputs ports B4-B7 as inputs
movlw   B'00000000'
movwf   TRISA         ;Port A0-A4 as output
;

movlw   B'00000000'    ; Weak pull-up on PORTB (bit7=0)
movwf   OPTION_REG

movlw   b'00000001'   ;Enable timer1 interrupt
movwf   PIE1

BCF   STATUS,5   ;Select register bank 0

movlw   b'00000001'   ;Configure timer 1
movwf   T1CON       ;Prescaler 1:1, Timer stopped.

movlw   0x0F
movwf   Dig_Port   ;Set all digit drives off (Active low)
clrf   LED_Port   ;Set value to 0

clrf   Dig_Count   ;Clear digit count (Note only bits 0 & 1 are used)
clrf   Counter_Low
clrf   Counter_High
clrf   Count_Low
clrf   Count_High
clrf   Gate_Count
movlw   LOW Preset
movwf   TMR1L       ;Set division value
movlw   HIGH Preset
movwf   TMR1H

;Enable RB4 - RB7  interupt on change Bit 3
;turn on global interrupts

movlw   B'11001000'       ;Global interrupts, Peripheral interrupts, Port B interrupt on change.
movwf    INTCON          ;turn on required interrupts

;                   ------------------------

MAIN               ;Main program loop Just loop waiting for interupt.
nop
nop

nop
goto   MAIN

;   ***************************************************************************************************

Interupt

;Sort interupts

btfsc   PIR1,TMR1IF   ;TMR1 interupt
goto   Timer_Int

btfsc   INTCON,RBIF      ; Change to port b interrupt on change.
goto   Count_Int

retfie           ;Return if no interupt bit set SHOULD NOT GET HERE !!!!!!!!!!!!

;               Counter input interupt handler

Count_Int

bcf   INTCON,GIE       ;Disable interrupts
movf   PORTB,W           ;Read port just to clear the port change bits

incf    Counter_Low, F
movfw   Counter_Low
andlw   0x0F
sublw   0x0A           ;Units Check for overflow
btfss   STATUS,Z
goto    CntintDone
movfw   Counter_Low

andlw   0x0F0
movwf   Counter_Low
sublw   0xA0               ;tens  Check for overflow
btfss   STATUS,Z
goto    CntintDone
clrf    Counter_Low

incf    Counter_High,F
movfw   Counter_High
andlw   0x0F
sublw   0x0A           ;hundreds Check for overflow
btfss   STATUS,Z
goto    CntintDone
movfw   Counter_High

andlw   0x0F0
movwf   Counter_High
sublw   0xA0           ;Thousands  Check for overflow
btfss   STATUS,Z
goto    CntintDone
clrf    Counter_High

;Add code here to deal with overflow

CntintDone
bcf   INTCON,RBIF       ;Clear port b interrupt on change flag
bsf   INTCON,GIE       ;Enable interrupts

retfie

;                   ---------------------------
;               Timer interupt handler
;               Should come here every 0.8616 ms
Timer_Int
bcf   INTCON,GIE       ;Disable interrupts               0.2 us
bcf   T1CON,0           ;Stop timer                   0.2 us
movlw   LOW Preset       ;                       0.2 us
movwf   TMR1L           ;Preset counter to set division value       0.2 us
movlw   HIGH Preset       ;                       0.2 us
movwf   TMR1H           ;                       0.2 us
bsf   T1CON,0           ;Start timer                   0.2 us (7 cpu cycles)
bcf   PIR1,TMR1IF       ;Clear timer 1 interupt flag
call   Display_Next_Dig
bsf   INTCON,GIE       ;Enable interrupts

retfie

;           ---------------------------------------------------------

;               +++++++++++++++ Subroutines from here +++++++++++++++++++++++++

;*****************************************************************************
;
;   Function : Display next digit       (4 Digit multiplex display driver)
;   Increment Gate_Count every fourth time through this routine
;
;*****************************************************************************

;
Display_Next_Dig

movlw   0x0F
movwf   Dig_Port       ;All digit drives off (Active low) Bits 0 - 3

incf   Dig_Count,f
btfsc   Dig_Count,1
goto   ThreeorFour
OneorTwo
btfsc   Dig_Count,0
goto   Two
One
swapf   Count_High,w
movwf   LED_Port       ;Output it.
bcf   Dig_Port,3       ;Clear bit 3 (Enable digit position 1) (Most significant)
goto   Disp_End
Two
movf   Count_High,w
movwf   LED_Port       ;Output it. (Bits 0 to 3 of LED_Port
bcf   Dig_Port,2       ;Clear bit 2 (Enable digit position 2)
goto   Disp_End

ThreeorFour
btfsc   Dig_Count,0
goto   Four
Three
swapf   Count_Low,w
movwf   LED_Port       ;Output it.
bcf   Dig_Port,1       ;Clear bit 1 (Enable digit position 3)
goto   Disp_End
Four
movf   Count_Low,w
movwf   LED_Port       ;Output it.
bcf   Dig_Port,0       ;Clear bit 0 (Enable digit position 4)

incfsz   Gate_Count,f       ;Increment Gate_Count and test for overflow
Goto   Disp_End
movlw   0x0F
movwf   Dig_Port       ;All digit drives off (Active low) Blank display

;Move count to display latches
movfw   Counter_Low
movwf   Count_Low
Movfw   Counter_High
movwf   Count_High
;Clear counters
clrf   Counter_Low
clrf   Counter_High

Disp_End
return

END               ;final line. This line must be retained.
This was the simplest program I had written using interrupts. I hope this helps you get started with your program.

Les.

#### Logjkos

##### New Member
Les

Thank you for the above, so looking at the interrupt code first, what is happening with the following hex numbers?:
0x0F
0x0A

0x0F0
0x010
and
0xA0

Thanks

#### Logjkos

##### New Member
Les

But from what you said earlier though I would only need to concern myself about the timer interrupt which would be under the section :
Timer interupt handler
However how and where have you have defined the interrupt to be every 0.2 uS defined?

#### Les Jones

##### Well-Known Member
It is not the code to do exactly what you require. It is just to give you an idea about initializing the PIC so it will do what you want and the layout of the interrupt service routine. We do not give direct answers to homework questions. Just guidance.
You only need a modified version of the code after "Timer_Int" The first thing it does is to disable interrrupts and stop the counter. It then loads TIMER1 with 16 bit value which has been given the name "Preset" This has to be loaded in two steps as a PIC is an 8 bit micro. So it first loads the low byte and then the high byte of the 16 bit timer. (You could load the high byte first. it would make no difference.) The actual value of "Preset" was defined near the start of the program. This is where is is given a value
;Preset = 65536 - 4308 = 61228 (4308 * 0.2 us = 861.6 us)
;But 7 CPU cycles required to reload timer so value to load timer with will be 61235
Preset EQU d'61235'
The first two of the three lines are just comments to explain how it is calculated.
The interrupt does not occure every 0.2 uS TIMER1 increments every 0.2 uS. The interrupt occures when TIMER1 overflows from 0xFFFF back to zero. This is why you need to subtract the number of counts from 65536 decimal (= 0xFFFF) The 0.2 uS is because I have used a 20 Mhz crystal (One instruction cycle is 4 clock cycles.) You will also not need the line
call Display_Next_Dig
I am using this to multiplex a 4 digit 7 segment display. (To display the RPM value)

The HEX numbers that you ask about at the start if the interrupt routine are to do with counting input pulsed from a gear tooth sensor. (You will not requre that part of the code.) This counting is done in BCD. The 0x0F and the 0xF0 are used to mask for the upper or lower nibble of a byte. (Two BCD digits stored in a byte) The 0x0A (10 decimal) is use to detect when a BCD digit oveflows fro 9 to zero. The 0x10 is added to increment a BCD digit when it is stored in the hugh nibble of the byte.

Les.

Last edited:

#### Logjkos

##### New Member
Hi Les

Thank you for your help but I am understanding very little of this.

Ok, so in order to produce what I need, I will have to modify the following code:

Timer_Int
bcf INTCON,GIE ;Disable interrupts 0.2 us
bcf T1CON,0 ;Stop timer 0.2 us
movlw LOW Preset ; 0.2 us
movwf TMR1L ;Preset counter to set division value 0.2 us
movlw HIGH Preset ; 0.2 us
movwf TMR1H ; 0.2 us
bsf T1CON,0 ;Start timer 0.2 us (7 cpu cycles)
bcf PIR1,TMR1IF ;Clear timer 1 interupt flag
call Display_Next_Dig
bsf INTCON,GIE ;Enable interrupts

retfie

So in your code Preset is set to equal 61235 (decimal) at the start of the code which equates to 0.8616 mS, the maths at the start is one of the few things I can understand what is going on.

So if I am using a 31 KHz oscillator and my timer /delay loop is lasting, in theory for one tenth of a second, before looping back on itself, how does this equate to what you have provided above?

#### Colin

##### Active Member
You are turning a very simple thing into something comlplex.
If you use the 4MHz inbuilt osc, each instruction wilt take 1uS
You only use the 32kHz osc for very low current applications

#### jpanhalt

##### Well-Known Member
We don't even know what chip he is using. Is it a basic, mid-range, or enhanced mid-range? (I think most of us are assuming a mid-range with internal oscillator.)

I agree, run the system oscillator at 4 MHz, if it has an internal oscillator. Then, you can get pretty close to 0.1 sec ticks with TMR1 or use another oscillator for 32,768 Hz. The op's new code talks about micoseconds. His original post needed 100-ms intervals:
OP said:
That the time recorded should be reasonably accurate, 10ths of a second should be sufficient for this.
I don't have clue as to why he seems to want 2 us intervals
Code:
movlw LOW Preset ; 0.2 us
movwf TMR1L ;Preset counter to set division value 0.2 us
movlw HIGH Preset ; 0.2 us
movwf TMR1H ; 0.2 us
What is the literal (i.e., "Preset") in WREG? We seem to be getting only fragments of his code despite my request that he post his entire revisions. With a clock frequency of 4 MHz, it seems "unlikely" that any "Preset" would give a 2 us 0.2us "tick."

Of course, 0.1 second intervals for 10 minutes = 6000 datapoints (0x1770). He does not seem to have addressed how that number will be stored.

So, I am a bit lost in where he is headed but take solice in this:
OP said:
The only problem that I have with this is that I will not learn very much if anything at all.
John

Fixed typo, changed 2 us to 0.2

Last edited:

#### Logjkos

##### New Member
Hi

Thank you for your feedback, we have had very little if any programming support from any of the three lecturers that we have had throughout the year, and everyone on the course had no prior programming experience, it has been a bit of a let down to put it mildly.

The use of the 4MHz oscillator sounds perfectly reasonable now that someone has explained it to me why the 4MHZ oscillator is prefereable, I did ask this question earlier but no one had replied to it.

As for the PIC Microchip that we are to use we have not been told which one to use, it is upto us to decide which one we want to use.

As for code I have as yet only got what I have already posted, i.e. practically nothing.

The section of code with the 0.2 uS is simply a copy and paste of that section of code that Les had posted earlier, not code that I have created, I still intend to have a timer incrementing 0.1 S.

What I would have liked to have seen in our first assignment is a complete program, that:
Is uncomented
Is undocumented
Contains bugs

We could then go away, learn how to use MPLAB to compile and run the software (this has not been covered in lectures/class so I m unsure how to do this).
Research each of the bugs and fix them (we could have gained a better understanding of test procedures and methods)
Research the lines of code to comment them (learn about code in a practical way, better than simply talking about TRISA and B)
Theis would then allow us to document the software.

This however was not the case, c'est la vie.

#### Pommie

##### Well-Known Member
I'll add a little about clock selection. In your original post you stated you needed an accuracy of 0.1 seconds in 10 minutes. That is an accuracy of 1 in 6000 - the internal oscillator is only accurate to 1 in 100 - looks like you need a crystal.

As for timing, I would avoid interrupts due to your lack of experience.

My approach would be,
Assuming mid range chip - 16F628 with a 4MHz crystal as shown in the datasheet. Crystal stuff on page 97.
I would use timer 2 as this is nice and simple. Timer 2 gets clocked a 1/4 of the crystal speed so at 1MHz.
It has a prescaler to divide that clock - I suggest setting it to 4. Clock now at 250kHz.
It has a period register (PR2) which resets it at a predetermined count. Setting this to 249 will reset it every 250 counts - we include zero in the count. Timer two now gets reset every 1000 cycles = 1mS.
Counting every mS is a little fast - luckily timer 2 has a postscaler - set that to 10 and we are counting 100th of a second.
Checking the datasheet, we need the following setup,

T2CON = 0b01001101 - see datasheet page 53 - 55 in pdf.
PR2=249

Now, timer 2 will run along quiet happy and everytime it resets it will set a bit called TMR2IF (bit 1 of PIR1).
You can test this bit and when it's set, increment a counter, clear the bit and wait for it to be set again.

See, two register to set and you have your basic timer.

Good luck.

Mike.

#### Les Jones

##### Well-Known Member
Hi jpanhalt,
I think the confusion is because I posted a complete program for a tachometer in post #10. I was just using the initialisaton of the timer module and the end of the interrupt routine as an example of how to generate timed interupts. The 16 bit constant "Preset" is defined at the start of the code (Post #10) The comments containing the 0.2 uS (Instruction time with the 20 Mhz crystal I was using.) were put in when I was writing the program for working out the time taken to load the preset value and restart the timer. In my next post I will answer Logjkos's question from post #14 . This should remove the confusion.

Les.

#### Les Jones

##### Well-Known Member
Hi Logjkos,
You have already done most of the calculation requred in post #6 You worked out that with your 31 Khz clock frequency that you needed 775 instruction cycles fot 0.1 seconds. (I agree with this.) As in the timer interrupt code there are 7 instructions between the interrupt occuring and the counter being restarted we only now need to count 775 - 7 = 768 instruction cycles. So as the 16 bit counter will overflow at a count 0f 65536 so it need to be preset with 65536 - 768 = 64768 (0xFD00 HEX) The code will become.
Code:
Timer_interrupt:
;               Timer interupt handler
;               Should come here every 100 ms
Timer_Int
bcf   INTCON,GIE       ;Disable interrupts               129 uS
bcf   T1CON,0           ;Stop timer                   129 uS
movlw   0x00           ;                       129 uS
movwf   TMR1L           ;Preset counter to set division value       129 uS
movlw   0xFD           ;                       129 uS
movwf   TMR1H           ;                       129 uS
bsf   T1CON,0           ;Start timer                   129 uS (7 cpu cycles)
bcf   PIR1,TMR1IF       ;Clear timer 1 interupt flag
;
;       Enter your code here to increment the 0.1 second counter
;
;
;
bsf   INTCON,GIE       ;Enable interrupts
retfie
I have not used the constsnt "Preset" instead I have loaded the hex values directly. As an exercise to your understanding you could try to modify this bit of code for use with a 4 Mhz clock frequency. (Which is a much better choice. I thought that you had been forced to us a 31 Khz clock to make it more of a challenge.)
What you program is required to do is very simialr the the tachometer example I posted. The only difference is the the tachometer program counts input pulses for a defined time. Your program counts timing pulses between two events. (The start and end of the opperation.) Other parts of the tachometer code could be used as example for displaying the result on a multiplexed 7 segment display. Doing the counting in decimal (Rather than binary and then converting to decimal.) The tachometer uses a 7447N IC to convert the BCD to the signals to drive the 7 segment displays. This function could be done with a lookup table in the code to save the use of this IC.

Les.

Status
Not open for further replies.