├── Modes.cpp ├── Debugging.cpp ├── Tools.cpp ├── Modes.h ├── Debugging.h ├── License.txt ├── Electronics ├── License.txt └── OpenRemote.brd ├── Tools.h ├── Driving_StateMachine.cpp ├── Pairing_StateMachine.h ├── Driving_StateMachine.h ├── Hardware.h ├── RF_Comm.cpp ├── RF_Comm.h ├── Hardware.cpp ├── Pairing_StateMachine.cpp └── QuatroRemote.ino /Modes.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | Author: Timo Birnschein (timo.birnschein@microforge.de) 3 | Date: 2018/07/03 4 | License: LGPL 5 | */ 6 | 7 | #include "Modes.h" 8 | -------------------------------------------------------------------------------- /Debugging.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | Author: Timo Birnschein (timo.birnschein@microforge.de) 3 | Date: 2018/07/03 4 | License: LGPL 5 | */ 6 | 7 | #include "Debugging.h" 8 | 9 | -------------------------------------------------------------------------------- /Tools.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | Author: Timo Birnschein (timo.birnschein@microforge.de) 3 | Date: 2018/07/03 4 | License: LGPL 5 | */ 6 | 7 | #include "Tools.h" 8 | 9 | 10 | -------------------------------------------------------------------------------- /Modes.h: -------------------------------------------------------------------------------- 1 | /* 2 | Author: Timo Birnschein (timo.birnschein@microforge.de) 3 | Date: 2018/07/03 4 | License: LGPL 5 | */ 6 | 7 | #ifndef __MODES_H__ 8 | #define __MODES_H__ 9 | 10 | #endif 11 | -------------------------------------------------------------------------------- /Debugging.h: -------------------------------------------------------------------------------- 1 | /* 2 | Author: Timo Birnschein (timo.birnschein@microforge.de) 3 | Date: 2018/07/03 4 | License: LGPL 5 | */ 6 | 7 | #ifndef __DEBUGGING_H__ 8 | #define __DEBUGGING_H__ 9 | 10 | #endif 11 | -------------------------------------------------------------------------------- /License.txt: -------------------------------------------------------------------------------- 1 | This work is licensed under a Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License. 2 | The full text of the license can be obtained here: https://creativecommons.org/licenses/by-nc-sa/4.0/ 3 | -------------------------------------------------------------------------------- /Electronics/License.txt: -------------------------------------------------------------------------------- 1 | This work is licensed under a Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License. 2 | The full text of the license can be obtained here: https://creativecommons.org/licenses/by-nc-sa/4.0/ 3 | -------------------------------------------------------------------------------- /Tools.h: -------------------------------------------------------------------------------- 1 | #ifndef __TOOLS_H__ 2 | #define __TOOLS_H__ 3 | 4 | /* 5 | * Author: Timo Birnschein (timo.birnschein@microforge.de) 6 | * Date: 2018/07/03 7 | * License: LGPL 8 | * Special thanks to Acton for making a remote so bad that it motivated me to 9 | * reverse engineer it and make my own. It was an interesting task and I 10 | * definitely learned a lot in the process. Cheers. 11 | * 12 | * Overview: 13 | */ 14 | 15 | 16 | 17 | #endif //__TOOLS__ 18 | -------------------------------------------------------------------------------- /Driving_StateMachine.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | Author: Timo Birnschein (timo.birnschein@microforge.de) 3 | Date: 2018/07/03 4 | License: LGPL 5 | */ 6 | 7 | #include "Driving_StateMachine.h" 8 | 9 | volatile mainStateMachine mainState; 10 | volatile driveStateMachine driveState; 11 | volatile performanceStateMachine performanceState; 12 | volatile velocityStateMachine velocityState; 13 | 14 | volatile bool messageType; // There are two messages to be sent to the board. False sends first, true the second 15 | volatile bool TX_DS_WasSetAlready_WaitFor_RX_DR = false; // Extremely unusual state where ACK_PAYLOAD was lost and RX_DR never comes on 16 | -------------------------------------------------------------------------------- /Pairing_StateMachine.h: -------------------------------------------------------------------------------- 1 | /* 2 | Author: Timo Birnschein (timo.birnschein@microforge.de) 3 | Date: 2018/07/03 4 | License: LGPL 5 | */ 6 | 7 | #ifndef __PAIRING_STATEMACHINE_H__ 8 | #define __PAIRING_STATEMACHINE_H__ 9 | 10 | #include 11 | #include 12 | #include 13 | 14 | enum pairingStateMachine 15 | { 16 | initPairing_1, 17 | checkForACK_1, 18 | pairingStage_2, 19 | checkForACK_2, 20 | setAsReceiver, 21 | receiveNewAddress, 22 | setupNewInitAfterPairing 23 | }; 24 | 25 | extern volatile pairingStateMachine pairingState; 26 | 27 | extern volatile uint8_t pairingAttempts; // Counts the number of pairing attempts until the system goes back into drive 28 | 29 | uint8_t pairRemote(void); 30 | 31 | #endif 32 | -------------------------------------------------------------------------------- /Driving_StateMachine.h: -------------------------------------------------------------------------------- 1 | /* 2 | Author: Timo Birnschein (timo.birnschein@microforge.de) 3 | Date: 2018/07/03 4 | License: LGPL 5 | */ 6 | 7 | #ifndef __DRIVING_STATEMACHINE_H__ 8 | #define __DRIVING_STATEMACHINE_H__ 9 | 10 | enum mainStateMachine {pairingRemote, initRemote, driveRemote, lostRemote, idleRemote, offRemote}; 11 | enum driveStateMachine {flushTX, clearPendingIRQ, sendMessage, checkStatus, receiveMessage}; 12 | enum performanceStateMachine {sport, cruiser, beginner}; 13 | enum velocityStateMachine {coast, accel, brake}; 14 | 15 | extern volatile mainStateMachine mainState; 16 | extern volatile driveStateMachine driveState; 17 | extern volatile performanceStateMachine performanceState; 18 | extern volatile velocityStateMachine velocityState; 19 | 20 | extern volatile bool messageType; // There are two messages to be sent to the board. False sends first, true the second 21 | extern volatile bool TX_DS_WasSetAlready_WaitFor_RX_DR; // Extremely unusual state where ACK_PAYLOAD was lost and RX_DR never comes on 22 | 23 | #endif // __DRIVING_STATEMACHINE_H__ 24 | -------------------------------------------------------------------------------- /Hardware.h: -------------------------------------------------------------------------------- 1 | /* 2 | Author: Timo Birnschein (timo.birnschein@microforge.de) 3 | Date: 2018/07/03 4 | License: LGPL 5 | */ 6 | 7 | #ifndef __HARDWARE_H__ 8 | #define __HARDWARE_H__ 9 | 10 | #include 11 | #include 12 | #include 13 | 14 | #define CS_PIN 7 15 | #define CSN_PIN 8 16 | #define IRQ_PIN 2 17 | 18 | #define battLED1_PIN 14 19 | #define battLED2_PIN 15 20 | #define battLED3_PIN 16 21 | #define battLED4_PIN 17 22 | #define lostLED_PIN 18 23 | 24 | #define mode_PIN 5 25 | #define pairing_PIN 10 26 | /* 27 | * TODO: Fine the correct pin for the battery voltage. 28 | * Used to measure current battery voltage and refuse throttle when low. 29 | * Only brakes available when battery is low to prevent sudden cutoff of all functions when remote batteries are low. 30 | */ 31 | #define batteryVoltage_PIN A0 32 | 33 | extern volatile uint8_t frequencies[16]; 34 | extern volatile uint8_t quatroAddress[5]; 35 | extern volatile uint8_t quatroPairingAddress1[5]; 36 | extern volatile uint8_t quatroPairingAddress2[5]; 37 | extern volatile uint8_t quatroMessage_1[5]; 38 | extern volatile uint8_t quatroMessage_2[5]; 39 | extern volatile uint8_t quatroPairingMessage[2]; 40 | extern volatile uint8_t quatroReturnMessage[8]; 41 | extern volatile uint8_t quatroPairingReturnMessage[3]; 42 | extern volatile uint8_t boardBatteryState; 43 | extern volatile float remoteBatteryVoltage; 44 | extern volatile bool remoteBatteryLevelCritical; 45 | 46 | void init_remote(void); 47 | 48 | void set_batteryState(uint8_t value); 49 | 50 | void set_remoteBatteryAlarm(); 51 | 52 | float rescaleADCThrottleValue(uint16_t input, uint16_t valueMIN, uint16_t valueMAX, uint16_t valueCenter); 53 | 54 | float exponentialCurve(float inputValue, float expoFactor); 55 | 56 | float pt1_damper (float input, float dampingFactor, float integralPart, float &lastDampedValue, float &lastIntegralPart); 57 | 58 | float pt1_damper (float input, float dampingFactor, float &lastDampedValue); 59 | 60 | float deadzoneCompensationAndRescale(float input, float posDeadZone, float negDeadZone, uint8_t boardMin, uint8_t boardMax, uint8_t boardDeadzoneMin, uint8_t boardDeadzoneMax, uint8_t boardCenter); 61 | 62 | 63 | #endif 64 | -------------------------------------------------------------------------------- /RF_Comm.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Author: Timo Birnschein (timo.birnschein@microforge.de) 3 | * Date: 2018/07/03 4 | * License: LGPL 5 | * Special thanks to Acton for making a remote so bad that it motivated me to 6 | * reverse engineer it and make my own. It was an interesting task and I 7 | * definitely learned a lot in the process. Cheers. 8 | * 9 | * Overview: 10 | * This is a bare bones and speciallized NRF24L01+ driver to communicate with 11 | * the Acton Blink QU4TRO and take control over it. 12 | * 13 | * Note: The NRF driver is actualy not really a driver as it only replicates all 14 | * instructions the microprocessor of the remote was also sending to its NRF. 15 | * Therefore, please take this with a grain of salt. 16 | * 17 | */ 18 | 19 | #include "RF_Comm.h" 20 | 21 | volatile uint8_t rtr_counter = 0; // MAX retry counter for counting how many times a message was tried to be sent or tried to be received 22 | volatile uint8_t freqCounter = 0; // Counter that holds the current communication frequency number. 23 | volatile uint8_t lastKnownFrequency = 0; // Counter that holds the current communication frequency number. 24 | volatile uint8_t lostCounter = 0; // If counter > 2, the remote should be reinitialized completely. Chances are, SPI communication messed up NRF chip. 25 | 26 | volatile uint8_t numHopsBeforeCheckingLastKnown = 3; // Holds the number of frequency hops that may be taken before the previously working will be tested 27 | volatile uint8_t numHopsTaken = 0; // Holds the number of frequency hops that may be taken before the previously working will be tested 28 | 29 | uint8_t write_ToAddress(uint8_t addr, uint8_t value) 30 | { 31 | uint8_t status = 0x00; 32 | digitalWrite(CSN_PIN, LOW); 33 | status = SPI.transfer(addr); 34 | SPI.transfer(value); 35 | digitalWrite(CSN_PIN, HIGH); 36 | 37 | delayMicroseconds(50); 38 | 39 | return status; 40 | } 41 | 42 | uint8_t read_FromAddress(uint8_t addr) 43 | { 44 | uint8_t status = 0x00; 45 | digitalWrite(CSN_PIN, LOW); 46 | status = SPI.transfer(addr); 47 | status = SPI.transfer(0x00); 48 | digitalWrite(CSN_PIN, HIGH); 49 | 50 | delayMicroseconds(50); 51 | 52 | return status; 53 | } 54 | 55 | 56 | uint8_t exec_command(uint8_t addr) 57 | { 58 | uint8_t status = 0x00; 59 | digitalWrite(CSN_PIN, LOW); 60 | status = SPI.transfer(addr); 61 | digitalWrite(CSN_PIN, HIGH); 62 | delayMicroseconds(50); 63 | 64 | return status; 65 | } 66 | 67 | // Writes a number of bytes to the NRF 68 | uint8_t write_BytesToAddress(uint8_t addr, uint8_t *values, uint8_t length) 69 | { 70 | uint8_t status = 0x00; 71 | digitalWrite(CSN_PIN, LOW); 72 | status = SPI.transfer(addr); 73 | for (uint8_t i = 0; i < length; i++) 74 | { 75 | status = SPI.transfer(values[i]); 76 | } 77 | digitalWrite(CSN_PIN, HIGH); 78 | delayMicroseconds(50); 79 | 80 | return status; 81 | } 82 | 83 | // Reads a number of bytes from the NRF 84 | uint8_t read_BytesFromAddress(uint8_t addr, uint8_t *values, uint8_t length) 85 | { 86 | uint8_t status = 0x00; 87 | digitalWrite(CSN_PIN, LOW); 88 | status = SPI.transfer(addr); 89 | for (uint8_t i = 0; i < length; i++) 90 | { 91 | values[i] = SPI.transfer(0xFF); // write as many fake bytes as we need to read values from the NRF 92 | } 93 | digitalWrite(CSN_PIN, HIGH); 94 | delayMicroseconds(50); 95 | 96 | return status; 97 | } 98 | 99 | // Pulses the CS line to let NRF know that data needs to be sent 100 | void trigger_CS(void) 101 | { 102 | digitalWrite(CS_PIN, HIGH); 103 | delayMicroseconds(50); 104 | digitalWrite(CS_PIN, LOW); 105 | } 106 | 107 | -------------------------------------------------------------------------------- /RF_Comm.h: -------------------------------------------------------------------------------- 1 | #ifndef __RF_COMM_H__ 2 | #define __RF_COMM_H__ 3 | 4 | 5 | /* 6 | * Author: Timo Birnschein (timo.birnschein@microforge.de) 7 | * Date: 2018/07/03 8 | * License: LGPL 9 | * Special thanks to Acton for making a remote so bad that it motivated me to 10 | * reverse engineer it and make my own. It was an interesting task and I 11 | * definitely learned a lot in the process. Cheers. 12 | * 13 | * Overview: 14 | * This is a bare bones and speciallized NRF24L01+ driver to communicate with 15 | * the Acton Blink QU4TRO and take control over it. 16 | * 17 | * Note: The NRF driver is actualy not really a driver as it only replicates all 18 | * instructions the microprocessor of the remote was also sending to its NRF. 19 | * Therefore, please take this with a grain of salt. 20 | * 21 | */ 22 | 23 | #include 24 | #include 25 | #include 26 | #include "Tools.h" 27 | #include "Hardware.h" 28 | 29 | // define all basic addesses for operating the NRF24L01+ 30 | // Please refere to NRF24L01+ Datasheet for more info on these 31 | #define R_REGISTER 0x00 // 0b00000000 // last five bits define register address: 000AAAAA 32 | #define W_REGISTER 0x20 // 0b00100000 // last five bits define register address: 000AAAAA 33 | #define R_RX_PAYLOAD 0x61 // 0b01100001 34 | #define W_TX_PAYLOAD 0xA0 // 0b10100001 35 | #define FLUSH_TX 0xE1 // 0b11100001 36 | #define FLUSH_RX 0xE2 // 0b11100010 37 | #define REUSE_TX_PL 0xE3 // 0b11100011 38 | #define R_RX_PL_WID 0x60 // 0b01100000 39 | #define W_ACK_PAYLOAD 0xA8 // 0b10101000 // last three bits define PIPE address: 00000PPP 40 | #define W_TX_PAYLOAD_NOACK 0xB0 // 0b10110000 41 | #define NOP 0xFF // 0b11111111 42 | 43 | // define all RW registers that - for some reason - are considered a 44 | // different category than the above 45 | // Please refere to NRF24L01+ Datasheet for more info on these 46 | #define CONFIG 0x00 47 | #define EN_AA 0x01 48 | #define EN_RXADDR 0x02 49 | #define SETUP_AW 0x03 50 | #define SETUP_RETR 0x04 51 | #define RF_CH 0x05 52 | #define RF_SETUP 0x06 53 | #define STATUS 0x07 54 | #define OBSERVE_TX 0x08 55 | #define RPD 0x09 56 | #define RX_ADDR_P0 0x0A 57 | #define RX_ADDR_P1 0x0B 58 | #define RX_ADDR_P2 0x0C 59 | #define RX_ADDR_P3 0x0D 60 | #define RX_ADDR_P4 0x0E 61 | #define RX_ADDR_P5 0x0F 62 | #define TX_ADDR 0x10 63 | #define RX_PW_P0 0x11 64 | #define RX_PW_P1 0x12 65 | #define RX_PW_P2 0x13 66 | #define RX_PW_P3 0x14 67 | #define RX_PW_P4 0x15 68 | #define RX_PW_P5 0x16 69 | #define FIFO_STATUS 0x17 70 | #define DYNPD 0x1C 71 | #define FEATURE 0x1D 72 | 73 | extern volatile uint8_t rtr_counter; // MAX retry counter for counting how many times a message was tried to be sent or tried to be received 74 | extern volatile uint8_t freqCounter; // Counter that holds the current communication frequency number. 75 | extern volatile uint8_t lastKnownFrequency; // Counter that holds the current communication frequency number. 76 | extern volatile uint8_t lostCounter; // If counter > 2, the remote should be reinitialized completely. Chances are, SPI communication messed up NRF chip. 77 | 78 | extern volatile uint8_t numHopsBeforeCheckingLastKnown; // Holds the number of frequency hops that may be taken before the previously working will be tested 79 | extern volatile uint8_t numHopsTaken; // Holds the number of frequency hops that may be taken before the previously working will be tested 80 | 81 | uint8_t write_ToAddress(uint8_t addr, uint8_t value); 82 | 83 | uint8_t read_FromAddress(uint8_t addr); 84 | 85 | uint8_t exec_command(uint8_t addr); 86 | 87 | // Writes a number of bytes to the NRF 88 | uint8_t write_BytesToAddress(uint8_t addr, uint8_t *values, uint8_t length); 89 | 90 | // Reads a number of bytes from the NRF 91 | uint8_t read_BytesFromAddress(uint8_t addr, uint8_t *values, uint8_t length); 92 | 93 | // Pulses the CS line to let NRF know that data needs to be sent 94 | void trigger_CS(void); 95 | 96 | #endif // __QUATRO_H__ 97 | 98 | -------------------------------------------------------------------------------- /Hardware.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | Author: Timo Birnschein (timo.birnschein@microforge.de) 3 | Date: 2018/07/03 4 | License: LGPL 5 | */ 6 | 7 | #include "Hardware.h" 8 | 9 | volatile uint8_t frequencies[16] = {0x02, 0x19, 0x30, 0x47, 0x05, 0x1C, 0x33, 0x4A, 0x08, 0x1F, 0x36, 0x4D, 0x0B, 0x22, 0x39, 0x50}; 10 | volatile uint8_t quatroAddress[5] = {0x51, 0xFC, 0xC9, 0xC9, 0xC9}; 11 | volatile uint8_t quatroPairingAddress1[5] = {0xE3, 0xE3, 0xE3, 0xE3, 0xE3}; 12 | volatile uint8_t quatroPairingAddress2[5] = {0xE3, 0xE3, 0xE3, 0xE3, 0xE3}; 13 | volatile uint8_t quatroMessage_1[5] = {0x00, 0x80, 0x00, 0x00, 0x00}; 14 | volatile uint8_t quatroMessage_2[5] = {0x80, 0x00, 0x00, 0x00, 0x00}; 15 | volatile uint8_t quatroPairingMessage[2] = {0xC0, 0x00}; 16 | volatile uint8_t quatroPairingReturnMessage[3] = {0x00, 0x00, 0x00}; 17 | volatile uint8_t quatroReturnMessage[8] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; 18 | 19 | volatile uint8_t boardBatteryState = 0; 20 | volatile float remoteBatteryVoltage = 0; // Stores the current remote battery voltage to allow turning off the throttle when battery reaches critical voltage. DCDC shuts off at 2.3V 21 | 22 | volatile bool remoteBatteryLevelCritical = false; // If true, no more throttle will be allowed, only brakes! 23 | 24 | void init_remote(void) 25 | { 26 | // Check if remote was successfully paired before. If yes, startup as normal. 27 | // If not paired successfully or pairing button was just pressed, goto pairing. 28 | 29 | if (EEPROM.read(7) == 0x5A) // we already successfully paired the remote 30 | { 31 | quatroAddress[0]= EEPROM.read(0); 32 | quatroAddress[1]= EEPROM.read(1); 33 | quatroAddress[2]= EEPROM.read(2); 34 | quatroAddress[3]= EEPROM.read(3); 35 | quatroAddress[4]= EEPROM.read(4); 36 | } 37 | return; 38 | } 39 | 40 | 41 | void set_batteryState(uint8_t value) 42 | { 43 | static uint32_t oldMillis = 0; // This is good for 1,193h of battery status blinking when remote left on - not that any remote battery would last that long 44 | 45 | if (millis() - oldMillis > 750) 46 | { 47 | digitalWrite(battLED1_PIN, (value & 0x08) ? LOW : HIGH); // Battery status < 100% 48 | digitalWrite(battLED2_PIN, (value & 0x04) ? LOW : HIGH); // Battery status < 75% 49 | digitalWrite(battLED3_PIN, (value & 0x02) ? LOW : HIGH); // Battery status < 50% 50 | digitalWrite(battLED4_PIN, (value & 0x01) ? LOW : HIGH); // Battery status < 25% 51 | 52 | oldMillis = millis(); 53 | } 54 | else if (millis() - oldMillis > 20) 55 | { 56 | digitalWrite(battLED1_PIN, LOW); // Battery status < 100% 57 | digitalWrite(battLED2_PIN, LOW); // Battery status < 75% 58 | digitalWrite(battLED3_PIN, LOW); // Battery status < 50% 59 | digitalWrite(battLED4_PIN, LOW); // Battery status < 25% 60 | } 61 | } 62 | 63 | void set_remoteBatteryAlarm() 64 | { static uint32_t oldMillis = 0; // This is good for 1,193h of battery status blinking when remote left on - not that any remote battery would last that long 65 | 66 | if (millis() - oldMillis > 750) 67 | { 68 | digitalWrite(battLED1_PIN, HIGH); // Battery status < 100% 69 | digitalWrite(battLED2_PIN, LOW); // Battery status < 75% 70 | digitalWrite(battLED3_PIN, LOW); // Battery status < 50% 71 | digitalWrite(battLED4_PIN, HIGH); // Battery status < 25% 72 | 73 | oldMillis = millis(); 74 | } 75 | else if (millis() - oldMillis > 20) 76 | { 77 | digitalWrite(battLED1_PIN, LOW); // Battery status < 100% 78 | digitalWrite(battLED2_PIN, LOW); // Battery status < 75% 79 | digitalWrite(battLED3_PIN, LOW); // Battery status < 50% 80 | digitalWrite(battLED4_PIN, LOW); // Battery status < 25% 81 | } 82 | } 83 | 84 | // Rescale input values to a range from -1 to 1 85 | float rescaleADCThrottleValue(uint16_t input, uint16_t valueMIN, uint16_t valueMAX, uint16_t valueCenter) 86 | { 87 | if (input < valueCenter) // e.g. input == 360 -> accelerating 88 | { 89 | uint16_t diff = valueCenter - valueMIN; // ~163 90 | float retVal = (float)(1.0f / (float)diff) * (float)((float)input - (float)valueCenter); 91 | if (retVal < -1.0f) 92 | retVal = -1.0f; 93 | return retVal; 94 | } 95 | else if (input > valueCenter) // e.g. input == 691 -> braking 96 | { 97 | uint16_t diff = valueMAX - valueCenter; // ~168 98 | float retVal = (float)(1.0f / (float)diff) * (float)((float)input - (float)valueCenter); 99 | if (retVal > 1.0f) 100 | retVal = 1.0f; 101 | return retVal; 102 | } 103 | else 104 | { 105 | return 0.0f; 106 | } 107 | return 0; 108 | } 109 | 110 | /* UNTESTED 111 | * Must be a value between -1 and 1 112 | */ 113 | float exponentialCurve(float inputValue, float expoFactor) 114 | { 115 | return ( (1 - expoFactor) * (inputValue * inputValue * inputValue) ) + (expoFactor * inputValue); 116 | } 117 | 118 | // This function implements a digital filter to smoothen out user jitter on the throttle potentiometer 119 | float pt1_damper (float input, float dampingFactor, float integralPart, float &lastDampedValue, float &lastIntegralPart) 120 | { 121 | lastDampedValue = lastDampedValue + ((input - lastDampedValue) * dampingFactor); 122 | 123 | if (lastDampedValue < input) 124 | lastIntegralPart = lastIntegralPart + integralPart; 125 | else if (lastDampedValue > input) 126 | lastIntegralPart = lastIntegralPart - integralPart; 127 | 128 | lastDampedValue = lastDampedValue + lastIntegralPart; 129 | 130 | return lastDampedValue; 131 | } 132 | 133 | // This function implements a digital filter to smoothen out user jitter on the throttle potentiometer -- UNTESTED 134 | float pt1_damper (float input, float dampingFactor, float &lastDampedValue) 135 | { 136 | lastDampedValue = lastDampedValue + ((input - lastDampedValue) * dampingFactor); 137 | return lastDampedValue; 138 | } 139 | 140 | // Removes the deadzone from an existing motor controller if programmed in 141 | float deadzoneCompensationAndRescale(float input, float posDeadZone, float negDeadZone, uint8_t boardMin, uint8_t boardMax, uint8_t boardDeadzoneMin, uint8_t boardDeadzoneMax, uint8_t boardCenter) 142 | { 143 | uint8_t retVal = 0; 144 | uint8_t centerToMin = (boardCenter - boardDeadzoneMin) - boardMin; 145 | uint8_t centerToMax = boardMax - (boardCenter + boardDeadzoneMax); 146 | 147 | if (input > (posDeadZone)) // posDeadZone in percent / 100 148 | { // braking 149 | retVal = (boardCenter + boardDeadzoneMax) + (centerToMax * input); 150 | if (retVal < boardCenter) 151 | retVal = boardCenter; 152 | } 153 | else if (input < (negDeadZone)) // negDeadZone in percent / 100 154 | { // accelerating 155 | retVal = (boardCenter - boardDeadzoneMin) - (centerToMin * -input); 156 | if (retVal > boardCenter) 157 | retVal = boardCenter; 158 | } 159 | else 160 | { 161 | retVal = boardCenter; 162 | } 163 | 164 | return retVal; 165 | } 166 | 167 | -------------------------------------------------------------------------------- /Pairing_StateMachine.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | Author: Timo Birnschein (timo.birnschein@microforge.de) 3 | Date: 2018/07/03 4 | License: LGPL 5 | 6 | WARNING: The pairing is rather complicated and elaborate. Lots of states and a strikt timing constraints. 7 | If timing is not met, the board will leave the pairing attempt immediately and go back to start. 8 | Make sure you communicate fast enough and avoid all unnecessary delayes and RS232 communication. 9 | 10 | STEPS REQUIRED: 11 | - Write to CONFIG 0x20 0x0E: CRC, 2 bytes, Activate power 12 | - Set powerlevel to -12db power and 2mbits 13 | - Set initial channel to 2 14 | - Set RX/TX address to 0xE3 0xE3 0xE3 0xE3 0xE3 15 | - Clear all pending IRQs 16 | - Flush TX 17 | - Clear all pending IRQs again 18 | - Send binding message: 0xC0 0xFIRST_BYTE_OF_LAST_ADDRESS 19 | - Check if an ACK was received by checking for TX_DS bit in STATUS register: 0x2E (TX_DS | RX_FIFO_EMPTY) 20 | - If no ACK was received MAX_RTR will turn on: 0x1E: (MAX_RTR | RX_FIFO_EMPTY) 21 | - Check STATUS again to make sure no ACK was received 22 | - Increment frequency hopper 23 | - Start over at the top with same pairing address: 0xE3 0xE3 0xE3 0xE3 0xE3 24 | - If ACK was received 25 | - Set RX/TX address to 0xFIRST_BYTE_OF_LAST_ADDRESS (0xFF - 0xFIRST_BYTE_OF_LAST_ADDRESS) 0xE3 0xE3 0xE3 26 | - Flush TX buffer 27 | - Set powerlevel to -6db power and 2mbits (slightly more power but this time we're reasonably sure we're communicating with the correct receiver) 28 | - Clear all pending IRQs 29 | - Clear all pending IRQs again 30 | - Send binding message again on new address: 0xC0 0xFIRST_BYTE_OF_LAST_ADDRESS 31 | - Check if an ACK was received by checking for TX_DS bit in STATUS register: 0x2E (TX_DS | RX_FIFO_EMPTY) 32 | - If no ACK was received MAX_RTR will turn on: 0x1E: (MAX_RTR | RX_FIFO_EMPTY) 33 | - Check STATUS again to make sure no ACK was received 34 | - Increment frequency hopper 35 | - Start over at the top with same pairing address: 0xE3 0xE3 0xE3 0xE3 0xE3 36 | - If ACK was received 37 | - Reset NRF to be a receiver: Write to CONFIG 0x20 0x0F: CRC, 2 bytes, Activate power, PRIM_RX 38 | - Flush RX Buffer 39 | - Clear all pending IRQ 40 | - Set powerlevel to 0db power and 2mbits 41 | - Check STATUS repeatedly until 0x40 is present: RX_DR IRQ was fired! 42 | - Retrieve message from the FIFO and store new address bytes from received message: 0x00 0xXX 0xXX 43 | - If all was successful, restart as usual using new address 44 | */ 45 | 46 | #include "Pairing_StateMachine.h" 47 | #include "RF_Comm.h" 48 | #include "Driving_StateMachine.h" 49 | 50 | volatile pairingStateMachine pairingState; 51 | 52 | volatile uint8_t pairingAttempts = 0; // Counts the number of pairing attempts until the system goes back into drive 53 | 54 | uint8_t pairRemote() 55 | { 56 | static uint8_t status = 0x00; // used for general storage of the STATUS 57 | static uint8_t status1 = 0x00; // used when two STATUS values need to be compared 58 | static uint8_t status2 = 0x00; // used when two STATUS values need to be compared 59 | 60 | 61 | //Serial.println("P Pairing Remote"); 62 | digitalWrite(lostLED_PIN, LOW); 63 | digitalWrite(3, HIGH); // Set debug pin 64 | 65 | // UNUSED AT THE MOMENT TODO! If pairing needs to be left, restart remote. Button is hard to reach anyways. 66 | if (pairingAttempts > 100) 67 | { 68 | // Pairing could not be performed successfully! Go back into drive 69 | pairingState = setupNewInitAfterPairing; 70 | return 0; // Return and let top state know to break 71 | } 72 | 73 | if (freqCounter > 15) // Sanity check on the frequency counter 74 | freqCounter = 0 ; 75 | 76 | // The frequency counter must be initialized somewhere else before reaching this 77 | switch (pairingState) // STATE CONFIRMED WORKING 78 | { 79 | case initPairing_1: // send out pairing command on standard pairing address 5 x 0xE3 80 | //Serial.println("P initPairing_1"); 81 | set_batteryState(1); 82 | status = write_ToAddress(W_REGISTER | CONFIG, 0x0E); // Enable CRC, 2 byte CRC, Power Up 83 | status = write_ToAddress(W_REGISTER | RF_SETUP, 0x0B); // set RF_Config to low power and 2 mBits 84 | status = write_ToAddress(W_REGISTER | RF_CH, frequencies[freqCounter++]); // Set operating frequency to 2: 2.402 Ghz 85 | status = write_BytesToAddress(W_REGISTER | TX_ADDR, quatroPairingAddress1, 5); // Set PAIRING TC address to board 86 | status = write_BytesToAddress(W_REGISTER | RX_ADDR_P0, quatroPairingAddress1, 5); // Set PAIRING RX address from board 87 | status = write_ToAddress(W_REGISTER | STATUS, 0x70); // Clear out all pending IRQs - We will ignore all - should return 0x0E 88 | status = exec_command(FLUSH_TX); // clean out the TX buffer - should return 0x0E 89 | status = write_ToAddress(W_REGISTER | STATUS, 0x70); // Clear out all pending IRQs - We will ignore all - should return 0x0E 90 | // Change pairing message to reflect the first byte of the last known address 91 | 92 | quatroPairingMessage[1] = quatroAddress[0]; 93 | status = write_BytesToAddress(W_TX_PAYLOAD, quatroPairingMessage, 2); // Send message 1 to the transmitter 94 | trigger_CS(); // Trigger the transmitter 95 | 96 | rtr_counter = 0; // Reset RTR counter so that we can cycle through checking for ACK 97 | pairingState = checkForACK_1; 98 | break; 99 | 100 | case checkForACK_1: 101 | set_batteryState(2); 102 | //Serial.println("P checkForACK_1"); 103 | status1 = exec_command(STATUS); // Read what's going on in the status - did we successfully send the message or timed out? 104 | status2 = exec_command(NOP); // Read a second time using the NOP command which should also return the same value 105 | 106 | if (status1 == status2) 107 | { 108 | if (status1 == 0x0E) // We have not yet received any ACK from the board. We will wait. 109 | { 110 | //Serial.println("P 0x0E"); 111 | set_batteryState(3); 112 | rtr_counter++; 113 | //Serial.println(rtr_counter); 114 | if (rtr_counter > 10) 115 | { 116 | rtr_counter = 0; // Reset the counter 117 | // we have reached the maximum number of retries and should concider this channel unused 118 | // Try again with a new frequncy which was already incremented in the init state 119 | // It is expected that this state will never be reached. If we did anyways, there is something wrong and we should restart pairing 120 | pairingState = initPairing_1; 121 | } 122 | //delay(1); 123 | } 124 | else if (status1 & 0x10) // MAX_RTR was set. We send the same message again 125 | { 126 | //Serial.print("P "); 127 | //Serial.println(status1); 128 | set_batteryState(4); 129 | rtr_counter++; 130 | //Serial.println(rtr_counter); 131 | 132 | status = write_ToAddress(W_REGISTER | STATUS, 0x70); // Clear out all pending IRQs - We will ignore all - should return 0x0E 133 | //delay(1); 134 | status = exec_command(FLUSH_TX); // clean out the TX buffer - should return 0x0E 135 | //delay(1); 136 | status = write_ToAddress(W_REGISTER | STATUS, 0x70); // Clear out all pending IRQs - We will ignore all - should return 0x0E 137 | //delay(1); 138 | // Change pairing message to reflect the first byte of the last known address 139 | 140 | quatroPairingMessage[1] = quatroAddress[0]; 141 | status = write_BytesToAddress(W_TX_PAYLOAD, quatroPairingMessage, 2); // Send message 1 to the transmitter 142 | trigger_CS(); 143 | //delay(1); 144 | 145 | if (rtr_counter > 1) 146 | { 147 | rtr_counter = 0; // Reset the counter 148 | // we have reached MAX_RTR maximum number of retries and need to start over with a new frequency! 149 | pairingState = initPairing_1; 150 | } 151 | } 152 | else if (status1 & 0x20) // Data was successfully sent and we can continue to the next state pairingStage_2 153 | { 154 | //Serial.print("P "); 155 | //Serial.println(status1); 156 | set_batteryState(5); 157 | // The Pairing message was sent, ACK was received and we can continue 158 | pairingState = pairingStage_2; 159 | } 160 | } 161 | break; 162 | 163 | case pairingStage_2: // Switch to negociated pairing address and send same pairing message again 164 | //Serial.println("P pairingStage_2"); 165 | set_batteryState(6); 166 | 167 | // Change pairing message to reflect the first byte of the last known address 168 | quatroPairingAddress2[0] = quatroAddress[0]; 169 | quatroPairingAddress2[1] = 0xFF - quatroAddress[0]; 170 | 171 | status = write_BytesToAddress(W_REGISTER | TX_ADDR, quatroPairingAddress2, 5); // Set PAIRING TC address to board 172 | status = write_BytesToAddress(W_REGISTER | RX_ADDR_P0, quatroPairingAddress2, 5); // Set PAIRING RX address from board 173 | status = exec_command(FLUSH_TX); // clean out the TX buffer - should return 0x0E 174 | status = write_ToAddress(W_REGISTER | RF_SETUP, 0x0D); // set RF_Config to medium power and 2 mBits 175 | status = write_ToAddress(W_REGISTER | STATUS, 0x70); // Clear out all pending IRQs - We will ignore all - should return 0x0E 176 | status = write_ToAddress(W_REGISTER | STATUS, 0x70); // Clear out all pending IRQs - We will ignore all - should return 0x0E 177 | 178 | // Change pairing message to reflect the first byte of the last known address 179 | quatroPairingMessage[1] = quatroAddress[0]; 180 | 181 | status = write_BytesToAddress(W_TX_PAYLOAD, quatroPairingMessage, 2); // Send message 1 to the transmitter 182 | trigger_CS(); 183 | 184 | pairingState = checkForACK_2; 185 | 186 | break; 187 | 188 | case checkForACK_2: // If everything goes well, we receive the ACK from the board 189 | //Serial.println("P checkForACK_2"); 190 | set_batteryState(7); 191 | 192 | status1 = exec_command(STATUS); // Read what's going on in the status - did we successfully send the message or timed out? 193 | status2 = exec_command(NOP); // Read a second time using the NOP command which should also return the same value 194 | 195 | if (status1 == status2) 196 | { 197 | if (status1 == 0x0E) // We have not yet received any ACK from the board. We will wait. 198 | { 199 | //Serial.println("P 0x0E"); 200 | set_batteryState(10); 201 | rtr_counter++; 202 | //Serial.println(rtr_counter); 203 | if (rtr_counter > 10) 204 | { 205 | rtr_counter = 0; // Reset the counter 206 | // we have reached the maximum number of retries and should concider this channel unused 207 | // Try again with a new frequncy which was already incremented in the init state 208 | // It is expected that this state will never be reached. If we did anyways, there is something wrong and we should restart pairing 209 | pairingState = initPairing_1; 210 | } 211 | } 212 | else if (status1 & 0x10) // MAX_RTR was set and RX FIFO is empty 213 | { 214 | //Serial.print("P "); 215 | //Serial.println(status1); 216 | set_batteryState(8); 217 | rtr_counter++; 218 | //Serial.println(rtr_counter); 219 | 220 | status = write_ToAddress(W_REGISTER | STATUS, 0x70); // Clear out all pending IRQs - We will ignore all - should return 0x0E 221 | status = exec_command(FLUSH_TX); // clean out the TX buffer - should return 0x0E 222 | status = write_ToAddress(W_REGISTER | STATUS, 0x70); // Clear out all pending IRQs - We will ignore all - should return 0x0E 223 | 224 | // Change pairing message to reflect the first byte of the last known address 225 | quatroPairingMessage[1] = quatroAddress[0]; 226 | status = write_BytesToAddress(W_TX_PAYLOAD, quatroPairingMessage, 2); // Send message 1 to the transmitter 227 | trigger_CS(); 228 | 229 | if (rtr_counter > 1) 230 | { 231 | rtr_counter = 0; // Reset the counter 232 | // we have reached MAX_RTR maximum number of retries and need to start over with a new frequency! 233 | pairingState = initPairing_1; 234 | } 235 | } 236 | else if (status1 & 0x20) // Data was successfully sent and we can continue to the next state pairingStage_2 237 | { 238 | //Serial.print("P "); 239 | //Serial.println(status1); 240 | set_batteryState(9); 241 | // The Pairing message was sent, ACK was received and we can continue 242 | pairingState = setAsReceiver; 243 | } 244 | } 245 | break; 246 | 247 | case setAsReceiver: // Switch roles using the same address to make sure it's really the board and listen 248 | //Serial.println("P setAsReceiver"); 249 | set_batteryState(10); 250 | 251 | status = write_ToAddress(W_REGISTER | CONFIG, 0x0F); // Enable CRC, 2 byte CRC, Power Up, PRX receive mode! 252 | status = exec_command(FLUSH_RX); // clean out the RX buffer - should return 0x2E at this stage 253 | status = write_ToAddress(W_REGISTER | STATUS, 0x70); // Clear out all pending IRQs - We will ignore all - should return 0x0E 254 | status = write_ToAddress(W_REGISTER | RF_SETUP, 0x0F); // set RF_Config to high power and 2 mBits 255 | digitalWrite(CS_PIN, HIGH); // MUST BE HIGH IN ORDER TO RECEIVE IN PRX MODE!!! <--- this pin has cost me 4h of my life 256 | 257 | pairingState = receiveNewAddress; 258 | break; 259 | 260 | case receiveNewAddress: // Listen on channel and wait for welcome message with two new address bytes 261 | digitalWrite(3, LOW); // Set debug pin 262 | //Serial.println("P receiveNewAddress"); 263 | set_batteryState(11); 264 | 265 | status1 = exec_command(STATUS); // Read what's going on in the status - did we successfully send the message or timed out? 266 | status2 = exec_command(NOP); // Read a second time using the NOP command which should also return the same value 267 | 268 | if (status1 == status2) 269 | { 270 | if (status1 == 0x0E) // We have not yet received anything from the board. We will wait. 271 | { 272 | //Serial.println("P 0x0E"); 273 | set_batteryState(12); 274 | rtr_counter++; 275 | //Serial.println(rtr_counter); 276 | if (rtr_counter > 100) 277 | { 278 | rtr_counter = 0; // Reset the counter 279 | // we have reached the maximum number of retries and should concider this channel unused 280 | // Try again with a new frequncy which was already incremented in the init state 281 | // It is expected that this state will never be reached. If we did anyways, there is something wrong and we should restart pairing 282 | pairingState = initPairing_1; 283 | digitalWrite(CS_PIN, LOW); // RESET CS PIN BECAUSE WE WILL GO BACK TO TRANSMIT MODE!!! <--- this pin has cost me 4h of my life 284 | } 285 | } 286 | else if (status1 & 0x40) // RX_DR was set and RX FIFO contains data in PIPE 0 ---- SUCCESS!!! 287 | { 288 | //Serial.print("P "); 289 | //Serial.println(status1); 290 | set_batteryState(13); 291 | // Get new address from board and store it in the EEProm so that we can use it later after power up 292 | status = read_BytesFromAddress(R_RX_PAYLOAD, quatroPairingReturnMessage, 3); 293 | // Take byte 1 and 2 as new address 294 | quatroAddress[0] = quatroPairingReturnMessage[1]; 295 | quatroAddress[1] = quatroPairingReturnMessage[2]; 296 | 297 | //Serial.println("******************** !!!!SUCCESSFULLY PAIRED!!!! ********************"); 298 | //Serial.println("P Store new address into EEPROM..."); 299 | // Store the newly received address into EEPROM 300 | EEPROM.write(0, quatroAddress[0]); 301 | EEPROM.write(1, quatroAddress[1]); 302 | EEPROM.write(2, quatroAddress[2]); 303 | EEPROM.write(3, quatroAddress[3]); 304 | EEPROM.write(4, quatroAddress[4]); 305 | 306 | // Also store that this remote was already initialized 307 | //Serial.println("P Set paired remote flag..."); 308 | EEPROM.write(7, 0x5A); 309 | 310 | digitalWrite(CS_PIN, LOW); // RESET CS PIN BECAUSE WE WILL GO BACK TO TRANSMIT MODE!!! <--- this pin has cost me 4h of my live 311 | 312 | for (int i = 0; i < 10; i++) 313 | { 314 | tone(6, 400, 100); 315 | delay(100); 316 | set_batteryState(0xFF); 317 | tone(6, 800, 100); 318 | delay(100); 319 | set_batteryState(0x00); 320 | } 321 | 322 | pairingState = setupNewInitAfterPairing; 323 | break; 324 | } 325 | else // something very much off happened and we should restart pairing 326 | { 327 | //Serial.println("P Leaving pairing receiveNewAddress to restart. Something happend..."); 328 | //Serial.print("P "); 329 | //Serial.println(status); 330 | set_batteryState(14); 331 | 332 | // Something went wrong, we received a status we didn't anticipate 333 | freqCounter = 0; // Fresh start. We'll find the right frequency fast 334 | mainState = initRemote; 335 | driveState = flushTX; 336 | pairingState = initPairing_1; 337 | digitalWrite(CS_PIN, LOW); // RESET CS PIN BECAUSE WE WILL GO BACK TO TRANSMIT MODE!!! <--- this pin has cost me 4h of my live 338 | digitalWrite(lostLED_PIN, HIGH); 339 | } 340 | } 341 | break; 342 | case setupNewInitAfterPairing: 343 | //Serial.println("P Resetting controller and leaving pairing states after successful pairing"); 344 | // Initialize state machines 345 | freqCounter = 0; 346 | mainState = initRemote; 347 | driveState = flushTX; 348 | pairingState = initPairing_1; 349 | set_batteryState(0xFF); 350 | messageType = false; 351 | 352 | init_remote(); // Read paired address from the EEPROM 353 | 354 | digitalWrite(3, LOW); // Set debug pin low 355 | break; 356 | } 357 | } 358 | 359 | -------------------------------------------------------------------------------- /QuatroRemote.ino: -------------------------------------------------------------------------------- 1 | /* 2 | Author: Timo Birnschein (timo.birnschein@microforge.de) 3 | Date: 2021/07/24 4 | License: LGPL 5 | Special thanks to Acton for making a remote so bad that it motivated me to 6 | reverse engineer it and make my own. It was an interesting task and I 7 | definitely learned a lot in the process. Cheers. 8 | 9 | Overview: 10 | This is a bare bones and speciallized NRF24L01+ driver to communicate with 11 | the Acton Blink QU4TRO and take control over it. 12 | This serves multiple functions that are important to me as of now: 13 | - Reduce the amount of deadzone in the controller. 14 | - Extend the throw of the throttle wheel 15 | - Use the entire travel to from 0 to 100% (or at least 90%) 16 | - Introduce a hysteresis to allow some wobble on the throttle wheel 17 | - Introduce a user definable ramp for different ride modes that does only 18 | change the acceleration and max speed but not the boards response to 19 | uneven surfaces and gravel 20 | - User definable exponential function for smooth cruising and fast acceleration 21 | - Behavior for quick responses for fast accel and quick strong brakes based 22 | on how fast and how much the throttle was moved. 23 | */ 24 | 25 | // TODO: Change all filters to use time based filtering instead of ticks 26 | 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include "RF_Comm.h" 32 | #include "Hardware.h" 33 | #include "Pairing_StateMachine.h" 34 | #include "Driving_StateMachine.h" 35 | 36 | 37 | enum calibrationStateMachine 38 | { 39 | findMIN, 40 | findMAX, 41 | findCenter 42 | }; 43 | 44 | calibrationStateMachine throttleCalibration; 45 | // Pairing button definition 46 | int pairingButtonState = HIGH; // the current reading from the input pin 47 | int lastPairingButtonState = HIGH; // the previous reading from the input pin 48 | int modeButtonState = HIGH; // the current reading from the input pin 49 | int lastModeButtonState = HIGH; // the previous reading from the input pin 50 | unsigned long lastPairingButtonDebounceTime = 0; // the last time the output pin was toggled 51 | unsigned long pairingButtonDebounceDelay = 50; // the debounce time; increase if the output flickers 52 | 53 | unsigned long lastModeButtonDebounceTime = 0; // the last time the output pin was toggled 54 | unsigned long modeButtonDebounceDelay = 50; // the debounce time; increase if the output flickers 55 | 56 | unsigned long lastTimeSinceStatusReport = 0; 57 | 58 | // Throttle calibration values 59 | uint16_t minThrottleADC = 1024; 60 | uint16_t centerThrottleADC = 512; 61 | uint16_t maxThrottleADC = 0; 62 | uint8_t throttleHysteresis = 20; 63 | uint16_t throttleCenterSettleTime = 1000; 64 | uint8_t throttleCenterNoise = 4; 65 | bool throttleCalibrated = true; 66 | 67 | // Throttle Output filtering 68 | float lastDampedValue = 0; 69 | float dampingFactor = 0.05; 70 | float dampingFactorDriveModeModifier = 10; 71 | float integralPart = 0.00; // LEAVE AT ZERO! NOT STABLE! 72 | float lastIntegralPart = 0; 73 | 74 | // ACTON BLINK QU4TRO related values 75 | float deadzone = 0.03; 76 | float posDeadZone = 0.05; // Brake deadzone - 0.05 is still to high 77 | float negDeadZone = -0.05; 78 | float deadzoneHysteresis = 0.01; // when leaving the deadzone, subtract this until back inside 79 | float expoFactor = 1.0; 80 | uint8_t boardDeadzoneMin = 4; 81 | uint8_t boardDeadzoneMax = -4; // Brake deadzone removal - 2 is still too high. I set it to -4 to get the complete range for brakes and then go from there. 82 | // TODO: All deadzone removal values should be > 0 to make sense to the user and start at 0 when set. 83 | uint8_t boardCenter = 128; 84 | uint8_t boardMin = 32; // Value at which the board produces max acceleration torque 85 | uint8_t boardMax = 230; // Value at which the board produces max brake torque - TODO GUESS BETTER! 86 | 87 | // Speed estimation model for less lag in cruise and beginner mode: 88 | float skateboardSpeed = 0.0; // value between 0 and 1 representing standstill to full throttle 89 | float lastSkateboardSpeed = 0.0; // value from last iteration of velocity model 90 | float skateboardSpeedAccelDampening = 0.004; // Factor describing acceleration behavior 91 | float skateboardSpeedCoastDecelIterator = 0.00017; // When coasting, using this simple integrator to reduce velocity 92 | float skateboardSpeedBrakeDecelFactor = 0.001; // When braking, using this factor to reduce velocity 93 | 94 | void setup() { 95 | // initialize serial communications at 115200 bps: 96 | Serial.begin(115200); 97 | lastTimeSinceStatusReport = millis(); 98 | 99 | // put your setup code here, to run once: 100 | pinMode(3, OUTPUT); // Debug Pin to measure total time of pairing 101 | pinMode(CS_PIN, OUTPUT); // CS is the chip send trigger of the NRF 102 | pinMode(CSN_PIN, OUTPUT); // CSN is chip select of the NRF 103 | pinMode(IRQ_PIN, INPUT); // IRQ is a signal from the NRF and is active low 104 | digitalWrite(CS_PIN, LOW); // CS is a trigger to send data and is active high 105 | digitalWrite(CSN_PIN, HIGH); // CS is a trigger to send data and is active high 106 | 107 | pinMode(battLED1_PIN, OUTPUT); // Battery status < 25% 108 | pinMode(battLED2_PIN, OUTPUT); // Battery status < 50% 109 | pinMode(battLED3_PIN, OUTPUT); // Battery status < 75% 110 | pinMode(battLED4_PIN, OUTPUT); // Battery status < 100% 111 | pinMode(lostLED_PIN, OUTPUT); // Connection to board lost 112 | 113 | pinMode(mode_PIN, INPUT_PULLUP); // Switch for changing modes 114 | pinMode(pairing_PIN, INPUT_PULLUP); // Button for repairing the remote to the board 115 | 116 | 117 | digitalWrite(lostLED_PIN, HIGH); // Connection to board lost - start search 118 | 119 | // when pin IRQ_PIN goes low, call the IRQ function -- This is actually unused. 120 | // The NRF chip gets polled all the time at the moment 121 | //attachInterrupt(digitalPinToInterrupt(IRQ_PIN), receivedIRQ, FALLING); 122 | 123 | // initialize SPI: 124 | SPI.begin(); 125 | SPI.beginTransaction(SPISettings(500000, MSBFIRST, SPI_MODE0)); 126 | //SPI.beginTransaction(SPISettings(1000000, MSBFIRST, SPI_MODE0)); 127 | 128 | // Initialize state machines 129 | freqCounter = 0; 130 | mainState = initRemote; 131 | driveState = sendMessage; 132 | pairingState = initPairing_1; 133 | performanceState = sport; 134 | velocityState = coast; 135 | set_batteryState(0xFF); 136 | messageType = false; 137 | 138 | init_remote(); // Read paired address from the EEPROM 139 | 140 | digitalWrite(3, LOW); // Set debug pin low 141 | 142 | tone(6, 440, 100); 143 | delay(100); 144 | tone(6, 600, 100); 145 | delay(100); 146 | tone(6, 800, 100); 147 | delay(100); 148 | tone(6, 1000, 100); 149 | delay(100); 150 | 151 | /* Throttle Calibration 152 | Check if the throttle was pulled back (value larger) and is too far away from a possible center to be considered center. 153 | User wants to recalibrate the throttle . 154 | 1. Store max ADC value 155 | 2. Refresh min value as long as ADC shrinks (user pushes throttle forward 156 | 3. When min hit, user will go back to center. After hysteresis threshold was past, store min value 157 | 4. Wait until throttle was still for 2 seconds 158 | 5. Store center 159 | 6. Save all values to EEPROM 160 | */ 161 | 162 | //if ((analogRead(A7) > 600))// && (digitalRead(mode_PIN) == LOW)) 163 | if ((analogRead(A7) > 600) && (digitalRead(mode_PIN) == LOW)) 164 | { 165 | Serial.println("Starting Throttle Calibration"); 166 | throttleCalibration = findMAX; 167 | throttleCalibrated = false; 168 | } 169 | else // read old data from EEPROM 170 | { 171 | Serial.print("Reading throttle values from EEPROM..."); 172 | // store all values to EEPROM 173 | maxThrottleADC = EEPROM.read(10); 174 | maxThrottleADC |= EEPROM.read(11) << 8; 175 | minThrottleADC = EEPROM.read(12); 176 | minThrottleADC |= EEPROM.read(13) << 8; 177 | centerThrottleADC = EEPROM.read(14); 178 | centerThrottleADC |= EEPROM.read(15) << 8; 179 | Serial.println("...[DONE]"); 180 | Serial.print("maxThrottleADC: "); 181 | Serial.println(maxThrottleADC); 182 | Serial.print("minThrottleADC: "); 183 | Serial.println(minThrottleADC); 184 | Serial.print("centerThrottleADC: "); 185 | Serial.println(centerThrottleADC); 186 | } 187 | 188 | while (throttleCalibrated == false) 189 | { 190 | switch (throttleCalibration) 191 | { 192 | case findMAX: 193 | /* 194 | Update max value until throttle is moved forward again (getting smaller). 195 | If value < max - hysteresis goto findMIN 196 | */ 197 | if (analogRead(A7) > maxThrottleADC) 198 | maxThrottleADC = analogRead(A7); 199 | 200 | if (analogRead(A7) < maxThrottleADC - throttleHysteresis) 201 | { 202 | Serial.print("Found MAX Value at: "); 203 | Serial.println(maxThrottleADC); 204 | tone(6, 200, 100); 205 | delay(100); 206 | throttleCalibration = findMIN; 207 | } 208 | 209 | break; 210 | case findMIN: 211 | /* 212 | Update min value until throttle is moved all the way forward. 213 | If value > min + hysteresis goto findCenter 214 | */ 215 | if (analogRead(A7) < minThrottleADC) 216 | minThrottleADC = analogRead(A7); 217 | 218 | if (analogRead(A7) > minThrottleADC + throttleHysteresis) 219 | { 220 | Serial.print("Found MIN Value at: "); 221 | Serial.println(minThrottleADC); 222 | tone(6, 1000, 100); 223 | delay(100); 224 | throttleCalibration = findCenter; 225 | } 226 | 227 | break; 228 | case findCenter: 229 | /* 230 | Center should be roughly in the middle but it can have an offset that needs to be taken into account. 231 | Release throttle, observe value until still within tolerance (4 ADC values). 232 | Wait for 1 seconds and set center if still the same (within 4 ADCs) 233 | */ 234 | uint32_t centerSettledFor = millis(); 235 | uint16_t lastThrottleValue = analogRead(A7); 236 | bool bSettled = false; 237 | while (millis() - centerSettledFor < throttleCenterSettleTime) 238 | { 239 | /* 240 | if lastValue > currentValue - centerNoise && lastValue < currentValue + centerNoise 241 | */ 242 | uint16_t currentValue = analogRead(A7); 243 | if ((lastThrottleValue > currentValue - throttleCenterNoise) && (lastThrottleValue < currentValue + throttleCenterNoise)) 244 | { 245 | if (bSettled == false) 246 | { 247 | centerSettledFor = millis(); 248 | bSettled = true; 249 | } 250 | centerThrottleADC = currentValue; 251 | } 252 | else 253 | { 254 | bSettled = false; 255 | centerSettledFor = millis(); 256 | } 257 | 258 | lastThrottleValue = analogRead(A7); 259 | } 260 | // finished finding center 261 | 262 | Serial.print("Found center Value at: "); 263 | Serial.println(centerThrottleADC); 264 | tone(6, 440, 100); 265 | delay(100); 266 | tone(6, 440, 100); 267 | delay(100); 268 | 269 | Serial.print("Writing values to EEPROM..."); 270 | // store all values to EEPROM 271 | EEPROM.write(10, (uint8_t)maxThrottleADC); 272 | EEPROM.write(11, (uint8_t)(maxThrottleADC >> 8)); 273 | EEPROM.write(12, (uint8_t)minThrottleADC); 274 | EEPROM.write(13, (uint8_t)(minThrottleADC >> 8)); 275 | EEPROM.write(14, (uint8_t)centerThrottleADC); 276 | EEPROM.write(15, (uint8_t)(centerThrottleADC >> 8)); 277 | Serial.println("...[DONE]"); 278 | 279 | throttleCalibrated = true; 280 | 281 | break; 282 | } // end switch case 283 | } // end throttleCalibration While 284 | 285 | /*while (1) 286 | { 287 | //Serial.println(analogRead(A0)); 288 | //Serial.println(analogRead(A1)); 289 | //Serial.println(analogRead(A2)); 290 | //Serial.println(analogRead(A3)); 291 | //Serial.println(analogRead(A4)); 292 | Serial.print(5.0f/1024.0f*(float)analogRead(A5)); 293 | //Serial.println(analogRead(A6)); 294 | //Serial.println(analogRead(A7)); 295 | Serial.println("V "); 296 | delay(100); 297 | }*/ 298 | } 299 | 300 | void loop() { 301 | if (mainState != pairingRemote) 302 | delay(5); // This is supposed to be the only delay in the loop that is needed permanently. Gives both board and remote time to process data within their respective NRF24L01 303 | 304 | //Serial.println("Main Loop"); 305 | 306 | // Check remote battery level: 307 | // ELSE is not required as one spike into low battery warning is enough to stop acceleration from functioning until after next restart. It can only be set once. 308 | 309 | remoteBatteryVoltage = 5.0f / 1024.0f * (float)analogRead(A5); 310 | if (remoteBatteryVoltage < 2.1f) 311 | { 312 | remoteBatteryLevelCritical = true; // Once set, it can't be reset until the remote is power cycled! 313 | tone(6, 2000, 100); 314 | //delay(100); 315 | //tone(6, 1000, 100); 316 | //delay(100); 317 | set_remoteBatteryAlarm(); 318 | Serial.println("Remote control battery level critical!!"); 319 | //delay(10); 320 | } 321 | 322 | // Report battery voltage every 5 seconds 323 | if (millis() - lastTimeSinceStatusReport > 5000) 324 | { 325 | lastTimeSinceStatusReport = millis(); 326 | Serial.print("Battery Voltage: "); 327 | Serial.println(remoteBatteryVoltage); 328 | } 329 | 330 | // Check for input: 331 | // read the state of the switch into a local variable: 332 | int reading = digitalRead(pairing_PIN); 333 | 334 | // check to see if you just pressed the button 335 | // (i.e. the input went from LOW to HIGH), and you've waited 336 | // long enough since the last press to ignore any noise: 337 | 338 | // If the switch changed, due to noise or pressing: 339 | if (reading != lastPairingButtonState) { 340 | // reset the debouncing timer 341 | lastPairingButtonDebounceTime = millis(); 342 | } 343 | 344 | if ((millis() - lastPairingButtonDebounceTime) > pairingButtonDebounceDelay) { 345 | // whatever the reading is at, it's been there for longer 346 | // than the debounce delay, so take it as the actual current state: 347 | 348 | // if the button state has changed: 349 | if (reading != pairingButtonState) { 350 | pairingButtonState = reading; 351 | 352 | // Go straight into pairing state if the new button state is LOW 353 | if (pairingButtonState == LOW) 354 | { 355 | freqCounter = 0; 356 | mainState = pairingRemote; 357 | driveState = sendMessage; 358 | pairingState = initPairing_1; 359 | set_batteryState(0xFF); 360 | } 361 | } 362 | } 363 | 364 | // save the reading. Next time through the loop, 365 | // it'll be the lastButtonState: 366 | lastPairingButtonState = reading; 367 | 368 | // Check for input: 369 | // read the state of the switch into a local variable: 370 | reading = digitalRead(mode_PIN); 371 | 372 | // check to see if you just pressed the button 373 | // (i.e. the input went from LOW to HIGH), and you've waited 374 | // long enough since the last press to ignore any noise: 375 | 376 | // If the switch changed, due to noise or pressing: 377 | if (reading != lastModeButtonState) { 378 | // reset the debouncing timer 379 | lastModeButtonDebounceTime = millis(); 380 | } 381 | 382 | if ((millis() - lastModeButtonDebounceTime) > modeButtonDebounceDelay) { 383 | // whatever the reading is at, it's been there for longer 384 | // than the debounce delay, so take it as the actual current state: 385 | 386 | // if the button state has changed: 387 | if (reading != modeButtonState) { 388 | if (reading == LOW) 389 | { 390 | //Serial.println("Mode key was pressed"); 391 | } 392 | if (reading == HIGH) 393 | { 394 | 395 | digitalWrite(battLED1_PIN, LOW); // Battery status < 100% 396 | digitalWrite(battLED2_PIN, LOW); // Battery status < 75% 397 | digitalWrite(battLED3_PIN, LOW); // Battery status < 50% 398 | digitalWrite(battLED4_PIN, LOW); // Battery status < 25% 399 | switch (performanceState) 400 | { 401 | case sport: 402 | Serial.println("Switching to cruise mode!"); 403 | performanceState = cruiser; 404 | tone(6, 400, 250); 405 | digitalWrite(battLED2_PIN, LOW); // Battery status < 75% 406 | digitalWrite(battLED3_PIN, LOW); // Battery status < 50% 407 | delay(100); 408 | tone(6, 600, 250); 409 | digitalWrite(battLED2_PIN, HIGH); // Battery status < 75% 410 | digitalWrite(battLED3_PIN, HIGH); // Battery status < 50% 411 | delay(100); 412 | tone(6, 600, 250); 413 | digitalWrite(battLED2_PIN, LOW); // Battery status < 75% 414 | digitalWrite(battLED3_PIN, LOW); // Battery status < 50% 415 | delay(100); 416 | tone(6, 400, 250); 417 | digitalWrite(battLED2_PIN, HIGH); // Battery status < 75% 418 | digitalWrite(battLED3_PIN, HIGH); // Battery status < 50% 419 | delay(100); 420 | break; 421 | case cruiser: 422 | Serial.println("Switching to beginner mode!"); 423 | performanceState = beginner; 424 | tone(6, 1000, 100); 425 | digitalWrite(battLED1_PIN, HIGH); // Battery status < 100% 426 | delay(100); 427 | tone(6, 800, 100); 428 | digitalWrite(battLED2_PIN, HIGH); // Battery status < 75% 429 | digitalWrite(battLED1_PIN, LOW); // Battery status < 100% 430 | delay(100); 431 | tone(6, 600, 100); 432 | digitalWrite(battLED3_PIN, HIGH); // Battery status < 50% 433 | digitalWrite(battLED2_PIN, LOW); // Battery status < 75% 434 | delay(100); 435 | tone(6, 440, 100); 436 | digitalWrite(battLED4_PIN, HIGH); // Battery status < 25% 437 | digitalWrite(battLED3_PIN, LOW); // Battery status < 50% 438 | delay(100); 439 | break; 440 | case beginner: 441 | Serial.println("Switching to sport mode!"); 442 | performanceState = sport; 443 | tone(6, 440, 100); 444 | digitalWrite(battLED4_PIN, HIGH); // Battery status < 25% 445 | delay(100); 446 | tone(6, 600, 100); 447 | digitalWrite(battLED3_PIN, HIGH); // Battery status < 50% 448 | digitalWrite(battLED4_PIN, LOW); // Battery status < 25% 449 | delay(100); 450 | tone(6, 800, 100); 451 | digitalWrite(battLED2_PIN, HIGH); // Battery status < 75% 452 | digitalWrite(battLED3_PIN, LOW); // Battery status < 50% 453 | delay(100); 454 | tone(6, 1000, 100); 455 | digitalWrite(battLED1_PIN, HIGH); // Battery status < 100% 456 | digitalWrite(battLED2_PIN, LOW); // Battery status < 75% 457 | delay(100); 458 | break; 459 | } 460 | digitalWrite(battLED1_PIN, LOW); // Battery status < 100% 461 | digitalWrite(battLED2_PIN, LOW); // Battery status < 75% 462 | digitalWrite(battLED3_PIN, LOW); // Battery status < 50% 463 | digitalWrite(battLED4_PIN, LOW); // Battery status < 25% 464 | } 465 | modeButtonState = reading; 466 | } 467 | } 468 | 469 | // save the reading. Next time through the loop, 470 | // it'll be the lastButtonState: 471 | lastModeButtonState = reading; 472 | 473 | /* Main state machine for pairing, init, drive, connection lost 474 | If the remote was never paired, it enforces pairing. 475 | If it was paired before, it will attempt to connect using the stored 476 | addresses in the flash eeprom. 477 | If the connection was successful, it will go into drive. 478 | If not, it will go into connection lost, searching through the frequencies 479 | the board uses. 480 | If connection lost or no motion is detected for timeout time, 481 | the controller will beep to get the user to turn it off. 482 | */ 483 | // Return value of each SPI transaction. Every first transaction will return the status register 484 | uint8_t status = 0x00; // used for general storage of the STATUS 485 | uint8_t status1 = 0x00; // used when two STATUS values need to be compared 486 | uint8_t status2 = 0x00; // used when two STATUS values need to be compared 487 | 488 | 489 | /******************************************************************************************************************/ 490 | /******************* READ AND PROCESS ALL USER INPUT *********************************************************/ 491 | /******************************************************************************************************************/ 492 | /* 493 | 1. Read ADC 494 | 2. Scale positive and negative values to float -1 and +1 with 0 in the center no matter if input values are asymmetrical 495 | 3. Calculate EXPO 496 | 4. Filter output for positive values and negative values separately 497 | - Letting go of the throttle, smoothly transitions to 0 torque 498 | - However, transitioning from throttle to brake and vice versa is instantaneous 499 | 5. Rescale to values of motor controller 500 | Remove DeadZone (if not included in preveous step) 501 | 6. Send value to board 502 | */ 503 | 504 | // 1. Read ACD 505 | uint16_t throttleValue = analogRead(A7); 506 | //Serial.println(throttleValue); 507 | // 2. calculate a value between -1 and +1 508 | float throttle = rescaleADCThrottleValue(throttleValue, minThrottleADC, maxThrottleADC, centerThrottleADC); 509 | //Serial.println(throttle); 510 | // 3. Calculate Expo 511 | throttle = exponentialCurve(throttle, expoFactor); // currently set to 1.0 which doesn't do anything. Turns out the remote already feels good without expo. 512 | //Serial.println(throttle); 513 | //Serial.println(lastDampedValue); 514 | 515 | // 4. Filter 516 | // TODO 517 | // 5. Modes 518 | switch(performanceState) 519 | { 520 | case sport: 521 | expoFactor = 1.0; 522 | break; 523 | 524 | case cruiser: 525 | if (throttle < -0.04) // accelerate 526 | { 527 | // If we braked and want to accellerate, it must be instant: 528 | if (lastDampedValue > 0) 529 | dampingFactorDriveModeModifier = 0.5; // Quick transition to braking 530 | else 531 | dampingFactorDriveModeModifier = 10; // smooth acceleration 532 | expoFactor = 1; 533 | throttle = throttle * 0.62; 534 | if (velocityState == coast || velocityState == brake) // To reduce acceleration latency, we use the velocity model data to set the new current throttle value 535 | { 536 | //lastDampedValue = lastSkateboardSpeed; 537 | velocityState = accel; 538 | } 539 | if (throttle > lastSkateboardSpeed) 540 | { 541 | lastDampedValue = throttle; 542 | if (throttle < -0.62) 543 | throttle = -0.62; 544 | } 545 | throttle = pt1_damper (throttle, dampingFactor / dampingFactorDriveModeModifier, integralPart, lastDampedValue, lastIntegralPart); 546 | } 547 | else if (throttle > 0.04) // brake 548 | { 549 | // If we accellerated and want to brake, it must be instant: 550 | if (lastDampedValue < 0) 551 | dampingFactorDriveModeModifier = 0.5; // Quick transition to acceleration 552 | else 553 | dampingFactorDriveModeModifier = 2.5; // Smooth braking 554 | expoFactor = 0.8; 555 | throttle = throttle * 1.0; 556 | throttle = pt1_damper (throttle, dampingFactor / dampingFactorDriveModeModifier, integralPart, lastDampedValue, lastIntegralPart); 557 | velocityState = brake; 558 | } 559 | else 560 | { 561 | dampingFactorDriveModeModifier = 2; // Smooth throttle release 562 | expoFactor = 1; 563 | throttle = throttle * 1.0; 564 | throttle = pt1_damper (throttle, dampingFactor / dampingFactorDriveModeModifier, integralPart, lastDampedValue, lastIntegralPart); 565 | velocityState = coast; 566 | } 567 | break; 568 | 569 | case beginner: 570 | if (throttle < -0.04) // accelerate 571 | { 572 | // If we braked and want to accellerate, it must be instant: 573 | if (lastDampedValue > 0) 574 | dampingFactorDriveModeModifier = 0.5; // Quick transition to acceleration 575 | else 576 | dampingFactorDriveModeModifier = 3; // Smooth braking 577 | expoFactor = 1; 578 | throttle = throttle * 0.35; 579 | if (velocityState == coast || velocityState == brake) // To reduce acceleration latency, we use the velocity model data to set the new current throttle value 580 | { 581 | //lastDampedValue = lastSkateboardSpeed; 582 | velocityState = accel; 583 | } 584 | if (throttle > lastSkateboardSpeed) 585 | { 586 | lastDampedValue = throttle; 587 | if (throttle < -0.35) 588 | throttle = -0.35; 589 | } 590 | throttle = pt1_damper (throttle, dampingFactor / dampingFactorDriveModeModifier, integralPart, lastDampedValue, lastIntegralPart); 591 | } 592 | else if (throttle > 0.04) // brake 593 | { 594 | // If we accellerated and want to brake, it must be instant: 595 | if (lastDampedValue < 0) 596 | dampingFactorDriveModeModifier = 0.2; // Quick transition to braking 597 | else 598 | dampingFactorDriveModeModifier = 3; // Smooth braking 599 | expoFactor = 1; 600 | throttle = throttle * 0.65; 601 | throttle = pt1_damper (throttle, dampingFactor / dampingFactorDriveModeModifier, integralPart, lastDampedValue, lastIntegralPart); 602 | velocityState = brake; 603 | } 604 | else 605 | { 606 | dampingFactorDriveModeModifier = 10; // Very smooth throttle release 607 | expoFactor = 1; 608 | throttle = throttle * 1.0; 609 | throttle = pt1_damper (throttle, dampingFactor / dampingFactorDriveModeModifier, integralPart, lastDampedValue, lastIntegralPart); 610 | velocityState = coast; 611 | } 612 | break; 613 | } 614 | 615 | // Estimating the skareboard speed 616 | if (skateboardSpeed >= throttle) 617 | { 618 | skateboardSpeed = pt1_damper(throttle, skateboardSpeedAccelDampening, lastSkateboardSpeed); 619 | } 620 | else if(throttle > 0.04) 621 | { 622 | skateboardSpeed += skateboardSpeedBrakeDecelFactor * throttle; 623 | lastSkateboardSpeed = skateboardSpeed; 624 | } 625 | else 626 | { 627 | skateboardSpeed += skateboardSpeedCoastDecelIterator; 628 | lastSkateboardSpeed = skateboardSpeed; 629 | } 630 | 631 | if (skateboardSpeed < -1) 632 | skateboardSpeed = -1; 633 | else if (skateboardSpeed > 0) 634 | skateboardSpeed = 0; 635 | 636 | // If desired, print the actual estimated miles per hour to serial 637 | //Serial.print(22.0 * -skateboardSpeed); Serial.println(" MPH"); 638 | //Serial.print(throttle); 639 | //Serial.print(" "); 640 | //Serial.println(lastSkateboardSpeed); 641 | 642 | // 6. Rescale 643 | // Remove Deadzone 644 | throttleValue = deadzoneCompensationAndRescale(throttle, 645 | posDeadZone, 646 | negDeadZone, 647 | boardMin, 648 | boardMax, 649 | boardDeadzoneMin, 650 | boardDeadzoneMax, 651 | boardCenter); 652 | 653 | //Serial.print(22.0 * -skateboardSpeed); Serial.println(" MPH"); 654 | // Todo: If the battery is empty, we don't just want to cut power as that might be extremely dangerous! 655 | // In that case, we want to ramp down the maximum available power within 10 seconds or so until idle is reached. 656 | // 6. Send value to board 657 | 658 | // Check for critical battery voltage 659 | if (remoteBatteryLevelCritical) 660 | quatroMessage_1[1] = (throttleValue >= boardCenter) ? throttleValue : 128; // If throttlevalue < boardCenter (braking) do it. Otherwise, no acceleration 661 | else 662 | quatroMessage_1[1] = throttleValue; // Remote Battery is fine, just throttle through 663 | //Serial.println(quatroMessage_1[1]); 664 | 665 | 666 | /******************************************************************************************************************/ 667 | /******************* CALCULATE ACCELERATION CURVES ***********************************************************/ 668 | /******************************************************************************************************************/ 669 | 670 | /* 671 | All of the following should run in a timed / scheduled environment. 100hz or 25hz should be sufficient. 672 | Defined timing is predictable and can easily be adjusted and tuned. Specifically, when the throttle value 673 | doesn't change, less communication needs to happen and hence, energy can be saved. 674 | 675 | Configurable values: 676 | Deadzone for acceleration AND brakes separately 677 | Expo value of acceleration curve AND brake curve separately 678 | Sliding hysteresis window size and rate of change 679 | Acceleration curve for acceleratio AND brakes separately 680 | I also had the idea of a faster response when the user input varies greatly from the current value and rate of change. 681 | Panic brake is one of these items. If the user decides I NEED BRAKES NOW!! he should get brakes immediately. 682 | How can this be implemented properly without loosing predictability of the controller? 683 | */ 684 | 685 | switch (mainState) 686 | { 687 | case pairingRemote: 688 | if (pairRemote()) 689 | { 690 | break; 691 | } 692 | else 693 | { 694 | break; 695 | } 696 | break; 697 | 698 | /******************************************************************************************************************/ 699 | /******************* REMOTE OPERATION AND INITIALIZATION *****************************************************/ 700 | /******************************************************************************************************************/ 701 | case initRemote: 702 | //Serial.println(" Init Remote"); 703 | digitalWrite(lostLED_PIN, HIGH); 704 | 705 | if (freqCounter > 15) // Sanity check on the frequency counter 706 | freqCounter = 0 ; 707 | 708 | lostCounter = 0; // Reset lostCounter because we are going to reinitialize the NRF chip 709 | 710 | // Set all registers according to SPI trace. Ignote STATUS register from board at this stage. 711 | status = write_ToAddress(W_REGISTER | CONFIG, 0x0E); // Enable CRC, 2 byte CRC, Power Up 712 | status = write_ToAddress(W_REGISTER | EN_AA, 0x01); // Enable Auto ACK for PIPE 0 713 | status = write_ToAddress(W_REGISTER | EN_RXADDR, 0x01); // Enable data PIPE 0 714 | status = write_ToAddress(W_REGISTER | SETUP_AW, 0x03); // Set RX/TX address length to 5 bytes 715 | status = write_ToAddress(W_REGISTER | SETUP_RETR, 0x22); // Set retransmit delay to 750 micro seconds and set 2 retransmits 716 | status = write_ToAddress(W_REGISTER | RF_SETUP, 0x0F); // set RF_Config to high power and 2 mBits 717 | status = write_ToAddress(W_REGISTER | DYNPD, 0x01); // Enable dynamic payload length on PIPE 0 718 | status = write_ToAddress(W_REGISTER | FEATURE, 0x06); // Enable dynamic payload, enable payload ACK 719 | status = write_ToAddress(W_REGISTER | RX_PW_P0, 0x03); // Set number of bytes in RX payload to 3 bytes 720 | status = write_ToAddress(W_REGISTER | RF_CH, frequencies[freqCounter]); // Set operating frequency to 2: 2.402 Ghz 721 | status = write_BytesToAddress(W_REGISTER | TX_ADDR, quatroAddress, 5); // Set TC address to board 722 | status = write_BytesToAddress(W_REGISTER | RX_ADDR_P0, quatroAddress, 5); // Set RX address from board 723 | 724 | mainState = driveRemote; 725 | driveState = sendMessage; 726 | pairingState = initPairing_1; 727 | break; 728 | 729 | case driveRemote: 730 | //Serial.println(" Drive Remote"); 731 | digitalWrite(lostLED_PIN, LOW); 732 | // flush TX buffer 733 | // Clear out all pending IRQs 734 | // Send message 1 to board 735 | // Check if a message was received back from board 736 | // Send message 2 to board 737 | // Check if a message was received back from board 738 | // Repeat until connection lost (MAX_RTR was fired) 739 | 740 | switch (driveState) 741 | { 742 | case sendMessage: 743 | //Serial.println(" Send Message"); 744 | 745 | status = exec_command(FLUSH_TX); // clean out the TX buffer - should return 0x0E 746 | status = exec_command(FLUSH_RX); // clean out the RX buffer because we only receive, after we send - should return 0x0E 747 | status = write_ToAddress(W_REGISTER | STATUS, 0x70); // Clear out all pending IRQs - We will ignore all - should return 0x0E 748 | if (!messageType) 749 | { 750 | status = write_BytesToAddress(W_TX_PAYLOAD, quatroMessage_1, 5); // Send message 1 to the transmitter 751 | trigger_CS(); 752 | } 753 | else 754 | { 755 | status = write_BytesToAddress(W_TX_PAYLOAD, quatroMessage_2, 5); // Send message 1 to the transmitter 756 | trigger_CS(); 757 | } 758 | 759 | messageType = !messageType; 760 | driveState = checkStatus; 761 | break; 762 | 763 | case checkStatus: 764 | //Serial.println(" Check Status"); 765 | status1 = exec_command(STATUS); // Read what's going on in the status - did we receive the message or timed out? 766 | status2 = exec_command(NOP); // Read a second time using the NOP command which should also return the same value 767 | 768 | if (status1 == status2) // both status byte reads are the same, therefore it didn't change in two subsequent reads 769 | { 770 | if (status1 & 0x60) // RX_DR as well as TX_DS are true: received data and sent data 771 | { 772 | //tone(6, 3000, 10); 773 | //Serial.println(status1, HEX); 774 | rtr_counter = 0; // Everything went well. Reset retry counter 775 | driveState = receiveMessage; 776 | break; 777 | } 778 | if (status1 & 0x40) // 0b0100 0000 = RX_DR is true, TX_DS is false, MAX_RTR is false 779 | { 780 | tone(6, 2000, 10); 781 | //Serial.println(status1, HEX); 782 | /* 783 | Just read whatever is there and restart as normal. 784 | Can't ever happen unless another remote sends data on the same channel with the same address. 785 | Has no influence on remote and can be ignored. 786 | */ 787 | driveState = receiveMessage; 788 | } 789 | if (status1 & 0x20) // 0b0010 0000 = TX_DS is true, RX_DR is false 790 | { 791 | tone(6, 1000, 10); 792 | //Serial.println(status1, HEX); 793 | /* 794 | We enter this state when the message was sent, an ACK was received but there was no data received as ACK_PAYLOAD. 795 | RX_DR is not set even though TX_DS is set! 796 | This is a potentially dangerous state, as the TX buffer is empty and the "lost" message cannot be resent. 797 | Also, since an ACK was received, the MAX_RTR flag is never set. The state machine would stop here. 798 | If the payload was lost, we should to send the message again after we waited once more. 799 | */ 800 | if (TX_DS_WasSetAlready_WaitFor_RX_DR == false) 801 | { 802 | TX_DS_WasSetAlready_WaitFor_RX_DR = true; 803 | // Data wasn't received, yet (no ack PAYLOAD received). Just wait a little more and check again until MAX_RTR or RX_DR is set. 804 | driveState = checkStatus; // This was flushTX before and that seems to make no sense at all 805 | } 806 | else 807 | { 808 | TX_DS_WasSetAlready_WaitFor_RX_DR = false; 809 | driveState = sendMessage; // We bail! Never received an ACK_PAYLOAD following the ACK. We need to send again. 810 | } 811 | } 812 | else if (status1 & 0x10) // MAX_RTR was set - this happens quite often! 813 | { 814 | //tone(6, 800, 20); 815 | //delay(20); 816 | //Serial.print("P A PACKAGE COULD NOT BE TRANSMITTED TO THE BOARD: 0x"); 817 | //Serial.println(status1, HEX); 818 | rtr_counter++; 819 | //Serial.println(rtr_counter); 820 | 821 | if (rtr_counter > 10) 822 | { 823 | // we have reached MAX_RTR maximum number of retries reached 824 | mainState = lostRemote; 825 | } 826 | else 827 | { 828 | status = read_FromAddress(R_REGISTER | FIFO_STATUS); // 0b0000 0010 -> RX_FIFO_FULL ! That seems to be the issue. Nothing can go in or out. 829 | //Serial.println(status, HEX); 830 | if (status & 0x10) // worst case TX_FIFO_EMPTY: The data was lost! Send message again immediately! 831 | { 832 | mainState = driveRemote; 833 | driveState = sendMessage; 834 | } 835 | else 836 | { 837 | status = write_ToAddress(W_REGISTER | STATUS, 0x70); // Clear out all pending IRQs - We will ignore all - should return 0x0E 838 | trigger_CS(); // Send last message again! 839 | } 840 | } 841 | } 842 | else // Whatever else happens, go back to check status. Since we captured all other IRQ sources above, this is obsolete and only serves reliability 843 | { 844 | //tone(6, 200, 20); 845 | //delay(20); 846 | //Serial.print("P EVERYTHING OTHER THAN 0x60 or 0x10: 0x"); 847 | //Serial.println(status1, HEX); 848 | 849 | // Report status every 5 seconds 850 | if (millis() - lastTimeSinceStatusReport > 5000) 851 | { 852 | Serial.println("FATAL: Remote in critical state! Re-pair with skateboard, recalibrate throttle!"); 853 | } 854 | 855 | driveState = checkStatus; 856 | } 857 | } 858 | break; 859 | 860 | case receiveMessage: 861 | //Serial.println(" Receive Message"); 862 | status = read_BytesFromAddress(R_RX_PAYLOAD, quatroReturnMessage, 8); // Read message back and please don't ask me why it was 8 bytes in the remote since only one byte actually contains any data 863 | boardBatteryState = quatroReturnMessage[0] & 0x0F; 864 | 865 | // Only display the board battery state as long as it's still relevant. It's not relevant if the remote is empty (TODO: That needs to be discussed!) 866 | if (!remoteBatteryLevelCritical) 867 | set_batteryState(boardBatteryState); 868 | 869 | digitalWrite(lostLED_PIN, LOW); // Connection to the board was successfully established 870 | //digitalWrite(lostLED_PIN, (quatroReturnMessage[0] & 0x10) == true ? LOW : HIGH); // Connection to the board was successfully established 871 | 872 | lostCounter = 0; // We can safely reset the lostCounter here, as the connection was definitely established in between losts. 873 | 874 | /* 875 | This section makes sure, we always jump back to the last known good frequency every once in a while during a frequency search. 876 | This can significantly reduce the time it takes to regain the connection. 877 | One exception is, the last known frequency was slightly higher than the new actually good frequency. That might cause a longer search of about 1 second. 878 | However, this should only happen upon board restart because the board (server) is the only peer to ever change the channel. The client always follows. 879 | */ 880 | if ((numHopsTaken != 0) && (lastKnownFrequency != freqCounter)) 881 | { 882 | lastKnownFrequency = freqCounter; 883 | Serial.print("Good Freq: "); 884 | Serial.println(lastKnownFrequency); 885 | } 886 | else 887 | { 888 | freqCounter = lastKnownFrequency; 889 | } 890 | driveState = sendMessage; 891 | break; 892 | } 893 | break; 894 | 895 | case lostRemote: 896 | // Report status every 5 seconds 897 | if (millis() - lastTimeSinceStatusReport > 5000) 898 | { 899 | Serial.println(" Connection to skateboard lost. Searching known frequencies using current pairing key..."); 900 | } 901 | digitalWrite(lostLED_PIN, HIGH); 902 | // tone(6, 500, 20); 903 | //delay(20); 904 | 905 | set_batteryState(0xFF); 906 | 907 | rtr_counter = 0; 908 | 909 | // Check if we should check the last known frequency again before continuing the search for the right channel 910 | if (numHopsTaken < numHopsBeforeCheckingLastKnown) 911 | { 912 | numHopsTaken++; 913 | freqCounter++; 914 | } 915 | 916 | if (freqCounter > 15) 917 | { 918 | /* 919 | This is the case where we searched all frequencies already and did not receive anything back from the board 920 | This is therfore pretty bad and we need to consider the possibility that the data and the ack was lost, 921 | or we are in an intermediate state where the data has left the TX register and a simple trigger on CS is unsufficient to regain connection. 922 | */ 923 | freqCounter = 0; 924 | lostCounter++; // Increment lostCounter as we have passed all frequencies twice and have found no connection 925 | 926 | if (lostCounter < 2) 927 | { 928 | //Serial.println("Lost It"); 929 | mainState = driveRemote; 930 | driveState = sendMessage; 931 | } 932 | else 933 | { 934 | mainState = initRemote; 935 | } 936 | } 937 | else // This is the normal case. Hop to another frequency and trigger CS to try and send the data again 938 | { 939 | if (numHopsTaken >= numHopsBeforeCheckingLastKnown) 940 | { 941 | numHopsTaken = 0; 942 | freqCounter--; // reduce frequency counter by one, otherwise we will never hit all of them but skip instead 943 | //Serial.println(lastKnownFrequency); 944 | status = write_ToAddress(W_REGISTER | RF_CH, frequencies[lastKnownFrequency]); // Set operating frequency to the last known working 945 | } 946 | else 947 | { 948 | //Serial.println(freqCounter); 949 | status = write_ToAddress(W_REGISTER | RF_CH, frequencies[freqCounter]); // Set operating frequency to the next possible frequency 950 | } 951 | 952 | status = read_FromAddress(R_REGISTER | FIFO_STATUS); 953 | //Serial.println(status, HEX); 954 | if (status & 0x10) // worst case: The data was lost! 955 | { 956 | mainState = driveRemote; 957 | driveState = sendMessage; 958 | } 959 | else 960 | { 961 | status = write_ToAddress(W_REGISTER | STATUS, 0x70); // Clear out all pending IRQs - We will ignore all - should return 0x0E 962 | trigger_CS(); // Trigger sending the still existing message in the TX FIFO again. It will remain there until PRX confirmed receipt with an ACK 963 | mainState = driveRemote; 964 | driveState = checkStatus; 965 | } 966 | } 967 | 968 | break; 969 | 970 | case idleRemote: 971 | //Serial.println(" Idle Remote"); 972 | digitalWrite(lostLED_PIN, LOW); 973 | break; 974 | 975 | case offRemote: 976 | //Serial.println(" Off Remote"); 977 | digitalWrite(lostLED_PIN, LOW); 978 | break; 979 | } 980 | if (millis() - lastTimeSinceStatusReport > 5000) 981 | { 982 | lastTimeSinceStatusReport = millis(); 983 | } 984 | } 985 | 986 | 987 | //IRQ routine that fires when data was sent, data was received, or max retries was reached 988 | void receivedIRQ(void) 989 | { 990 | // The IRQ pin is not actually used in this remote. 991 | // It's there, but we're polling STATUS all the time anyways so it makes no sense to bother with this. 992 | 993 | digitalWrite(lostLED_PIN, !digitalRead(lostLED_PIN)); 994 | return; 995 | } 996 | 997 | 998 | -------------------------------------------------------------------------------- /Electronics/OpenRemote.brd: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | 237 | 238 | 239 | 240 | 241 | 242 | 243 | 244 | 245 | 246 | 247 | 248 | 249 | 250 | 251 | 252 | 253 | 254 | 255 | 256 | 257 | 258 | 259 | 260 | 261 | 262 | 263 | 264 | 265 | 266 | 267 | 268 | 269 | 270 | <b>Arduino Nano V3.0</b><br> 271 | <p>The Nano was designed and is being produced by Gravitech.<br> 272 | 273 | <a href="http://www.gravitech.us/arna30wiatp.html">Gravitech Arduino Nano V3.0</a></p> 274 | 275 | 276 | 277 | 278 | 279 | 280 | 281 | 282 | 283 | 284 | 285 | 286 | 287 | 288 | 289 | 290 | 291 | 292 | 293 | 294 | 295 | 296 | 297 | 298 | 299 | 300 | 301 | 302 | 303 | 304 | 305 | 306 | 307 | 308 | 309 | 310 | 311 | 312 | 313 | 314 | 315 | 316 | 317 | 318 | 319 | 320 | >NAME 321 | >VALUE 322 | 323 | Reset 324 | 1 325 | Mini-B USB 326 | 327 | 328 | 329 | 330 | 331 | 332 | 333 | 334 | 335 | 336 | ICSP 337 | 338 | 339 | 340 | 341 | <b>Resistors, Capacitors, Inductors</b><p> 342 | Based on the previous libraries: 343 | <ul> 344 | <li>r.lbr 345 | <li>cap.lbr 346 | <li>cap-fe.lbr 347 | <li>captant.lbr 348 | <li>polcap.lbr 349 | <li>ipc-smd.lbr 350 | </ul> 351 | All SMD packages are defined according to the IPC specifications and CECC<p> 352 | <author>Created by librarian@cadsoft.de</author><p> 353 | <p> 354 | for Electrolyt Capacitors see also :<p> 355 | www.bccomponents.com <p> 356 | www.panasonic.com<p> 357 | www.kemet.com<p> 358 | http://www.secc.co.jp/pdf/os_e/2004/e_os_all.pdf <b>(SANYO)</b> 359 | <p> 360 | for trimmer refence see : <u>www.electrospec-inc.com/cross_references/trimpotcrossref.asp</u><p> 361 | 362 | <table border=0 cellspacing=0 cellpadding=0 width="100%" cellpaddding=0> 363 | <tr valign="top"> 364 | 365 | <! <td width="10">&nbsp;</td> 366 | <td width="90%"> 367 | 368 | <b><font color="#0000FF" size="4">TRIM-POT CROSS REFERENCE</font></b> 369 | <P> 370 | <TABLE BORDER=0 CELLSPACING=1 CELLPADDING=2> 371 | <TR> 372 | <TD COLSPAN=8> 373 | <FONT SIZE=3 FACE=ARIAL><B>RECTANGULAR MULTI-TURN</B></FONT> 374 | </TD> 375 | </TR> 376 | <TR> 377 | <TD ALIGN=CENTER> 378 | <B> 379 | <FONT SIZE=3 FACE=ARIAL color="#FF0000">BOURNS</FONT> 380 | </B> 381 | </TD> 382 | <TD ALIGN=CENTER> 383 | <B> 384 | <FONT SIZE=3 FACE=ARIAL color="#FF0000">BI&nbsp;TECH</FONT> 385 | </B> 386 | </TD> 387 | <TD ALIGN=CENTER> 388 | <B> 389 | <FONT SIZE=3 FACE=ARIAL color="#FF0000">DALE-VISHAY</FONT> 390 | </B> 391 | </TD> 392 | <TD ALIGN=CENTER> 393 | <B> 394 | <FONT SIZE=3 FACE=ARIAL color="#FF0000">PHILIPS/MEPCO</FONT> 395 | </B> 396 | </TD> 397 | <TD ALIGN=CENTER> 398 | <B> 399 | <FONT SIZE=3 FACE=ARIAL color="#FF0000">MURATA</FONT> 400 | </B> 401 | </TD> 402 | <TD ALIGN=CENTER> 403 | <B> 404 | <FONT SIZE=3 FACE=ARIAL color="#FF0000">PANASONIC</FONT> 405 | </B> 406 | </TD> 407 | <TD ALIGN=CENTER> 408 | <B> 409 | <FONT SIZE=3 FACE=ARIAL color="#FF0000">SPECTROL</FONT> 410 | </B> 411 | </TD> 412 | <TD ALIGN=CENTER> 413 | <B> 414 | <FONT SIZE=3 FACE=ARIAL color="#FF0000">MILSPEC</FONT> 415 | </B> 416 | </TD><TD>&nbsp;</TD> 417 | </TR> 418 | <TR> 419 | <TD BGCOLOR="#cccccc" ALIGN=CENTER><FONT FACE=ARIAL SIZE=3 > 420 | 3005P<BR> 421 | 3006P<BR> 422 | 3006W<BR> 423 | 3006Y<BR> 424 | 3009P<BR> 425 | 3009W<BR> 426 | 3009Y<BR> 427 | 3057J<BR> 428 | 3057L<BR> 429 | 3057P<BR> 430 | 3057Y<BR> 431 | 3059J<BR> 432 | 3059L<BR> 433 | 3059P<BR> 434 | 3059Y<BR></FONT> 435 | </TD> 436 | <TD BGCOLOR="#cccccc" ALIGN=CENTER><FONT FACE=ARIAL SIZE=3> 437 | -<BR> 438 | 89P<BR> 439 | 89W<BR> 440 | 89X<BR> 441 | 89PH<BR> 442 | 76P<BR> 443 | 89XH<BR> 444 | 78SLT<BR> 445 | 78L&nbsp;ALT<BR> 446 | 56P&nbsp;ALT<BR> 447 | 78P&nbsp;ALT<BR> 448 | T8S<BR> 449 | 78L<BR> 450 | 56P<BR> 451 | 78P<BR></FONT> 452 | </TD> 453 | <TD BGCOLOR="#cccccc" ALIGN=CENTER><FONT FACE=ARIAL SIZE=3> 454 | -<BR> 455 | T18/784<BR> 456 | 783<BR> 457 | 781<BR> 458 | -<BR> 459 | -<BR> 460 | -<BR> 461 | 2199<BR> 462 | 1697/1897<BR> 463 | 1680/1880<BR> 464 | 2187<BR> 465 | -<BR> 466 | -<BR> 467 | -<BR> 468 | -<BR></FONT> 469 | </TD> 470 | <TD BGCOLOR="#cccccc" ALIGN=CENTER><FONT FACE=ARIAL SIZE=3> 471 | -<BR> 472 | 8035EKP/CT20/RJ-20P<BR> 473 | -<BR> 474 | RJ-20X<BR> 475 | -<BR> 476 | -<BR> 477 | -<BR> 478 | 1211L<BR> 479 | 8012EKQ&nbsp;ALT<BR> 480 | 8012EKR&nbsp;ALT<BR> 481 | 1211P<BR> 482 | 8012EKJ<BR> 483 | 8012EKL<BR> 484 | 8012EKQ<BR> 485 | 8012EKR<BR></FONT> 486 | </TD> 487 | <TD BGCOLOR="#cccccc" ALIGN=CENTER><FONT FACE=ARIAL SIZE=3> 488 | -<BR> 489 | 2101P<BR> 490 | 2101W<BR> 491 | 2101Y<BR> 492 | -<BR> 493 | -<BR> 494 | -<BR> 495 | -<BR> 496 | -<BR> 497 | -<BR> 498 | -<BR> 499 | -<BR> 500 | 2102L<BR> 501 | 2102S<BR> 502 | 2102Y<BR></FONT> 503 | </TD> 504 | <TD BGCOLOR="#cccccc" ALIGN=CENTER><FONT FACE=ARIAL SIZE=3> 505 | -<BR> 506 | EVMCOG<BR> 507 | -<BR> 508 | -<BR> 509 | -<BR> 510 | -<BR> 511 | -<BR> 512 | -<BR> 513 | -<BR> 514 | -<BR> 515 | -<BR> 516 | -<BR> 517 | -<BR> 518 | -<BR> 519 | -<BR></FONT> 520 | </TD> 521 | <TD BGCOLOR="#cccccc" ALIGN=CENTER><FONT FACE=ARIAL SIZE=3> 522 | -<BR> 523 | 43P<BR> 524 | 43W<BR> 525 | 43Y<BR> 526 | -<BR> 527 | -<BR> 528 | -<BR> 529 | -<BR> 530 | 40L<BR> 531 | 40P<BR> 532 | 40Y<BR> 533 | 70Y-T602<BR> 534 | 70L<BR> 535 | 70P<BR> 536 | 70Y<BR></FONT> 537 | </TD> 538 | <TD BGCOLOR="#cccccc" ALIGN=CENTER><FONT FACE=ARIAL SIZE=3> 539 | -<BR> 540 | -<BR> 541 | -<BR> 542 | -<BR> 543 | -<BR> 544 | -<BR> 545 | -<BR> 546 | -<BR> 547 | RT/RTR12<BR> 548 | RT/RTR12<BR> 549 | RT/RTR12<BR> 550 | -<BR> 551 | RJ/RJR12<BR> 552 | RJ/RJR12<BR> 553 | RJ/RJR12<BR></FONT> 554 | </TD> 555 | </TR> 556 | <TR> 557 | <TD COLSPAN=8>&nbsp; 558 | </TD> 559 | </TR> 560 | <TR> 561 | <TD COLSPAN=8> 562 | <FONT SIZE=4 FACE=ARIAL><B>SQUARE MULTI-TURN</B></FONT> 563 | </TD> 564 | </TR> 565 | <TR> 566 | <TD ALIGN=CENTER> 567 | <FONT SIZE=3 FACE=ARIAL><B>BOURN</B></FONT> 568 | </TD> 569 | <TD ALIGN=CENTER> 570 | <FONT SIZE=3 FACE=ARIAL><B>BI&nbsp;TECH</B></FONT> 571 | </TD> 572 | <TD ALIGN=CENTER> 573 | <FONT SIZE=3 FACE=ARIAL><B>DALE-VISHAY</B></FONT> 574 | </TD> 575 | <TD ALIGN=CENTER> 576 | <FONT SIZE=3 FACE=ARIAL><B>PHILIPS/MEPCO</B></FONT> 577 | </TD> 578 | <TD ALIGN=CENTER> 579 | <FONT SIZE=3 FACE=ARIAL><B>MURATA</B></FONT> 580 | </TD> 581 | <TD ALIGN=CENTER> 582 | <FONT SIZE=3 FACE=ARIAL><B>PANASONIC</B></FONT> 583 | </TD> 584 | <TD ALIGN=CENTER> 585 | <FONT SIZE=3 FACE=ARIAL><B>SPECTROL</B></FONT> 586 | </TD> 587 | <TD ALIGN=CENTER> 588 | <FONT SIZE=3 FACE=ARIAL><B>MILSPEC</B></FONT> 589 | </TD> 590 | </TR> 591 | <TR> 592 | <TD BGCOLOR="#cccccc" ALIGN=CENTER><FONT FACE=ARIAL SIZE=3> 593 | 3250L<BR> 594 | 3250P<BR> 595 | 3250W<BR> 596 | 3250X<BR> 597 | 3252P<BR> 598 | 3252W<BR> 599 | 3252X<BR> 600 | 3260P<BR> 601 | 3260W<BR> 602 | 3260X<BR> 603 | 3262P<BR> 604 | 3262W<BR> 605 | 3262X<BR> 606 | 3266P<BR> 607 | 3266W<BR> 608 | 3266X<BR> 609 | 3290H<BR> 610 | 3290P<BR> 611 | 3290W<BR> 612 | 3292P<BR> 613 | 3292W<BR> 614 | 3292X<BR> 615 | 3296P<BR> 616 | 3296W<BR> 617 | 3296X<BR> 618 | 3296Y<BR> 619 | 3296Z<BR> 620 | 3299P<BR> 621 | 3299W<BR> 622 | 3299X<BR> 623 | 3299Y<BR> 624 | 3299Z<BR></FONT> 625 | </TD> 626 | <TD BGCOLOR="#cccccc" ALIGN=CENTER><FONT FACE=ARIAL SIZE=3> 627 | -<BR> 628 | 66P&nbsp;ALT<BR> 629 | 66W&nbsp;ALT<BR> 630 | 66X&nbsp;ALT<BR> 631 | 66P&nbsp;ALT<BR> 632 | 66W&nbsp;ALT<BR> 633 | 66X&nbsp;ALT<BR> 634 | -<BR> 635 | 64W&nbsp;ALT<BR> 636 | -<BR> 637 | 64P&nbsp;ALT<BR> 638 | 64W&nbsp;ALT<BR> 639 | 64X&nbsp;ALT<BR> 640 | 64P<BR> 641 | 64W<BR> 642 | 64X<BR> 643 | 66X&nbsp;ALT<BR> 644 | 66P&nbsp;ALT<BR> 645 | 66W&nbsp;ALT<BR> 646 | 66P<BR> 647 | 66W<BR> 648 | 66X<BR> 649 | 67P<BR> 650 | 67W<BR> 651 | 67X<BR> 652 | 67Y<BR> 653 | 67Z<BR> 654 | 68P<BR> 655 | 68W<BR> 656 | 68X<BR> 657 | 67Y&nbsp;ALT<BR> 658 | 67Z&nbsp;ALT<BR></FONT> 659 | </TD> 660 | <TD BGCOLOR="#cccccc" ALIGN=CENTER><FONT FACE=ARIAL SIZE=3> 661 | 5050<BR> 662 | 5091<BR> 663 | 5080<BR> 664 | 5087<BR> 665 | -<BR> 666 | -<BR> 667 | -<BR> 668 | -<BR> 669 | -<BR> 670 | -<BR> 671 | -<BR> 672 | T63YB<BR> 673 | T63XB<BR> 674 | -<BR> 675 | -<BR> 676 | -<BR> 677 | 5887<BR> 678 | 5891<BR> 679 | 5880<BR> 680 | -<BR> 681 | -<BR> 682 | -<BR> 683 | T93Z<BR> 684 | T93YA<BR> 685 | T93XA<BR> 686 | T93YB<BR> 687 | T93XB<BR> 688 | -<BR> 689 | -<BR> 690 | -<BR> 691 | -<BR> 692 | -<BR></FONT> 693 | </TD> 694 | <TD BGCOLOR="#cccccc" ALIGN=CENTER><FONT FACE=ARIAL SIZE=3> 695 | -<BR> 696 | -<BR> 697 | -<BR> 698 | -<BR> 699 | -<BR> 700 | -<BR> 701 | -<BR> 702 | -<BR> 703 | -<BR> 704 | -<BR> 705 | 8026EKP<BR> 706 | 8026EKW<BR> 707 | 8026EKM<BR> 708 | 8026EKP<BR> 709 | 8026EKB<BR> 710 | 8026EKM<BR> 711 | 1309X<BR> 712 | 1309P<BR> 713 | 1309W<BR> 714 | 8024EKP<BR> 715 | 8024EKW<BR> 716 | 8024EKN<BR> 717 | RJ-9P/CT9P<BR> 718 | RJ-9W<BR> 719 | RJ-9X<BR> 720 | -<BR> 721 | -<BR> 722 | -<BR> 723 | -<BR> 724 | -<BR> 725 | -<BR> 726 | -<BR></FONT> 727 | </TD> 728 | <TD BGCOLOR="#cccccc" ALIGN=CENTER><FONT FACE=ARIAL SIZE=3> 729 | -<BR> 730 | -<BR> 731 | -<BR> 732 | -<BR> 733 | -<BR> 734 | -<BR> 735 | -<BR> 736 | -<BR> 737 | -<BR> 738 | -<BR> 739 | 3103P<BR> 740 | 3103Y<BR> 741 | 3103Z<BR> 742 | 3103P<BR> 743 | 3103Y<BR> 744 | 3103Z<BR> 745 | -<BR> 746 | -<BR> 747 | -<BR> 748 | -<BR> 749 | -<BR> 750 | -<BR> 751 | 3105P/3106P<BR> 752 | 3105W/3106W<BR> 753 | 3105X/3106X<BR> 754 | 3105Y/3106Y<BR> 755 | 3105Z/3105Z<BR> 756 | 3102P<BR> 757 | 3102W<BR> 758 | 3102X<BR> 759 | 3102Y<BR> 760 | 3102Z<BR></FONT> 761 | </TD> 762 | <TD BGCOLOR="#cccccc" ALIGN=CENTER><FONT FACE=ARIAL SIZE=3> 763 | -<BR> 764 | -<BR> 765 | -<BR> 766 | -<BR> 767 | -<BR> 768 | -<BR> 769 | -<BR> 770 | -<BR> 771 | -<BR> 772 | -<BR> 773 | -<BR> 774 | -<BR> 775 | -<BR> 776 | -<BR> 777 | -<BR> 778 | -<BR> 779 | -<BR> 780 | -<BR> 781 | -<BR> 782 | -<BR> 783 | -<BR> 784 | -<BR> 785 | EVMCBG<BR> 786 | EVMCCG<BR> 787 | -<BR> 788 | -<BR> 789 | -<BR> 790 | -<BR> 791 | -<BR> 792 | -<BR> 793 | -<BR> 794 | -<BR></FONT> 795 | </TD> 796 | <TD BGCOLOR="#cccccc" ALIGN=CENTER><FONT FACE=ARIAL SIZE=3> 797 | 55-1-X<BR> 798 | 55-4-X<BR> 799 | 55-3-X<BR> 800 | 55-2-X<BR> 801 | -<BR> 802 | -<BR> 803 | -<BR> 804 | -<BR> 805 | -<BR> 806 | -<BR> 807 | -<BR> 808 | -<BR> 809 | -<BR> 810 | -<BR> 811 | -<BR> 812 | -<BR> 813 | 50-2-X<BR> 814 | 50-4-X<BR> 815 | 50-3-X<BR> 816 | -<BR> 817 | -<BR> 818 | -<BR> 819 | 64P<BR> 820 | 64W<BR> 821 | 64X<BR> 822 | 64Y<BR> 823 | 64Z<BR> 824 | -<BR> 825 | -<BR> 826 | -<BR> 827 | -<BR> 828 | -<BR></FONT> 829 | </TD> 830 | <TD BGCOLOR="#cccccc" ALIGN=CENTER><FONT FACE=ARIAL SIZE=3> 831 | RT/RTR22<BR> 832 | RT/RTR22<BR> 833 | RT/RTR22<BR> 834 | RT/RTR22<BR> 835 | RJ/RJR22<BR> 836 | RJ/RJR22<BR> 837 | RJ/RJR22<BR> 838 | RT/RTR26<BR> 839 | RT/RTR26<BR> 840 | RT/RTR26<BR> 841 | RJ/RJR26<BR> 842 | RJ/RJR26<BR> 843 | RJ/RJR26<BR> 844 | RJ/RJR26<BR> 845 | RJ/RJR26<BR> 846 | RJ/RJR26<BR> 847 | RT/RTR24<BR> 848 | RT/RTR24<BR> 849 | RT/RTR24<BR> 850 | RJ/RJR24<BR> 851 | RJ/RJR24<BR> 852 | RJ/RJR24<BR> 853 | RJ/RJR24<BR> 854 | RJ/RJR24<BR> 855 | RJ/RJR24<BR> 856 | -<BR> 857 | -<BR> 858 | -<BR> 859 | -<BR> 860 | -<BR> 861 | -<BR> 862 | -<BR></FONT> 863 | </TD> 864 | </TR> 865 | <TR> 866 | <TD COLSPAN=8>&nbsp; 867 | </TD> 868 | </TR> 869 | <TR> 870 | <TD COLSPAN=8> 871 | <FONT SIZE=4 FACE=ARIAL><B>SINGLE TURN</B></FONT> 872 | </TD> 873 | </TR> 874 | <TR> 875 | <TD ALIGN=CENTER> 876 | <FONT SIZE=3 FACE=ARIAL><B>BOURN</B></FONT> 877 | </TD> 878 | <TD ALIGN=CENTER> 879 | <FONT SIZE=3 FACE=ARIAL><B>BI&nbsp;TECH</B></FONT> 880 | </TD> 881 | <TD ALIGN=CENTER> 882 | <FONT SIZE=3 FACE=ARIAL><B>DALE-VISHAY</B></FONT> 883 | </TD> 884 | <TD ALIGN=CENTER> 885 | <FONT SIZE=3 FACE=ARIAL><B>PHILIPS/MEPCO</B></FONT> 886 | </TD> 887 | <TD ALIGN=CENTER> 888 | <FONT SIZE=3 FACE=ARIAL><B>MURATA</B></FONT> 889 | </TD> 890 | <TD ALIGN=CENTER> 891 | <FONT SIZE=3 FACE=ARIAL><B>PANASONIC</B></FONT> 892 | </TD> 893 | <TD ALIGN=CENTER> 894 | <FONT SIZE=3 FACE=ARIAL><B>SPECTROL</B></FONT> 895 | </TD> 896 | <TD ALIGN=CENTER> 897 | <FONT SIZE=3 FACE=ARIAL><B>MILSPEC</B></FONT> 898 | </TD> 899 | </TR> 900 | <TR> 901 | <TD BGCOLOR="#cccccc" ALIGN=CENTER><FONT FACE=ARIAL SIZE=3> 902 | 3323P<BR> 903 | 3323S<BR> 904 | 3323W<BR> 905 | 3329H<BR> 906 | 3329P<BR> 907 | 3329W<BR> 908 | 3339H<BR> 909 | 3339P<BR> 910 | 3339W<BR> 911 | 3352E<BR> 912 | 3352H<BR> 913 | 3352K<BR> 914 | 3352P<BR> 915 | 3352T<BR> 916 | 3352V<BR> 917 | 3352W<BR> 918 | 3362H<BR> 919 | 3362M<BR> 920 | 3362P<BR> 921 | 3362R<BR> 922 | 3362S<BR> 923 | 3362U<BR> 924 | 3362W<BR> 925 | 3362X<BR> 926 | 3386B<BR> 927 | 3386C<BR> 928 | 3386F<BR> 929 | 3386H<BR> 930 | 3386K<BR> 931 | 3386M<BR> 932 | 3386P<BR> 933 | 3386S<BR> 934 | 3386W<BR> 935 | 3386X<BR></FONT> 936 | </TD> 937 | <TD BGCOLOR="#cccccc" ALIGN=CENTER><FONT FACE=ARIAL SIZE=3> 938 | 25P<BR> 939 | 25S<BR> 940 | 25RX<BR> 941 | 82P<BR> 942 | 82M<BR> 943 | 82PA<BR> 944 | -<BR> 945 | -<BR> 946 | -<BR> 947 | 91E<BR> 948 | 91X<BR> 949 | 91T<BR> 950 | 91B<BR> 951 | 91A<BR> 952 | 91V<BR> 953 | 91W<BR> 954 | 25W<BR> 955 | 25V<BR> 956 | 25P<BR> 957 | -<BR> 958 | 25S<BR> 959 | 25U<BR> 960 | 25RX<BR> 961 | 25X<BR> 962 | 72XW<BR> 963 | 72XL<BR> 964 | 72PM<BR> 965 | 72RX<BR> 966 | -<BR> 967 | 72PX<BR> 968 | 72P<BR> 969 | 72RXW<BR> 970 | 72RXL<BR> 971 | 72X<BR></FONT> 972 | </TD> 973 | <TD BGCOLOR="#cccccc" ALIGN=CENTER><FONT FACE=ARIAL SIZE=3> 974 | -<BR> 975 | -<BR> 976 | -<BR> 977 | T7YB<BR> 978 | T7YA<BR> 979 | -<BR> 980 | -<BR> 981 | -<BR> 982 | -<BR> 983 | -<BR> 984 | -<BR> 985 | -<BR> 986 | -<BR> 987 | -<BR> 988 | -<BR> 989 | -<BR> 990 | -<BR> 991 | TXD<BR> 992 | TYA<BR> 993 | TYP<BR> 994 | -<BR> 995 | TYD<BR> 996 | TX<BR> 997 | -<BR> 998 | 150SX<BR> 999 | 100SX<BR> 1000 | 102T<BR> 1001 | 101S<BR> 1002 | 190T<BR> 1003 | 150TX<BR> 1004 | 101<BR> 1005 | -<BR> 1006 | -<BR> 1007 | 101SX<BR></FONT> 1008 | </TD> 1009 | <TD BGCOLOR="#cccccc" ALIGN=CENTER><FONT FACE=ARIAL SIZE=3> 1010 | ET6P<BR> 1011 | ET6S<BR> 1012 | ET6X<BR> 1013 | RJ-6W/8014EMW<BR> 1014 | RJ-6P/8014EMP<BR> 1015 | RJ-6X/8014EMX<BR> 1016 | TM7W<BR> 1017 | TM7P<BR> 1018 | TM7X<BR> 1019 | -<BR> 1020 | 8017SMS<BR> 1021 | -<BR> 1022 | 8017SMB<BR> 1023 | 8017SMA<BR> 1024 | -<BR> 1025 | -<BR> 1026 | CT-6W<BR> 1027 | CT-6H<BR> 1028 | CT-6P<BR> 1029 | CT-6R<BR> 1030 | -<BR> 1031 | CT-6V<BR> 1032 | CT-6X<BR> 1033 | -<BR> 1034 | -<BR> 1035 | 8038EKV<BR> 1036 | -<BR> 1037 | 8038EKX<BR> 1038 | -<BR> 1039 | -<BR> 1040 | 8038EKP<BR> 1041 | 8038EKZ<BR> 1042 | 8038EKW<BR> 1043 | -<BR></FONT> 1044 | </TD> 1045 | <TD BGCOLOR="#cccccc" ALIGN=CENTER><FONT FACE=ARIAL SIZE=3> 1046 | -<BR> 1047 | -<BR> 1048 | -<BR> 1049 | 3321H<BR> 1050 | 3321P<BR> 1051 | 3321N<BR> 1052 | 1102H<BR> 1053 | 1102P<BR> 1054 | 1102T<BR> 1055 | RVA0911V304A<BR> 1056 | -<BR> 1057 | RVA0911H413A<BR> 1058 | RVG0707V100A<BR> 1059 | RVA0607V(H)306A<BR> 1060 | RVA1214H213A<BR> 1061 | -<BR> 1062 | -<BR> 1063 | -<BR> 1064 | -<BR> 1065 | -<BR> 1066 | -<BR> 1067 | -<BR> 1068 | -<BR> 1069 | -<BR> 1070 | 3104B<BR> 1071 | 3104C<BR> 1072 | 3104F<BR> 1073 | 3104H<BR> 1074 | -<BR> 1075 | 3104M<BR> 1076 | 3104P<BR> 1077 | 3104S<BR> 1078 | 3104W<BR> 1079 | 3104X<BR></FONT> 1080 | </TD> 1081 | <TD BGCOLOR="#cccccc" ALIGN=CENTER><FONT FACE=ARIAL SIZE=3> 1082 | EVMQ0G<BR> 1083 | EVMQIG<BR> 1084 | EVMQ3G<BR> 1085 | EVMS0G<BR> 1086 | EVMQ0G<BR> 1087 | EVMG0G<BR> 1088 | -<BR> 1089 | -<BR> 1090 | -<BR> 1091 | EVMK4GA00B<BR> 1092 | EVM30GA00B<BR> 1093 | EVMK0GA00B<BR> 1094 | EVM38GA00B<BR> 1095 | EVMB6<BR> 1096 | EVLQ0<BR> 1097 | -<BR> 1098 | EVMMSG<BR> 1099 | EVMMBG<BR> 1100 | EVMMAG<BR> 1101 | -<BR> 1102 | -<BR> 1103 | EVMMCS<BR> 1104 | -<BR> 1105 | -<BR> 1106 | -<BR> 1107 | -<BR> 1108 | -<BR> 1109 | EVMM1<BR> 1110 | -<BR> 1111 | -<BR> 1112 | EVMM0<BR> 1113 | -<BR> 1114 | -<BR> 1115 | EVMM3<BR></FONT> 1116 | </TD> 1117 | <TD BGCOLOR="#cccccc" ALIGN=CENTER><FONT FACE=ARIAL SIZE=3> 1118 | -<BR> 1119 | -<BR> 1120 | -<BR> 1121 | 62-3-1<BR> 1122 | 62-1-2<BR> 1123 | -<BR> 1124 | -<BR> 1125 | -<BR> 1126 | -<BR> 1127 | -<BR> 1128 | -<BR> 1129 | -<BR> 1130 | -<BR> 1131 | -<BR> 1132 | -<BR> 1133 | -<BR> 1134 | 67R<BR> 1135 | -<BR> 1136 | 67P<BR> 1137 | -<BR> 1138 | -<BR> 1139 | -<BR> 1140 | -<BR> 1141 | 67X<BR> 1142 | 63V<BR> 1143 | 63S<BR> 1144 | 63M<BR> 1145 | -<BR> 1146 | -<BR> 1147 | 63H<BR> 1148 | 63P<BR> 1149 | -<BR> 1150 | -<BR> 1151 | 63X<BR></FONT> 1152 | </TD> 1153 | <TD BGCOLOR="#cccccc" ALIGN=CENTER><FONT FACE=ARIAL SIZE=3> 1154 | -<BR> 1155 | -<BR> 1156 | -<BR> 1157 | RJ/RJR50<BR> 1158 | RJ/RJR50<BR> 1159 | RJ/RJR50<BR> 1160 | -<BR> 1161 | -<BR> 1162 | -<BR> 1163 | -<BR> 1164 | -<BR> 1165 | -<BR> 1166 | -<BR> 1167 | -<BR> 1168 | -<BR> 1169 | -<BR> 1170 | -<BR> 1171 | -<BR> 1172 | -<BR> 1173 | -<BR> 1174 | -<BR> 1175 | -<BR> 1176 | -<BR> 1177 | -<BR> 1178 | -<BR> 1179 | -<BR> 1180 | -<BR> 1181 | -<BR> 1182 | -<BR> 1183 | -<BR> 1184 | -<BR> 1185 | -<BR> 1186 | -<BR> 1187 | -<BR></FONT> 1188 | </TD> 1189 | </TR> 1190 | </TABLE> 1191 | <P>&nbsp;<P> 1192 | <TABLE BORDER=0 CELLSPACING=1 CELLPADDING=3> 1193 | <TR> 1194 | <TD COLSPAN=7> 1195 | <FONT color="#0000FF" SIZE=4 FACE=ARIAL><B>SMD TRIM-POT CROSS REFERENCE</B></FONT> 1196 | <P> 1197 | <FONT SIZE=4 FACE=ARIAL><B>MULTI-TURN</B></FONT> 1198 | </TD> 1199 | </TR> 1200 | <TR> 1201 | <TD> 1202 | <FONT SIZE=3 FACE=ARIAL><B>BOURNS</B></FONT> 1203 | </TD> 1204 | <TD> 1205 | <FONT SIZE=3 FACE=ARIAL><B>BI&nbsp;TECH</B></FONT> 1206 | </TD> 1207 | <TD> 1208 | <FONT SIZE=3 FACE=ARIAL><B>DALE-VISHAY</B></FONT> 1209 | </TD> 1210 | <TD> 1211 | <FONT SIZE=3 FACE=ARIAL><B>PHILIPS/MEPCO</B></FONT> 1212 | </TD> 1213 | <TD> 1214 | <FONT SIZE=3 FACE=ARIAL><B>PANASONIC</B></FONT> 1215 | </TD> 1216 | <TD> 1217 | <FONT SIZE=3 FACE=ARIAL><B>TOCOS</B></FONT> 1218 | </TD> 1219 | <TD> 1220 | <FONT SIZE=3 FACE=ARIAL><B>AUX/KYOCERA</B></FONT> 1221 | </TD> 1222 | </TR> 1223 | <TR> 1224 | <TD BGCOLOR="#cccccc" ALIGN=CENTER><FONT FACE=ARIAL SIZE=3> 1225 | 3224G<BR> 1226 | 3224J<BR> 1227 | 3224W<BR> 1228 | 3269P<BR> 1229 | 3269W<BR> 1230 | 3269X<BR></FONT> 1231 | </TD> 1232 | <TD BGCOLOR="#cccccc" ALIGN=CENTER><FONT FACE=ARIAL SIZE=3> 1233 | 44G<BR> 1234 | 44J<BR> 1235 | 44W<BR> 1236 | 84P<BR> 1237 | 84W<BR> 1238 | 84X<BR></FONT> 1239 | </TD> 1240 | <TD BGCOLOR="#cccccc" ALIGN=CENTER><FONT FACE=ARIAL SIZE=3> 1241 | -<BR> 1242 | -<BR> 1243 | -<BR> 1244 | ST63Z<BR> 1245 | ST63Y<BR> 1246 | -<BR></FONT> 1247 | </TD> 1248 | <TD BGCOLOR="#cccccc" ALIGN=CENTER><FONT FACE=ARIAL SIZE=3> 1249 | -<BR> 1250 | -<BR> 1251 | -<BR> 1252 | ST5P<BR> 1253 | ST5W<BR> 1254 | ST5X<BR></FONT> 1255 | </TD> 1256 | <TD BGCOLOR="#cccccc" ALIGN=CENTER><FONT FACE=ARIAL SIZE=3> 1257 | -<BR> 1258 | -<BR> 1259 | -<BR> 1260 | -<BR> 1261 | -<BR> 1262 | -<BR></FONT> 1263 | </TD> 1264 | <TD BGCOLOR="#cccccc" ALIGN=CENTER><FONT FACE=ARIAL SIZE=3> 1265 | -<BR> 1266 | -<BR> 1267 | -<BR> 1268 | -<BR> 1269 | -<BR> 1270 | -<BR></FONT> 1271 | </TD> 1272 | <TD BGCOLOR="#cccccc" ALIGN=CENTER><FONT FACE=ARIAL SIZE=3> 1273 | -<BR> 1274 | -<BR> 1275 | -<BR> 1276 | -<BR> 1277 | -<BR> 1278 | -<BR></FONT> 1279 | </TD> 1280 | </TR> 1281 | <TR> 1282 | <TD COLSPAN=7>&nbsp; 1283 | </TD> 1284 | </TR> 1285 | <TR> 1286 | <TD COLSPAN=7> 1287 | <FONT SIZE=4 FACE=ARIAL><B>SINGLE TURN</B></FONT> 1288 | </TD> 1289 | </TR> 1290 | <TR> 1291 | <TD> 1292 | <FONT SIZE=3 FACE=ARIAL><B>BOURNS</B></FONT> 1293 | </TD> 1294 | <TD> 1295 | <FONT SIZE=3 FACE=ARIAL><B>BI&nbsp;TECH</B></FONT> 1296 | </TD> 1297 | <TD> 1298 | <FONT SIZE=3 FACE=ARIAL><B>DALE-VISHAY</B></FONT> 1299 | </TD> 1300 | <TD> 1301 | <FONT SIZE=3 FACE=ARIAL><B>PHILIPS/MEPCO</B></FONT> 1302 | </TD> 1303 | <TD> 1304 | <FONT SIZE=3 FACE=ARIAL><B>PANASONIC</B></FONT> 1305 | </TD> 1306 | <TD> 1307 | <FONT SIZE=3 FACE=ARIAL><B>TOCOS</B></FONT> 1308 | </TD> 1309 | <TD> 1310 | <FONT SIZE=3 FACE=ARIAL><B>AUX/KYOCERA</B></FONT> 1311 | </TD> 1312 | </TR> 1313 | <TR> 1314 | <TD BGCOLOR="#cccccc" ALIGN=CENTER><FONT FACE=ARIAL SIZE=3> 1315 | 3314G<BR> 1316 | 3314J<BR> 1317 | 3364A/B<BR> 1318 | 3364C/D<BR> 1319 | 3364W/X<BR> 1320 | 3313G<BR> 1321 | 3313J<BR></FONT> 1322 | </TD> 1323 | <TD BGCOLOR="#cccccc" ALIGN=CENTER><FONT FACE=ARIAL SIZE=3> 1324 | 23B<BR> 1325 | 23A<BR> 1326 | 21X<BR> 1327 | 21W<BR> 1328 | -<BR> 1329 | 22B<BR> 1330 | 22A<BR></FONT> 1331 | </TD> 1332 | <TD BGCOLOR="#cccccc" ALIGN=CENTER><FONT FACE=ARIAL SIZE=3> 1333 | ST5YL/ST53YL<BR> 1334 | ST5YJ/5T53YJ<BR> 1335 | ST-23A<BR> 1336 | ST-22B<BR> 1337 | ST-22<BR> 1338 | -<BR> 1339 | -<BR></FONT> 1340 | </TD> 1341 | <TD BGCOLOR="#cccccc" ALIGN=CENTER><FONT FACE=ARIAL SIZE=3> 1342 | ST-4B<BR> 1343 | ST-4A<BR> 1344 | -<BR> 1345 | -<BR> 1346 | -<BR> 1347 | ST-3B<BR> 1348 | ST-3A<BR></FONT> 1349 | </TD> 1350 | <TD BGCOLOR="#cccccc" ALIGN=CENTER><FONT FACE=ARIAL SIZE=3> 1351 | -<BR> 1352 | EVM-6YS<BR> 1353 | EVM-1E<BR> 1354 | EVM-1G<BR> 1355 | EVM-1D<BR> 1356 | -<BR> 1357 | -<BR></FONT> 1358 | </TD> 1359 | <TD BGCOLOR="#cccccc" ALIGN=CENTER><FONT FACE=ARIAL SIZE=3> 1360 | G4B<BR> 1361 | G4A<BR> 1362 | TR04-3S1<BR> 1363 | TRG04-2S1<BR> 1364 | -<BR> 1365 | -<BR> 1366 | -<BR></FONT> 1367 | </TD> 1368 | <TD BGCOLOR="#cccccc" ALIGN=CENTER><FONT FACE=ARIAL SIZE=3> 1369 | -<BR> 1370 | -<BR> 1371 | DVR-43A<BR> 1372 | CVR-42C<BR> 1373 | CVR-42A/C<BR> 1374 | -<BR> 1375 | -<BR></FONT> 1376 | </TD> 1377 | </TR> 1378 | </TABLE> 1379 | <P> 1380 | <FONT SIZE=4 FACE=ARIAL><B>ALT =&nbsp;ALTERNATE</B></FONT> 1381 | <P> 1382 | 1383 | &nbsp; 1384 | <P> 1385 | </td> 1386 | </tr> 1387 | </table> 1388 | 1389 | 1390 | <b>CAPACITOR</b> 1391 | 1392 | 1393 | 1394 | 1395 | 1396 | 1397 | 1398 | 1399 | >NAME 1400 | >VALUE 1401 | 1402 | 1403 | 1404 | 1405 | 1406 | 1407 | 1408 | 1409 | 1410 | 1411 | 1412 | 1413 | 1414 | 1415 | 1416 | 1417 | <b>EAGLE Design Rules</b> 1418 | <p> 1419 | Die Standard-Design-Rules sind so gewählt, dass sie für 1420 | die meisten Anwendungen passen. Sollte ihre Platine 1421 | besondere Anforderungen haben, treffen Sie die erforderlichen 1422 | Einstellungen hier und speichern die Design Rules unter 1423 | einem neuen Namen ab. 1424 | <b>EAGLE Design Rules</b> 1425 | <p> 1426 | The default Design Rules have been set to cover 1427 | a wide range of applications. Your particular design 1428 | may have different requirements, so please make the 1429 | necessary adjustments and save your customized 1430 | design rules under a new name. 1431 | 1432 | 1433 | 1434 | 1435 | 1436 | 1437 | 1438 | 1439 | 1440 | 1441 | 1442 | 1443 | 1444 | 1445 | 1446 | 1447 | 1448 | 1449 | 1450 | 1451 | 1452 | 1453 | 1454 | 1455 | 1456 | 1457 | 1458 | 1459 | 1460 | 1461 | 1462 | 1463 | 1464 | 1465 | 1466 | 1467 | 1468 | 1469 | 1470 | 1471 | 1472 | 1473 | 1474 | 1475 | 1476 | 1477 | 1478 | 1479 | 1480 | 1481 | 1482 | 1483 | 1484 | 1485 | 1486 | 1487 | 1488 | 1489 | 1490 | 1491 | 1492 | 1493 | 1494 | 1495 | 1496 | 1497 | 1498 | 1499 | 1500 | 1501 | 1502 | 1503 | 1504 | 1505 | 1506 | 1507 | 1508 | 1509 | 1510 | 1511 | 1512 | 1513 | 1514 | 1515 | 1516 | 1517 | 1518 | 1519 | 1520 | 1521 | 1522 | 1523 | 1524 | 1525 | 1526 | 1527 | 1528 | 1529 | 1530 | 1531 | 1532 | 1533 | 1534 | 1535 | 1536 | 1537 | 1538 | 1539 | 1540 | 1541 | 1542 | 1543 | 1544 | 1545 | 1546 | 1547 | 1548 | 1549 | 1550 | 1551 | 1552 | 1553 | 1554 | 1555 | 1556 | 1557 | 1558 | 1559 | 1560 | 1561 | 1562 | 1563 | 1564 | 1565 | 1566 | 1567 | 1568 | 1569 | 1570 | 1571 | 1572 | 1573 | 1574 | 1575 | 1576 | 1577 | 1578 | 1579 | 1580 | 1581 | 1582 | 1583 | 1584 | 1585 | 1586 | 1587 | 1588 | 1589 | 1590 | 1591 | 1592 | 1593 | 1594 | 1595 | 1596 | 1597 | 1598 | 1599 | 1600 | 1601 | 1602 | 1603 | 1604 | 1605 | 1606 | 1607 | 1608 | 1609 | 1610 | 1611 | 1612 | 1613 | 1614 | 1615 | 1616 | 1617 | 1618 | 1619 | 1620 | 1621 | 1622 | 1623 | 1624 | 1625 | 1626 | 1627 | 1628 | 1629 | 1630 | 1631 | 1632 | 1633 | 1634 | 1635 | 1636 | 1637 | 1638 | 1639 | 1640 | 1641 | 1642 | 1643 | 1644 | 1645 | 1646 | 1647 | 1648 | 1649 | 1650 | 1651 | 1652 | 1653 | 1654 | 1655 | 1656 | 1657 | 1658 | 1659 | 1660 | 1661 | 1662 | 1663 | --------------------------------------------------------------------------------