Continue to Site

Welcome to our site!

Electro Tech is an online community (with over 170,000 members) who enjoy talking about and building electronic circuits, projects and gadgets. To participate you need to register. Registration is free. Click here to register now.

  • Welcome to our site! Electro Tech is an online community (with over 170,000 members) who enjoy talking about and building electronic circuits, projects and gadgets. To participate you need to register. Registration is free. Click here to register now.

Basic Digital I/O Setup Template for 16F887 in ASM

Status
Not open for further replies.

Jon Wilder

Active Member
For those who are new to the 16F887, this is an example ASM code template complete with initialization routine, interrupt handler placement, as well as a fixed 100mS and a variable delay routine. Use it and make changes as you see fit. As shown the config bits are set up for an 8MHz internal oscillator. For the sake of the example, I've placed the minimum needed instructions to set up ports A, B and E as digital output with ports C and D as digital input.

I have also included data EEPROM write, read and verify subroutines as well.

This init code has been fully tested and debugged. This particular code as shown, while non-functional until you add in your code, has also been test assembled and it assembled with no errors, messages or warnings.

Code:
;*********************************************************************************************************
;**													**
;**			Basic Digital I/O Setup Template for 16F887					**
;**			By: Jon Wilder									**
;**			Date: 8/9/2011									**
;**													**
;*********************************************************************************************************


;*********************************************************************************************************
;**													**
;**				Header Information							**
;**													**
;*********************************************************************************************************

		list		p=16F887, r=dec, w=-302
		include		<P16F887.INC>
		__config	_CONFIG1,b'1110000011100100'
		__config	_CONFIG2,b'1111100011111111'

;config word 1_____                                  ___   __         _____
;|  -  |  -  |DEBUG|FCMEN|IESO | LVP |BOREN1|BOREN0| CPD | CP  |MCLRE|PWRTE|WDTE |FOSC2|FOSC1|FOSC0|
;|  1  |  1  |  1  |  0  |  0  |  0  |  0   |  0   |  1  |  1  |  1  |  0  |  0  |  1  |  0  |  0  |

;config word 2
;|  -  |  -  |  -  |  -  |  -  |WRT0 | WRT1 |BOR4V |  -  |  -  |  -  |  -  |  -  |  -  |  -  |  -  |
;|  1  |  1  |  1  |  1  |  1  |  0  |  0   |  0   |  1  |  1  |  1  |  1  |  1  |  1  |  1  |  1  |

;Processor is PIC16F887 (p=16F887)
;Default Radix is Decimal (r=dec)
;Suppress all bank select assembler messages (w=-302)

;In Circuit Debug Mode Off (DEBUG)
;Fail Safe Clock Monitor Bit Off (FCMEN)
;Internal External Switchover Bit Off (IESO)
;Low Voltage Programming Off (LVP)
;Brown Out Reset Off (BOREN1-BOREN0)
;Data Code Protection Off (CPD)
;Program Code Protection Off (CP)
;RE3 is External Master Clear (MCLRE)
;Power Up Timer On (PWRTE)
;Watchdog Timer Off (WDTE)
;Internal Oscillator RA6-RA7 digital I/O

;*********************************************************************************************************
;**													**
;**				Variable Declarations							**
;**													**
;*********************************************************************************************************

;declare variables here

		cblock		0x20
				COUNT1		;delay counter 1
				COUNT2		;delay counter 2
                                COUNT3          ;delay counter 3
				<Delete this line, then and add more variable names if you need more>
		endc

;software stack

		cblock		0x70
				W_TEMP		;software stack for interrupt handler
				STATUS_TEMP	;software stack for interrupt handler
				PCLATH_TEMP	;software stack for interrupt handler
				DATA_EE_ADDR	;data EEPROM address buffer
				DATA_EE_DATA	;data EEPROM data buffer
		endc


;*********************************************************************************************************
;**													**
;**			    Place Non-Relocatable Code							**
;**													**
;*********************************************************************************************************

		org		0x000		;reset vector address
		goto		START
	
		org		0x004		;interrupt vector address
;*********************************************************************************************************
;**													**
;**				Interrupt Handler							**
;**													**
;*********************************************************************************************************

ISR		;software stack		
		movwf		W_TEMP		;back up W
		swapf		STATUS,W	;back up STATUS
		movwf		STATUS_TEMP
		banksel		0x00		;bank 0
		movfw		PCLATH		;back up PCLATH
		movwf		PCLATH_TEMP

;		<Delete this line and place rest of Interrupt Handler code here>

ISRExit		;software stack
		movfw		PCLATH_TEMP	;restore PCLATH
		movwf		PCLATH
		swapf		STATUS,W	;restore STATUS
		movwf		STATUS
		swapf		W_TEMP,F	;restore W
		swapf		W_TEMP,W
		retfie				;done with interrupt



;*********************************************************************************************************
;**													**
;**				Initialization Routine							**
;**													**
;*********************************************************************************************************

START		clrf		INTCON		;disable interrupts
		banksel		OSCCON		;bank 1
		movlw		b'01110000'	;8MHz Internal Oscillator
		movwf		OSCCON
		btfss		OSCCON,HTS	;is oscillator stable?
		goto		$-1		;no, check again
		banksel		ANSEL		;yes, bank 3
		movlw		b'00000000'	;all ports digital I/O
		movwf		ANSEL
		movwf		ANSELH
		banksel		PORTA		;bank 0
		clrf		PORTA		;initialize all ports
		clrf		PORTB
		clrf		PORTC
		clrf		PORTD
		clrf		PORTE
		banksel		TRISA		;bank 1
		movlw		b'00000000'	;ports A, B and E outputs
		movwf		TRISA
		movwf		TRISB
		movwf		TRISE
		movlw		b'11111111'	;ports C and D inputs
		movwf		TRISC
		movwf		TRISD
		banksel		PORTA		;bank 0

;*********************************************************************************************************
;**													**
;**				     Main Program							**
;**													**
;*********************************************************************************************************

MAIN		<Delete this line and place the first line of your main program code here>
		goto		MAIN		;last line of code (can change where it jumps to)

;*********************************************************************************************************
;**													**
;**				     Delay Routines							**
;**													**
;*********************************************************************************************************

;fixed 100mS delay

Delay_100mS	movlw		0xFF		;init delay counter 1 and 2
		movwf		COUNT1
		movwf		COUNT2
		decfsz		COUNT1,F	;decrement counter 1 to 0
		goto		$-1		;continue decrementing counter 1 if counter 1 > 0
		decfsz		COUNT2,F	;decrement counter 2
		goto		$-3		;continue decrementing counter 1 if counter 2 > 0
		return				;done

;variable delay - load a value into W prior to calling this delay for a variable delay in 100mS increments
;value of 0x05 yields a 500mS delay

Delay		movwf		COUNT3		;init delay counter 2
		call		Delay_100mS	;run 100mS delay
		decfsz		COUNT3,F	;decrement counter 2
		goto		$-2		;jump back to run 100mS delay if counter 2 > 0
		return				;done

;*********************************************************************************************************
;**													**
;**				Other Subroutines							**
;**													**
;*********************************************************************************************************

;write address and data to write should already be loaded into EEADR and EEDATA buffers
;prior to calling write

EEWrite		bcf		INTCON,GIE	;disable interrupts
		banksel		EEADR		;bank 3
		movfw		DATA_EE_ADDR	;move contents of EEADR buffer to W
		movwf		EEADR		;load address into EEADR
		movfw		DATA_EE_DATA	;move contents of EEDATA buffer to W
		movwf		EEDAT		;load data into EEDATA
		banksel		EECON1		;bank 4
		bcf		EECON1,EEPGD	;select data EEPROM for write
		bsf		EECON1,WREN	;write mode enable
		movlw		0x55		;unlock codes
		movwf		EECON2
		movlw		0xAA
		movwf		EECON2
		bsf		EECON1,WR	;initiate write
		btfsc		EECON1,WR	;is write complete?
		goto		$-1		;no, check again
		bcf		EECON1,WREN	;yes, disable write mode
		banksel		PIR2		;bank 0
		bcf		PIR2,EEIF	;clear EEPROM write interrupt flag
		bsf		INTCON,GIE	;enable unmasked interrupts
		

		return				;done

;read address should already be loaded into EEADR buffer prior to calling read

EERead		bcf		INTCON,GIE	;disable interrupts
		banksel		EEADR		;bank 3
		movfw		DATA_EE_ADDR	;move read address to W
		movwf		EEADR		;move read address to EEADR
		banksel		EECON1		;bank 4
		bcf		EECON1,EEPGD	;select data EEPROM
		bsf		EECON1,RD	;initiate read
		banksel		EEDAT		;bank 3
		movfw		EEDAT		;move EEPROM data into W
		movwf		DATA_EE_DATA	;move data to EEPROM data buffer
		banksel		0x00		;bank 0
		bsf		INTCON,GIE	;enable all unmasked interrupts
		return				;done

;verify address should already be loaded into EEADR prior to calling verify

EEVerify	bcf		INTCON,GIE	;disable interrupts
		banksel		EEADR		;bank 3
		movfw		DATA_EE_ADDR	;move verify address to W
		movwf		EEADR		;move verify address to EEADR
		banksel		EECON1		;bank 4
		bcf		EECON1,EEPGD	;select data EEPROM
		bsf		EECON1,RD	;initiate read
		banksel		EEDATA		;bank 3
		movfw		EEDATA		;move EEPROM data into W
		xorwf		EEDATA,W	;compare to data in EEDATA
		btfss		STATUS,Z	;is data in W equal to data in EEDATA?
		goto		EEErr		;no, set EEPROM error
		banksel		0x00		;bank 0
		bsf		INTCON,GIE	;enable unmasked interrupts
		return				;done


EEErr		<Delete this line and place EEPROM Verify error code here (write it yourself)>

		<Delete this line and place all subroutine codes here>

		end				;end of file
 
Last edited:
Nice effort Jon.

I'm on my way out the door so perhaps someone else will jump in and explain why the Microchip template uses the shared memory region at 0x70..0x7F for interrupt context variables.

Regards, Mike
 
Nice effort Jon.

I'm on my way out the door so perhaps someone else will jump in and explain why the Microchip template uses the shared memory region at 0x70..0x7F for interrupt context variables.

Regards, Mike

More than likely because those registers are mapped in all 4 banks so you can always access them no matter what bank you're in. I always forget about those registers.

In any event I've changed the code above to move the software stack down to those registers.
 
Last edited:
Nice effort Jon.

I'm on my way out the door so perhaps someone else will jump in and explain why the Microchip template uses the shared memory region at 0x70..0x7F for interrupt context variables.

As Jon says, so they can be accessed from any bank. It's usual to have an instruction in the ISR to reset to bank 0 - the original bank is restored on exit of course.

Yet another reason to move the enhanced 16F1627 series - automatic context saving :D
 
Heh...a writable stack would also be a nice convenience as you'd be able to push those values onto the stack upon entering the interrupt, then pop them off the stack upon returning like you'd do on the MCS-51 family. Then again vectored interrupts would also be nice (yes yes I know...migrate to the 18F series).
 
Last edited:
Heh...a writable stack would also be a nice convenience as you'd be able to push those values onto the stack upon entering the interrupt, then pop them off the stack upon returning like you'd do on the MCS-51 family.

Rather more complicated than the 16F1267 where it just saves and restores them automatically :D
 
Nice template, but I think that there is a bit of problem here:

The interupt exit code does not restore the STATUS or W
Code:
ISRExit     ;software stack
  movfw    PCLATH_TEMP       ;restore PCLATH
  movwf    PCLATH
  swapf     STATUS,W            ;restore STATUS
  movwf    STATUS
  movfw    W_TEMP               ;restore W
  retfie                                ;done with interrupt

I think it should be a bit more like this:
Code:
  ISRExit                                 ;software stack
  movfw    PCLATH_TEMP           ;restore PCLATH
  movwf    PCLATH
  swapf    STATUS_TEMP, W      ;restore the W and STATUS registers
  movwf   STATUS
  swapf    W_TEMP, W
  retfie                                ;done with interrupt

JimB
 
Last edited:
Actually we are both incorrect. As per Microchip, it should go like this -

Code:
ISR		;software stack		
		movwf		W_TEMP		;back up W
		swapf		STATUS,W	;back up STATUS
		movwf		STATUS_TEMP
		movfw		PCLATH		;back up PCLATH
		movwf		PCLATH_TEMP

		<Delete this line and place rest of Interrupt Handler code here>

ISRExit		;software stack
		movfw		PCLATH_TEMP	;restore PCLATH
		movwf		PCLATH
		swapf		STATUS_TEMP,W	;restore STATUS
		movwf		STATUS
		swapf		W_TEMP,F	;restore W
		swapf		W_TEMP,W	
		retfie				;done with interrupt

Thanks for the catch. It has been corrected in the template above. The W and STATUS backup/restore as shown above is as per Section 8 of the Microchip PIC Midrange MCU Family reference manual.
 
Last edited:
Jon
Yes I agree with your new version (from Microchip), I have actually been using that in some code which I have been playing with this afternoon.
I was comparing your code fragment with mine and saw your faux pas and then looked at mine and thought "there is a redundant line in there", forgetting that the SWAPF instruction swaps nibbles and assuming that it swaps bytes.
I then deleted the "redundant" line and posted the fragment here as a possible improvement.

Perhaps I should put the redundant line back in my code, it would probably explain some of the odd glitches I have been having!!

JimB
 
The swapf instruction swaps NIBBLES...not bytes. In your code segment, W only gets swapped once whereas in mine it gets swapped twice so that by the time it ends up in W it's back to the original value.

Say the value in W_TEMP is 0xCA. After the first swapf instruction (swapf W_TEMP,F), the nibbles get swapped but stay in the source file. So the file W_TEMP now contains value 0xAC. Then on the next swapf (swapf W_TEMP,W), it gets swapped again but into W this time so that W now contains the original value of 0xCA.

Without the second swapf instruction, the nibbles would stay flipped and W would contain the flipped value of 0xAC instead of its original value of 0xCA.

Make sense?
 
Last edited:
Yup, no problem.
As the man said in the film "I've got my mind right boss".

JimB
 
Made some corrections and improvements to the template. I had forgotten that EECON1 and EECON2 were in different banks than EEDAT and EEADR so that has been corrected. Also, the DATA_EE_ADDR and DATA_EE_DATA buffer registers have been relocated to common RAM space to reduce the required amount of bank selecting.

I've just tested both the EEWrite and EERead routines on a 16F887 and both work as advertised.
 
Status
Not open for further replies.

Latest threads

New Articles From Microcontroller Tips

Back
Top