;**** **** **** **** **** ; ;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 !! ; ; tp-18a ; October 2004 ; autor: Bernhard Konze ; email: bernhard.konze@versanet.de ;-- ; Based on upon Bernhard's "tp-18a" and others; see ; http://home.versanet.de/~b-konze/blc_18a/blc_18a.htm ; Copyright (C) 2004 Bernhard Konze ; Copyright (C) 2011-2012 Simon Kirby and other contributors ; NO WARRANTY EXPRESSED OR IMPLIED. USE AT YOUR OWN RISK. Always test ; without propellers! Please respect Bernhard Konze's license above. ;-- ; WARNING: I have blown FETs on Turnigy Plush 18A ESCs in previous versions ; of this code with my modifications. Some bugs have since been fixed, such ; as leaving PWM enabled while busy-looping forever outside of ISR code. ; However, this does run with higher PWM frequency than most original code, ; so higher FET temperatures may occur! USE AT YOUR OWN RISK, and maybe see ; how it compares and let me know! ; ; WARNING: This does not check temperature or voltage ADC inputs. ; ; NOTE: We do 16-bit PWM on timer2 at full CPU clock rate resolution, using ; tcnt2h to simulate the high byte. An input FULL to STOP range of 800 plus ; a MIN_DUTY of 56 (a POWER_RANGE of 856) gives 800 unique PWM steps at an ; about 18kHz on a 16MHz CPU clock. The output frequency is slightly lower ; than F_CPU / POWER_RANGE due to cycles used in the interrupt as TCNT2 is ; reloaded. ; ; Simon Kirby ; ;-- Device ---------------------------------------------------------------- ; .include "m8def.inc" ; ; 8K Bytes of In-System Self-Programmable Flash ; 512 Bytes EEPROM ; 1K Byte Internal SRAM ; ;-- Fuses ----------------------------------------------------------------- ; ; Old fuses for internal RC oscillator at 8MHz were lfuse=0xa4 hfuse=0xdf, ; but since we now set OSCCAL to 0xff (about 16MHz), running under 4.5V is ; officially out of spec. We'd better set the brown-out detection to 4.0V. ; The resulting code works with or without external 16MHz oscillators. ; Boards with external oscillators can use lfuse=0x3f. ; ; If the boot loader is enabled, the last nibble of the hfuse should be set ; to 'a' or '2' to also enable EESAVE - save EEPROM on chip erase. This is ; a 512-word boot flash section (0xe00), and enable BOOTRST to jump to it. ; Setting these fuses actually has no harm even without the boot loader, ; since 0xffff is nop, and it will just nop-sled around into normal code. ; ; Suggested fuses with 4.0V brown-out voltage: ; Without external oscillator: avrdude -U lfuse:w:0x24:m -U hfuse:w:0xda:m ; With external oscillator: avrdude -U lfuse:w:0x3f:m -U hfuse:w:0xca:m ; ; Don't set WDTON if using the boot loader. We will enable it on start. ; ;-- Board ----------------------------------------------------------------- ; ; The following only works with avra or avrasm2. ; For avrasm32, just comment out all but the include you need. #if defined(afro_esc) #include "afro.inc" ; AfroESC (ICP PWM, I2C, UART) #elif defined(afro2_esc) #include "afro2.inc" ; AfroESC 2 (ICP PWM, I2C, UART) #elif defined(afro_hv_esc) #include "afro_hv.inc" ; AfroESC HV with drivers (ICP PWM, I2C, UART) #elif defined(afro_nfet_esc) #include "afro_nfet.inc" ; AfroESC 3 with all nFETs (ICP PWM, I2C, UART) #elif defined(afro_pr0_esc) #include "afro_pr0.inc" ; AfroESC prototype rev0 with NCP5911 (ICP PWM) #elif defined(afro_pr1_esc) #include "afro_pr1.inc" ; AfroESC prototype rev1 with NCP5911 (ICP PWM) #elif defined(arctictiger_esc) #include "arctictiger.inc" ; Arctic Tiger 30A ESC with all nFETs (ICP PWM) #elif defined(birdie70a_esc) #include "birdie70a.inc" ; Birdie 70A with all nFETs (INT0 PWM) #elif defined(mkblctrl1_esc) #include "mkblctrl1.inc" ; MK BL-Ctrl v1.2 (ICP PWM, I2C, UART, high side PWM, sense hack) #elif defined(bs_esc) #include "bs.inc" ; HobbyKing BlueSeries / Mystery (INT0 PWM) #elif defined(bs_nfet_esc) #include "bs_nfet.inc" ; HobbyKing BlueSeries / Mystery with all nFETs (INT0 PWM) #elif defined(bs40a_esc) #include "bs40a.inc" ; HobbyKing BlueSeries / Mystery 40A (INT0 PWM) #elif defined(dlu40a_esc) #include "dlu40a.inc" ; Pulso Advance Plus 40A DLU40A inverted-PWM-opto (INT0 PWM) #elif defined(dlux_esc) #include "dlux.inc" ; HobbyKing Dlux Turnigy ESC 20A #elif defined(diy0_esc) #include "diy0.inc" ; HobbyKing DIY Open ESC (unreleased rev 0) #elif defined(dys_nfet_esc) #include "dys_nfet.inc" ; DYS 30A ESC with all nFETs (ICP PWM, I2C, UART) #elif defined(hk200a_esc) #include "hk200a.inc" ; HobbyKing SS Series 190-200A with all nFETs (INT0 PWM) #elif defined(hm135a_esc) #include "hm135a.inc" ; Hacker/Jeti Master 135-O-F5B 135A inverted-PWM-opto (INT0 PWM) #elif defined(hxt200a_esc) #include "hxt200a.inc" ; HexTronik F3J HXT200A HV ESC (INT0 PWM, I2C, UART) #elif defined(kda_esc) #include "kda.inc" ; Keda/Multistar 12A, 20A, 30A (original) (inverted INT0 PWM) #elif defined(kda_8khz_esc) #include "kda_8khz.inc" ; Keda/Multistar 30A (early 2014) (inverted INT0 PWM) #elif defined(kda_nfet_esc) #include "kda_nfet.inc" ; Keda/Multistar 30A with all nFETs (inverted INT0 PWM) #elif defined(kda_nfet_ni_esc) #include "kda_nfet_ni.inc" ; Keda/Multistar/Sunrise ~30A with all nFETs (INT0 PWM) #elif defined(rb50a_esc) #include "rb50a.inc" ; Red Brick 50A with all nFETs (INT0 PWM) #elif defined(rb70a_esc) #include "rb70a.inc" ; Red Brick 70A with all nFETs (INT0 PWM) #elif defined(rb70a2_esc) #include "rb70a2.inc" ; Newer Red Brick 70A with blue pcb and all nFETs (INT0 PWM) #elif defined(rct50a_esc) #include "rct50a.inc" ; RCTimer 50A (MLF version) with all nFETs (INT0 PWM) #elif defined(tbs_esc) #include "tbs.inc" ; TBS 30A ESC (Team BlackSheep) with all nFETs (ICP PWM, UART) #elif defined(tbs_hv_esc) #include "tbs_hv.inc" ; TBS high voltage ESC (Team BlackSheep) with all nFETs (ICP PWM, UART) #elif defined(tp_esc) #include "tp.inc" ; TowerPro 25A/HobbyKing 18A "type 1" (INT0 PWM) #elif defined(tp_8khz_esc) #include "tp_8khz.inc" ; TowerPro 25A/HobbyKing 18A "type 1" (INT0 PWM) at 8kHz PWM #elif defined(tp_i2c_esc) #include "tp_i2c.inc" ; TowerPro 25A/HobbyKing 18A "type 1" (I2C) #elif defined(tp_nfet_esc) #include "tp_nfet.inc" ; TowerPro 25A with all nFETs "type 3" (INT0 PWM) #elif defined(tp70a_esc) #include "tp70a.inc" ; TowerPro 70A with BL8003 FET drivers (INT0 PWM) #elif defined(tgy6a_esc) #include "tgy6a.inc" ; Turnigy Plush 6A (INT0 PWM) #elif defined(tgy_8mhz_esc) #include "tgy_8mhz.inc" ; TowerPro/Turnigy Basic/Plush "type 2" w/8MHz oscillator (INT0 PWM) #elif defined(tgy_esc) #include "tgy.inc" ; TowerPro/Turnigy Basic/Plush "type 2" (INT0 PWM) #else #error "Unrecognized board type." #endif .equ CPU_MHZ = F_CPU / 1000000 .equ BOOT_LOADER = 1 ; Include Turnigy USB linker STK500v2 boot loader on PWM input pin .equ BOOT_JUMP = 1 ; Jump to any boot loader when PWM input stays high .equ BOOT_START = THIRDBOOTSTART .if !defined(COMP_PWM) .equ COMP_PWM = 0 ; During PWM off, switch high side on (unsafe on some boards!) .endif .if !defined(DEAD_LOW_NS) .equ DEAD_LOW_NS = 300 ; Low-side dead time w/COMP_PWM (62.5ns steps @ 16MHz, max 2437ns) .equ DEAD_HIGH_NS = 300 ; High-side dead time w/COMP_PWM (62.5ns steps @ 16MHz, max roughly PWM period) .endif .equ DEAD_TIME_LOW = DEAD_LOW_NS * CPU_MHZ / 1000 .equ DEAD_TIME_HIGH = DEAD_HIGH_NS * CPU_MHZ / 1000 .if !defined(MOTOR_ADVANCE) .equ MOTOR_ADVANCE = 18 ; Degrees of timing advance (0 - 30, 30 meaning no delay) .endif .if !defined(TIMING_OFFSET) .equ TIMING_OFFSET = 0 ; Motor timing offset in microseconds .endif .equ MOTOR_BRAKE = 0 ; Enable brake during neutral/idle ("motor drag" brake) .equ LOW_BRAKE = 0 ; Enable brake on very short RC pulse ("thumb" brake like on Airtronics XL2P) .if !defined(MOTOR_REVERSE) .equ MOTOR_REVERSE = 0 ; Reverse normal commutation direction .endif .equ RC_PULS_REVERSE = 0 ; Enable RC-car style forward/reverse throttle .equ RC_CALIBRATION = 1 ; Support run-time calibration of min/max pulse lengths .equ SLOW_THROTTLE = 0 ; Limit maximum throttle jump to try to prevent overcurrent .equ BEACON = 1 ; Beep periodically when RC signal is lost .equ BEACON_IDLE = 0 ; Beep periodically if idle for a long period .if !defined(CHECK_HARDWARE) .equ CHECK_HARDWARE = 0 ; Check for correct pin configuration, sense inputs, and functioning MOSFETs .endif .equ CELL_MAX_DV = 43 ; Maximum battery cell deciV .equ CELL_MIN_DV = 35 ; Minimum battery cell deciV .equ CELL_COUNT = 0 ; 0: auto, >0: hard-coded number of cells (for reliable LVC > ~4S) .equ BLIP_CELL_COUNT = 0 ; Blip out cell count before arming .equ DEBUG_ADC_DUMP = 0 ; Output an endless loop of all ADC values (no normal operation) .equ MOTOR_DEBUG = 0 ; Output sync pulses on MOSI or SCK, debug flag on MISO .equ I2C_ADDR = 0x50 ; MK-style I2C address .equ MOTOR_ID = 1 ; MK-style I2C motor ID, or UART motor number .equ RCP_TOT = 2 ; Number of 65536us periods before considering rc pulse lost ; These are now defaults which can be adjusted via throttle calibration ; (stick high, stick low, (stick neutral) at start). ; These might be a bit wide for most radios, but lines up with POWER_RANGE. .equ STOP_RC_PULS = 1060 ; Stop motor at or below this pulse length .equ FULL_RC_PULS = 1860 ; Full speed at or above this pulse length .equ MAX_RC_PULS = 2400 ; Throw away any pulses longer than this .equ MIN_RC_PULS = 768 ; Throw away any pulses shorter than this .equ MID_RC_PULS = (STOP_RC_PULS + FULL_RC_PULS) / 2 ; Neutral when RC_PULS_REVERSE = 1 .equ RCP_ALIAS_SHIFT = 3 ; Enable 1/8th PWM input alias ("oneshot125") .equ BEEP_RCP_ERROR = 0 ; Beep at stop if invalid PWM pulses were received .if RC_PULS_REVERSE .equ RCP_DEADBAND = 50 ; Do not start until this much above or below neutral .equ PROGRAM_RC_PULS = (STOP_RC_PULS + FULL_RC_PULS * 3) / 4 ; Normally 1660 .else .equ RCP_DEADBAND = 0 .equ PROGRAM_RC_PULS = (STOP_RC_PULS + FULL_RC_PULS) / 2 ; Normally 1460 .endif .if LOW_BRAKE .equ RCP_LOW_DBAND = 60 ; Brake at this many microseconds below low pulse .endif .equ MAX_DRIFT_PULS = 10 ; Maximum jitter/drift microseconds during programming ; Minimum PWM on-time (too low and FETs won't turn on, hard starting) .if !defined(MIN_DUTY) .equ MIN_DUTY = 56 * CPU_MHZ / 16 .endif ; Number of PWM steps (too high and PWM frequency drops into audible range) .if !defined(POWER_RANGE) .equ POWER_RANGE = 800 * CPU_MHZ / 16 + MIN_DUTY .endif .equ MAX_POWER = (POWER_RANGE-1) .equ PWR_COOL_START = (POWER_RANGE/24) ; Power limit while starting to reduce heating .equ PWR_MIN_START = (POWER_RANGE/6) ; Power limit while starting (to start) .equ PWR_MAX_START = (POWER_RANGE/4) ; Power limit while starting (if still not running) .equ PWR_MAX_RPM1 = (POWER_RANGE/4) ; Power limit when running slower than TIMING_RANGE1 .equ PWR_MAX_RPM2 = (POWER_RANGE/2) ; Power limit when running slower than TIMING_RANGE2 .equ BRAKE_POWER = MAX_POWER*2/3 ; Brake force is exponential, so start fairly high .equ BRAKE_SPEED = 3 ; Speed to reach MAX_POWER, 0 (slowest) - 8 (fastest) .equ LOW_BRAKE_POWER = MAX_POWER*2/3 .equ LOW_BRAKE_SPEED = 5 .equ TIMING_MIN = 0x8000 ; 8192us per commutation .equ TIMING_RANGE1 = 0x4000 ; 4096us per commutation .equ TIMING_RANGE2 = 0x2000 ; 2048us per commutation .equ TIMING_RANGE3 = 0x1000 ; 1024us per commutation .equ TIMING_MAX = 0x0080 ; 32us per commutation (312,500eRPM) .equ TIMEOUT_START = 48000 ; Timeout per commutation for ZC during starting .if !defined(START_DELAY_US) .equ START_DELAY_US = 0 ; Initial post-commutation wait during starting .endif .equ START_DSTEP_US = 8 ; Microseconds per start delay step .equ START_DELAY_INC = 15 ; Wait step count increase (wraps in a byte) .equ START_MOD_INC = 4 ; Start power modulation step count increase (wraps in a byte) .equ START_MOD_LIMIT = 48 ; Value at which power is reduced to avoid overheating .equ START_FAIL_INC = 16 ; start_tries step count increase (wraps in a byte, upon which we disarm) .equ ENOUGH_GOODIES = 12 ; This many start cycles without timeout will transition to running mode .equ ZC_CHECK_FAST = 12 ; Number of ZC check loops under which PWM noise should not matter .equ ZC_CHECK_MAX = POWER_RANGE / 32 ; Limit ZC checking to about 1/2 PWM interval .equ ZC_CHECK_MIN = 3 .equ T0CLK = (1<> 8, @4 .endif .endmacro ; Subtract any 16-bit immediate from a register pair (@0:@1 -= @2), no Z flag .macro sbi2 .if byte1(@2) subi @0, byte1(@2) sbci @1, byte2(@2) .else subi @1, byte2(@2) .endif .endmacro ; Smaller version for r24 and above, Z flag not reliable .macro sbiwx .if !(@2 & 0xff) || (@2) & ~0x3f sbi2 @0, @1, @2 .else sbiw @0, @2 .endif .endmacro ; Load 2-byte immediate .macro ldi2 ldi @0, byte1(@2) ldi @1, byte2(@2) .endmacro ; Load 3-byte immediate .macro ldi3 ldi @0, byte1(@3) ldi @1, byte2(@3) ldi @2, byte3(@3) .endmacro ; Register out to any address (memory-mapped if necessary) .macro outr .if @0 < 64 out @0, @1 .else sts @0, @1 .endif .endmacro ; Register in from any address (memory-mapped if necessary) .macro inr .if @1 < 64 in @0, @1 .else lds @0, @1 .endif .endmacro ; Immediate out to any port (possibly via @2 as a temporary) .macro outi .if @1 ldi @2, @1 outr @0, @2 .else outr @0, ZH .endif .endmacro ;-- LED macros ----------------------------------------------------------- .if !defined(red_led) .macro RED_on .endmacro .macro RED_off .endmacro .endif .if !defined(green_led) .macro GRN_on .endmacro .macro GRN_off .endmacro .endif .if !defined(blue_led) .macro BLUE_on .endmacro .macro BLUE_off .endmacro .endif ;-- FET driving macros --------------------------------------------------- ; Careful: "if" conditions split over multiple lines (with backslashes) ; work with arva, but avrasm2.exe silently produces wrong results. .if !defined(HIGH_SIDE_PWM) .equ HIGH_SIDE_PWM = 0 .endif .if defined(AnFET) ; Traditional direct high and low side drive, inverted if INIT_Px is set. ; Dead-time insertion is supported for COMP_PWM. .equ CPWM_SOFT = COMP_PWM .macro FET_on .if (INIT_PB & ((@0 == PORTB) << @1)) | (INIT_PC & ((@0 == PORTC) << @1)) | (INIT_PD & ((@0 == PORTD) << @1)) cbi @0, @1 .else sbi @0, @1 .endif .endmacro .macro FET_off .if (INIT_PB & ((@0 == PORTB) << @1)) | (INIT_PC & ((@0 == PORTC) << @1)) | (INIT_PD & ((@0 == PORTD) << @1)) sbi @0, @1 .else cbi @0, @1 .endif .endmacro .macro AnFET_on FET_on AnFET_port, AnFET .endmacro .macro AnFET_off FET_off AnFET_port, AnFET .endmacro .macro ApFET_on FET_on ApFET_port, ApFET .endmacro .macro ApFET_off FET_off ApFET_port, ApFET .endmacro .macro BnFET_on FET_on BnFET_port, BnFET .endmacro .macro BnFET_off FET_off BnFET_port, BnFET .endmacro .macro BpFET_on FET_on BpFET_port, BpFET .endmacro .macro BpFET_off FET_off BpFET_port, BpFET .endmacro .macro CnFET_on FET_on CnFET_port, CnFET .endmacro .macro CnFET_off FET_off CnFET_port, CnFET .endmacro .macro CpFET_on FET_on CpFET_port, CpFET .endmacro .macro CpFET_off FET_off CpFET_port, CpFET .endmacro .macro PWM_FOCUS_A_on .if COMP_PWM cpse temp3, temp4 PWM_COMP_A_on .endif .endmacro .macro PWM_FOCUS_A_off .if COMP_PWM in temp3, PWM_COMP_A_PORT_in PWM_COMP_A_off in temp4, PWM_COMP_A_PORT_in .endif .endmacro .macro PWM_FOCUS_B_on .if COMP_PWM cpse temp3, temp4 PWM_COMP_B_on .endif .endmacro .macro PWM_FOCUS_B_off .if COMP_PWM in temp3, PWM_COMP_B_PORT_in PWM_COMP_B_off in temp4, PWM_COMP_B_PORT_in .endif .endmacro .macro PWM_FOCUS_C_on .if COMP_PWM cpse temp3, temp4 PWM_COMP_C_on .endif .endmacro .macro PWM_FOCUS_C_off .if COMP_PWM in temp3, PWM_COMP_C_PORT_in PWM_COMP_C_off in temp4, PWM_COMP_C_PORT_in .endif .endmacro ; For PWM state mirroring in commutation routines .if HIGH_SIDE_PWM .equ PWM_A_PORT_in = ApFET_port .equ PWM_B_PORT_in = BpFET_port .equ PWM_C_PORT_in = CpFET_port .equ PWM_COMP_A_PORT_in = AnFET_port .equ PWM_COMP_B_PORT_in = BnFET_port .equ PWM_COMP_C_PORT_in = CnFET_port .else .equ PWM_A_PORT_in = AnFET_port .equ PWM_B_PORT_in = BnFET_port .equ PWM_C_PORT_in = CnFET_port .equ PWM_COMP_A_PORT_in = ApFET_port .equ PWM_COMP_B_PORT_in = BpFET_port .equ PWM_COMP_C_PORT_in = CpFET_port .endif .macro PWM_ALL_off .if HIGH_SIDE_PWM all_pFETs_off @0 .else all_nFETs_off @0 .endif .endmacro .macro all_pFETs_off .if ApFET_port != BpFET_port || ApFET_port != CpFET_port ApFET_off BpFET_off CpFET_off .else in @0, ApFET_port .if (INIT_PB & ((ApFET_port == PORTB) << ApFET)) | (INIT_PC & ((ApFET_port == PORTC) << ApFET)) | (INIT_PD & ((ApFET_port == PORTD) << ApFET)) sbr @0, (1< cbi PWM_X_PORT, PWM_X (drain through ext pull-down) ; XnFET_off -> sbi PWM_X_PORT, PWM_X (pull-up pin with ext pull-down) ; XpFET_on -> sbi PWM_X_DDR, PWM_X (drive-up pin) ; XpFET_off -> cbi PWM_X_DDR, PWM_X (pull-up pin with ext pull-down) ; ; COMP_PWM on these is done in hardware rather than software, so we can ; just toggle the PORT value after PWM_FOCUS sets DDR (output mode). ; This results in the following macro arrangement: ; ; TRI CPWM HIGH_SIDE_PWM : PWM ON PWM OFF PWM_X_PORT_in ; 0 0 0 : XnFET_on XnFET_off XnFET_port ; 0 0 1 : XpFET_on XpFET_off XpFET_port ; 0 1 0 : XnFET_on XnFET_off XnFET_port ; 0 1 1 : XpFET_on XpFET_off XpFET_port ; 1 0 0 : XnFET_on XnFET_off PWM_X_PORT ; 1 0 1 : XpFET_on XpFET_off PWM_X_DDR ; 1 1 0 : XnFET_on XnFET_off PWM_X_PORT ; 1 1 1 : XnFET_off XnFET_on PWM_X_PORT ; ; For the last case, PWM_X_off actually turns low side on which isn't how ; we want to leave the phase after commutating. PWM_X_clear will take care ; of this. ; ; We leave ENABLE high once initialized as some drivers will actually ; shut down rather than just using the input as a logic gate. ; ; We prefer HIGH_SIDE_PWM as diode emulation mode in these drivers ; typically allows active freewheeling only in this orientation. .equ CPWM_SOFT = 0 .macro AnFET_on cbi PWM_A_PORT, PWM_A .endmacro .macro AnFET_off sbi PWM_A_PORT, PWM_A .endmacro .macro ApFET_on sbi PWM_A_DDR, PWM_A .endmacro .macro ApFET_off cbi PWM_A_DDR, PWM_A .endmacro .macro BnFET_on cbi PWM_B_PORT, PWM_B .endmacro .macro BnFET_off sbi PWM_B_PORT, PWM_B .endmacro .macro BpFET_on sbi PWM_B_DDR, PWM_B .endmacro .macro BpFET_off cbi PWM_B_DDR, PWM_B .endmacro .macro CnFET_on cbi PWM_C_PORT, PWM_C .endmacro .macro CnFET_off sbi PWM_C_PORT, PWM_C .endmacro .macro CpFET_on sbi PWM_C_DDR, PWM_C .endmacro .macro CpFET_off cbi PWM_C_DDR, PWM_C .endmacro .macro PWM_FOCUS_A_on .if COMP_PWM sbrc flags1, POWER_ON ApFET_on .endif .endmacro .macro PWM_FOCUS_A_off .if COMP_PWM ApFET_off .endif .endmacro .macro PWM_FOCUS_B_on .if COMP_PWM sbrc flags1, POWER_ON BpFET_on .endif .endmacro .macro PWM_FOCUS_B_off .if COMP_PWM BpFET_off .endif .endmacro .macro PWM_FOCUS_C_on .if COMP_PWM sbrc flags1, POWER_ON CpFET_on .endif .endmacro .macro PWM_FOCUS_C_off .if COMP_PWM CpFET_off .endif .endmacro ; For PWM state mirroring in commutation routines .if COMP_PWM || !HIGH_SIDE_PWM .equ PWM_A_PORT_in = PWM_A_PORT .equ PWM_B_PORT_in = PWM_B_PORT .equ PWM_C_PORT_in = PWM_C_PORT .else .equ PWM_A_PORT_in = PWM_A_DDR .equ PWM_B_PORT_in = PWM_B_DDR .equ PWM_C_PORT_in = PWM_C_DDR .endif .macro PWM_ALL_off all_nFETs_off @0 .endmacro .macro all_pFETs_off .if PWM_A_DDR != PWM_B_DDR || PWM_A_DDR != PWM_C_DDR ApFET_off BpFET_off CpFET_off .else in @0, PWM_A_DDR cbr @0, (1<= MAX_BUSY_WAIT_CYCLES .error "cycle_delay too long" .endif .if @0 > 0 .if @0 & 1 nop .endif .if @0 & 2 rjmp PC + 1 .endif .if @0 & 4 rjmp PC + 1 rjmp PC + 1 .endif .if @0 & 8 nop rcall wait_ret ; 3 cycles to call + 4 to return .endif .if @0 & 16 rjmp PC + 1 rcall wait_ret rcall wait_ret .endif .endif .endmacro ;-----bko----------------------------------------------------------------- ; Timer2 overflow interrupt (output PWM) -- the interrupt vector actually ; "ijmp"s to Z, which should point to one of these entry points. ; ; We try to avoid clobbering (and thus needing to save/restore) flags; ; in, out, mov, ldi, cpse, etc. do not modify any flags, while dec does. ; ; We used to check the comparator (ACSR) here to help starting, since PWM ; switching is what introduces noise that affects the comparator result. ; However, timing of this is very sensitive to FET characteristics, and ; would work well on some boards but not at all on others without waiting ; another 100-200ns, which was enough to break other boards. So, instead, ; we do all of the ACSR sampling outside of the interrupt and do digital ; filtering. The AVR interrupt overhead also helps to shield the noise. ; ; We reload TCNT2 as the very last step so as to reduce PWM dead areas ; between the reti and the next interrupt vector execution, which still ; takes a good 4 (reti) + 4 (interrupt call) + 2 (ijmp) cycles. We also ; try to keep the switch on close to the start of pwm_on and switch off ; close to the end of pwm_aff to minimize the power bump at full power. ; ; pwm_*_high and pwm_again are called when the particular on/off cycle ; is longer than will fit in 8 bits. This is tracked in tcnt2h. .if MOTOR_BRAKE || LOW_BRAKE pwm_brake_on: cpse tcnt2h, ZH rjmp pwm_again in i_sreg, SREG nFET_brake i_temp1 ldi i_temp1, 0xff cp off_duty_l, i_temp1 ; Check for 0 off-time cpc off_duty_h, ZH breq pwm_brake_on1 ldi ZL, pwm_brake_off ; Not full on, so turn it off next lds i_temp2, brake_sub sub sys_control_l, i_temp2 brne pwm_brake_on1 neg duty_l ; Increase duty sbc duty_h, i_temp1 ; i_temp1 is 0xff aka -1 com duty_l com off_duty_l ; Decrease off duty sbc off_duty_l, ZH sbc off_duty_h, ZH com off_duty_l pwm_brake_on1: mov tcnt2h, duty_h out SREG, i_sreg out TCNT2, duty_l reti pwm_brake_off: cpse tcnt2h, ZH rjmp pwm_again in i_sreg, SREG ldi ZL, pwm_brake_on mov tcnt2h, off_duty_h all_nFETs_off i_temp1 out SREG, i_sreg out TCNT2, off_duty_l reti .endif .if DEAD_TIME_HIGH > 7 .equ EXTRA_DEAD_TIME_HIGH = DEAD_TIME_HIGH - 7 .else .equ EXTRA_DEAD_TIME_HIGH = 0 .endif pwm_on_fast_high: .if CPWM_SOFT && EXTRA_DEAD_TIME_HIGH > MAX_BUSY_WAIT_CYCLES in i_sreg, SREG dec tcnt2h brne pwm_on_fast_high_again ldi ZL, pwm_on_fast pwm_on_fast_high_again: out SREG, i_sreg reti .endif pwm_on_high: in i_sreg, SREG dec tcnt2h brne pwm_on_again ldi ZL, pwm_on pwm_on_again: out SREG, i_sreg reti pwm_again: in i_sreg, SREG dec tcnt2h out SREG, i_sreg reti pwm_on: .if CPWM_SOFT sbrc flags2, A_FET PWM_COMP_A_off sbrc flags2, B_FET PWM_COMP_B_off sbrc flags2, C_FET PWM_COMP_C_off .if EXTRA_DEAD_TIME_HIGH > MAX_BUSY_WAIT_CYCLES ; Reschedule to interrupt once the dead time has passed .if high(EXTRA_DEAD_TIME_HIGH) ldi i_temp1, high(EXTRA_DEAD_TIME_HIGH) mov tcnt2h, i_temp1 ldi ZL, pwm_on_fast_high .else ldi ZL, pwm_on_fast .endif ldi i_temp1, 0xff - low(EXTRA_DEAD_TIME_HIGH) out TCNT2, i_temp1 reti ; Do something else while we wait .equ CPWM_OVERHEAD_HIGH = 7 + 8 + EXTRA_DEAD_TIME_HIGH .else ; Waste cycles to wait for the dead time cycle_delay EXTRA_DEAD_TIME_HIGH .equ CPWM_OVERHEAD_HIGH = 7 + EXTRA_DEAD_TIME_HIGH ; Fall through .endif .endif pwm_on_fast: sbrc flags2, A_FET PWM_A_on sbrc flags2, B_FET PWM_B_on sbrc flags2, C_FET PWM_C_on ldi ZL, pwm_off mov tcnt2h, duty_h out TCNT2, duty_l reti pwm_wdr: ; Just reset watchdog wdr reti pwm_off: cpse tcnt2h, ZH ; 2 cycles to skip when tcnt2h is 0 rjmp pwm_again wdr ; 1 cycle: watchdog reset sbrc flags1, FULL_POWER ; 2 cycles to skip if not full power rjmp pwm_on ; None of this off stuff if full power lds ZL, pwm_on_ptr ; 2 cycles mov tcnt2h, off_duty_h ; 1 cycle sbrc flags2, A_FET ; 2 cycles if skip, 1 cycle otherwise PWM_A_off ; 2 cycles (off at 12 cycles from entry) sbrc flags2, B_FET ; Offset by 2 cycles here, PWM_B_off ; but still equal on-time sbrc flags2, C_FET PWM_C_off out TCNT2, off_duty_l ; 1 cycle .if CPWM_SOFT sbrc flags2, SKIP_CPWM ; 2 cycles if skip, 1 cycle otherwise reti .if DEAD_TIME_LOW > 9 .equ EXTRA_DEAD_TIME_LOW = DEAD_TIME_LOW - 9 .else .equ EXTRA_DEAD_TIME_LOW = 0 .endif cycle_delay EXTRA_DEAD_TIME_LOW - 2 .equ CPWM_OVERHEAD_LOW = 9 + EXTRA_DEAD_TIME_LOW sbrc flags2, A_FET PWM_COMP_A_on sbrc flags2, B_FET PWM_COMP_B_on sbrc flags2, C_FET PWM_COMP_C_on .endif reti ; 4 cycles .if high(pwm_off) .error "high(pwm_off) is non-zero; please move code closer to start or use 16-bit (ZH) jump registers" .endif ;-----bko----------------------------------------------------------------- ; timer1 output compare interrupt t1oca_int: in i_sreg, SREG lds i_temp1, ocr1ax subi i_temp1, 1 brcc t1oca_int1 cbr flags0, (1< 0xffff .error "MAX_RC_PULS * CPU_MHZ too big to fit in two bytes -- adjust it or the rcp_int code" .endif sbr flags1, (1<= 0xf5 ; is received, where we start counting to MOTOR_ID, at which ; the received byte is used as throttle input. 0 is neutral, ; >= 200 is FULL_POWER. .if USE_UART in i_sreg, SREG in i_temp1, UDR cpi i_temp1, 0xf5 ; Start throttle byte sequence breq urxc_x3d_sync sbrs flags0, UART_SYNC rjmp urxc_exit ; Throw away if not UART_SYNC brcc urxc_unknown lds i_temp2, motor_count dec i_temp2 brne urxc_set_exit ; Skip when motor_count != 0 mov rx_h, i_temp1 ; Save 8-bit input sbr flags1, (1<32 (bottom 16 discarded) mov temp7, temp6 ; Save byte 2 of result, discard byte 1 already mul temp2, temp3 add temp7, temp5 adc YL, temp6 adc YH, ZH mul temp1, temp4 add temp7, temp5 adc YL, temp6 adc YH, ZH mul temp2, temp4 add YL, temp5 adc YH, temp6 ; Product is now in Y, flags set ret ;-- Hardware diagnostics ------------------------------------------------- ; Any brushless ESC based on the ATmega8 or similar must tie the sense ; neutral star to AIN0, and the three sense lines to three ADC pins or ; two ADC pins and AIN1. All of these pins are also normal I/O pins, so ; we can drive them and see if the ADC values move. A value that does not ; move indicates a shorted FET or that an incorrect board target has been ; flashed. ; ; Note: Some FET drivers such as the LM5109 can pull up the output a bit, ; making the "stuck high" test return a false positive. Perhaps it would ; be sufficient to test that the phases read as 0 _or_ can be pulled down ; by driving AIN0 to ground. ; ; In typical conditions on the ATmega8, I/O pins transition to low at ; about 1.42V and to high at about 1.86V. The ADC is 10-bit, however, and ; should work even with a strong sense voltage divider. ; ; Throughout all of this, the motor may be spinning. If so, we should wait ; long enough that each phase falls to 0V and all tests succeed. ; .if CHECK_HARDWARE .set ADC_READ_NEEDED = 1 .equ MAX_CHECK_LOOPS = 5000 ; ADC check takes ~200us hardware_check: clt ; First, check that all sense lines are low. .if defined(mux_a) ldi XL, 1 ; Error code 1: Phase A stuck high ldi temp4, mux_a rcall check_sense_low .endif .if defined(mux_b) ldi XL, 2 ; Error code 2: Phase B stuck high ldi temp4, mux_b rcall check_sense_low .endif .if defined(mux_c) ldi XL, 3 ; Error code 3: Phase C stuck high ldi temp4, mux_c rcall check_sense_low .endif .if !defined(mux_a) || !defined(mux_b) || !defined(mux_c) ldi XL, 4 ; Error code 4: AIN1 stuck high ldi2 YL, YH, MAX_CHECK_LOOPS check_ain1_low: sbiw YL, 1 sbic PIND, 7 ; Skip loop if AIN1 low brne check_ain1_low rcall hw_error_eq .endif ldi XL, 5 ; Error code 5: AIN0 stuck high ldi2 YL, YH, MAX_CHECK_LOOPS check_ain0_low: sbiw YL, 1 sbic PIND, 6 ; Skip loop if AIN0 low brne check_ain0_low rcall hw_error_eq brts hardware_check ; Do not allow further tests if stuck high ; If nothing is stuck high, pull up the motor by driving ; the emulated neutral (AIN0) high, and try driving each ; phase low. While driven, leakage through the star is ; eliminated, so one phase will not influence another ; unless a motor is connected. A phase on AIN1 cannot be ; read by the ADC, so we must skip it. sbi DDRD, 6 sbi PORTD, 6 ; Drive AIN0 high .if defined(mux_a) rcall wait30ms ; There might be some capacitance ldi XL, 6 ; Error code 6: Phase A low-side drive broken ldi temp4, mux_a rcall adc_read movw YL, temp1 ; Save ADC value (hopefully non-zero) AnFET_on ; Drive down this phase (we've established that it was 0V above). rcall adc_read ; FET turn-on will easily beat ADC initialization AnFET_off rcall hw_error_y_le_temp12 .endif .if defined(mux_b) rcall wait30ms ldi XL, 7 ; Error code 7: Phase B low-side drive broken ldi temp4, mux_b rcall adc_read movw YL, temp1 ; Save ADC value (hopefully non-zero) BnFET_on ; Drive down this phase (we've established that it was 0V above). rcall adc_read ; FET turn-on will easily beat ADC initialization BnFET_off rcall hw_error_y_le_temp12 .endif .if defined(mux_c) rcall wait30ms ldi XL, 8 ; Error code 8: Phase C low-side drive broken ldi temp4, mux_c rcall adc_read movw YL, temp1 ; Save ADC value (hopefully non-zero) CnFET_on ; Drive down this phase (we've established that it was 0V above). rcall adc_read ; FET turn-on will easily beat ADC initialization CnFET_off rcall hw_error_y_le_temp12 .endif cbi PORTD, 6 ; Sink on AIN0 (help to pull down the outputs) rcall wait30ms .if defined(mux_a) ldi XL, 9 ; Error code 9: Phase A high-side drive broken ldi temp4, mux_a rcall adc_read movw YL, temp1 ; Save ADC value (hopefully non-zero) ApFET_on ; Drive up this phase. rcall adc_read ; Waste time for high side to turn off ApFET_off rcall hw_error_temp12_le_y .endif .if defined(mux_b) ldi XL, 10 ; Error code 10: Phase B high-side drive broken ldi temp4, mux_b rcall adc_read movw YL, temp1 ; Save ADC value (hopefully non-zero) BpFET_on ; Drive up this phase. rcall adc_read ; Waste time for high side to turn off BpFET_off rcall hw_error_temp12_le_y .endif .if defined(mux_c) ldi XL, 11 ; Error code 11: Phase C high-side drive broken ldi temp4, mux_c rcall adc_read movw YL, temp1 ; Save ADC value (hopefully non-zero) CpFET_on ; Drive up this phase. rcall adc_read ; Waste time for high side to turn off CpFET_off rcall hw_error_temp12_le_y .endif cbi DDRD, 6 ; Restore tristated AIN0 ret check_sense_low: ldi2 YL, YH, MAX_CHECK_LOOPS check_sense_low1: rcall adc_read ; Up to 3.5V to account for ADC offset or driver pull-up. .equ OFF_MAX_ADC = 35 * 1024 * O_GROUND / (50 * (O_POWER + O_GROUND)) sbiwx temp1, temp2, OFF_MAX_ADC brcs check_sense_low_ret ; Return if pin reads low sbiw YL, 1 brne check_sense_low1 ; Loop until timeout rjmp hw_error check_sense_low_ret: ret hw_error_temp12_le_y: cp YL, temp1 cpc YH, temp2 brcc hw_error ret hw_error_y_le_temp12: cp temp1, YL cpc temp2, YH brcc hw_error ret ;-- Hardware error ------------------------------------------------------- ; Blink an LED or beep XL times to indicate a hardware error. ; Beeping is possibly unsafe. The only other option is to stop. hw_error_eq: brne hw_error_ret hw_error: mov YL, XL hw_error1: .if defined(red_led) RED_on rcall wait120ms RED_off .elif defined(blue_led) BLUE_on rcall wait120ms BLUE_off .elif defined(green_led) GRN_on rcall wait120ms GRN_off .else rcall beep_f1 ; Low frequency is safer .endif rcall wait240ms dec YL brne hw_error1 rcall wait240ms rcall wait240ms set hw_error_ret: ret .endif ;------------------------------------------------------------------------- ; ADC value dumping via the UART. Expects vt100ish. .if DEBUG_ADC_DUMP .set DEBUG_TX = 1 .set ADC_READ_NEEDED = 1 adc_input_dump: ldi temp4, 27 rcall tx_byte ldi temp4, '[' rcall tx_byte ldi temp4, '2' rcall tx_byte ldi temp4, 'J' rcall tx_byte adc_input_dump1: ldi temp2, 5 rcall wait1 ldi temp4, 27 rcall tx_byte ldi temp4, '[' rcall tx_byte ldi temp4, 'H' rcall tx_byte .if defined(mux_a) ldi temp4, 'A' rcall tx_byte ldi temp4, mux_a rcall adc_read rcall colon_hex_write .endif .if defined(mux_b) ldi temp4, 'B' rcall tx_byte ldi temp4, mux_b rcall adc_read rcall colon_hex_write .endif .if defined(mux_c) ldi temp4, 'C' rcall tx_byte ldi temp4, mux_c rcall adc_read rcall colon_hex_write .endif .if defined(mux_voltage) ldi temp4, '#' rcall tx_byte rcall adc_cell_count clr temp2 rcall colon_hex_write .endif rcall tx_crlf clr YL adc_loop: ldi temp4, 'A' rcall tx_byte ldi temp4, 'D' rcall tx_byte ldi temp4, 'C' rcall tx_byte mov temp4, YL rcall tx_hex_nibble mov temp4, YL rcall adc_read rcall colon_hex_write inc YL andi YL, 0xf breq adc_input_dump1 cpi YL, 8 brne adc_loop ldi YL, 0xe ; Jump to band-gap reference (no ADC8 - ADC13) rjmp adc_loop ret .endif .if DEBUG_TX init_debug_tx: .if !defined(txd) && DIR_PD & (1<<1) .error "Cannot use UART TX with this pin configuration" .endif ; Initialize TX for debugging on boards with free TX pin .equ D_BAUD_RATE = 38400 .equ D_UBRR_VAL = F_CPU / D_BAUD_RATE / 16 - 1 outi UBRRH, high(D_UBRR_VAL), temp1 outi UBRRL, low(D_UBRR_VAL), temp1 ; sbi UCSRA, U2X ; Double speed sbi UCSRB, TXEN outi UCSRC, (1< temp5:temp6) clr temp7 cpi YL, low(puls_high_l) ; Are we learning the high pulse? brne rc_prog3 ; No, maybe the low pulse ldi temp2, 32 * 31/32 ; Full speed pulse averaging count (slightly below exact) cpi2 temp3, temp4, PROGRAM_RC_PULS * CPU_MHZ, temp1 brcc rc_prog5 ; Equal to or higher than PROGRAM_RC_PULS - start measuring rjmp evaluate_rc_puls ; Lower than PROGRAM_RC_PULS - exit programming rc_prog3: lds temp1, puls_high_l ; If not learning the high pulse, we should stay below it cp temp3, temp1 lds temp1, puls_high_h cpc temp4, temp1 rc_prog_brcc_prog1: ; Branch trampoline brcc rc_prog1 ; Restart while pulse not lower than learned high pulse ldi temp2, 32 * 17/16 ; Stop/reverse pulse (slightly above exact) cpi YL, low(puls_low_l) ; Are we learning the low pulse? breq rc_prog5 ; Yes, start measuring rc_prog4: lds temp1, puls_low_l cp temp3, temp1 lds temp1, puls_low_h cpc temp4, temp1 brcs rc_prog1 ; Restart while pulse lower than learned low pulse ldi temp2, 32 ; Neutral pulse measurement (exact) rc_prog5: mov tcnt2h, temp2 ; Abuse tcnt2h as pulse counter rc_prog6: wdr sbrs flags1, EVAL_RC ; Wait for next pulse rjmp rc_prog6 cbr flags1, (1<> RCP_ALIAS_SHIFT, temp3 brcs puls_length_error cpi2 temp1, temp2, (MAX_RC_PULS * CPU_MHZ) >> RCP_ALIAS_SHIFT, temp3 sbrc flags0, RCP_ALIAS ; If alias range expected brcs puls_alias .endif puls_length_error: sbr flags0, (1< 0000xxx0b swap YL ; 0000xxx0b -> xxx00000b adiw YL, 0 ; 16-bit zero-test breq rc_duty_set ; Power off ; Scale so that YH == 247 is MAX_POWER, to support reaching full ; power from the highest MaxGas setting in MK-Tools. Bernhard's ; original version reaches full power at around 245. movw temp1, YL ldi2 temp3, temp4, 0x100 * (POWER_RANGE - MIN_DUTY) / 247 rjmp rc_do_scale ; The rest of the code is common .endif ;-----bko----------------------------------------------------------------- .if USE_UART evaluate_rc_uart: mov YH, rx_h ; Copy 8-bit input cbr flags1, (1< Y lds YH, last_tcnt1_h lds temp7, last_tcnt1_x sts last_tcnt1_l, temp1 sts last_tcnt1_h, temp2 sts last_tcnt1_x, temp3 lds temp5, l2_tcnt1_l ; last2 -> temp5 lds temp6, l2_tcnt1_h lds temp4, l2_tcnt1_x sts l2_tcnt1_l, YL sts l2_tcnt1_h, YH sts l2_tcnt1_x, temp7 ; Cancel DC bias by starting our timing from the average of the ; last two zero-crossings. Commutation phases always alternate. ; Next start = (cur(c) - last2(a)) / 2 + last(b) ; -> start=(c-b+(c-a)/2)/2+b ; ; (c - a) ; (c - b + -------) ; 2 ; start = ----------------- + b ; 2 sub temp1, temp5 ; c' = c - a sbc temp2, temp6 sbc temp3, temp4 ; Limit maximum RPM (fastest timing) cpi3 temp1, temp2, temp3, TIMING_MAX * CPU_MHZ / 2, temp4 brcc update_timing1 ldi3 temp1, temp2, temp3, TIMING_MAX * CPU_MHZ / 2 lsr sys_control_h ; limit by reducing power ror sys_control_l update_timing1: ; Calculate a hopefully sane duty cycle limit from this timing, ; to prevent excessive current if high duty is requested at low ; speed. This is the best we can do without a current sensor. ; The actual current peak will depend on motor KV and voltage, ; so this is just an approximation. This is calculated smoothly ; with a (very slow) software divide only if timing permits. cpi2 temp2, temp3, (TIMING_RANGE3 * CPU_MHZ / 2) >> 8, temp4 ldi2 XL, XH, MAX_POWER brcs update_timing4 ; Fast timing: no duty limit. ; 24.8-bit fixed-point unsigned divide, inlined with available registers: ; duty (XL:XH) = MAX_POWER * (TIMING_RANGE3 * CPU_MHZ / 2) / period (temp1:temp2:temp3) ; This takes about one microsecond per loop, but we only take this path ; when the motor is spinning slowly. ldi XL, byte3(MAX_POWER * (TIMING_RANGE3 * CPU_MHZ / 2) / 0x100) ldi XH, 33 ; Iteration counter movw timing_duty_l, XL ldi2 XL, XH, MAX_POWER * (TIMING_RANGE3 * CPU_MHZ / 2) / 0x100 mul ZH, ZH ; Zero temp5, temp6 sub temp4, temp4 ; Zero temp4, clear carry rjmp fudiv24_ep ; Jump with carry clear fudiv24_loop: rol temp4 rol temp5 rol temp6 cp temp4, temp1 ; Divide by commutation period cpc temp5, temp2 cpc temp6, temp3 brcs fudiv24_ep sub temp4, temp1 sbc temp5, temp2 sbc temp6, temp3 fudiv24_ep: rol XL rol XH rol timing_duty_l dec timing_duty_h brne fudiv24_loop com XL com XH cpi2 XL, XH, PWR_MAX_RPM1, temp4 brcc update_timing4 ldi2 XL, XH, PWR_MAX_RPM1 update_timing4: movw timing_duty_l, XL sts timing_l, temp1 ; Store timing (120 degrees) sts timing_h, temp2 sts timing_x, temp3 lsr temp3 ; c'>>= 1 (shift to 60 degrees) ror temp2 ror temp1 .if defined(DC_BIAS_CANCEL) lds temp5, last_tcnt1_l ; restore original c as a' lds temp6, last_tcnt1_h lds temp4, last_tcnt1_x sub temp5, YL ; a'-= b sbc temp6, YH sbc temp4, temp7 add temp5, temp1 ; a'+= c' adc temp6, temp2 adc temp4, temp3 lsr temp4 ; a'>>= 1 ror temp6 ror temp5 add YL, temp5 ; b+= a' -> YL:YH:temp7 become filtered ZC time adc YH, temp6 adc temp7, temp4 .else lds YL, last_tcnt1_l ; restore original c as a' lds YH, last_tcnt1_h lds temp7, last_tcnt1_x .endif ldi temp4, (30 - MOTOR_ADVANCE) * 256 / 60 rcall update_timing_add_degrees .if TIMING_OFFSET sbiwx YL, YH, TIMING_OFFSET * CPU_MHZ ldi temp4, byte3(TIMING_OFFSET * CPU_MHZ) sbc temp7, temp4 .endif sts com_time_l, YL ; Store start of next commutation sts com_time_h, YH sts com_time_x, temp7 cpi temp2, 0x10 ; Will 240 degrees fit in 15 bits? cpc temp3, ZH brcs update_timing_fast cbr flags2, (1<= PWR_MIN_START set_new_duty12: lsl temp1 rol temp2 cp sys_control_l, temp1 cpc sys_control_h, temp2 brcs set_new_duty13 movw sys_control_l, temp1 set_new_duty13: .endif ldi2 temp1, temp2, MAX_POWER sub temp1, YL ; Calculate OFF duty sbc temp2, YH breq set_new_duty_full adiw YL, 0 breq set_new_duty_zero ; Not off and not full power cbr flags1, (1<= 0x100 cpi2 temp1, temp2, CPWM_OVERHEAD_HIGH + CPWM_OVERHEAD_LOW, temp3 brcs set_new_duty21 ; Off period < off-to-on cycle count plus interrupt overhead clt ; Not short off period, unset SKIP_CPWM sbiwx temp1, temp2, CPWM_OVERHEAD_HIGH .endif ldi temp4, pwm_on ; Off period < 0x100 cpse temp2, ZH ldi temp4, pwm_on_high ; Off period >= 0x100 set_new_duty21: com YL ; Save one's complement of both com temp1 ; low bytes for up-counting TCNT2 movw duty_l, YL ; Atomic set new ON duty for PWM interrupt cli ; Critical section (off_duty & flags together) movw off_duty_l, temp1 ; Set new OFF duty for PWM interrupt sts pwm_on_ptr, temp4 ; Set Next PWM ON interrupt vector .if CPWM_SOFT bld flags2, SKIP_CPWM ; If to skip complementary PWM .endif sei ret set_new_duty_full: ; Full power sbr flags1, (1<=~5 LiPo cells becomes ambiguous based on charge state ldi temp1, 0 cell_count_good: .else ldi temp1, CELL_COUNT .endif mov YL, temp1 ; Beep clobbers temp1-temp5 cpi YL, 0 breq cell_blipper1 cell_blipper: rcall wait120ms ldi temp2, 10 ; Short blip (not too long for this) rcall beep_f4_freq dec YL brne cell_blipper rcall wait120ms cell_blipper1: .endif control_disarm: ; LEDs off while disarmed BLUE_off GRN_off RED_off cbr flags0, (1<