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.

Software PWM for 10F200

Status
Not open for further replies.

raitl

New Member
Hello, I'm building a cheapo line follower robot with pic10F200 as its brains.

At the moment I am turning motors on and off when I need to turn, but that results in a jerky movement. I want to go with PWM for smooth ride. I have managed to find some examples of software PWM online, but there are two major flaws with them:

1 - they are for bigger and more powerful chips with multiple timers
2 - the PWM cycle is not easily modified "on-the-run"


Has anyone done soft PWM for small chips like that?
 
You could do software PWM on the 10F200 but it would be difficult. No interrupts, only timer 0 and 256 instructions is very limiting. How is your code arranged at the moment, how nested are your calls and what sensors are you reading?

Mike.
I am just on my way out but will look for a reply tomorrow.
 
At the moment the code is very simple, I have two inputs from two IR sensors following the line. The sensors are not on the line when running, they are on either side of it, so if a sensor sees the line, the bot turns in that direction until it does not see the line anymore.

basically:
----loop----
is RightSensor on line?
Yes, stop Rightmotor
No, run Rightmotor
is LeftSensor on line?
Yes, stop Leftmotor
No, run Leftmotor
----loop----
 
Here is a bit of code you may find useful.

;FADES THREE LEDS IN SEQUENCE AT ABOUT ONE HERTZ WHEN ENABLE IS APPLIED
;

#INCLUDE <P10F200.INC> ; PROCESSOR SPECIFIC VARIABLES

__CONFIG _MCLRE_OFF & _CP_OFF & _WDT_OFF & _IntRC_OSC

ORG 0X1FF

CBLOCK .16

COUNT ;COUNT BUFFER
COUNT1 ;COUNT BUFFER #1
PWM_BUF ;PWM BUFFER

ENDC

ORG .0

; MOVWF OSCCAL ; LOADS THE OSCILLATOR CALIBRATION FACTOR AUTOMATICALY.

INIT
;-----[OPTION]---------------------------------------------------------------
; 7 = GLOBAL WAKE UP ON CHANGE - GP0,GP1,GP3 - 1=ON 0=OFF
; 6 = GLOBAL WEAK PULL UP - GP0,GP1,GP3 - 1=ON 0=OFF
; 5 = T0CS - TIMER 0 CLOCK SOURCE SELECT - 1=EXTERNAL 0=INTERNAL
; 4 = T0SE - TIMER 0 SOURCE EDGE SELECT - 1=HIGH TO LOW 0=LOW TO HIGH
; 3 = PSA - PRE SCALE ASSIGNMENT - 1=WDT 0=TIMER 0
; 2:1 = PRE SCALER RATE

MOVLW B'10000111' ; SET PULL-UPS ONLY, MAX PRE-SCALER
OPTION ;

;-----[TRIS]-----------------------------------------------------------------
; 3 = GPIO 3 - INPUT ONLY
; 2 = GPIO 2 - I/O
; 1 = GPIO 1 - I/O
; 0 = GPIO 0 - I/O

MOVLW B'00001000' ; SET GPIO PIN 3 TO INPUT, PINS 0,1,2 TO OUTPUT.
TRIS GPIO ;

;-----[CMCON0 FOR 10F204 AND 10F206 ONLY]-------------------------------------
; 7 = CMPOUT - 1=VIN+ > VIN : 0=VIN+ < VIN
; 6 = COUTEN - OUTPUT PLACED ON COMPARATOR PIN 1=YES : 0=NO
; 5 = POL - COMPARATOR OUTPUT BIT IS INVERTED 1=NOT : 0=IS
; 4 = CMPT0CS - 1=TMR0 SOURCE BY T0CKS BIT : 0=COMP OUT USED AS SOURCE
; 3 = CMPON - 1=COMPARATOR ON : 0=COMPARATOR OFF
; 2 = CNREF - NEGATIVE REF SELECT 1=CIN : 0=INTERNAL VOLTAGE
; 1 = CPREF - POSITIVE REF SELECT 1=CIN+ :0= CIN-
; 0 = CWU - COMPARATOR WAKE UP ON CHANGE 1=DISABLED 0=ENABLED

;-----[MAIN ROUTINE]---------------------------------------------------------

MAIN
BCF GPIO,0 ; START WITH LED TURNED OFF
BCF GPIO,1 ; START WITH LED TURNED OFF
BCF GPIO,2
; BTFSC GPIO,3 ; PRESS THE PUSHBUTTON TO BEGIN
; GOTO MAIN ; ...LOOP, IF THE PUSHBUTTON IS NOT PRESSED
CLRF COUNT ; THE REG COUNT CAUSES THE SUBROUTINE TO LOOP 255 TIMES.
CLRF COUNT1 ; THE REG COUNT1 SETS THE PWM VALUE
CLRF PWM_BUF ; THE REG PWM_BUF HOLDS THE PWM RATIO OF 'ON' TO 'OFF'
MAIN1
MOVF COUNT1,W ; TRANSFER THE VALUE IN REG COUNT1 TO REG PWM_BUF
MOVWF PWM_BUF
CALL PWM
INCFSZ COUNT1,F ; HAS COUNT1 COUNTED FROM 0 TO 255?
GOTO MAIN1 ; ...NO, LOOP AGAIN
MAIN2
DECF COUNT1,F ; ...YES, AND NOW COUNT1 HOLDS THE DECREMENTING PWM VALUE
MAIN3
MOVF COUNT1,W ; TRANSFER THE VALUE IN REG COUNT1 TO REG PWM_BUF
MOVWF PWM_BUF
CALL PWMA
DECFSZ COUNT1,F ; HAS COUNT1 COUNTED FROM 255 TO 0?
GOTO MAIN3 ; ...NO, LOOP AGAIN
MAIN4
MOVF COUNT1,W ; TRANSFER THE VALUE IN REG COUNT1 TO REG PWM_BUF
MOVWF PWM_BUF
CALL PWMB
INCFSZ COUNT1,F ; HAS COUNT1 COUNTED FROM 0 TO 255?
GOTO MAIN4 ; ...NO, LOOP AGAIN
MAIN5
DECF COUNT1,F ; ...YES, AND NOW COUNT1 HOLDS THE DECREMENTING PWM VALUE
MAIN6
MOVF COUNT1,W ; TRANSFER THE VALUE IN REG COUNT1 TO REG PWM_BUF
MOVWF PWM_BUF
CALL PWMC
DECFSZ COUNT1,F ; HAS COUNT1 COUNTED FROM 255 TO 0?
GOTO MAIN6 ; ...NO, LOOP AGAIN
GOTO MAIN



PWM
MOVLW .255 ; MOVE THE LITERAL COUNT OF 255 TO REG 'W'
MOVWF COUNT ; ...THEN STORE IT IN THE REGISTER 'COUNT'
PWM1
MOVF PWM_BUF,F ; TEST IF THE REGISTER PWM_BUF IS ZERO
BTFSC STATUS,Z ; ...IS PWM_BUF = 0?
GOTO LED_OFF ; ...NO, NOT YET, SKIP THIS INSTRUCTION FOR NOW.
LED_ON
BSF GPIO,0 ; ...YES, SET GPIO PIN-0 HIGH, TURN ON LED
BCF GPIO,1 ; ...YES, SET GPIO PIN-1 LOW, TURN OFF LED
DECF PWM_BUF,F ; DECREMENT THE REGISTER PWM_BUF
GOTO EXIT
LED_OFF
BCF GPIO,0 ; SET GPIO PIN-0 LOW, TURN-OFF LED
BSF GPIO,1 ; SET GPIO PIN-1 HIGH, TURN-ON LED
NOP
GOTO EXIT ;
EXIT
DECFSZ COUNT,F ; HAS THIS SUBROUTINE LOOPED 255 TIMES?
GOTO PWM1 ; ...NO, KEEP LOOPING
RETLW 0 ; ...YES, RETURN TO THE CALLING PROGRAM

PWMA
MOVLW .255 ; MOVE THE LITERAL COUNT OF 255 TO REG 'W'
MOVWF COUNT ; ...THEN STORE IT IN THE REGISTER 'COUNT'
PWM1A
MOVF PWM_BUF,F ; TEST IF THE REGISTER PWM_BUF IS ZERO
BTFSC STATUS,Z ; ...IS PWM_BUF = 0?
GOTO LED_OFFA ; ...NO, NOT YET, SKIP THIS INSTRUCTION FOR NOW.
LED_ONA
BSF GPIO,0 ; ...YES, SET GPIO PIN-0 HIGH, TURN ON LED
BCF GPIO,2 ; ...YES, SET GPIO PIN-2 LOW, TURN OFF LED
DECF PWM_BUF,F ; DECREMENT THE REGISTER PWM_BUF
GOTO EXITA
LED_OFFA
BCF GPIO,0 ; SET GPIO PIN-0 LOW, TURN-OFF LED
BSF GPIO,2 ; SET GPIO PIN-2 HIGH, TURN-ON LED
NOP
GOTO EXITA ;
EXITA
DECFSZ COUNT,F ; HAS THIS SUBROUTINE LOOPED 255 TIMES?
GOTO PWM1A ; ...NO, KEEP LOOPING
RETLW 0 ; ...YES, RETURN TO THE CALLING PROGRAM

PWMB
MOVLW .255 ; MOVE THE LITERAL COUNT OF 255 TO REG 'W'
MOVWF COUNT ; ...THEN STORE IT IN THE REGISTER 'COUNT'
PWM1B
MOVF PWM_BUF,F ; TEST IF THE REGISTER PWM_BUF IS ZERO
BTFSC STATUS,Z ; ...IS PWM_BUF = 0?
GOTO LED_OFFB ; ...NO, NOT YET, SKIP THIS INSTRUCTION FOR NOW.
LED_ONB
BSF GPIO,0 ; ...YES, SET GPIO PIN-0 HIGH, TURN ON LED
BCF GPIO,2 ; ...YES, SET GPIO PIN-2 LOW, TURN OFF LED
DECF PWM_BUF,F ; DECREMENT THE REGISTER PWM_BUF
GOTO EXITB
LED_OFFB
BCF GPIO,0 ; SET GPIO PIN-0 LOW, TURN-OFF LED
BSF GPIO,2 ; SET GPIO PIN-2 HIGH, TURN-ON LED
NOP
GOTO EXITB ;
EXITB
DECFSZ COUNT,F ; HAS THIS SUBROUTINE LOOPED 255 TIMES?
GOTO PWM1B ; ...NO, KEEP LOOPING
RETLW 0 ; ...YES, RETURN TO THE CALLING PROGRAM

PWMC
MOVLW .255 ; MOVE THE LITERAL COUNT OF 255 TO REG 'W'
MOVWF COUNT ; ...THEN STORE IT IN THE REGISTER 'COUNT'
PWM1C
MOVF PWM_BUF,F ; TEST IF THE REGISTER PWM_BUF IS ZERO
BTFSC STATUS,Z ; ...IS PWM_BUF = 0?
GOTO LED_OFFC ; ...NO, NOT YET, SKIP THIS INSTRUCTION FOR NOW.
LED_ONC
BSF GPIO,0 ; ...YES, SET GPIO PIN-O HIGH, TURN ON LED
BCF GPIO,1 ; ...YES, SET GPIO PIN-1 LOW, TURN OFF LED
DECF PWM_BUF,F ; DECREMENT THE REGISTER PWM_BUF
GOTO EXITC
LED_OFFC
BCF GPIO,0 ; SET GPIO PIN-O LOW, TURN-OFF LED
BSF GPIO,1 ; SET GPIO PIN-1 HIGH, TURN-ON LED
NOP
GOTO EXITC ;
EXITC
DECFSZ COUNT,F ; HAS THIS SUBROUTINE LOOPED 255 TIMES?
GOTO PWM1C ; ...NO, KEEP LOOPING
RETLW 0 ; ...YES, RETURN TO THE CALLING PROGRAM

END ;
 
Here is what I use.
dutyc_msb and dutyc_lsb are the duty cycle. This gives you 12 bit duty cycle resolution, so the bottom 4 bits of dutyc_lsb are ignored.

The PWM period is 4096 cycles or about 4ms and the resolution is 1 cycle.

Your code has to change dutyc_msb and dutyc_lsb to what you want.

count1 and count2 are temporary registers.

out_dc_port, out_dc is the port that the duty cycle appears on.

As others have pointed out, the 10f200 is not a powerful processor. The program has to output the mark space ratio, so it has to spend a lot of time waiting for the timer.

At the point marked "run" is where you insert your code. The part that I have written always goes to "run" at the start of the larger half of the duty cycle. If the duty cycle is 25% it will go to "run" as soon as the output falls, but if the duty cycle is 60% it will go to "run" as soon as the output rises.

In other words, your code can take up to about 2000 instruction cycles, and it will be run every 4 ms.



Code:
	movlw	b'11000011'		
	option					;set timer to prescale of 16

chk_1
	movf		dutyc_msb, w	;collect time
	subwf		timer,w		;subtract, result in w
	btfsc		status, carry	;see if time is less than w (dutyc_msb)
	goto		chk_1		; If it is, output a 1

;we now want to wait until the next increment of the timer,
;trap the time exactly, 
;In this direction, timer=0 here, so we are trying to find when it 
;hits 1

   	nop		;timer now at 0, wait a bit
	movlw	0x02
	movwf	count1
wait
	decfsz	count1,f
	goto		wait                          ;short pause needed

	clrw		; This is the minimum number of steps
	addwf	timer,w	;1st place timer hits 1
	addwf	timer,w	;2nd	
	addwf	timer,w	;3rd
	addwf	timer,w	;4th

;w is now between 0 and 4

	andlw 	0x07		;shouldn't be needed
	addwf	pc, f		;increment PC restore timing
	nop
	nop
	nop
	nop

	movlw	0x05
	movwf	count1
wait2
	decfsz	count1,f
	goto 	        wait2

	nop		;These get the mark space ratio equal to dutyc

	bsf		out_dc_port, out_dc	;output 1
	
;the calulation done if duty cycle is below half

	btfsc		dutyc_msb, 7
	goto		run

;	Output is high when chk_0 is running. 
;	dutyc_msb>time, so w>timer at the subtract
;	so carry is not set
;	When time equals result_time, w<=timer at the subract
;	so carry is set and 0 is output

chk_0	
        movf		dutyc_msb, w	;collect time
	subwf		timer,w		;subtract, result in w
	btfss		status, carry	;see if time is >= than w (dutyc_msb)
	goto		chk_0		

;we now want to wait until the next increment of the timer,
;trap the time exactly, 
;In this direction, timer=0 here, so we are trying to find when it 
;hits 1

	swapf		dutyc_lsb,w	; collect the lsb word, biggest half
	movwf	count1		; put in count 1
	clrf		count2		; 
	subwf		count2,f	; get the negative 
	movlw	0x03		;
	andwf	count2,f	; bits 0 and 1 only.
				;This part must be 6 cycles long exactly
				;to be ready for the following bit

	clrw
	btfsc		dutyc_msb,0	;
	iorlw		0x04		
;This is so that we get the same last 3 bits after the additions

	addwf	timer,w	;1st place timer hits 1
	addwf	timer,w	;2nd	
	addwf	timer,w	;3rd
	addwf	timer,w	;4th
	andlw		0x07		;take last 3 bit only

;w is now between 0 and 4
	addwf	pc, f		;increment PC restore timing
	nop
	nop
	nop
	nop	;this has to be here for restoring timing

	rrf		count1,f	;
	rrf		count1,f	;
	movlw	0x03		;
	andwf	count1,f	;
	incf		count1, f

time1
	nop
	decfsz	count1,f	;
	goto		time1		;This is to add cycles for bits 8 and 9
						;of duty cycle. Each count of bit 9 represents 4 cycles

	swapf		dutyc_lsb,w	;This adds the next two bits of dutyc_lsb
	movwf	count1		;bits 4 and 5 
	comf		count1,f	
	movlw	0x03
	andwf	count1,w
	addwf	pc,f

	nop
	nop
	nop

	bcf		out_dc_port,  out_dc	;output 0
;this happens dutyc_lsb (bits 4 - 7 only)
;clock cycles after the timer gets to 2 more than dutyc_msb

;The calculation done if duty cycle is below half

	btfsc		dutyc_msb, 7
	goto		chk_1		;wait for timer

run

;This is where your calculation code goes

calc_fin

	clrwdt			;reset watchdog

; Check output state. we mustn't come back
; here before checking the output state.

	btfsc		out_dc_port,  out_dc	
	goto		chk_0		
	goto		chk_1
 
Last edited:
The low frequency of instruction execution (1 MHz) places rather strict limits on the frequency and resolution of the software generated PWM. I was a mentor for a LEGO robotics team for several years. IIRC there were only five power levels for the motors and we got along rather well. If you can tolerate 8 levels (0.0%, 12.5%, 25%, etc) and a frequency of about 122 Hz, I believe that it is doable.

Use a prescaler of 4 so that the timer overflows every 1024 instruction cycles. With an 8 level resolution a PWM cycle is 8196 usec for a frequency of about 122 Hz. Each time the timer overflow execute the PWM routine to see if an output needs to be changed. Then execute the rest of your code and afterwards go into a wait loop checking for the timer to overflow, i.e. for the msb to change from 1 to 0. Every 8 PWM execcutions you reset the PWM and start a new cycle. You have a total of 1024 instruction cycles to get all your computing done. If you can get your computing done in 512 cycle then you can double the frequency or the resolution. If you can get everything done in 256 cycle, you could double again.

I have some code somewhere that works this way. I don't know how long it will take me to find it or if I can find it at all.
 
Last edited:
Here is a code fragment taken from a working program. It should work, but there was a bit of editting so no guarantees. Go to PWM each time the timer overflows. You load the duty cycle that you desire into Sduty_0 and Sduty_1 anytime you need to change.


Code:
PWM
	movf	Duty_0,w
	subwf	PWM_cntr,w
	btfsc	STATUS,Z
	bsf	SPORT,D0_pin	; turn on output 0
;
	movf	Duty_1,w
	subwf	PWM_cntr,w
	btfsc	STATUS,Z
	bsf	SPORT,D1_pin	; turn on output 1
;
	movf	SPORT,w
	movwf	GPIO
	decfsz	PWM_cntr,f
	goto	Work
;
;           Setup for next cycle
;
	movlw	PWM_cycle
	movwf	PWM_cntr	; reset PWM_cntr
	bcf	SPORT,D0_pin	; Clear output 0
	bcf	SPORT,D1_pin	; Clear output 1
;
	movf	Sduty_0,w
	movwf	Duty_0
	movf	Sduty_1,w
	movwf	Duty_1
;
Work

  your code here

You need to allocate these variables:

SPORT ; shadow variable for GPIO
Sduty_0 ; shadow variable for Duty_0
Sduty_1 ; shadow variable for Duty_1
Duty_0 ; duty cycle variable for D0
Duty_1 ; duty cycle variable for D1
PWM_cntr ; counter for PWM timing

And you need these defines:

#define PWM_cycle 8
#define D0_pin 0
#define D1_pin 1
 
Here's a 2 channel 4 bit (0-15) PWM routine that runs at 4.7kHz. Not bad for a 1MHz chip.
Code:
;M0 & M1 are the port numbers for the motors
;PWM1 is motor 1 speed (0-15)
;PWM2 is motor 2 speed (0-15)
DoPWM		bsf	Shadow,M0	;turn motor 0 on
		bsf	Shadow,M1	;and motor 1
		movlw	1		;preload W
PwmLoop		subwf	PWM1,F		;sub 1 from PWM1
		btfss	STATUS,DC	;was there a borrow from bit 4
		bcf	Shadow,M0	;yes so turn motor 0 off
		subwf	PWM2,F		;now do second channel
		btfss	STATUS,DC
		bcf	Shadow,M1
		movfw	Shadow		;copy shadow register
		movwf	GPIO		;to I/O register
		movlw	1		;reload W
		addwf	Count,F		;inc count but set flags
		btfss	STATUS,DC	;have we been around 16 times
		goto	PwmLoop		;no, so go around inner loop
		btfss	STATUS,Z	;have we done 256 times
		goto	DoPWM		;no so repeat outer loop
		retlw	0		;done

To use it just place values in PWM1 and PWM2, 0 is off through to 15 is full on. There is no need to reload the PWM variables as they always get decremented 256 times and therefore go back to there original value.:)

Every time you call DoPWM it does 16 complete cycles. I would read your sensors and then call DoPWM 3 times to give a loop time of around 100Hz.

For anyone interested, the above code is possible because it uses the Digit Carry (DC) bit. The DC status bit gets set when there is a carry from bit 3 to 4 or in the case of subtraction it is cleared when there is a borrow from bit 4.

Have fun,

Mike.
 
Now that it has been established that it is possible to do software PWM with this chip, it would be nice if the OP would let us know what sort of control law he proposes, i.e. how is he going to establish the duty cycles for the PWM from his sensor readings?
 
Here's a 2 channel 4 bit (0-15) PWM routine that runs at 4.7kHz. Not bad for a 1MHz chip.
Code:
;M0 & M1 are the port numbers for the motors
;PWM1 is motor 1 speed (0-15)
;PWM2 is motor 2 speed (0-15)
DoPWM        bsf    Shadow,M0    ;turn motor 0 on
        bsf    Shadow,M1    ;and motor 1
        movlw    1        ;preload W
PwmLoop        subwf    PWM1,F        ;sub 1 from PWM1
        btfss    STATUS,DC    ;was there a borrow from bit 4
        bcf    Shadow,M0    ;yes so turn motor 0 off
        subwf    PWM2,F        ;now do second channel
        btfss    STATUS,DC
        bcf    Shadow,M1
        movfw    Shadow        ;copy shadow register
        movwf    GPIO        ;to I/O register
        movlw    1        ;reload W
        addwf    Count,F        ;inc count but set flags
        btfss    STATUS,DC    ;have we been around 16 times
        goto    PwmLoop        ;no, so go around inner loop
        btfss    STATUS,Z    ;have we done 256 times
        goto    DoPWM        ;no so repeat outer loop
        retlw    0        ;done
To use it just place values in PWM1 and PWM2, 0 is off through to 15 is full on. There is no need to reload the PWM variables as they always get decremented 256 times and therefore go back to there original value.:)

Every time you call DoPWM it does 16 complete cycles. I would read your sensors and then call DoPWM 3 times to give a loop time of around 100Hz.

For anyone interested, the above code is possible because it uses the Digit Carry (DC) bit. The DC status bit gets set when there is a carry from bit 3 to 4 or in the case of subtraction it is cleared when there is a borrow from bit 4.

Have fun,

Mike.

A simple, clever and elegant solution Mike. Bravo!
 
Status
Not open for further replies.

New Articles From Microcontroller Tips

Back
Top