Marksphotos main

Electronics main


PIC16F628A serial controlled PWM generator and frequency counter


I recommend Realterm to connect to the PIC - the settings required are 9600 baud, 1 stop bit, no parity and the number of columns needs increasing from the default to get everything on one line.

PWM Generator

It should be able to generate PWM from 244 Hz to 1MHz with up to 10 bit resolution on the PWM.
Just program the PIC, wire it up to a MAX232, ST232 or similar and connect to your serial port.
Output is on pin 9 and MCLR needs holding high with a resistor.
Check the PIC16F628A datasheet for formulas to calculate the actual frequency and duty ratio.

Frequency counter

Frequency is in Hz (just a count for 1/4 second so only 4Hz accuracy which also means low frequencies can't be measured accurately or at all). Measured duty cycle is 0-255, rather than % because that is more useful to me. The duty cycle is calculated by sampling the input 16384 times during the quarter second so won't be very accurate especially at some frequencies. Realterm works fine for this, I haven't tested anything else. Pins RB6 and RB5 are connected together and used as the input. Frequency works fine with a 50% duty cycle up to 1MHz (I haven't got anything faster handy to test). In theory it should go up to 16MHz. Duty cycle measurements are not good at high frequencies but seem to be good below 100kHz.

Schematic and screenshot.





The source code in assembler(.asm)




        LIST P=16F628, R=DEC  

        #include "P16F628.INC"  

        __config  _INTRC_OSC_NOCLKOUT & _LVP_OFF & _WDT_OFF & _PWRTE_ON & _BODEN_ON

        CBLOCK 0x20             ;variables
    
	count
	count2

	eetemp
	PORTAbuffer
	PORTBbuffer
	iTMR0countH
	iTMR0countM
	iTMR0countL				;TMR0 value
	TMPH
	TMPL
	TMR1MSB
	period
	dutysamplecountH
	dutysamplecountL
	duty_H
	duty_L
	indutyH					;samples of input to get avg duty
	indutyM
	indutyL
	prescale
	usartdone
	freqdone
	usartrxcount
	usartrxbuf:10
	digitcount
	setstate					;0 
	H_byte
	M_byte
	L_byte
	H_temp
	L_temp
	R0
	R1
	R2
	R3
	temp
        ENDC

	CBLOCK 0x70
	W_TEMP
	STATUS_TEMP
	ENDC

	org 0x2100		;message store
	;length, text
msgintro
	de D'24',"www.marksphotos.info/fg",0x0D
msgperiod
	de d'14',"Period(0-255)="
msgduty
	de d'13',"Duty(0-1023)="
msgprescale
	de d'20',"Prescale(1,4 or 16)="
msgspace
	de 0x01," "
msginvalid
	de 0x08,"INVALID",0x0D
msgfreqin
	de 0x08,"Freq IN="
msgdutyin
	de 0x08,"Duty IN="


    ORG    0x000           
	goto init


	org 0x004	;interrupt goes here
	MOVWF W_TEMP ; Copy W to TEMP register,
	SWAPF STATUS, W ; Swap status to be saved into W
	MOVWF STATUS_TEMP ; Save status to STATUS_TEMP register
	BCF STATUS, RP0				;bank 0
	btfss INTCON, T0IF				;timer0 interrupt flag
	goto notTMR0
;TMR0 interrupt
	decfsz iTMR0countM, F
	goto TMR0a
	decfsz iTMR0countH, F
	goto TMR0a
;time up
	bcf T1CON, TMR1ON
	; All interrupts are disabled
	MOVF TMR1H, W ;Read high byte
	MOVWF TMPH ;
	MOVF TMR1L, W ;Read low byte
	MOVWF TMPL ;
	MOVF TMR1H, W ;Read high byte
	SUBWF TMPH, W ;Sub 1st read
	; with 2nd read
	BTFSC STATUS,Z ;Is result = 0
	GOTO CONTINUE ;Good 16-bit read
	;
	; TMR1L may have rolled over between the read
	; of the high and low bytes. Reading the high
	; and low bytes now will read a good value.
	;
	MOVF TMR1H, W ;Read high byte
	MOVWF TMPH ;
	MOVF TMR1L, W ;Read low byte
	MOVWF TMPL ;
	; Re-enable the Interrupts (if required)
CONTINUE ;Continue with your code
	movlw 0x04
	movwf iTMR0countH
	movlw 0xD1
	movwf iTMR0countM
	movlw 0x85
	movwf TMR0
	clrf TMR1H
	clrf TMR1L
	bcf PIR1, TMR1IF			;don't know why
	bsf T1CON, TMR1ON
	bsf freqdone, 0



TMR0a
	bcf INTCON, T0IF			;timer0 interrupt flag
;	goto endint

notTMR0

	btfss PIR1, TMR1IF
	goto notTMR1
;TMR1 interrupt
	bcf PIR1, TMR1IF
	incf TMR1MSB, F
;	goto endint

notTMR1
	btfss PIR1, 5				;rcif
	goto endint

	bcf STATUS, RP0
	movlw usartrxbuf
	addwf usartrxcount, W
	movwf FSR
	movf RCREG, W
	movwf INDF
	incf usartrxcount, F
	sublw 0x0D
	btfsc STATUS, Z
	incf usartdone, F
	sublw 0x03
	btfsc STATUS, Z
	incf usartdone, F		

notusart
	
endint
	 ; should configure Bank as required
	 ;
	SWAPF STATUS_TEMP,W 	; Swap nibbles in STATUS_TEMP register
	; and place result into W
	MOVWF STATUS 			; Move W into STATUS register
	; (sets bank to original state)
	SWAPF W_TEMP, F 		; Swap nibbles in W_TEMP and place result in W_TEMP
	SWAPF W_TEMP, W 		; Swap nibbles in W_TEMP and place result into W

	retfie


init

	CLRWDT ;Clear WDT and
	;prescaler
	BSF STATUS, RP0
	MOVLW b'11011111' ;Select TMR0, and internal clock source
	MOVWF OPTION_REG
	BCF STATUS, RP0


	movlw 7
    movwf CMCON             ;comparators off
    movlw b'00000000'       ;porta outputs
    movwf PORTA

    movlw b'00000100'       ; RB2(TX)=1 others are 0
    movwf PORTB

    bsf STATUS,RP0          ; page 1

	movlw B'00100001'
	movwf PIE1				;USART receive and TMR1 int enabled	

    movlw 0x00
    movwf TRISA             ; portA all pins output

    movlw b'11110010'       ; RB7-RB4 and RB1(RX)=input, others output
    movwf TRISB




	movlw 0x19      			; 9600 baud, no Parity, 1 stop bit        
    movwf SPBRG
    movlw b'00100100'       
    movwf TXSTA  
    bcf STATUS,RP0
    movlw b'10010000'       
    movwf RCSTA
    clrf count
wait  
	decfsz count,F			;pause just in case
    goto wait

    movf RCREG,W
    movf RCREG,W
    movf RCREG,W            ; flush receive buffer
	bcf RCSTA, CREN				;might not be required IRL
	bsf RCSTA, CREN				;reset rx

	movlw b'11100000'		;timer0, peripherals and global interrupts on
	movwf INTCON
	call clrusartbuf
	clrf setstate
	clrf usartdone
	clrf period
	clrf duty_H
	clrf duty_L
	clrf prescale
	incf prescale, F			;prescale = 1
	movlw 0x04
	movwf iTMR0countH
	movlw 0xD1
	movwf iTMR0countM
	movlw 0x85
	movwf TMR0
	movlw d'64'
	movwf dutysamplecountH
	clrf dutysamplecountL
	movlw b'00001111'
	movwf T1CON
	bsf STATUS, RP0					;setup PWM
	clrf PR2
	bcf STATUS, RP0
	clrf CCPR1L
	movlw b'00001100'
	movwf CCP1CON
	bsf STATUS, RP0
	bcf TRISB, 3
	bcf STATUS, RP0
	movlw b'00000100'
	movwf T2CON

	movlw LOW msgintro
	call message 



loop

	btfsc usartdone, 0
	goto changesettings
	btfsc freqdone, 0
	goto display				;showfreq

	decfsz dutysamplecountL, F
	goto lskp1
	decfsz dutysamplecountH, F
	goto lskp1
	movlw 0x01
	movwf dutysamplecountH		;messy - makes sure code returns here
	movwf dutysamplecountL
	goto loop

lskp1

	btfss PORTB, 5
	goto lskp0					;to slow it down
	incfsz indutyL, F
	goto loop
	incfsz indutyM, F
	goto loop
	incf indutyH, F
lskp0
	goto loop



changesettings
	clrf usartdone
	decf usartrxcount, W
	btfsc STATUS,Z
	goto bufferparsed
	movlw 0x05
	movwf digitcount
	bcf INTCON, GIE				;protect FSR
	clrf R0
	clrf R1
	clrf R2
	movlw usartrxbuf			;location of usartbuf
	addwf usartrxcount, W		;last char (probably LF or CR)
	movwf FSR
testnumber
	movlw 0x30					;"0"
	subwf INDF, W				;carry clear if >= "0"
	btfss STATUS, C
	goto lowerthan0
higherthan0
	movlw 0x3A
	subwf INDF, W				;carry clear if > "9"
	btfsc STATUS, C
	goto higherthan9
;it's a number
	movlw 0x30	
	subwf INDF, F				;from ASCII to BCD
	movf digitcount, W
	addwf PCL, F
	nop
	goto digit1
	goto digit2
	goto digit3
	goto digit4
	goto digit5


digit1
	movf INDF, W
	movwf R0
	goto decdigit
digit2
	swapf INDF, W
	iorwf R1, F
	goto decdigit
digit3
	movf INDF, W
	movwf R1
	goto decdigit
digit4
	swapf INDF, W
	iorwf R2, F
	goto decdigit
digit5
	movf INDF, W
	movwf R2
	goto decdigit


decdigit
	decfsz digitcount, F
	goto nextchar
;something

lowerthan0
higherthan9



nextchar
	decf FSR, F
	movlw usartrxbuf			;location of file
	subwf FSR, W
	btfsc STATUS, C				;check if we got past the start of the buffer
	goto testnumber
	bsf INTCON, GIE
bufferparsed
	call BCDtoB
	movf setstate, W
	addwf PCL, F
	goto setperiod				;state=0
	goto setduty				;state=1
	goto setprescale			;state=2

setperiod				;state=0
	decf usartrxcount, W			;check for no characters recieved
	btfsc STATUS,Z					
	goto skp1						;don't update

	movf H_byte, W
	andlw b'11111111'				;is period >255?
	btfsc STATUS, Z
	goto periodOK
	movlw LOW msginvalid
	call message
	goto skp1
periodOK
	clrf H_byte
	movf L_byte, W
	movwf period
	bsf STATUS, RP0
	movwf PR2
	bcf STATUS, RP0

skp1
	call clrusartbuf
	incf setstate, F
	goto loop
setduty				;state=1
	decf usartrxcount, W			;check for no characters recieved
	btfsc STATUS,Z					
	goto skp2						;don't update

	movf H_byte, W
	andlw b'11111100'				;is duty >1023?
	btfsc STATUS, Z
	goto dutyOK
	movlw LOW msginvalid
	call message
	goto skp2
dutyOK
	movf H_byte, W
	movwf duty_H
	movf L_byte, W
	movwf duty_L
;movwf CCPR1L
;movlw b'00001100'

;H_byte:L_byte > CCPR1L+CCP1CON<5:4>
	clrf temp
	bcf STATUS,C
	rrf duty_H, F
	rrf duty_L, F
	rrf temp, F
	rrf duty_H, F
	rrf duty_L, F					;duty_L now contains highest 8 bits
	rrf temp, F						;temp now contains low 2 bits in <7:6>
	rrf temp, F
	rrf temp, F						;temp now contains low 2 bits in <5:4>
									;phew
	
	movf duty_L, W
	movwf CCPR1L
	
	movf temp, W					;low 2 bits to CCPR1CON <5:4> IOR with PWM on
	iorlw b'00001100'
	movwf CCP1CON

	movf H_byte, W				;fix duty
	movwf duty_H
	movf L_byte, W
	movwf duty_L


skp2
	call clrusartbuf
	incf setstate, F
	goto loop
setprescale			;state=2
	decf usartrxcount, W			;check for no characters recieved
	btfsc STATUS,Z					
	goto skp3						;don't update

	movf H_byte, W
	andlw b'11111111'				;is prescale >255?
	btfss STATUS, Z
	goto prescaleINVALID

	movf L_byte, W
	sublw 1							;is prescale 1?
	btfsc STATUS, Z
	goto prescale1

	movf L_byte, W
	sublw 4							;is prescale 4?
	btfsc STATUS, Z
	goto prescale4

	movf L_byte, W
	sublw D'16'							;is prescale 16?
	btfsc STATUS, Z
	goto prescale16

	goto prescaleINVALID
prescale1
	bcf T2CON, T2CKPS1
	bcf T2CON, T2CKPS0
	goto prescaleOK

prescale4
	bcf T2CON, T2CKPS1
	bsf T2CON, T2CKPS0
	goto prescaleOK
prescale16
	bsf T2CON, T2CKPS1
	goto prescaleOK


prescaleINVALID
	movlw LOW msginvalid
	call message
	goto skp3

prescaleOK
	clrf H_byte
	movf L_byte, W
	movwf prescale
skp3
	call clrusartbuf
	clrf setstate
	goto loop

display
;stop the clock interrupts
	bcf INTCON, T0IE
	bcf T1CON, TMR1ON

;	period				;state=0
;	duty				;state=1
;	prescale			;state=2

	movf setstate, W		;check if zero
	movlw 0x3E 				; ">" character
	btfss STATUS, Z
	movlw 0x20				;space
	call send
	movlw LOW msgperiod			;display new values
	call message
	clrf H_byte
	movf period, W
	movwf L_byte
	call B2_BCD
	call sendnum
	movf setstate, W		;check if zero
	movlw 0x3C				; "<" character
	btfss STATUS, Z
	movlw 0x20				;space
	call send
	movlw LOW msgspace
	call message
	movlw 0x01
	subwf setstate, W		;check if 1
	movlw 0x3E 				; ">" character
	btfss STATUS, Z
	movlw 0x20				;space
	call send
	movlw LOW msgduty
	call message
	movf duty_H, W
	movwf H_byte
	movf duty_L, W
	movwf L_byte
	call B2_BCD
	call sendnum
	movlw 0x01
	subwf setstate, W		;check if 1
	movlw 0x3C				; "<" character
	btfss STATUS, Z
	movlw 0x20				;space
	call send
	movlw LOW msgspace
	call message
	movlw 0x02
	subwf setstate, W		;check if 2
	movlw 0x3E 				; ">" character
	btfss STATUS, Z
	movlw 0x20				;space
	call send
	movlw LOW msgprescale
	call message
	clrf H_byte
	movf prescale, W
	movwf L_byte
	call B2_BCD
	call sendnum
	movlw 0x02
	subwf setstate, W		;check if 2
	movlw 0x3C				; "<" character
	btfss STATUS, Z
	movlw 0x20				;space
	call send
	movlw 0x20				;space
	call send
	call send
	call send
	movlw LOW msgfreqin
	call message
	movf TMR1MSB, W			;timer1 values
	movwf H_byte
	movf TMPH, W			
	movwf M_byte
	movf TMPL, W
	movwf L_byte
	bcf STATUS, C			;multiply by 4
	rlf L_byte, F
	rlf M_byte, F
	rlf H_byte, F
	bcf STATUS, C
	rlf L_byte, F
	rlf M_byte, F
	rlf H_byte, F
	call B2_BCD_24
	call sendnum_24
	clrf TMR1MSB
	movlw 0x20				;space
	call send
	movlw LOW msgdutyin
	call message
	bcf STATUS, C			;div by 64 result in indutyM
	rlf indutyL, F
	rlf indutyM, F
	bcf STATUS, C
	rlf indutyL, F
	rlf indutyM, F
	clrf H_byte
	movf indutyM, W
	movwf L_byte
	call B2_BCD
	call sendnum
	bcf freqdone, 0
	clrf indutyH
	clrf indutyM
	clrf indutyL
	movlw d'64'
	movwf dutysamplecountH
	clrf dutysamplecountL
	movlw 0x0D				;enter
	call send

;restart the clock interrupts
	clrf TMR0
	bcf INTCON, T0IF
	bsf INTCON, T0IE
	movlw 0x04
	movwf iTMR0countH
	movlw 0xD1
	movwf iTMR0countM
	movlw 0x85
	movwf TMR0
	clrf TMR1H
	clrf TMR1L
	bcf PIR1, TMR1IF
	bsf T1CON, TMR1ON
    goto loop




sendnum							;converts BCD to ascii and sends
	movf R0, W
	andlw b'00001111'
	addlw 0x30
	call send

	swapf R1, W
	andlw b'00001111'
	addlw 0x30
	call send
	movf R1, W
	andlw b'00001111'
	addlw 0x30
	call send

	swapf R2, W
	andlw b'00001111'
	addlw 0x30
	call send
	movf R2, W
	andlw b'00001111'
	addlw 0x30
	call send	
	return

sendnum_24						;converts BCD to ascii and sends
	swapf R0, W
	andlw b'00001111'
	addlw 0x30
	call send
	movf R0, W
	andlw b'00001111'
	addlw 0x30
	call send

	swapf R1, W
	andlw b'00001111'
	addlw 0x30
	call send
	movf R1, W
	andlw b'00001111'
	addlw 0x30
	call send

	swapf R2, W
	andlw b'00001111'
	addlw 0x30
	call send
	movf R2, W
	andlw b'00001111'
	addlw 0x30
	call send

	swapf R3, W
	andlw b'00001111'
	addlw 0x30
	call send
	movf R3, W
	andlw b'00001111'
	addlw 0x30
	call send	
	return





send
	movwf TXREG             ; send data in W
	bsf STATUS,RP0          ; RAM PAGE 1
send1

	btfss TXSTA,TRMT        ; test if transmission complete
    goto send1
    bcf STATUS,RP0          ; RAM PAGE 0
    return


clrusartbuf						;works fine
	
	clrf usartrxcount
	clrf usartrxbuf
	clrf usartrxbuf+1
	clrf usartrxbuf+2
	clrf usartrxbuf+3
	clrf usartrxbuf+4
	clrf usartrxbuf+5
	clrf usartrxbuf+6
	clrf usartrxbuf+7
	clrf usartrxbuf+8
	clrf usartrxbuf+9
	return

message							;sends message saved in eeprom, start location is in W
	movwf count2				;count up to message end
	movwf count					
	call readEE					;first location is message length
	addwf count, F				;get message end
	incf count, F				;add 1
m1
	incf count2,F
	movf count, W
	subwf count2, W
	btfsc STATUS, Z
	goto messageend
	movf count2, W
	call readEE
	call send
	goto m1
messageend
	return
	
	

readEE
	BSF STATUS, RP0 ; Bank 1
	MOVWF EEADR ; Address to read
	BSF EECON1, RD ; EE Read
	MOVF EEDATA, W ; W = EEDATA
	BCF STATUS, RP0 ; Bank 0
	return


;**********************************************************************
; BCD To Binary Conversion AN526
;
; This routine converts a 5 digit BCD number to a 16 bit binary
; number.
; The input 5 digit BCD numbers are asumed to be in locations
; R0, R1 & R2 with R0 containing the MSD in its right most nibble.
;
; The 16 bit binary number is output in registers H_byte & L_byte
; ( high byte & low byte repectively ).
;
; The method used for conversion is :
; input number X = abcde ( the 5 digit BCD number )
; X = abcde = 10[10[10[10a+b]+c]+d]+e
;
; Performance :
; Program Memory : 30
; Clock Cycles : 121
;
;
; Program: BCD2BIN.ASM
; Revision Date:
; 1-13-97 Compatibility with MPASMWIN 1.40
;
;*******************************************************************;
;
;H_byte equ 10
;L_byte equ 11
;R0 equ 12 ; RAM Assignments
;R1 equ 13
;R2 equ 14
;
;H_temp equ 15 ; temporary register
;L_temp equ 16 ; temporary register

mpy10b
	andlw 0x0F
	addwf L_byte, F
	btfsc STATUS,C
	incf H_byte, F
mpy10a
	bcf STATUS,C ; multiply by 2
	rlf L_byte,W
	movwf L_temp
	rlf H_byte,W ; (H_temp,L_temp) = 2*N
	movwf H_temp
	
	bcf STATUS,C ; multiply by 2
	rlf L_byte, F
	rlf H_byte, F
	bcf STATUS,C ; multiply by 2
	rlf L_byte, F
	rlf H_byte, F
	bcf STATUS,C ; multiply by 2
	rlf L_byte, F
	rlf H_byte, F ; (H_byte,L_byte) = 8*N
	
	movf L_temp,W
	addwf L_byte, F
	btfsc STATUS,C
	incf H_byte, F
	movf H_temp,W
	addwf H_byte, F
	retlw 0 ; (H_byte,L_byte) = 10*N
	
	
BCDtoB
	clrf H_byte
	movf R0,W
	andlw 0x0F
	movwf L_byte
	call mpy10a ; result = 10a+b
	
	swapf R1,W
	call mpy10b ; result = 10[10a+b]
	
	movf R1,W
	call mpy10b ; result = 10[10[10a+b]+c]
	
	swapf R2,W
	call mpy10b ; result = 10[10[10[10a+b]+c]+d]
	
	movf R2,W
	andlw 0x0F
	addwf L_byte, F
	btfsc STATUS,C
	incf H_byte, F ; result = 10[10[10[10a+b]+c]+d]+e
	retlw 0 ; BCD to binary conversion done



 ;********************************************************************
 ; Binary To BCD Conversion Routine
 ; This routine converts a 16 Bit binary Number to a 5 Digit
 ; BCD Number. This routine is useful since PIC16C55 & PIC16C57
 ; have two 8 bit ports and one 4 bit port ( total of 5 BCD digits)
 ;
 ; The 16 bit binary number is input in locations H_byte and
 ; L_byte with the high byte in H_byte.
 ; The 5 digit BCD number is returned in R0, R1 and R2 with R0
 ; containing the MSD in its right most nibble.
 ;
 ; Performance :
 ; Program Memory : 35
 ; Clock Cycles : 885
 ;
 ;
 ; Program: B16TOBCD.ASM
 ; Revision Date:
 ; 1-13-97 Compatibility with MPASMWIN 1.40

B2_BCD 
	bcf STATUS,0 ; clear the carry bit
	movlw .16
	movwf count
	clrf R0
	clrf R1
	clrf R2
loop16
	rlf L_byte, F
	rlf H_byte, F
	rlf R2, F
	rlf R1, F
	rlf R0, F
	
	decfsz count, F
	goto adjDEC
	RETLW 0
adjDEC
	movlw R2
	movwf FSR
	call adjBCD
	
	movlw R1
	movwf FSR
	call adjBCD
	
	movlw R0
	movwf FSR
	call adjBCD
	
	goto loop16

adjBCD
	movlw 3
	addwf 0,W
	movwf temp
	btfsc temp,3 ; test if result > 7
	movwf 0
	movlw 0x30
	addwf 0,W
	movwf temp
	btfsc temp,7 ; test if result > 7
	movwf 0 ; save as MSD
	RETLW 0


B2_BCD_24
	bcf STATUS,0 ; clear the carry bit
	movlw .24
	movwf count
	clrf R0
	clrf R1
	clrf R2
	clrf R3
loop24
	rlf L_byte, F
	rlf M_byte, F
	rlf H_byte, F
	rlf R3, F
	rlf R2, F
	rlf R1, F
	rlf R0, F
	
	decfsz count, F
	goto adjDEC_24
	RETLW 0
adjDEC_24
	movlw R3
	movwf FSR
	call adjBCD_24
	
	movlw R2
	movwf FSR
	call adjBCD_24
	
	movlw R1
	movwf FSR
	call adjBCD_24
	
	movlw R0
	movwf FSR
	call adjBCD_24
	
	goto loop24
		
adjBCD_24
	movlw 3
	addwf 0,W
	movwf temp
	btfsc temp,3 ; test if result > 7
	movwf 0
	movlw 0x30
	addwf 0,W
	movwf temp
	btfsc temp,7 ; test if result > 7
	movwf 0 ; save as MSD
	RETLW 0

    END