The Mass-EV Software Lab
The software is possibly the most complex part of the project to develop.
Here we strongly believe in simplicity, small steps and peer reviewing.
Because of this, the software will be open source and the steps well documented for maximum peer understanding.
You are free to use any part of this in your own project so long as you understand the terms of the licence:
1. You must acknowledge that you are using Turbo Electric Ltd Mass-EV technology.
2. You must share your modifications and findings with us so we can add it to our project.

PIC16F628 Instruction set
Your very basic PCM routine is thus:

Which for a PIC is:
; *********************************************
; * PCM Routine which give 125kHz *
; *********************************************
PCM_LOOP MOVLW B'00000000' ;
MOVWF PORTB
NOP ; Give symmetry
NOP ; Give symmetry
MOVLW B'00111111' ;
MOVWF PORTB
GOTO PCM_LOOP
However this is a fixed 50/50 mark/space.
This is good as a starting point, but we need to vary the mark/space.
This is where things go wrong, since every instruction take time.
So we need to add loops to add a variable delay.

Which for a PIC is:
; Intialise the mark/space times
MOVLW 63 ; 1 <= count <= 63
MOVWF SPACELENGTH ; Set the space time
MOVLW 1 ; 1 <= count <= 63
MOVWF MARKLENGTH ; Set the mark time
GOTO PCM_LOOP
; *********************************************
; * PCM Routine *
; *********************************************
PCM_LOOP MOVLW B'00000000' ; Set all LEDS off
MOVWF PORTB ; Send to PORT B
MOVF SPACELENGTH,W ; Get the space time
MOVWF SPACETIMER ; Initialise the timer
SPACE_LOOP DECFSZ SPACETIMER,F ; Count down
GOTO SPACE_LOOP ; Loop until zero
NOP ; Give symmetry
NOP ; Give symmetry
MOVLW B'00111111' ; Set all LEDS on
MOVWF PORTB ; Send to PORT B
MOVF MARKLENGTH,W ; Get the mark time
MOVWF MARKTIMER ; Initialise the timer
MARK_LOOP DECFSZ MARKTIMER,F ; Count down
GOTO MARK_LOOP ; Loop until zero
GOTO PCM_LOOP
So we need a mechanism for varying the mark/space ratio.
There is another way to to this:
You can have a fixed loop which counts 0 to 255 and have a variable which holds the mark time.
The loop scans with bits high until the magic value then flips to low.
This is similar to a frame relay used in mobile phones to transmit and receive data to/from the base radio.
This is probably more robust than timers since the the loop always executes the same instructions and may loop faster.
You may be able to have two(or more) registers for 16-bit (or higher) resolution.
At this stage it might be worth pointing out that it's obviously unrealistic to expect the final controller to be using a PIC.
Most likely it will be using an EPIA motherboard or similar and running a real-time Linux kernel (livecd).
This will make communication easy via ethernet and the ability to develop the real-time controller software using a frame relay built as a RT process in the pre-emptive scheduler.
Also the PIC16F628 which is being tested has built-in PWM outputs, so it would seem a better course of action to use these.
Brushless motor sequencing

This is an animation of the fields and phases of 3-phase DC motor commutation.
This is actually the same whether brushed or brushless.
In a brushed motor the commutation is mechanical using a commutator attached to the shaft of the armature and usually carbon brushes.
The animation represents the fields on the armature with respect to the shaft as it rotates.
The actual field with respect to the stator magnets (or the case) is more or less in the same direction at approximately 90 degrees to the stator field for maximum torque.
In a brushless motor the magnets are on the rotor and the stator field is rotated to draw the rotor field around.
This rotating stator field is provided by 3 electromagnets (A, B and C in the animation) at 120 degrees to each other and energised in sequence using a controller.
This is the commutation we are aiming for in the PIC software, which is easy to translate using the table on the left of the animation.
; ***********************************************
; * PCM Routine which does BLDC commutation *
; * Without the delays rotates in 48uS (21kHz) *
; ***********************************************
PCM_LOOP MOVLW B'00011010' ; 1
MOVWF PORTB
CALL DELAY_ROUTINE
NOP ; Give symmetry
NOP ; Give symmetry
MOVLW B'00010010' ; 2
MOVWF PORTB
CALL DELAY_ROUTINE
NOP ; Give symmetry
NOP ; Give symmetry
MOVLW B'00010110' ; 3
MOVWF PORTB
CALL DELAY_ROUTINE
NOP ; Give symmetry
NOP ; Give symmetry
MOVLW B'00000110' ; 4
MOVWF PORTB
CALL DELAY_ROUTINE
NOP ; Give symmetry
NOP ; Give symmetry
MOVLW B'00100110' ; 5
MOVWF PORTB
CALL DELAY_ROUTINE
NOP ; Give symmetry
NOP ; Give symmetry
MOVLW B'00100100' ; 6
MOVWF PORTB
CALL DELAY_ROUTINE
NOP ; Give symmetry
NOP ; Give symmetry
MOVLW B'00100101' ; 7
MOVWF PORTB
CALL DELAY_ROUTINE
NOP ; Give symmetry
NOP ; Give symmetry
MOVLW B'00100001' ; 8
MOVWF PORTB
CALL DELAY_ROUTINE
NOP ; Give symmetry
NOP ; Give symmetry
MOVLW B'00101001' ; 9
MOVWF PORTB
CALL DELAY_ROUTINE
NOP ; Give symmetry
NOP ; Give symmetry
MOVLW B'00001001' ; 10
MOVWF PORTB
CALL DELAY_ROUTINE
NOP ; Give symmetry
NOP ; Give symmetry
MOVLW B'00011001' ; 11
MOVWF PORTB
CALL DELAY_ROUTINE
NOP ; Give symmetry
NOP ; Give symmetry
MOVLW B'00011000' ; 12
MOVWF PORTB
CALL DELAY_ROUTINE
GOTO PCM_LOOP
DELAY_ROUTINE MOVLW H'7F'
MOVWF TIMER1
DEL_LOOP1 MOVLW H'FF'
MOVWF TIMER2
DEL_LOOP2 DECFSZ TIMER2,F
GOTO DEL_LOOP2
DECFSZ TIMER1,F
GOTO DEL_LOOP1
RETLW 0
A simple commutation loop which doesn't use PCM, but steps the motor quite adequately.
You can see it working in the Electronics Lab.
Obviously this is a fixed speed and is sensorless, so you need to spin the motor up by hand and then it runs on it's own.
Adding manual control
This has been improved with the ability to vary the speed using buttons

; + + +
; AABBCC
; - - -
STEP01 EQU B'00011010'
STEP02 EQU B'00010010'
STEP03 EQU B'00010110'
STEP04 EQU B'00000110'
STEP05 EQU B'00100110'
STEP06 EQU B'00100100'
STEP07 EQU B'00100101'
STEP08 EQU B'00100001'
STEP09 EQU B'00101001'
STEP10 EQU B'00001001'
STEP11 EQU B'00011001'
STEP12 EQU B'00011000'
BLDC_LOOP MOVLW STEP01 ; Get bit pattern for BLDC step
MOVWF PORTB ; Set output lines
CALL DELAY_ROUTINE ; Do intersample delay
NOP ; Give symmetry
NOP ; Give symmetry
. . . steps 02 - 11 removed for clarity
MOVLW STEP12 ; Get bit pattern for BLDC step
MOVWF PORTB ; Set output lines
CALL DELAY_ROUTINE ; Do intersample delay
GOTO BLDC_LOOP ; Repeat BLDC sequence (2 cycles)
DELAY_ROUTINE MOVF TIME1,W ; Get initial count
MOVWF TIMER1 ; Set the timer
DEL_LOOP DECFSZ TIMER1,F ; Count down
GOTO DEL_LOOP ; Repeat
MOVF TIME3,W ; Get initial count
MOVWF TIMER3 ; Set the timer
DEL_LOOP4 DECFSZ TIMER3,F ; Count down
GOTO DEL_LOOP4 ; Repeat
INCFSZ TIME2,1 ; Count up button scan timer (256 wrap around)
GOTO ENDLOOP ; Jump out if non-zero
GOTO UPDOWN ; Jump to button scan if zero (every 256 delays)
ENDLOOP NOP ; Give symmetry
NOP ; Give symmetry
NOP ; Give symmetry
NOP ; Give symmetry
NOP ; Give symmetry
RETLW 0 ; Return to BLDC loop
UPDOWN BTFSC PORTA,SW1 ; Skip next if SW1 is up
DECFSZ TIME1,F ; Decrement first timer initial value
GOTO UP ; Jump if first timer initial value is non-zero
INCF TIME1,F ; Re-increment first timer if zero
UP BTFSC PORTA,SW2 ; Skip next if SW2 is up
INCFSZ TIME1,F ; Increment first timer initial value
GOTO UPDOWN3 ; Jump if first timer initial value is non-zero
DECF TIME1,F ; Re-decrement to 255 first timer if zero
UPDOWN3 BTFSC PORTA,SW3 ; Skip next if SW3 is up
DECFSZ TIME3,F ; Decrement second timer initial value
GOTO UP3 ; Jump if second timer initial value is non-zero
INCF TIME3,F ; Re-increment second timer if zero
UP3 BTFSC PORTA,SW4 ; Skip next if SW4 is up
INCFSZ TIME3,F ; Increment second timer initial value
GOTO BACK ; Jump if second timer initial value is non-zero
DECF TIME3,F ; Re-decrement to 255 first timer if zero
BACK INCF TIME2,F ; Bump up button scan counter
RETLW 0 ; Return to BLDC loop
This actually uses 2 loops with 2 separate button controls (SW1 & 2 first loop, SW3 & 4 second loop).
This was done to experiment with the motor's limits and extra delay is required to slow the loop down to usable periods.
Using a oscilloscope BLDC frequency input was 555.5Hz (the 12 step sequence repeated 555.5 times a second).
The motor under experiment is a 7 phase model helicopter motor running at 3v.
So we can now work out that the highest rpm was found to be 4,761.4 RPM (555.5Hz / 7 phases x 60 seconds).
The PIC could drive it to much higher RPMs, but the motor didn't seem capable of physically spinning any faster.
Once I increased the voltage to 6v, which was the maximum my 3A power pack would allow before cutting out,
I got the frequency up to 943.3Hz, which translates to 8,085 RPM.
That's over 6 times the rated RPM!
Not bad for a motor rated at max 1,300 RPM
Of course, in a vehicle the motor will in no way require these extreme RPMs (more likely a twentieth of this),
but it proves the humble low spec PIC is quite able to operate fast enough to run a BLDC motor in a real car.
PWM control
In order to modularise things it might be easier to have separate PWM control to the commutation.
Not sure about this as it might end up more expensive, but we need to have all solutions tested before we can pick the best one.
In that light a simple PWM algorithm has been created to test PWM control on a brushed series would motor.
This is much the same as the BLDC controller but with 2 steps and separate delays for each step.
; **********************************
; ** RESET : main boot routine **
; **********************************
RESET MOVLW B'00000111' ; Disable Comparator module's
MOVWF CMCON
BSF STATUS,RP0 ; Switch to register bank 1
; Disable pull-ups
; INT on rising edge
; TMR0 to CLKOUT
; TMR0 Incr low2high trans.
; Prescaler assign to Timer0
; Prescaler rate is 1:256
MOVLW B'11010111' ; Set PIC options (See datasheet).
MOVWF OPTION_REG ; Write the OPTION register.
CLRF INTCON ; Disable interrupts
MOVLW B'11000000' ; RB7 & RB6 are inputs, RB5...RB0 are outputs.
MOVWF TRISB ; Set BLDC sequence port
MOVLW B'11111111' ; all RA ports are inputs
MOVWF TRISA ; Set button port
BCF STATUS,RP0 ; Switch Back to reg. Bank 0
CLRF PORTB ; Reset BLDC sequence port
MOVLW MARKINITIAL ; Get default value for MARKTIMER
MOVWF MARKPERIOD ; Initialise MARKTIMER
MOVLW SPACEINITIAL ; Get default value for SPACETIMER
MOVWF SPACEPERIOD ; Initialise SPACETIMER
GOTO PCMLOOP ; Start the PCM loop (non return)
; ***********************************************
; * PCM Routine which does Mark-Space *
; ***********************************************
PCMLOOP MOVLW SOMEON ; Get bit pattern for PWM HIGH
MOVWF PORTB ; Set output lines
CALL MARKDELAY ; Do intersample delay
NOP ; Give symmetry
NOP ; Give symmetry
MOVLW SOMEOFF ; Get bit pattern for PWM LOW
MOVWF PORTB ; Set output lines
CALL SPACEDELAY ; Do intersample delay
GOTO PCMLOOP ; Repeat
; ***********************************************
; * Button read and period adjustments *
; ***********************************************
MARKDELAY MOVF MARKPERIOD,W ; Get initial count
MOVWF MARKTIMER ; Set the timer
MARKDELAYLOOP DECFSZ MARKTIMER,F ; Count down
GOTO MARKDELAYLOOP ; Repeat
INCFSZ BUTTONTIMER,W ; Count up button scan timer (256 wrap around)
GOTO ENDMARKDELAY ; Jump out if non-zero
GOTO MARKUPDOWN ; Jump to button scan if zero (every 256 delays)
ENDMARKDELAY NOP ; Give symmetry
NOP ; Give symmetry
NOP ; Give symmetry
NOP ; Give symmetry
NOP ; Give symmetry
RETLW 0 ; Return to BLDC loop
MARKUPDOWN BTFSC PORTA,SW1 ; Skip next if SW1 is up
DECFSZ MARKPERIOD,F ; Decrement first timer initial value
GOTO MARKUP ; Jump if first timer initial value is non-zero
INCF MARKPERIOD,F ; Re-increment first timer if zero
MARKUP BTFSC PORTA,SW2 ; Skip next if SW2 is up
INCFSZ MARKPERIOD,F ; Increment first timer initial value
GOTO MARKBACK ; Jump if first timer initial value is non-zero
DECF MARKPERIOD,F ; Re-decrement to 255 first timer if zero
MARKBACK INCF BUTTONTIMER,F ; Bump up button scan counter
RETLW 0 ; Return to BLDC loop
SPACEDELAY MOVF SPACEPERIOD,W ; Get initial count
MOVWF SPACETIMER ; Set the timer
SPACEDELAYLOOP DECFSZ SPACETIMER,F ; Count down
GOTO SPACEDELAYLOOP ; Repeat
INCFSZ BUTTONTIMER,W ; Count up button scan timer (256 wrap around)
GOTO ENDSPACEDELAY ; Jump out if non-zero
GOTO SPACEUPDOWN ; Jump to button scan if zero (every 256 delays)
ENDSPACEDELAY NOP ; Give symmetry
NOP ; Give symmetry
NOP ; Give symmetry
NOP ; Give symmetry
NOP ; Give symmetry
RETLW 0 ; Return to BLDC loop
SPACEUPDOWN BTFSC PORTA,SW3 ; Skip next if SW3 is up
DECFSZ SPACEPERIOD,F ; Decrement first timer initial value
GOTO SPACEUP ; Jump if first timer initial value is non-zero
INCF SPACEPERIOD,F ; Re-increment first timer if zero
SPACEUP BTFSC PORTA,SW4 ; Skip next if SW4 is up
INCFSZ SPACEPERIOD,F ; Increment first timer initial value
GOTO SPACEBACK ; Jump if first timer initial value is non-zero
DECF SPACEPERIOD,F ; Re-decrement to 255 first timer if zero
SPACEBACK INCF BUTTONTIMER,F ; Bump up button scan counter
RETLW 0 ; Return to BLDC loop
END
Now the PIC has a PWM output controller built in as well (called a CCP module), but we are not going to use it as yet.
This is for two reasons:
The code for a PWM was created as precursor to the BLDC so it's relatively simple to update it.
The CCP module is a little in-depth and is not required just to build/test the electronics/electrics.
This is worth a mention:
http://open-bldc.org/wiki/Open-BLDC