Woodward "Woody" Stanford
Published

S3 PWM Controller

Need TTL serial to multi-channel hobby-servo out? The S3 chip is for you. Turn a $3 PIC16F88 into an 8-channel PWM servo/ESC controller.

IntermediateWork in progress1 hour250
S3 PWM Controller

Story

Read more

Code

S3 firmware code in PIC assembly (PIC16F88)

Assembly x86
THIS IS NOT DONE! It will compile and if you know what you are doing a GREAT place to start (about 80%-90% done), will post the actual firmware code as soon as I get it running.
list        p=16f88   ; list directive to define processor
    #include    <p16f88.inc>   ; processor specific variable definitions

    __CONFIG    _CONFIG1, _LVP_OFF & _FCMEN_ON & _IESO_OFF & _CPD_OFF & _CP_OFF & _MCLRE_OFF & _PWRTE_ON & _WDT_OFF 
    __CONFIG    _CONFIG2, _WRT_OFF 

    org 0x0000                     ;start at 0x0000

;important note: did you know you can set your general purpose registers to
; 0x70h to 0x7F and it doesn't matter the bank settings, they always resolve
    
;current internal values for the pwm signals (8 bit)
VALUE0 EQU 0x20 ;not used; padding so indirect addr, hits right channel number
VALUE1 EQU 0x21 ; remember to set the bank to Bank 0 to access correctly 
VALUE2 EQU 0x22
VALUE3 EQU 0x23
VALUE4 EQU 0x24
VALUE5 EQU 0x25
VALUE6 EQU 0x26
VALUE7 EQU 0x27
VALUE8 EQU 0x28
LED1 EQU 0x29
LED2 EQU 0x2A
LED3 EQU 0x2B
LED4 EQU 0x2C
LED5 EQU 0x2D

;generic registers for loop counting
COUNT1 EQU 0x30
COUNT2 EQU 0x31
 
;Register Restore vars
CACHEW EQU 0x70		;save W register HERE for restore on subroutine exit
			;note that it doesn't matter how your banks are set
PROSTATE EQU 0x71	;packet receive state, 0=awaiting first channel byte, 1=awating value byte
			;note that it doesn't matter how your banks are set
PROCHANNEL EQU 0x72	;received channel value
			;note that it doesn't matter how your banks are set
PROVALUE EQU 0x73	;received value value
			;note that it doesn't matter how your banks are set

;define values not in the INC file that we need
SYNCH EQU 0x04		;SYNCH is bit 4 of TXSTA (clearing enables Asynch tranmission/reception)
SPEN EQU 0x07		;SPEN is bit 7 of RCSTA (setting enables UART)
CREN EQU 0x04		;CREN is bit 4 of RCSTA (setting enables receiving bytes on UART RX)
TXEN EQU 0x05		;TXEN is bit 5 of TXSTA (setting enables transmitting bytes on UART TX
BRGH EQU 0x02		;BRHG is bit 2 of TXSTA (setting enables high-speed options on baud rate)
			;(reference baud rate tables in PIC manual for how to set SPBRG for baud rate)
			
initalize
	
    movlw 0x00	    ;initialize protocol state to state 0
    movwf PROSTATE
    
port_config
    
    call selbank1
    movlw b'00000000'           ;set PORTA to output - 8 software PWM channels
    movwf TRISA
    movlw b'00000100'           ;set RB1,3-7 on PORTB to output (except RB2 
    movwf TRISB		        ;which we want to use for UART RX, input)
    
    call selbank0		;select Registers at Bank 0
				;important to flip back to Bank 0 because this
				;is where our variables are
	
uart_config
		
    bsf TXSTA, BRGH		;set UART for high baud rate
				
    movlw 0x81	    ;decimal 129 corresponds to 9600 baud when PIC is running at 20 Mhz
		    ;maximum baud rate for a 20 Mhz 16F88 is 1.25Mbaud SPBRG=0
		    
    call selbank1
    movwf SPBRG	    ;set the baud rate
    
    bcf TXSTA, SYNCH ; clear synch bit for ASYNCH reception/transmission
    bsf RCSTA, SPEN ; enable UART
    
    ;code later ;set up PortB-2 (Pin 8) for UART RX
    
    bsf RCSTA, CREN ; enable reception
    bcf RCSTA, TXEN ; disable transmission
    
    ; Bank 0,PIR,RCIF indicates data is ready for read
    ; Bank 0, RCREG read byte from this register
    
    call selbank0		;select Registers at Bank 0
				;important to flip back to Bank 0 because this
				;is where our variables are
				
debug1	; remove from program when actually writing to chip (for ICE only)
    movlw 0x00	    ;set VALUE1 -> VALUE 8 with a time delay proportional 
    movwf VALUE1    ;to their channel number, allows you to walk the servo
    movlw 0x20	    ;or ESC signal pin over the PIC pins and watch it move
    movwf VALUE2    ;incrementally illustrating that the PWM is set up correctly
    movlw 0x40
    movwf VALUE3
    movlw 0x60
    movwf VALUE4
    movlw 0x80
    movwf VALUE5
    movlw 0xA0
    movwf VALUE6
    movlw 0xC0
    movwf VALUE7
    movlw 0xE0
    movwf VALUE8
    
    movlw 0x01	    ;set LED's alternating ON steady
    movwf LED1
    movlw 0x01	    
    movwf LED2
    movlw 0x01	    
    movwf LED3
    movlw 0x01	    
    movwf LED4
    movlw 0x01	    
    movwf LED5
    
    ;end of debug - remove in actual production version! (maybe, can serve as a preconfig)
    
start
;to implement software-based 8 channel PWM of the hobby servo kind
;as the 3-phase drone motors we are programming for
;it is pretty off-cycle tolerant but we need to correctly time
; the ON time of the PWM on that particular channel
    
;PWM specification:
; 1 milliseconds on - left-most position (0 RPM on 3-phase drone motor)
; 1.5 milliseconds on - center position (middle RPM on 3-phase drone motor)
; 2 milliseconds on - right-most position (Highest RPM on 3-phase drone motor)
; off time doesn't matter that much so long as its refreshed every once in a while
; guessing every 40 milliseconds
    
; instruction time on this 4 x clock cycle time or 0.2 usecs or 200 nanoseconds
;(assuming MCU clock by XTAL at 20 Mhz)
    
;how we build the waveform is by calling a standarized 1 millisecond
;delay (delay1) after throwing the channel's pin HIGH; then we call delay2
;with the CHANNEL NUMBER (NOT the delay value) which completes the rest of
; the waveform (basicaly a linear, programable fraction of 1 millisecond)
    
    call selbank0	;make sure VALUE1->8 are visible
    
    movfw VALUE1	; servicing CHANNEL 1
    bsf PORTA, 0	; throw channel #1 (PORTA-Pin 1) HIGH
    call delay1		; call the 1 ms delay	
    call delay2		; W is already loaded with the channel #
			; calls the actual delay value from VALE1 - VALUE 8
			; which is set by the UART routines
    bcf PORTA, 0	; throw channel #1 (PORTA-Pin 1) LOW
    
    movfw VALUE2	; servicing CHANNEL 2
    bsf PORTA, 1	; throw channel #2 (PORTA-Pin 2) HIGH
    call delay1		; call the 1 ms delay	
    call delay2		; W is already loaded with the channel #
			; calls the actual delay value from VALUE1 - VALUE 8
			; which is set by the UART routines
    bcf PORTA, 1	; throw channel #2 (PORTA-Pin 2) LOW
    
    movfw VALUE3	; servicing CHANNEL 3
    bsf PORTA, 2	; throw channel #3 (PORTA-Pin 3) HIGH
    call delay1		; call the 1 ms delay	
    call delay2		; W is already loaded with the channel #
    bcf PORTA, 2	; throw channel #3 (PORTA-Pin 3) LOW
    
    movfw VALUE4	; servicing CHANNEL 4
    bsf PORTA, 3	; throw channel #4 (PORTA-Pin 4) HIGH
    call delay1		; call the 1 ms delay	
    call delay2		; W is already loaded with the channel #
    bcf PORTA, 3	; throw channel #4 (PORTA-Pin 4) LOW
    
    movfw VALUE5	; servicing CHANNEL 5
    bsf PORTA, 4	; throw channel #5 (PORTA-Pin 5) HIGH
    call delay1		; call the 1 ms delay	
    call delay2		; W is already loaded with the channel #
    bcf PORTA, 4	; throw channel #1 (PORTA-Pin 5) LOW
    
    movfw VALUE6		; servicing CHANNEL 6
    bsf PORTA, 5	; throw channel #6 (PORTA-Pin 6) HIGH
    call delay1		; call the 1 ms delay	
    call delay2		; W is already loaded with the channel #
    bcf PORTA, 5	; throw channel #6 (PORTA-Pin 6) LOW
    
    movfw VALUE7		; servicing CHANNEL 7
    bsf PORTB, 0	; throw channel #1 (PORTA-Pin 7) HIGH
    call delay1		; call the 1 ms delay	
    call delay2		; W is already loaded with the channel #
    bcf PORTB, 0	; throw channel #1 (PORTA-Pin 7) LOW
    
    movfw VALUE8		; servicing CHANNEL 8
    bsf PORTB, 1	; throw channel #1 (PORTA-Pin 8) HIGH
    call delay1		; call the 1 ms delay	
    call delay2		; W is already loaded with the channel #
    bcf PORTB, 1	; throw channel #8 (PORTA-Pin 8) LOW
    
    ; **** Beginning of LED service routine
    ; we need to be able to BOTH turn them on and off programatically via UART
    btfsc LED1,0
    goto led1_on
    bcf PORTB,3	;throw LED #1 (PORTB_3 - Pin 9) LOW
    goto continue_led2
    
led1_on
    bsf PORTB,3	;throw LED #1 (PORTB_3- Pin 9) HIGH
 
continue_led2 
    btfsc LED2,0
    goto led2_on
    bcf PORTB,4	;throw LED #2 (PORTB_4- Pin 10) LOW
    goto continue_led3
    
led2_on
    bsf PORTB,3	;throw LED #2 (PORTB_4- Pin 10) HIGH
    
continue_led3 
    btfsc LED3,0
    goto led3_on
    bcf PORTB,5	;throw LED #3 (PORTB_5- Pin 11) LOW
    goto continue_led4
    
led3_on
    bsf PORTB,5	;throw LED #3 (PORTB_5- Pin 11) HIGH
    
continue_led4
    btfsc LED4,0
    goto led4_on
    bcf PORTB,6		;throw LED #4 (PORTB_6- Pin 12) LOW
    goto continue_led5
    
led4_on
    bsf PORTB,6		;throw LED #4 (PORTB_6 - Pin 12) HIGH
    
continue_led5
    btfsc LED5,0
    goto led5_on
    bcf PORTB,7		;throw LED #5 (PORTB_7 - Pin 13) LOW
    goto continue_led_finished
    
led5_on
    bsf PORTB,7		;throw LED #5 (PORTB_7 - Pin 13) HIGH
    
continue_led_finished	;end of LED servicing
   
uart_service
    ;implement state machine (
    ;   0 = waiting for channel byte,
    ;   1 = waiting for value byte,
    ;	2 = "frame" received, process it! )
    
    ; Bank 0,PIR,RCIF indicates data is ready for read
    ; Bank 0, RCREG read byte from this register
    
prostate0
    
    movfw PROSTATE	    ;remember this "corrupts" W
    sublw 0x00		    ;sets the zero flag if PROSTATE is 0 (ie. state 0)
    btfss STATUS, Z
    goto prostate1	    ;proceeds to the next state test if not this state 
    
    ;is there a channel byte ready for reception?
    call selbank0
    btfss PIR1,RCIF	    ;if bit set indicates data is ready for read
    goto prostate1    ; proceeds without processing if channel byte hasn't hit yet
	
    movfw RCREG	    ;move received byte into W
    movwf PROCHANNEL    ;store received channel byte to PROCHANNEL (bank doesn't matter)
	
    incf PROSTATE	    ;wait for value byte reception by advancing PROSTATE
	
prostate1
    
    movfw PROSTATE	    ;refresh W 
    sublw 0x01		   ;sets the zero flag if PROSTATE is 1 (ie. state 1)
    btfsc STATUS,Z	    ;proceeds to the next state test if not this state 
    
    ;is there a value byte ready for reception?
    call selbank0
    btfss PIR1,RCIF	    ;if bit set indicates data is ready for read
    goto prostate1	; proceeds without processing if value byte hasn't hit yet
	
    movfw RCREG		    ;move received byte into W
    movwf PROVALUE	    ;store received channel byte to PROCHANNEL (bank doesn't matter)
	
    incf PROSTATE	    ;wait for value byte reception by advancing PROSTATE
    
prostate2
    
    movfw PROSTATE	    ;refresh W
    sublw 0x02		    ;sets the zero flag if PROSTATE is 2 (ie. state 2)
    btfsc STATUS,Z	    ;proceeds to end of state machine if not this state
    goto end_of_uart_service
    
    ;process the channel and value command!
    ; to process move PROVALUE contents to appropraite VALUE1->8/LED1->5 register
    movlw VALUE0
    addwf PROCHANNEL
    
    movwf FSR		    ;use register indirection pointed ot by FSR
    movfw PROVALUE	    ;fetch the value we want to save
    movwf 0x00		    ;Register 0x00 is a virtual indirect register
			    ;in usage here it saves PROVALUE directly to
			    ;VALUE1->8/LED1->5 register
			    
    ;that's all we have to do, the channel service routines handle the rest!
    
    clrf PROSTATE	    ;remember to set the protocol state machine to its first state
			    ;so we can receive and process more commands
    
end_of_uart_service
			    
; IMPORTANT: see if we are calling the UART service routine often enough waiting for the
; single-tasking channel service routines. Might just want to turn down the baud rate
; if we are getting overruns. Also remember a 16F88 has a 2-byte receive buffer
; so the host CAN "beam" a command over (ie. shoot over in rapid succession a 2
; byte command as it will be recevied and stored by hardware).
    
    goto start		;all this chip does is loop the algorythm ad infinitum

delay1
;we need to delay for exactly 1 millisecond
;our loop takes 2+(25*W)*200 ns to complete
;since we need a 1 ms delay, or 1000000 ns,
;W needs to be 250. 
    
    movwf CACHEW    ;cache W for restore
    
    movlw 0xFA
    movwf COUNT1
loop_d1
    decfsz COUNT1,1
    goto continue_d1	;a bit of leap frog here
    goto end_d1
continue_d1
    nop	;time wasters (to optimize put a second 20 instruction loop here)
    nop 
    nop
    nop
    nop
    nop
    nop
    nop
    nop
    nop 
    nop
    nop
    nop
    nop
    nop
    nop
    nop
    nop
    nop
    nop
    goto loop_d1
    
end_d1
    movfw CACHEW    ;remember to restore the W register on exit
    return
    
delay2
;remember we pass the channel number in W, NOT the timer value
;fetch the actual timer value by indirect addressing to VALUE1-VALUE8
;initialize the loop with the timer value and do the delay
    
;we need to delay for exactly a linear fraction of millisecond
;our loop takes 2+(25*W)*200 ns to complete
;since we need a certain delay, or 0 -> 1000000 ns,
;W needs to be 250 (or 255 it doesn't matter) for max, 0 for min. 
    movwf CACHEW    ;cache W for restore
    
    movwf FSR	    ;FSR = file select register (how you do indirect addressing)
    movfw INDF	    ;INDF = the indirectly addressed register you are accessing
		    ;the value in VALUE1 - VALUE 8 is now in W
	       
    movwf COUNT2
    
loop_d2
    decfsz COUNT2,1
    goto continue_d2	;a bit of leap frog here
    goto end_d2
continue_d2
    nop			;time wasters (to optimize put a second 
    nop			;20 instruction loop here)
    nop
    nop
    nop
    nop
    nop
    nop
    nop
    nop 
    nop
    nop
    nop
    nop
    nop
    nop
    nop
    nop
    nop
    nop
    goto loop_d2
    
end_d2

    movfw CACHEW    ;remember to restore the W register on exit
    return
    
selbank0
    
    bcf    STATUS,RP0           ;select Registers at Bank 0
    bcf    STATUS,RP1           
    return
	
selbank1
    
    bsf    STATUS,RP0           ;select Registers at Bank 1
    bcf    STATUS,RP1           
    return
	
selbank2
    
    bcf    STATUS,RP0           ;select Registers at Bank 2
    bsf    STATUS,RP1           
    return
	
selbank3
    
    bsf    STATUS,RP0           ;select Registers at Bank 3
    bsf    STATUS,RP1           
    return
    
    end
    

    

Credits

Woodward "Woody" Stanford

Woodward "Woody" Stanford

3 projects • 9 followers
Beagleboard afficianado and physics "disrupter"

Comments