; BRUSHLESS ESC (flea101_dt1a.asm) ; This program is intended for small 3-phase permanent magnet brushless motors ; It is based on Bernhard Konze's 'Flea101' software with the following changes: ; (i) Modified to run on Turnigy 6A commercial ESC ; (ii) Commented to aid interpretation/understanding ; (iii) Modified to be a free-flight timer (FF) ; (iv) Implemented a simple stalled motor check ; (v) Added an LED to aid programming run time and flying at night ; See www.flyelectric.ukgateway.net for more info ; Copyright David Theunissen May 2008 and... ;------------------------------------------------------------------------- ;Die Benutzung der Software ist mit folgenden Bedingungen verbunden: ; ;1. Da ich alles kostenlos zur Verfügung stelle, gebe ich keinerlei Garantie ; und übernehme auch keinerlei Haftung für die Folgen der Benutzung. ; ;2. Die Software ist ausschließlich zur privaten Nutzung bestimmt. Ich ; habe nicht geprüft, ob bei gewerblicher Nutzung irgendwelche Patentrechte ; verletzt werden oder sonstige rechtliche Einschränkungen vorliegen. ; ;3. Jeder darf Änderungen vornehmen, z.B. um die Funktion seinen Bedürfnissen ; anzupassen oder zu erweitern. Ich würde mich freuen, wenn ich weiterhin als ; Co-Autor in den Unterlagen erscheine und mir ein Link zur entprechenden Seite ; (falls vorhanden) mitgeteilt wird. ; ;4. Auch nach den Änderungen sollen die Software weiterhin frei sein, d.h. kostenlos bleiben. ; ;!! Wer mit den Nutzungbedingungen nicht einverstanden ist, darf die Software nicht nutzen !! ; ; October 2004 ; autor: Bernhard Konze ; email: bernhard.konze(at)versanet.de ; web: http://home.versanet.de/~b-konze/blmc_flea/blmc_en.htm ;--------------------------------------------- ; Changes made to fblmc101.asm ; 1. changed portc OUT instructions in 'reset' (portd was being set twice) ; 2. deleted dedundant steps from 'switch_power_off' (already done in calling routine) ; ldi temp1, INIT_PB ; all off ; out PORTB, temp1 ; ldi temp1, CHANGE_TIMEOUT ; reset change-timeout ; mov tcnt0_change_tot, temp1 ; 3. PORTB changed to PORTD where pFets are on a different port ; 4. changed sbi to cbi and visa versa for all pFETs because driven via transistors ; 5. merged .inc file with this one for convenience ; 6. changed state0 to flags0 and state1 to flags1 register names (leaving state1 as sub-routine) ; 7. changed 'subi temp2, 0xca' (in calculate_next_timing_values) to 0xcc to match quoted max rpm ; 8. i_temp1 changed to temp1 in evaluate_sys_state ; 9. added new FF code/variables and changed sram addressing to dseg/.byte directive ; 10. load .eep file at least once to get default FF run time into eeprom ; 11. added LED to beeps ; 12. deleted 'evaluate_rc_puls' from state2 (triggered with another flag for wait_OCT1_before_switch) ; 13. deleted 'calculate_next_timing_values' from state6 (triggered with flag as well) ; 14. changed 'wait_if_spike' to 4us as intended according to comment (was ~14us) ; 15. added free flight code ; 16. added stalled motor check ; Queries ; should change 'ff_program' from sram to flag2 ; does comparator need to be configured every time in wait_OCT1_tot ; add beeps after stopping? (lost model) ; lost signal counter rcpuls_timeout not working (when radio switched off) - reduce RCP_TOT? ; External Interrupt config would need changing if 1 instead of 0 was used (ISC00 ISC01) ; could change GIMSK to GICR as new name but both work ; voltage and current monitoring not enabled ; should cut throttle if over-current in evaluate_sys_state ; occassional hiccup while running ; FF setup program hangs or corrupts eeprom run time sometimes (setting added to restore default) ;==================================================================== ; P R O G R A M D E S C R I P T I O N ; FREE FLIGHT ; The push-button sets the throttle to max and existing acceleration dampner increases power smoothly ; An existing timer function has been modified to time run time and then cut the throttle ; The duration of the orginal decelleration dampner has been doubled to provide a 6s ramp down ; A stalled motor function has been written to cut the motor ; This should work in normal 'RC' mode as well as 'FF' ; CORE ESC OPERATION ; 3 phases are activated in 6 states considered to be 1 electrical revolution, ie: 360' ; 1 electrical revolution assumes 2 magnet poles; more physical poles = slower rpm ; Commutation occurs every 60' in a 2pole motor (360/6 states) ; BEMF is used to determine mid-point between states (30') called 'zero-crossing' (ZC) ; The next commutation occurs 30' later, earlier if using advance timing ; (this program uses 7.5' advance timing so commutation occurs at 30 - 7.5 = 22.5') ; The core program operation is summarised below ; Functions (:) are described in a tiered structure as they are called (ie: text-based flow chart) ; This sequence, along with clock speed and pwm frequency, limits max possible rpm ;-------------------------------------------------------------------- ; reset: ; (main configuration function) ; switch_power_off: ; (setup configuration) ; beep328ms: ; (single beep) ; control_start: ; (setup configuration) ; waits for button press to program run time or start motor ; eeprom_read: ; (reads FF run time from eeprom) ; eeprom_write: ; (saves new run times to eeprom ; beep250: ; (lower pitch beep) ; beep328ms: ; (double beep after radio checks) ; calculate_next_timing_values: ; (see below) ; set_default_timeout: ; (see below) ; wait_OCT1_tot: ; (sets startup timing (part of wait_OCT1_before_comp_scan) ;--------------------------------------------- ; state#: ; (6 states, each starting shortly before ZC) ; (works closely with various interrupts, particularly t0ovfl_int which switches fets) ; (timer1 times speed of operation (=rpm) and is used for timing (adjusted once per revolution) ; waits for ZC (or startup timeout if shorter) ; evaluate_ff_stop: ; check for button press to stop motor manually (FF, state1 only) ; evaluate_sys_state: ; performed in state 5 only ; check over-current/under-voltage every 65ms (not enabled) ; set_new_duty: ; controls throttle behaviour, essential for responding to throttle changes/enabling fets ; sets pwm duty cycle and creates a throttle curve for low rpm ; reduces throttle if under-voltage detected (not enabled) ; wait_if_spike: ; waits 14us after ZC to confirm ZC (bypassed during startup) ; wait_OCT1_before_switch: ; (imposes delay after ZC for next commutation to achieve desired advance timing) ; tcnt1_to_temp: ; gets current commutation time-stamp ; add advance timing delay (22.5' if using 7.5' advance timing) ; wait greater of that and performing following checks... ; evaluate_rc_puls: ; performed in state 2 only ; converts throttle setting to fet duty cycle ; calculate_next_timing_values: ; performed in state 6 only ; determines rpm, sets rpm limits, sets commutation timing ; tcnt1_to_temp: ; get current commutation time-stamp ; calculate rpm/time taken to perform 6 state changes (360') ; calculate 22.5' duration for delays/rpm comparisons ; convert throttle position to an rpm scale to compare against this ; set_default_timeout: ; resets timing if rpm too slow or too fast ; calculate advance timing delay (also 22.5' but different examples are provided) ; set advance timing delay for next 6 states (22.5 if 7.5') ; perform commutation (ie: enable/disable fets for next state) ; configure comparator for use during next state ; evaluate_stall: ; stalled motor check (state3 only) ; soft_reset: ; resets program ; wait_OCT1_before_comp_scan: ; imposes delay after commutation before scanning for ZC (to avoid inductive spike) ; tcnt1_to_temp: ; get current commutation time-stamp ; wait 22.5' to avoid inductive spike ; wait_OCT1_tot: ; configure startup timing delay (but don't wait) ; move on to next state (endless loop unless errors detected) ;--------------------------------------------- ; ext_int0: ; interrupt to time rc pulses from receiver ; performs some glitch validation ; pulses typically go high every ~20ms and low after 1-2ms (= 2 interrupts) ;--------------------------------------------- ; t1oca_int: ; interrupt to signal end of commutation timing delays ; occurs twice in every state ; clears OCT1_PENDING flag ;--------------------------------------------- ; t1ovfl_int: ; interrupt to create a 24bit commutation timer ; also acts as a lost signal counter ; times FF run time and cuts throttle ; occurs every 65ms ;--------------------------------------------- ; t0ovfl_int: ; interrupt to create pwm mechanism for fets ; works in pairs (ON and OFF cycles) - 2 interrupts per period ; one pair intended to sum to 100us (10kHz pwm period) ; toggles nFETs only, working closely with the 6 'state' functions ; imposes acceleration and deceleration dampening ;--------------------------------------------- ; read_uart: ; various uart control functions ;==================================================================== ; P R O G R A M S T A R T S H E R E ;==================================================================== ; set fuses to 8MHz with programmer ; device file .include "m8def.inc" .equ UART_CONTROL = 0 ; UART (1) RC (0) FF (0) .equ UART_FULL = 0 ; UART (1) RC (0) FF (0) .equ RC_PULS = 1 ; UART (0) RC (1) FF (1) .equ FREE_FLIGHT = 1 ; UART (0) RC (0) FF (1) ; 1=enabled, 0=disabled ; for RC use, set only RC_PULS to 1 ; for FF use, set RC_PULS and FREE_FLIGHT to 1 ; UART control has not beet tested in this version ;-------------------------------------------------------------------- ; port definitions ; (Comments: port#, pin#, direction, initial state/pullup enabled, purpose) ; (DDRx: 1=output, 0=input) ; (PORTx: 1=inputs:pullup enabled/outputs:high, 0=inputs:pu disabled/outputs:low) ; (to minimise current, unused ports made inputs with internal pullup (pu) enabled) ;.equ spare = 7 ; PB7 pin8 input high/pu - ;.equ spare = 6 ; PB6 pin7 input high/pu - .equ led = 5 ; PB5 pin17 output low - LED (also SCK for programming) ;.equ miso = 4 ; PB4 pin16 output low - ;.equ mosi = 3 ; PB3 pin15 input low - .equ CnFET = 2 ; PB2 pin14 output low - N-fet (low=off) .equ AnFET = 1 ; PB1 pin13 output low - .equ BnFET = 0 ; PB0 pin12 output low - .equ DIR_PB = 0b00110111 ; Configures Data Direction register 'DDRB' .equ INIT_PB = 0b11000000 ; Configures Data Register 'PORTB' state/pu's ;.equ spare = 7 ; ADC7 pin22 input high/pu - ;.equ spare = 6 ; ADC6 pin19 input high/pu - .equ mux_c = 5 ; PC5 pin28 input low - comparators (pu off) .equ mux_a = 4 ; PC4 pin27 input low - ;.equ spare = 3 ; PC3 pin26 input high/pu - .equ mux_b = 2 ; PC2 pin25 input low - ;.equ spare = 1 ; PC1 pin24 input high/pu - .equ voltage = 0 ; PC0 pin23 input low - voltage measurement .equ DIR_PC = 0b00000000 ; Configures Data Direction register 'DDRC' .equ INIT_PC = 0b11001010 ; Configures Data Register 'PORTC' state/pu's ;.equ spare = 7 ; PD7 pin11 input high/pu - .equ c_comp = 6 ; PD6 pin10 input low - comparator common (pu off) .equ CpFET = 5 ; PD5 pin9 output low - P-fet (via tranny low=off) .equ ApFET = 4 ; PD4 pin2 output low - .equ BpFET = 3 ; PD3 pin1 output low - .equ rcp_in = 2 ; PD2 pin32 input low - Rx / FF push-button ;.equ spare = 1 ; PD1 pin31 input high/pu - ;.equ spare = 0 ; PD0 pin30 input high/pu - .equ DIR_PD = 0b00111000 ; Configures Data Direction register 'DDRD' .if FREE_FLIGHT == 1 .equ INIT_PD = 0b10000111 ; enables pullup for FF push-button (press goes low) .else .equ INIT_PD = 0b10000011 ; Configures Data Register 'PORTD' state/pu's .endif ;-------------------------------------------------------------------- ; variables and other configuration constants .if FREE_FLIGHT == 1 .equ CHANGE_TIMEOUT = 150 ; ramp up time (150 ~1s motor start) .equ CHANGE_TOT_LOW = 255 ; ramp down time (255 ~6s motor fade) ; 255 is max possible; works with FF_STRETCH flag to double effect .else .equ CHANGE_TIMEOUT = 50 ; acceleration dampner (50 lowest recommended = responsive) ; forces 'n' PWM periods before allowing duty cycle to be increased .equ CHANGE_TOT_LOW = 40 ; deceleration dampner (40 lowest recommended) ; forces 'n' PWM periods before allowing duty cycle to be reduced .endif .equ POWER_RANGE = 100 ; max PWM duty cycle/period (100us = 10kHz) .equ MIN_DUTY = 10 ; min PWM duty cycle (=minimum startup energy/current) .equ NO_POWER = 256-MIN_DUTY ; timer0 interrupts at 256 so start at 256-duration (eg: 246 for 10us) ; used to switch FETs off (eg: when a smaller duty cycle exists) .equ MAX_POWER = 256-POWER_RANGE ; as above but generates full duration (eg: 256-100=156) ; used to hold FETs on when running at max duty cycle .equ defaultTIMEOUT = 32000 ; default time for 1 electrical revolution to calculate advance timing .equ compScanTIMEOUT = 24000 ; time between states on startup (before ZC is detected) ; smaller = more eager to start .equ T1STOP = 0 ; Disables timer 1 .equ T1CK8 = 0b010 ; Enables timer1 with clock/8 prescaler (=1us/count) ; leaves defaults eg: negative/falling edge .equ EXT0_DIS = 0 ; disables ext0int in GIMSK settings .if RC_PULS == 1 .equ EXT0_EN = 0b01000000 ; enables ext0int (int0) .else .equ EXT0_EN = 0 ; disables ext0int .endif .equ MIN_RC_PULS = 1100 ; threshold for zero throttle ; program expects above number + 800 to be high throttle .equ FF_DEF_TIME = 1 ; default FF run time (x 5s) + ramp up and down time ;-------------------------------------------------------------------- ; Register Definitions .def i_sreg = r1 ; status register saved in interrupts .def tcnt0_power_on = r2 ; timer0 counts duration nFETs are switched on .def tcnt0_change_tot = r3 ; counter used to slow down rate at which changes are made ; value set to either CHANGE_TIMEOUT or CHANGE_TOT_LOW .def TCNT1X = r4 ; extra 8bit timer1 register (to make 24bit) .def uart_cnt = r5 ; .def tcnt0_pwron_next = r6 ; duty cycle required for next PWM cycle .def start_rcpuls_l = r7 ; previous timer value at start of a new pulse measurement .def start_rcpuls_h = r8 .def new_rcpuls_l = r9 ; latest pulse length .def new_rcpuls_h = r10 .def rcpuls_timeout = r11 ; lost signal detection (counts down from 100) .equ RCP_TOT = 100 ; Timer1 overflows with no rc pulse being received before resetting .def current_err = r12 ; counts over-current errors .equ CURRENT_ERR_MAX = 3 ; performs a reset after MAX errors .def sys_control = r13 ; counts under-voltage errors (100 max) which is used to reduce duty cycle .def ee_h = r14 ; eeprom addresses (0-511) .def ee_l = r15 .def temp1 = r16 ; main temporary .def temp2 = r17 ; main temporary .def temp3 = r18 ; main temporary .def temp4 = r19 ; main temporary .def i_temp1 = r20 ; interrupt temporary .def i_temp2 = r21 ; interrupt temporary .def i_temp3 = r22 ; interrupt temporary .def flags0 = r23 ; state flags (all cleared on reset) - numbers are bit numbers .equ OCT1_PENDING = 0 ; if set, Output Compare/Timer 1 interrupt (OCT1) is pending (0=delay complete) ; cleared after comparator interrupt, set during delays before and after commutation ; and set on startup .equ UB_LOW = 1 ; set if accu voltage low .equ I_pFET_HIGH = 2 ; set if over-current detect .equ GET_STATE = 3 ; set if state is to be send .equ C_FET = 4 ; if set, C-FET state is to be changed .equ A_FET = 5 ; if set, A-FET state is to be changed ; if neither A nor C are set, B-FET state is to be changed .equ I_OFF_CYCLE = 6 ; if set, PWM ON cycle; 0 = OFF cycle .equ EVAL_UB = 7 ; if set, next PWM on should check voltage .def flags1 = r24 ; state flags (all cleared on reset) - numbers are bit numbers .equ POWER_OFF = 0 ; if set, don't switch FETs on .equ FULL_POWER = 1 ; if set, don't switch FETs off (but still do other steps in OFF cycles) .equ CALC_NEXT_OCT1 = 2 ; if set, calculate OCT1 offset (state6 only) .equ RC_PULS_UPDATED = 3 ; new rc-puls value available (set when new rc pulse received) .equ EVAL_RC_PULS = 4 ; if set, new rc puls is evaluated while waiting for OCT1 (state2 only) .equ EVAL_SYS_STATE = 5 ; if set, overcurrent and undervoltage are checked (state5-not used) .equ EVAL_I_pFET = 6 ; if set, next PWM on should look for current .equ T1OVFL_FLAG = 7 ; each timer1 overflow sets this flag - used for voltage + current checks every 65ms .def flags2 = r25 ; state flags .equ FF_STRETCH = 0 ; toggles between 0 and 1 to double ramp down time in lower_pwm: ; XYZ registers (r26-r31) - no alias defined ;-------------------------------------------------------------------- ; RAM Definitions (addresses for saving values in SRAM) .dseg ; SRAM definitions .org 0x0060 ; start address ; 24-bit timer1 value (commutation timing etc) tcnt1_sav_l: .byte 1 ; latest tcnt1_sav_h: .byte 1 tcnt1_sav_x: .byte 1 last_tcnt1_l: .byte 1 ; previous last_tcnt1_h: .byte 1 last_tcnt1_x: .byte 1 ; commutation timing delays wt_comp_scan_l: .byte 1 ; wait time from switching FET off to comparator scan wt_comp_scan_h: .byte 1 ; (22.5' delay before expected 30' zero-crossing) wt_FET_switch_l: .byte 1 ; time from zero-crossing to next commute wt_FET_switch_h: .byte 1 ; (22.5' delay if using 7.5' advance timing) ; (timing is hard-coded in calculate_next_timing_values) OCT1_high: .byte 1 ; indicates rpm for part of range (>14.7k 2pole if 0, 1831 2pole if 8, etc) ; FF variables ff_runtime_x5: .byte 1 ; run time (multiples of 5sec) - saved to eeprom ff_runtime_x65: .byte 1 ; run time (multiples of 65ms) - timer1 overflow interrupt ff_program: .byte 1 ; 1=Program mode, 0=normal run mode stall_buffer: .byte 1 ; counter to buffer stalled motor checks uart_data: .byte 100 ; only for debug requirements ;------------------------------------------------------------------------- ; Reset and Interrupt jump table ; this section configures what happens at startup (vector 1) ; and if there are interrupts (vectors 2:19) ; each vector either has a 'jump to' instruction or does nothing (nop) .eseg ; define eeprom segment .org 16 ; start address .db FF_DEF_TIME ; default run time (x 5s) ;.db 1 ; default run time (x 5s) .cseg ; start of program code .org 0 ; originating at address 0 (vector 1) rjmp reset ;vector 1 - jump to label for start of real program .if RC_PULS == 1 .if FREE_FLIGHT == 0 rjmp ext_int0 ;2 rc pulse interrupt (Rx pulses in normal use) .else nop ;2 int0 (not used when RC_PULS==0) .endif .endif nop ;3 ext_int1 (not used) nop ;4 t2oc_int (not used) nop ;5 t2ovfl_int (not used) nop ;6 icp1 (not used) rjmp t1oca_int ;7 timer1/compA interrupt (commutation and delays) nop ;8 t1ocb_int (not used) rjmp t1ovfl_int ;9 timer1 overflow (every 65536µs) rjmp t0ovfl_int ;10 timer0 overflow (every 256µs) nop ;11 spi_int (not used) nop ;12 urxc (not used) nop ;13 udre (not used) .if UART_CONTROL == 1 .if UART_FULL == 1 rjmp utxc ;14 uart control (when enabled for testing) .else nop ;14 utxc .endif .else nop ;14 utxc .endif ; not used nop ;15 adc_int (not used) ; not used nop ;16 eep_int (not used) ; not used nop ;17 aci_int (not used) ; not used nop ;18 wire2_int (not used) ; not used nop ;19 spmc_int (not used) version: .db "bkf101" ;stores the version number in ram ;------------------------------------------------------------------------- ; Initialisation on reset ; run-once normally (startup) but also over-current abort and if rc signal is lost reset: ; define SRAM for use as Stack ldi temp1, high(RAMEND) ;loads highest available SRAM address out SPH, temp1 ;into the stack pointer ldi temp1, low(RAMEND) out SPL, temp1 ; portB config ldi temp1, INIT_PB ;defines initial state (high/low) out PORTB, temp1 ;configures state ldi temp1, DIR_PB ;defines input/output direction out DDRB, temp1 ;configures data direction ; portC config ldi temp1, INIT_PC out PORTC, temp1 ;configures state ldi temp1, DIR_PC out DDRC, temp1 ;configures data direction ; portD config ldi temp1, INIT_PD out PORTD, temp1 ;configures state ldi temp1, DIR_PD out DDRD, temp1 ;configures data direction ; timer0 (8bit) config: PWM + beep control ldi temp1, 0b010 ;clock/8 prescaler (=1us/count) out TCCR0, temp1 ;configures CS bits ; timer1 (16bit) config: commutation control + rc pulse length ldi temp1, T1CK8 ;clock/8 prescaler (=1us/count) out TCCR1B, temp1 ;configures CS bits; rest left at default ; reset state flags clr flags0 clr flags1 clr flags2 clr temp1 sts ff_program, temp1 ; set stalled motor timer ldi temp1, 8 sts stall_buffer, temp1 ; power off rcall switch_power_off ;sets some registers and flags1 to 1 ; reset rc puls timeout ldi temp1, RCP_TOT ;100 (used to detect no rc pulse and reset) mov rcpuls_timeout, temp1 .if UART_CONTROL == 1 ldi ZH,high(version*2) ldi ZL,low(version*2) ldi temp1, 0 ; set to 38400baud (8MHz) out UBRRH, temp1 ldi temp1, 12 out UBRRL, temp1 ldi temp1, 0x18 out UCSRB, temp1 ; enable rx+tx - no interrupts ! in temp1, UDR ; clear possibly pending rxc lpm adiw ZL,1 ;increment Z-pointer mov temp1, r0 ; b rcall send_byte .endif ; disable watchdog timer ; to stop motor once running, WDT can be enabled to reset program ; enabled in evaluate_ff_stop (FF only) and evaluate_stall WDR ; Write logical one to WDCE and WDE in temp1, WDTCR ori temp1, (1<0 (1=already in program mode) ;------------------------ ; normal run mode ; check whether now enabling program mode (press >3s) cpi temp4, 8 ; compare duration with 8 (3s) brcc pgm_mode_enable ; branch if carry 0 (0 when duration >= 8) ; for shorter presses, check that run time >0 lds temp3, ff_runtime_x5 ; load run time (already retrieved from eeprom) tst temp3 ; check if already 0 brne run_config ; configure motor if >0 rcall beep250 ; error beep if 0 rcall beep250 rcall beep250 rcall beep250 rjmp ff_hold ; 0 so wait for another instruction run_config: ; if run time >0, prepare to start motor ldi temp1, 0b11010000 ; prepare to configure HIGH throttle setting (2000us) ldi temp2, 0b111 ; high byte mov new_rcpuls_l, temp1 ; save new pulse duration mov new_rcpuls_h, temp2 sbr flags1, (1<3s press in normal run mode ldi temp1, 1 sts ff_program, temp1 ; set flag for program mode rcall beep328ms ; start/end tone rcall beep328ms rcall beep328ms rcall beep328ms rcall beep328ms rjmp ff_hold ; wait for next button press pgm_mode_eval: ; evaluates config options in program mode ; check whether selecting Default time (used to replace corrupted values) cpi temp4, 8 ; compare duration with 8 (>3s) brcc pgm_mode_def ; branch if carry 0 (0 when duration >= 8) ; check whether Incrementing run time (press >1s but <3s) cpi temp4, 2 ; compare duration with 2 (1s) brcc pgm_mode_inc ; branch if carry 0 (0 when duration >= 2) ; shorter presses Decrement but check if run time is >0 (to avoid making it negative) lds temp3, ff_runtime_x5 ; load run time (already retrieved from eeprom) tst temp3 ; check if already 0 brne pgm_mode_dec ; branch if >0 (proceed) rcall beep250 ; error beep if 0 rcall beep250 rcall beep250 rcall beep250 rjmp ff_hold ; jump if 0 (do nothing) ; decrement run time if >0 (press <1s) pgm_mode_dec: lds temp3, ff_runtime_x5 ; load run time dec temp3 ; reduce run time by '1' (5s) sts ff_runtime_x5, temp3 ; store new run time in SRAM ; save to eeprom clr ee_h ldi temp1, 16 mov ee_l, temp1 ; use address 16 out EEDR, temp3 ; save run time to eeprom rcall eeprom_write ; beep and exit clr temp1 sts ff_program, temp1 ; clear program mode flag rjmp beep_ff_run_time ; beeps current run time rcall wait260ms rjmp ff_hold ; await next instruction ; increment run time (press >1s <3s) pgm_mode_inc: lds temp3, ff_runtime_x5 ; load run time inc temp3 ; increase run time by '1' (5s) sts ff_runtime_x5, temp3 ; store new run time in SRAM ; save to eeprom clr ee_h ldi temp1, 16 mov ee_l, temp1 ; use address 16 out EEDR, temp3 ; save run time to eeprom rcall eeprom_write ; beep and exit clr temp1 sts ff_program, temp1 ; clear program mode flag rjmp beep_ff_run_time ; beeps current run time rcall wait260ms rjmp ff_hold ; await next instruction ; default run time (press >3s) pgm_mode_def: ldi temp3, FF_DEF_TIME ; load default run time sts ff_runtime_x5, temp3 ; store new run time in SRAM ; save to eeprom clr ee_h ldi temp1, 16 mov ee_l, temp1 ; use address 16 out EEDR, temp3 ; save run time to eeprom rcall eeprom_write ; beep and exit clr temp1 sts ff_program, temp1 ; clear program mode flag rjmp beep_ff_run_time ; beeps current run time rcall wait260ms rjmp ff_hold ; await next instruction ;------------------------ motor_start: .endif ;============================================================================== ; enable interrupt registers ldi temp1, (1<min) - loop forever until yes (branch if carry 0) ; carry flag is 1 when new_rcpuls < minimum (OK) dec temp3 ; yes (= power_on ; eg1: power_on = 246 (10us) at startup (set in control_start after reset) ; next = 246 (10us) at startup (set in switch_power_off from reset) ; next == power_on so jump to lower_pwm (although no change required) ; eg2: power_on = 206 (50us) ; next = 226 (30us) ; 226 > 206 so jump to lower_pwm (want shorter ON duration) higher_pwm: ; longer ON duration (accelerate/more power) ; 'next' < 'power_on' (longer duration required) ; 'tcnt0_change_tot' is a counter which slows down rate at which changes are made to pwm duty cycle ; eg: 5ms between changes if CHANGE_TIMEOUT is set to 50 (50*100us/cycle) dec tcnt0_change_tot ; reduce counter by 1 brne nFET_off ; skip (no pwm change) if counter is not 0 yet ; counter reached 0 ldi i_temp2, CHANGE_TIMEOUT mov tcnt0_change_tot, i_temp2 ; reset counter when it does reach 0 dec i_temp1 ; used to reduce tcnt0_power_on which increases pwm ON time rjmp set_next_pwm lower_pwm: ; shorter ON duration (decelerate/less power or no change) ; 'tcnt0_change_tot' forces 4ms between changes if CHANGE_TOT_LOW is set to 40 (40*100us/cycle) breq nFET_off ; branch if 0 (next=power_on) (no pwm or counter changes) .if FREE_FLIGHT == 1 ; decrement counter every second loop in FF mode ; FF_STRETCH flag is toggled to double decelleration time sbrs flags2, FF_STRETCH ; skip next if 1 rjmp lower_pwm_0 ; if 0, jump lower_pwm_1: dec tcnt0_change_tot ; if 1, reduce counter by 1 cbr flags2, (1<0, unrealistic so reset pwm etc to slowest settings (branch if not 0) sts OCT1_high, temp2 ; if 0, motor could be spinning so save 2nd byte ; temp2 (YH) evaluation: ; > 14,648 rpm (2pole) tst temp2 ; test for zero breq calc_OCT1_of10 ; if 0, (> 14,648 rpm), branch to #10 test against throttle position subi temp1, low (defaultTIMEOUT) ; if >0, (< 14,648 rpm) reduce by 32000 to test 114rpm threshold sbci temp2, high(defaultTIMEOUT) ; < 114 rpm brcc calc_OCT1_res ; if 1 (a negative value ie: <114rpm) too slow so reset ; 114 - 14,648 rpm rjmp calc_OCT1_of80 ; if 0, neither too fast nor too slow so just save timing ; > 14,648 rpm (2pole) / 2441rpm (12 pole) - fast idle to full power calc_OCT1_of10: ; convert throttle pwm range to a scale that can be used to compare against the '1/16th' rpm durations ; the formula is somewhat obscure but has the effect of compressing the throttle range ; while also aligning it to the temp1 value that represents maximum rpm ; throttle range (timer0 count) is normally 156-246 and is converted to 27-49 ; '27' therefore represents both max throttle and max rpm to prevent an over-rpm condition ; '49' represents zero throttle and 76,531 rpm ; these has the effect of checking for over-rpm between 76,531 and 138,888 over the throttle range mov temp2, tcnt0_power_on ; load current throttle position (156-246) asr temp2 ; eg: 156/0b10011100 becomes 206/0b11001110 asr temp2 ; eg: 206/0b11001110 becomes 231/0b11100111 subi temp2, 0xcc ; eg: 231 becomes 27 (ie: 156 becomes 27) cp temp1, temp2 ; compare temp2 (converted throttle level) with temp1 (rpm) ; carry flag=0 if temp1 >= temp2 brcc calc_OCT1_of80 ; branch to #80 (normal action) if throttle >= rpm (branch if carry flag 0) ; continue to reset if throttle < rpm (rpm too high/possible timing error) ; reset pwm if unrealistic values detected calc_OCT1_res: rcall set_default_timeout ; set default commutation settings rjmp calc_OCT1_of90 ; save timer values and exit ; normal operation calc_OCT1_of80: ; save 1st timing value sts wt_comp_scan_l, YL ; save 22.5' offset used to bypass inductive spike sts wt_comp_scan_h, YH ; if an other timing than 7.5° is wanted, change YL,YH here: ; example (13.1°): ; lsr YH ; ror YL ; mov temp1,YL ; 1/32 + 1/64 = 1/21.33 (30°-16.9°=13.1°) ; mov temp2,YH ; lsr temp2 ; ror temp1 ; add YL, temp1 ; adc YH, temp2 ; example (10.3°): ; lsr YH ; ror YL ; mov temp1,YL ; 1/32 + 1/64 + 1/128 = 1/18.3 (30°-19.7°=10.3°) ; mov temp2,YH ; lsr temp2 ; ror temp1 ; add YL, temp1 ; adc YH, temp2 ; lsr temp2 ; ror temp1 ; add YL, temp1 ; adc YH, temp2 ; example (8.9°): ; lsr YH ; ror YL ; mov temp1,YL ; 1/32 + 1/64 + 1/128 + 1/256 = 1/17.1 (30°-21.1°=8.9°) ; mov temp2,YH ; lsr temp2 ; ror temp1 ; add YL, temp1 ; adc YH, temp2 ; lsr temp2 ; ror temp1 ; add YL, temp1 ; adc YH, temp2 ; lsr temp2 ; ror temp1 ; add YL, temp1 ; adc YH, temp2 ; example (7.5°): ; do nothing ; example (6.1°): ; mov temp1,YL ; 1/16 + 1/256 = 1/15.06 (30°-23.9°=6.1°) ; mov temp2,YH ; ldi temp4, 4 ; divide by 16 ;calc_OCT1_dt: lsr temp2 ; ror temp1 ; dec temp4 ; brne calc_OCT1_dt (branch if not equal) ; add YL, temp1 ; adc YH, temp2 ; example (4.7°): ; mov temp1,YL ; 1/16 + 1/128 = 1/14.22 (30°-25.3°=4.7°) ; mov temp2,YH ; ldi temp4, 3 ; divide by 8 ;calc_OCT1_dt: lsr temp2 ; ror temp1 ; dec temp4 ; brne calc_OCT1_dt (branch if not equal) ; add YL, temp1 ; adc YH, temp2 ; save 2nd timing value sts wt_FET_switch_l, YL ; store time from ZC to next commutation to achieve correct advance timing sts wt_FET_switch_h, YH calc_OCT1_of90: lds temp1, tcnt1_sav_l ; reload current cycle duration lds temp2, tcnt1_sav_h lds temp3, tcnt1_sav_x sts last_tcnt1_l, temp1 ; save as 'previous' for next cycle sts last_tcnt1_h, temp2 sts last_tcnt1_x, temp3 ret .if RC_PULS == 1 ;------------------------------------------------------------------------- ; interprets desired throttle position and sets fet's pwm duty cycle ; assumes >800us throttle range (rc pulse length) ; throttle considered to be closed if below 1100us (MIN_RC_PULS==1100) ; throttle considered to be at max if above 1900us (1100 + assumed 800 range) ; by deducting the 1100 zero throttle 'offset', remaining value represents a throttle scale (0-800) ; by dividing that by 8 scale ranges from 0-100 ; this is used as the fet pwm duty cycle (subject to other adjustments) ; function called in state2 via wait_OCT1_before_switch evaluate_rc_puls: sbrs flags1, RC_PULS_UPDATED ; if 1, skip next, a new rc pulse exists rjmp eval_rc_p90 ; if 0, exit - new rc pulse is not available ; new rc pulse exists mov temp1, new_rcpuls_l ; read new pulse length (typically 1000 to 2000us) mov temp2, new_rcpuls_h cbr flags1, (1<100 eval_rc_p10: ; use actual if <100 mov ZH, temp3 ; ZH is new duty cycle (0 and 100) eval_rc_p90: ret .endif .if FREE_FLIGHT == 1 ;------------------------------------------------------------------------- ; checks if button has been pressed while motor is running (FF) ; stops motor by resetting program ; function called in state1 evaluate_ff_stop: ; check if button is being pressed sbic PIND, rcp_in ; skip next if 0 (0=button pressed) rjmp ff_stop_exit ; exit if 1 (button not pressed) rcall soft_reset ; force program to reset when button pressed ff_stop_exit: ret .endif ;------------------------------------------------------------------------- ; checks for stalled motor (won't start or model crashed) ; limits time that controller will keep trying to run motor ; function called in state3 evaluate_stall: ; check rpm lds temp1, OCT1_high ; get current RPM cpi temp1, 16 ; carry flag=1 if 16>temp1 - occurs when rpm>915(2pole)/152(12pole) brcs stall_exit ; branch if carry 1 (rpm>1831) - exit if running (not stalled) ; motor stopped/slow so check power setting mov temp1, tcnt0_power_on ; get current FET duty cycle (156-246) cpi temp1, 246 ; carry=1 if temp2>temp1 (min(246)>current(156-246) =power ON) brsh stall_exit ; branch if current>=246 (power OFF) ; motor stopped, power on = stalled so decrement counter to bypass startup phase lds temp1, stall_buffer ; load counter dec temp1 ; reduce counter breq stalled ; branch if 0 (delay complete; motor stalled) sts stall_buffer, temp1 ; save count if not 0 yet rjmp stall_exit ; exit stalled: rcall soft_reset ; force program to reset stall_exit: ret ;------------------------------------------------------------------------- ; resets program on error conditions ; WDT timer used to ensure all registers are initialised properly soft_reset: ; clear ports to disable fets (brake effect if not done) clr temp1 out PORTB, temp1 ; n-fets out PORTD, temp1 ; p-fets ; enable watchdog timer ldi temp1, (1<0, reduce count eval_sys_ub: ; check for under-voltage sbrs flags0, UB_LOW ; if 1, voltage is low proceed with checks (skip next if 1) ; UB_LOW never gets set rjmp eval_sys_ub_ok ; if 0, under-voltage not detected ; under-voltage detected cbr flags0, (1<0, reduce count eval_sys_s99: ; sets throttle curve and pwm duty cycle rcall set_new_duty ; reduces duty cycle by increasing amounts as low voltage count goes up ret panic_exit: ; !!!!!! OVERCURRENT !!!!!!!! cli ; disable all interrupts rjmp reset ; restart ESC program (why not reduce throttle?) ;------------------------------------------------------------------------- ; delay between last commute and looking for zero-crossing ; the motor's windings store energy when powered (act as an inductor) ; this is released when no longer driven (called the inductive spike) ; purpose of delay this is to avoid looking for zero-crossing (ZC) until after this spike ; function is called from all 6 states ; the last part (wait_OCT1_tot) is called on startup to initialise settings ; the delay comprises 2 parts: ; 1. 30 electrical degrees exist between switching fets off and ZC; this delay bypasses initial 22.5' ; 2. startup timing to force states to change on starup ; (starup timing and reduce hangups) ; the OCT1_PENDING flag is used to indicate when delay is complete (cleared in an interrupt) ; inductive spike delay (wait 22.5') wait_OCT1_before_comp_scan: ; configure delay sbr flags0, (1< 1831 (2pole) lds temp2, OCT1_high ; get actual RPM cpi temp2, 8 ; carry flag=1 if 8>temp2 (occurs when rpm>1831) brcs set_new_duty20 ; if 1 (rpm>1831), proceed ; rpm < 1831 ensure throttle < 25% at this low rpm ldi temp2, POWER_RANGE ; if 0 (rpm <1831) check throttle position lsr temp2 ; /2 lsr temp2 ; /2 cp temp1, temp2 ; carry flag=1 if temp2>temp1 (occurs when 25% > throttle now) brcs set_new_duty30 ; if 1 (throttle is not > 25%), no restriction (use actual) mov temp1, temp2 ; if 0, throttle too high for actual rpm so set to 25% rjmp set_new_duty30 ; exit set_new_duty20: ; 1831 < rpm < 3662 cpi temp2, 4 ; carry flag=1 if 4>temp2 (occurs when rpm>3662) brcs set_new_duty30 ; if 1 (rpm>3662), exit no change ldi temp2, POWER_RANGE ; if 0 (rpm<3662) check throttle position lsr temp2 ; /2 cp temp1, temp2 ; carry flag=1 if temp2>temp1 (occurs when 50% > throttle now) brcs set_new_duty30 ; if 1 (throttle is not > 50%), no restriction (use actual) mov temp1, temp2 ; if 0, throttle too high for actual rpm so set to 50% set_new_duty30: com temp1 ; invert bits to load into timer0 counter ; eg: 64 (0b01000000) becomes 191 (0b10111111) ie: 64 from 255 mov tcnt0_pwron_next, temp1 ; save as 'next' pwm duty cycle ret ;------------------------------------------------------------------------- ; restart/unrealistic commutation/rpm values detected set_default_timeout: ldi temp1, NO_POWER ; 246 / low throttle mov tcnt0_power_on, temp1 ; set FET duty cycle to minimum (eg: 246=10us) ldi temp1, 8 ; 8 represents 1831 rpm sts OCT1_high, temp1 ; set flag which forces throttle to 25% or less ldi YL, low (defaultTIMEOUT) ; set 6state duration to 1831 equiv (overrides timer1 value) ldi YH, high(defaultTIMEOUT) sts wt_comp_scan_l, YL ; set 'commutation to scan' delay sts wt_comp_scan_h, YH ret ;------------------------------------------------------------------------- ; uart function .if UART_CONTROL == 1 read_uart: in temp1,UDR ; read data - clear rxc-flag cpi temp1,'+' brne read_uart_10 ; do (+) job cpi ZH, POWER_RANGE brsh read_uart_90 rcall send_byte inc ZH rcall set_new_duty rjmp read_uart_90 read_uart_10: cpi temp1,'-' brne read_uart_20 ; do (-) job cpi ZH, MIN_DUTY-1 breq read_uart_90 (branch if equal) rcall send_byte dec ZH rcall set_new_duty rjmp read_uart_90 read_uart_20: .if UART_FULL == 1 cpi temp1,'#' brne read_uart_30 ; do (#) job clr XH ldi XL, uart_data sbr flags0, (1< + input) state1: ; OCT1_PENDING = 1 = Delay pending (set on startup and during delays before and after commutation) ; OCT1_PENDING = 0 = Delay complete (after 'manual' startup timing delays) sbrc flags0, OCT1_PENDING ; if 0, skip next to move to next state (delay is complete) rjmp state1_2 ; if 1, check for ZC (startup & normal running) .if FREE_FLIGHT == 1 rcall evaluate_ff_stop ; check if button pressed to stop FF motor .endif rjmp s1_close ; move to next state to force states to change 'manually' (startup) state1_2: sbic ACSR, ACO ; if 0 (after ZC), skip next and move on rjmp state1 ; if 1 (before ZC), keep looping rcall wait_if_spike ; 4us delay sbic ACSR, ACO ; if 0 (still low after ZC) skip next and move on rjmp state1 ; if 1, is high again so was just a spike (loop again) rcall wait_OCT1_before_switch ; ZC has occurred so wait for advance timing before commuting ; switch fets and configure comparator for next state s1_close: .if UART_CONTROL == 1 .if UART_FULL == 1 sbrc flags0, GET_STATE rcall save_state .endif .endif cbi PORTD, BpFET ; switch Bp off sbrs flags1, POWER_OFF ; if 1 (throttle is off), skip next (avoid enabling fet unnecessarily) sbi PORTD, ApFET ; if 0 (power is required), switch Ap on ldi temp1, mux_b out ADMUX, temp1 ; set comparator multiplexer to phase B rcall wait_OCT1_before_comp_scan ; delay to skip inductive spike (waits until complete) ; configure startup timing delay (but don't wait) ; ========================= ; state2 = A(p-on) + C(n-choppered) - comparator B evaluated ; out_cB changes from high to low state2: sbrc flags0, OCT1_PENDING ; if 0, skip next ZC not detected/starting motor so move on to next phase rjmp state2_2 ; if 1, check if zero-crossing has occurred rjmp s2_close ; move to next state state2_2: sbis ACSR, ACO ; if 1 (after ZC), skip next to check if just a spike rjmp state2 ; if 0 (before ZC), loop again rcall wait_if_spike ; 4us delay sbis ACSR, ACO ; if 1 (confirms ZC), skip next rjmp state2 ; if 0 (just a glitch), keep looping sbr flags1, (1<