; -----------------------------------------------------------------------------
; 300 bytes brushless motor control
; 
; Copyright (c) Matthias Kramm, 2008
;
; Permission is hereby granted, free of charge, to any person obtaining a copy
; of this software and associated documentation files (the "Software"), to deal
; in the Software without restriction, including without limitation the rights
; to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
; copies of the Software, and to permit persons to whom the Software is
; furnished to do so, subject to the following condition:
; 
; THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
; IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
; FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
; AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
; LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
; OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
; THE SOFTWARE.
; -----------------------------------------------------------------------------

; This code demonstrates sensorless commutation of a brushless dc motor, as
; well as twi bus communication and current limiting (emergency shutdown).
; It's been designed to work with the "mikrokopter" motor control hardware,
; which can be obtained from http://www.mikrokopter.de/
; (In particular, the TWI communication is compatible with the communication
;  protocol employed by the flight control used on mikrokopters, and
;  the microcontroller is assumed to be an atmel atmega8)

.include "atmega8.inc"

; ------------- registers -------------------

.def TMP1 = r16
.def TMP2 = r17
.def WAITFORLEVELSWITCH = r24
.def ZERO = r0
.def EIGHT = r1
.def DELAY = r26
.def CURRENT = r28
.def PWM_MAX = r29 ; right now always 255
.def PHASE = r18
.def STEPPTRL = r30
.def STEPPTRH = r31

; irlr mosfets
;.equ CURRENT_MAX1 = 120
;.equ CURRENT_MAX2 = 200

; standard mosfets
.equ CURRENT_MAX1 = 65
.equ CURRENT_MAX2 = 130

; ------------- interrupts -------------------

; atmega8

.org 0 ; program start address

        ldi r29, 0xff
        out SPL, r29 ; init stack
        clr ZERO
        out SPH, ZERO
        rjmp init

; lookup table for motor commutation
steps:
.db 0x08,0x10,2,32, 0x08,0x20,1,0, 0x04,0x20,0,32, 0x04,0x08,2,0, 0x02,0x08,1,32, 0x02,0x10,0,0

.org 0x11
; twi
twiint:
        in r20, SREG
slaveint:
        ldi r19, (1<<TWEN) | (1<<TWINT) | (1<<TWEA) | (1<<TWIE)
        in r21, TWSR

        andi r21, 0xf8

        ; states not explicitly handled:
        ; 0x60,0x68,0x70,0x78 all mean we received a +w

        out PORTC, ZERO ; clear red led

        cpi r21, 0xd0
        brsh twerror
        cpi r21, 0x60
        brsh noerror
        ; All states < 0x60 or > 0xd0 mean that either there's a bus error (twsr=0x00,0xf8)
        ; or we accidently switched into a master transmitter/receiver mode.
        ; Send a stop to free/reset the line.
twerror:ori r19, (1<<TWSTO)

noerror:
        cpi r21, 0x80
        brne nodata ; 0x80 means we got a data byte
        in r22, TWDR
        cp r22, PWM_MAX
        brlo noclamp
        mov r22, PWM_MAX
noclamp:
        out OCR1AL, r22
        out OCR1BL, r22
        out OCR2, r22
nodata:
        cpi r21, 0xa8
        brne nosend1
        ; 0xa8 means we can send our first byte
        ; (with ea=1 to notify twi that more are to come)
        out TWDR, CURRENT
        ldi r19, (1<<TWEN) | (1<<TWINT) | (1<<TWIE) | (1<<TWEA)
nosend1:
        cpi r21, 0xb8
        brne nosend2
        ; send our second byte
        ; (with ea=0 to signal the end of the transmission)
        out TWDR, PWM_MAX
        ldi r19, (1<<TWEN) | (1<<TWINT) | (1<<TWIE)
nosend2:
done:
        out TWCR, r19
leaveint:
        out SREG, r20
        reti


init:
        ldi r16, 0x08
        mov EIGHT, r16

        out SFIOR,EIGHT ; switch on adc multiplexer

        ldi r16, 0x40    ; store initial motor pwm
        out OCR1AL, r16
        out OCR1BL, r16
        out OCR2, r16

        ldi r16, 0xb8
        out DDRD, r16
        ldi r16, 2<<COM1A0|2<<COM1B0|1<<WGM10 ; 0xa1
        out TCCR1A, r16
        ldi r16, 1<<CS10 ; 0x01
        out TCCR1B, r16
        ldi r16, 1<<WGM20|2<<COM20|1<<CS20 ; 0x61
        out TCCR2, r16

        out PORTD, ZERO
        
        out DDRC, EIGHT
        out PORTC, EIGHT
      
        ; set twi speed
        ; ldi r16, 0xff
        ; out TWBR, r16
        ; out TWSR, ZERO

        ; read out motor address, store as twi address
        ldi r16, 0xc0
        out PORTB, r16
wait:   dec r16
        brne wait
        
        ldi r16, (0x28+4)<<TWA0|0<<TWGCE ; twi: my address+0x28, no general calls
        sbis PINB, 7
        subi r16, 2
        sbis PINB, 6
        subi r16, 4
        ldi r16, (0x28+1)*2
        out TWAR, r16 

        sei
        ldi r16, (1<<TWEN) | (1<<TWINT) | (1<<TWEA) | (1<<TWIE) ; switch twi on
        out TWCR, r16

turn1:
        ldi ZL, LOW(steps*2)
        ldi ZH, HIGH(steps*2)
        ldi r18, 6 ; 6 phases

startmotor:
        ldi r26,255 ; wait 255 loops before commutation
        ldi r24,16 ; not running counter

work:   dec r18
        brmi turn1
    
        lpm r16, Z+
        out DDRB, r16
        lpm r16, Z+
        out PORTD, r16
        ;lpm r16, Z+
        ;out ACSR, r16 ; only needed for interrupts

        ; check current flow
powerlevel:
        out SFIOR, ZERO ; switch off acomp
        ldi r16, 0xe6
        out ADMUX, r16
        ldi r16, 1<<ADEN|1<<ADSC|1<<ADIF|3<<ADPS0 ; single shot conversion, 8 prescale
        out ADCSRA, r16
        ;sbi ADCSRA, ADIF
        ;ldi r16, 6
        ;out ADMUX, r16
        ;sbi ADCSRA, ADSC
waitforadccomplete:
        sbis ADCSRA, ADIF
        rjmp waitforadccomplete
        in r16, ADCH
        lsl r16
        lsl r16
        cp r28, r16
        brsh noinc
        inc r28
noinc:  cp r16, r28
        brsh nodec
        dec r28
nodec:  
        
        ; decrease maximum pwm if current to high
        cpi r28, CURRENT_MAX1
        brlo currentok
        cpi r28, CURRENT_MAX2
        brlo nopowerdown
        out OCR1AL, ZERO
        out OCR1BL, ZERO
        out OCR2, ZERO
nopowerdown:
        tst r29
        breq nochange
revert: dec r29
        rjmp nochange
currentok:
        inc r29
        breq revert
nochange:

        out ADCSRA, ZERO ; back to analog comparator
        out SFIOR, EIGHT
        
        lpm r16, Z+
        out ADMUX, r16
        lpm r17, Z+
      
        clr r27
        clr r15
wait3:  in r16, ACSR
        andi r16, 32
        cp r16, r17
        brne continue
        ldi r16, 0x10
        add r27, r16
        brcc nooverflow
        ldi r27, 0xff
nooverflow:
        mov r26, r27 ; store new delay time, use this if we miss a commutation
        ldi r24, 16 ; reset counter

        rjmp work
continue:
        dec r15
        brne wait3
        inc r27
        cp r27, r26
        brne wait3
      
        dec r24
        brne work
        ; if we didn't sense a commutation 16 times in a row, slow down our
        ; "startup" speed

        rjmp startmotor