The Mass-EV Software Lab

<..back to Mass-EV Forum

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.


Full manual
PIC16F628 Instruction set

Your very basic PCM routine is thus:


Original OpenOffice Drawing

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

Full code

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.


Original OpenOffice Drawing

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
Full code

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


Original QCAD Design
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

Full code
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


Original OpenOffice Drawing

;                            + + +
;                           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

Full code
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
Full code

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