├── .gitignore ├── README.md ├── examples ├── single_motor │ └── single_motor.ino ├── two_motors │ └── two_motors.ino └── two_motors_synchronized │ └── two_motors_synchronized.ino ├── keywords.txt ├── l6470 Manual.pdf ├── library.json ├── library.properties └── src ├── L6470.cpp └── L6470.h /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | .DS_Store 3 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Arduino-L6470 2 | 3 | A small library supporting ST micro L6470 stepper drivers with true 128 micro-steps resolution. 4 | 5 | ## Project Status 6 | 7 | This library was never fully finished. It may be useful for someone to use but I assure you there are more than a few bugs in it. 8 | 9 | Currently this is being patched up, so if you encounter any issues, please let us know. -------------------------------------------------------------------------------- /examples/single_motor/single_motor.ino: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #define SS_PIN 10 4 | #define SCK_PIN 11 5 | #define MOSI_PIN 12 6 | #define MISO_PIN 13 7 | #define RESET_PIN 14 8 | #define BUSYN_PIN 15 9 | 10 | L6470 stepper(SS_PIN); // create stepper object 11 | 12 | void setup() { 13 | stepper.set_pins(SCK_PIN, MOSI_PIN, MISO_PIN, RESET_PIN, BUSYN_PIN); //use library's soft SPI 14 | stepper.init(); 15 | stepper.setAcc(100); // Set acceleration 16 | stepper.setMaxSpeed(800); 17 | stepper.setMinSpeed(1); 18 | stepper.setMicroSteps(2); // 1,2,4,8,16,32,64 or 128 19 | stepper.setThresholdSpeed(1000); 20 | stepper.setOverCurrent(6000); // Set overcurrent protection 21 | stepper.setStallCurrent(3000); 22 | 23 | //stepper.run(1, 200); 24 | 25 | //stepper.softStop(); 26 | 27 | stepper.goTo(200); 28 | } 29 | 30 | void loop() { 31 | while (stepper.isBusy()) delay(10); 32 | stepper.goTo(-200); 33 | while (stepper.isBusy()) delay(10); 34 | stepper.goTo(2000); 35 | } 36 | -------------------------------------------------------------------------------- /examples/two_motors/two_motors.ino: -------------------------------------------------------------------------------- 1 | // two motors using independent SPIs 2 | 3 | 4 | #include 5 | 6 | #define A_SS_PIN 10 7 | #define A_SCK_PIN 11 8 | #define A_MOSI_PIN 12 9 | #define A_MISO_PIN 13 10 | #define A_RESET_PIN 14 11 | #define A_BUSYN_PIN 15 12 | 13 | #define B_SS_PIN 20 14 | #define B_SCK_PIN 21 15 | #define B_MOSI_PIN 22 16 | #define B_MISO_PIN 23 17 | #define B_RESET_PIN 24 18 | #define B_BUSYN_PIN 25 19 | 20 | L6470 stepperA(A_SS_PIN); 21 | L6470 stepperB(B_SS_PIN); 22 | 23 | void setup() { 24 | 25 | stepperA.set_pins(A_SCK_PIN, A_MOSI_PIN, A_MISO_PIN, A_RESET_PIN, A_BUSYN_PIN); //use library's soft SPI 26 | stepperA.init(); 27 | stepperA.setAcc(100); // Set acceleration 28 | stepperA.setMaxSpeed(800); 29 | stepperA.setMinSpeed(1); 30 | stepperA.setMicroSteps(2); // 1,2,4,8,16,32,64 or 128 31 | stepperA.setThresholdSpeed(1000); 32 | stepperA.setOverCurrent(6000); // Set overcurrent protection 33 | stepperA.setStallCurrent(3000); 34 | 35 | stepperB.set_pins(B_SCK_PIN, B_MOSI_PIN, B_MISO_PIN, B_RESET_PIN, B_BUSYN_PIN); //use library's soft SPI 36 | stepperB.init(); 37 | stepperB.setAcc(100); // Set acceleration 38 | stepperB.setMaxSpeed(800); 39 | stepperB.setMinSpeed(1); 40 | stepperB.setMicroSteps(2); // 1,2,4,8,16,32,64 or 128 41 | stepperB.setThresholdSpeed(1000); 42 | stepperB.setOverCurrent(6000); // Set overcurrent protection 43 | stepperB.setStallCurrent(3000); 44 | 45 | //stepper.run(1, 200); 46 | 47 | //stepper.softStop(); 48 | 49 | stepperA.goTo(200); // stepperA starts as soon as it's SS_PIN goes high 50 | stepperB.goTo(200); // stepperB's start is delayed by SPI transmission time 51 | } 52 | 53 | void loop() { 54 | while (stepperB.isBusy()) delay(10); 55 | stepperA.goTo(-200); 56 | stepperB.goTo(-200); 57 | while (stepperB.isBusy()) delay(10); 58 | stepperA.goTo(2000); 59 | stepperB.goTo(2000); 60 | } 61 | -------------------------------------------------------------------------------- /examples/two_motors_synchronized/two_motors_synchronized.ino: -------------------------------------------------------------------------------- 1 | /** 2 | * This example synchronizes the actions of two motors by using 3 | * SPI daisy chaining. 4 | * 5 | * The hardware setup is: 6 | * MOSI from controller tied to SDI on the first device 7 | * SDO of the first device is tied to SDI of the next device 8 | * SDO of the last device is tied to MISO of the controller 9 | * all devices share the same SCK, SS_PIN and RESET_PIN 10 | * 11 | * Each L6470 passes the data it saw on its SDI to its neighbor 12 | * on the NEXT SPI cycle (8 bit delay). 13 | * 14 | * Each L6470 acts on the last SPI data it saw when the SS_PIN goes high. 15 | */ 16 | 17 | /** 18 | * Two different SPI routines are used. One routine is used to send 19 | * commands to individual devices. A different one is used for motion 20 | * commands so that all devices act at the same time. 21 | */ 22 | 23 | /** 24 | * The array "chain[]" is used to tell the software how the hardware is hooked up 25 | * [0] - number of drivers in chain 26 | * [1] - axis index for first device in the chain (closest to MOSI) 27 | * [2] - axis index for next device in the chain 28 | * 29 | * Axis index is an arbitrary identifier assigned by the user 30 | */ 31 | 32 | #include 33 | 34 | #define SS_PIN 10 35 | #define SCK_PIN 11 36 | #define MOSI_PIN 12 37 | #define MISO_PIN 13 38 | #define RESET_PIN 14 39 | 40 | class TwoMotors : public L64XXHelper { 41 | public: 42 | TwoMotors() { L64XX::set_helper(*this); } 43 | 44 | /** 45 | * Initialize pins for non-library SPI software 46 | */ 47 | static void spi_init() { 48 | pinMode(SS_PIN, OUTPUT); 49 | pinMode(SCK_PIN, OUTPUT); 50 | pinMode(MOSI_PIN, OUTPUT); 51 | digitalWrite(SS_PIN, HIGH); 52 | digitalWrite(SCK_PIN, HIGH); 53 | digitalWrite(MOSI_PIN, HIGH); 54 | pinMode(MISO_PIN, INPUT); 55 | } 56 | 57 | static inline uint8_t transfer(uint8_t data, const int16_t ss_pin) { } 58 | 59 | /** 60 | * Used in all non-motion commands/transfers 61 | * Send/receive one uint8_t to the target device. All other devices get a NOOP command. 62 | * Data for the last device in the chain is sent out first! 63 | */ 64 | static inline uint8_t transfer(uint8_t data, const int16_t ss_pin, const uint8_t chain_position) { 65 | #define CMD_NOP 0 66 | uint8_t data_out = 0; 67 | data--; 68 | // first device in chain has data sent last 69 | digitalWrite(ss_pin, LOW); 70 | 71 | for (uint8_t i = L64XX::chain[0]; i >= 1; i--) { 72 | uint8_t temp = L6470_SpiTransfer_Mode_3(uint8_t(i == chain_position ? data : CMD_NOP)); 73 | if (L64XX::chain[i] == chain_position) data_out = temp; 74 | } 75 | 76 | digitalWrite(ss_pin, HIGH); 77 | return data_out; 78 | } 79 | 80 | static inline uint8_t L6470_SpiTransfer_Mode_3(uint8_t b) { // using Mode 3 81 | uint8_t bits = 8; 82 | do { 83 | digitalWrite(SCK_PIN, LOW); 84 | digitalWrite(MOSI_PIN, b & 0x80); 85 | //DELAY_NS(125); 86 | digitalWrite(SCK_PIN, HIGH); 87 | b <<= 1; // little setup time 88 | b |= (digitalRead(MISO_PIN) != 0); 89 | } while (--bits); 90 | //DELAY_NS(125); 91 | return b; 92 | } 93 | 94 | /** 95 | * This is the routine that sends the motion commands. 96 | * 97 | * This routine sends a buffer of data to be filled by the application. The 98 | * library is not involved with it. 99 | */ 100 | static inline void Buffer_Transfer(uint8_t buffer[], const uint8_t length) { 101 | //uint8_t buffer[number of steppers + 1]; 102 | // [0] - not used 103 | // [1] - command for first device 104 | // [2] - command for second device 105 | 106 | // first device in chain has data sent last 107 | digitalWrite(SS_PIN, LOW); 108 | for (uint8_t i = length; i >= 1; i--) 109 | buffer[i] = L6470_SpiTransfer_Mode_3(uint8_t (buffer[i])); 110 | digitalWrite(SS_PIN, HIGH); 111 | } 112 | 113 | static inline void goTo(long location_1, long location_2) { 114 | // the command string to move a stepper to an absolute position is 115 | // four uint8_t long so four arraya are used for convenience 116 | 117 | uint8_t buffer_command[3] = { dSPIN_GOTO, dSPIN_GOTO }; // create and fill buffer with commands 118 | 119 | if (location_1 > 0x3FFFFF) location_1 = 0x3FFFFF; // limit to 22 bits 120 | if (location_1 > 0x3FFFFF) location_1 = 0x3FFFFF; 121 | 122 | uint8_t addr21_16[3] = { 0, uint8_t(location_1 >> 16), uint8_t(location_2 >> 16) }; 123 | uint8_t addr15_8[3] = { 0, uint8_t(location_1 >> 8), uint8_t(location_2 >> 8) }; 124 | uint8_t addr7_0[3] = { 0, uint8_t(location_1) , uint8_t(location_2) }; 125 | 126 | Buffer_Transfer(buffer_command, L64XX::chain[0]); // send the commands 127 | Buffer_Transfer(addr21_16 , L64XX::chain[0]); // send the MSB of the position 128 | Buffer_Transfer(addr15_8 , L64XX::chain[0]); 129 | Buffer_Transfer(addr7_0 , L64XX::chain[0]); // this one results in the motors moving 130 | } 131 | 132 | static inline void _setup() { 133 | pinMode(RESET_PIN,OUTPUT); // Reset all drivers 134 | digitalWrite(RESET_PIN, LOW); // Do this before any setup commands are sent to the drivers 135 | delay(10); 136 | digitalWrite(RESET_PIN, HIGH); 137 | 138 | stepperA.set_chain_info(56, 1); // Completely setup chain[] before 139 | stepperB.set_chain_info(56, 2); // Any SPI traffic is sent 140 | 141 | stepperA.init(); 142 | stepperA.setAcc(100); // Set acceleration 143 | stepperA.setMaxSpeed(800); 144 | stepperA.setMinSpeed(1); 145 | stepperA.setMicroSteps(2); // 1,2,4,8,16,32,64 or 128 146 | stepperA.setThresholdSpeed(1000); 147 | stepperA.setOverCurrent(6000); // Set overcurrent protection 148 | stepperA.setStallCurrent(3000); 149 | 150 | stepperB.init(); 151 | stepperB.setAcc(100); // Set acceleration 152 | stepperB.setMaxSpeed(800); 153 | stepperB.setMinSpeed(1); 154 | stepperB.setMicroSteps(2); // 1,2,4,8,16,32,64 or 128 155 | stepperB.setThresholdSpeed(1000); 156 | stepperB.setOverCurrent(6000); // Set overcurrent protection 157 | stepperB.setStallCurrent(3000); 158 | 159 | goTo(200,200); // Spin the motors at the same time 160 | } 161 | 162 | static inline void _loop() { 163 | while (stepperB.isBusy()) delay(10); 164 | goTo(-200,-200); 165 | while (stepperB.isBusy()) delay(10); 166 | goTo(2000,2000); 167 | } 168 | 169 | private: 170 | static L6470 stepperA; 171 | static L6480 stepperB; 172 | static powerSTEP01 stepperC; 173 | }; 174 | 175 | L6470 TwoMotors::stepperA(SS_PIN); 176 | L6480 TwoMotors::stepperB(SS_PIN); 177 | powerSTEP01 TwoMotors::stepperC(SS_PIN); 178 | 179 | TwoMotors two_motors; 180 | 181 | void setup() { two_motors._setup(); } 182 | void loop() { two_motors._loop(); } 183 | -------------------------------------------------------------------------------- /keywords.txt: -------------------------------------------------------------------------------- 1 | ####################################################### 2 | # keywords.txt - keywords file for the L6470 library 3 | # 4 | # ORIGINAL CODE 12/12/2011- Mike Hord, SparkFun Electronics 5 | # Library by Adam Meyer of bildr Aug 18th 2012 6 | # 7 | # Released as MIT license 8 | ####################################################### 9 | 10 | ####################################### 11 | # Datatypes (KEYWORD1) 12 | ####################################### 13 | 14 | L6470 KEYWORD1 15 | 16 | ####################################### 17 | # Methods and Functions (KEYWORD2) 18 | ####################################### 19 | 20 | AccCalc KEYWORD2 21 | DecCalc KEYWORD2 22 | convert KEYWORD2 23 | free KEYWORD2 24 | FSCalc KEYWORD2 25 | GetParam KEYWORD2 26 | getPos KEYWORD2 27 | getStatus KEYWORD2 28 | goHome KEYWORD2 29 | goMark KEYWORD2 30 | goTo KEYWORD2 31 | goTo_DIR KEYWORD2 32 | goUntil KEYWORD2 33 | hardHiZ KEYWORD2 34 | hardStop KEYWORD2 35 | init KEYWORD2 36 | IntSpdCalc KEYWORD2 37 | isBusy KEYWORD2 38 | L6470 KEYWORD2 39 | MaxSpdCalc KEYWORD2 40 | MinSpdCalc KEYWORD2 41 | move KEYWORD2 42 | Param KEYWORD2 43 | ParamHandler KEYWORD2 44 | releaseSW KEYWORD2 45 | resetDev KEYWORD2 46 | resetPos KEYWORD2 47 | run KEYWORD2 48 | setAcc KEYWORD2 49 | setAsHome KEYWORD2 50 | setCurrent KEYWORD2 51 | setDec KEYWORD2 52 | SetLowSpeedOpt KEYWORD2 53 | setMark KEYWORD2 54 | setMaxSpeed KEYWORD2 55 | setMicroSteps KEYWORD2 56 | setMinSpeed KEYWORD2 57 | setOverCurrent KEYWORD2 58 | setParam KEYWORD2 59 | setStallCurrent KEYWORD2 60 | setThresholdSpeed KEYWORD2 61 | softFree KEYWORD2 62 | softStop KEYWORD2 63 | SpdCalc KEYWORD2 64 | Step_Clock KEYWORD2 65 | Xfer KEYWORD2 66 | 67 | ####################################### 68 | # Constants (LITERAL1) 69 | ####################################### 70 | -------------------------------------------------------------------------------- /l6470 Manual.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ameyer/Arduino-L6470/ba04e5238eb394d1f1d7af125881291c23256713/l6470 Manual.pdf -------------------------------------------------------------------------------- /library.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Arduino-L6470", 3 | "keywords": "l6470, stepper, driver", 4 | "description": "L6470 stepper driver library", 5 | "repository": 6 | { 7 | "type": "git", 8 | "url": "https://github.com/ameyer/Arduino-L6470.git" 9 | }, 10 | "version": "0.8.0", 11 | "authors": 12 | [ 13 | { 14 | "url": "http://adam-meyer.com", 15 | "maintainer": true, 16 | "email": "sternmeyer@gmail.com", 17 | "name": "Adam Meyer" 18 | }, 19 | { 20 | "url": "http://www.thinkyhead.com", 21 | "maintainer": false, 22 | "email": "L6470@thinkyhead.com", 23 | "name": "Scott Lahteine" 24 | } 25 | ], 26 | "frameworks": "arduino", 27 | "platforms": "atmelavr, atmelsam" 28 | } 29 | -------------------------------------------------------------------------------- /library.properties: -------------------------------------------------------------------------------- 1 | name=Arduino-L6470 2 | version=0.7.1 3 | author=Adam Meyer 4 | maintainer=Adam Meyer , Scott Lahteine 5 | sentence=L6470 stepper driver library 6 | paragraph=A simple library supporting the features of the L6470 stepper driver by STMicro. 7 | category=Device Control 8 | url=https://github.com/ameyer/Arduino-L6470 9 | architectures=avr,sam 10 | includes=L6470.h 11 | -------------------------------------------------------------------------------- /src/L6470.cpp: -------------------------------------------------------------------------------- 1 | //////////////////////////////////////////////////////////////////// 2 | // // 3 | // ORIGINAL CODE 12 Dec 2011 Mike Hord, SparkFun Electronics // 4 | // // 5 | // LIBRARY Created by Adam Meyer (@ameyer) of bildr 18 Aug 2012 // 6 | // Released as MIT license // 7 | // // 8 | // Changes: // 9 | // Scott Lahteine (@thinkyhead) - Cleanup 06 Mar 2018 // 10 | // Bob Kuhn (@bob-the-kuhn) - Chain / SPI 06 Jan 2019 // 11 | // Scott Lahteine (@thinkyhead) - L64XXHelper 01 Mar 2019 // 12 | // // 13 | //////////////////////////////////////////////////////////////////// 14 | 15 | #include "L6470.h" 16 | 17 | #include 18 | 19 | L64XXHelper nullHelper; 20 | L64XXHelper& L64XX::helper = nullHelper; 21 | 22 | uint8_t L64XX::chain[21]; 23 | 24 | // Generic init function to set up communication with the dSPIN chip. 25 | 26 | // Call this after setting up the SPI(s) (after all "L64XX::set_pins" and "set_chain_info" commands) 27 | void L64XX::init() { 28 | 29 | if (pin_SS >= 0) { //init pin_SS if it has been set for this chip 30 | pinMode(pin_SS, OUTPUT); 31 | digitalWrite(pin_SS, HIGH); 32 | } 33 | 34 | // initialize SPI for the dSPIN chip's needs: 35 | // most significant bit first, 36 | // SPI clock not to exceed 5MHz, 37 | // SPI_MODE3 (clock idle high, latch data on rising edge of clock) 38 | if (pin_SCK < 0) helper.spi_init(); // Use external SPI init function to init it 39 | // internal SPI already initialized 40 | 41 | // First things first: let's check communications. The L64XX_CONFIG register should 42 | // power up to 0x2E88, so we can use that to check the communications. 43 | //Serial.println(GetParam(L64XX_CONFIG) == 0x2E88 ? "good to go" : "Comm issue"); 44 | 45 | // First, let's set the step mode register: 46 | // - SYNC_EN controls whether the BUSY/SYNC pin reflects the step 47 | // frequency or the BUSY status of the chip. We want it to be the BUSY 48 | // status. 49 | // - STEP_SEL_x is the microstepping rate- we'll go full step. 50 | // - SYNC_SEL_x is the ratio of (micro)steps to toggles on the 51 | // BUSY/SYNC pin (when that pin is used for SYNC). Make it 1:1, despite 52 | // not using that pin. 53 | //SetParam(L6470_STEP_MODE, !SYNC_EN | STEP_SEL_1 | SYNC_SEL_1); 54 | 55 | // Set up the L64XX_CONFIG register as follows: 56 | // PWM frequency divisor = 1 57 | // PWM frequency multiplier = 2 (62.5kHz PWM frequency) 58 | // Slew rate is 110V/us 59 | // Do NOT shut down bridges on overcurrent 60 | // Disable motor voltage compensation 61 | // Hard stop on switch low 62 | // 16MHz internal oscillator, nothing on output 63 | SetParam(L64XX_CONFIG, CONFIG_PWM_DIV_1 | CONFIG_PWM_MUL_2 | CONFIG_SR_110V_us | CONFIG_OC_SD_DISABLE | CONFIG_VS_COMP_DISABLE | CONFIG_SW_HARD_STOP | CONFIG_INT_16MHZ); 64 | 65 | // Configure the dSPIN_RUN KVAL. This defines the duty cycle of the PWM of the bridges 66 | // during running. 0xFF means that they are essentially NOT PWMed during run; this 67 | // MAY result in more power being dissipated than you actually need for the task. 68 | // Setting this value too low may result in failure to turn. 69 | // There are L6470_ACC, L6470_DEC, and HOLD KVAL registers as well; you may need to play with 70 | // those values to get acceptable performance for a given application. 71 | SetParam(L6470_KVAL_RUN, 0xFF); 72 | SetParam(L6470_KVAL_ACC, 0xFF); 73 | SetParam(L6470_KVAL_DEC, 0xFF); 74 | 75 | // Calling getStatus() clears the UVLO bit in the status register, which is set by 76 | // default on power-up. The driver may not run without that bit cleared by this 77 | // read operation. 78 | getStatus(); 79 | 80 | hardStop(); // engage motors 81 | } 82 | 83 | // Add to the chain array and save chain info for this stepper 84 | void L64XX::set_chain_info(const uint8_t axis, const uint8_t chain_position) { 85 | if (chain_position) { 86 | chain[0]++; 87 | chain[chain_position] = axis; 88 | position = chain_position; 89 | axis_index = axis; 90 | } 91 | else 92 | chain[0] = 0; // Reset array back to uninitialized 93 | } 94 | 95 | // Set optional pins for this stepper 96 | // pin_SS is set by the instantiation call. 97 | void L64XX::set_pins(const _pin_t sck, const _pin_t mosi, const _pin_t miso, const _pin_t reset, const _pin_t busyn) { 98 | pin_SCK = sck; 99 | pin_MOSI = mosi; 100 | pin_MISO = miso; 101 | pin_RESET = reset; 102 | pin_BUSYN = busyn; 103 | 104 | if (pin_SCK >= 0) { // init SPI pins if they are valid 105 | pinMode(pin_MOSI, OUTPUT); 106 | pinMode(pin_MISO, INPUT); 107 | pinMode(pin_SCK, OUTPUT); 108 | digitalWrite(pin_SCK, HIGH); 109 | } 110 | 111 | if (pin_BUSYN >= 0) 112 | pinMode(pin_BUSYN, INPUT); 113 | 114 | // reset the dSPIN chip. This could also be accomplished by 115 | // calling the "L64XX::ResetDev()" function after SPI is initialized. 116 | // 117 | // Reset should be done here ONLY if each chip has a dedicated reset pin. Otherwise 118 | // the already initialized chip(s) will be set back to power up values. 119 | 120 | // This need to be done BEFORE calling L64XX::init() or the data written by the init 121 | // will be ceared to power up values. 122 | if (pin_RESET >= 0) { 123 | pinMode(pin_RESET, OUTPUT); 124 | digitalWrite(pin_RESET, HIGH); 125 | delay(10); 126 | digitalWrite(pin_RESET, LOW); 127 | delay(10); 128 | digitalWrite(pin_RESET, HIGH); 129 | delay(10); 130 | } 131 | } 132 | 133 | boolean L64XX::isBusy() { return !(getStatus() & 0x0002); } 134 | 135 | void L64XX::setMicroSteps(int16_t microSteps) { 136 | uint8_t stepVal; 137 | for (stepVal = 0; stepVal < 8; stepVal++) { 138 | if (microSteps == 1) break; 139 | microSteps >>= 1; 140 | } 141 | SetParam(L6470_STEP_MODE, !SYNC_EN | stepVal | SYNC_SEL_1); 142 | } 143 | 144 | // Configure the L6470_FS_SPD register- this is the speed at which the driver ceases 145 | // microstepping and goes to full stepping. FSCalc() converts a value in steps/s 146 | // to a value suitable for this register; to disable full-step switching, you 147 | // can pass 0x3FF to this register. 148 | void L64XX::setThresholdSpeed(const float thresholdSpeed) { 149 | SetParam(L6470_FS_SPD, thresholdSpeed ? FSCalc(thresholdSpeed) : 0x3FF); 150 | } 151 | 152 | // Configure the L6470_MAX_SPEED register- this is the maximum number of (micro)steps per 153 | // second allowed. You'll want to mess around with your desired application to see 154 | // how far you can push it before the motor starts to slip. The ACTUAL parameter 155 | // passed to this function is in steps/tick; MaxSpdCalc() will convert a number of 156 | // steps/s into an appropriate value for this function. Note that for any move or 157 | // goto type function where no speed is specified, this value will be used. 158 | void L64XX::setMaxSpeed(const int16_t speed) { SetParam(L6470_MAX_SPEED, MaxSpdCalc(speed)); } 159 | 160 | // Configure the L6470_MAX_SPEED register- this is the maximum number of (micro)steps per 161 | // second allowed. You'll want to mess around with your desired application to see 162 | // how far you can push it before the motor starts to slip. The ACTUAL parameter 163 | // passed to this function is in steps/tick; MaxSpdCalc() will convert a number of 164 | // steps/s into an appropriate value for this function. Note that for any move or 165 | // goto type function where no speed is specified, this value will be used. 166 | void L64XX::setMinSpeed(const int16_t speed) { SetParam(L6470_MIN_SPEED, MinSpdCalc(speed)); } 167 | 168 | // Configure the acceleration rate, in steps/tick/tick. There is also a L6470_DEC register; 169 | // both of them have a function (AccCalc() and DecCalc() respectively) that convert 170 | // from steps/s/s into the appropriate value for the register. Writing L6470_ACC to 0xfff 171 | // sets the acceleration and deceleration to 'infinite' (or as near as the driver can 172 | // manage). If L6470_ACC is set to 0xFFF, L6470_DEC is ignored. To get infinite deceleration 173 | // without infinite acceleration, only hard stop will work. 174 | void L64XX::setAcc(const float acceleration) { SetParam(L6470_ACC, AccCalc(acceleration)); } 175 | 176 | void L64XX::setDec(const float deceleration) { SetParam(L6470_DEC, DecCalc(deceleration)); } 177 | 178 | long L64XX::getPos() { return convert(GetParam(L6470_ABS_POS)); } 179 | 180 | // L6470_SPEED 181 | // The L6470_SPEED register contains the current motor speed, expressed in step/tick (format unsigned fixed point 0.28). 182 | // In order to convert the L6470_SPEED value in step/s the following formula can be used: 183 | // Equation 4 184 | // where L6470_SPEED is the integer number stored into the register and tick is 250 ns. 185 | // The available range is from 0 to 15625 step/s with a resolution of 0.015 step/s. 186 | // Note: The range effectively available to the user is limited by the L6470_MAX_SPEED parameter. 187 | float L64XX::getSpeed() { 188 | return (float)GetParam(L6470_SPEED); 189 | //return (float) speed * pow(8, -22); 190 | //return FSCalc(speed); NEEDS FIX 191 | } 192 | 193 | // Configure the overcurrent detection threshold. 194 | void L64XX::setOverCurrent(float ma_current) { 195 | if (ma_current > OCD_CURRENT_CONSTANT_INV * (OCD_TH_MAX +1)) ma_current = OCD_CURRENT_CONSTANT_INV * (OCD_TH_MAX +1); // keep the OCD_TH calc from overflowing 8 bits 196 | const uint8_t OCValue = (uint8_t)floor(uint8_t(ma_current * OCD_CURRENT_CONSTANT - 1)); 197 | SetParam(L6470_OCD_TH, OCValue < OCD_TH_MAX ? OCValue : OCD_TH_MAX); 198 | } 199 | 200 | void L64XX::setStallCurrent(float ma_current) { 201 | if (ma_current > STALL_CURRENT_CONSTANT_INV * (STALL_TH_MAX +1)) ma_current = STALL_CURRENT_CONSTANT_INV * (STALL_TH_MAX +1); // keep the STALL_TH calc from overflowing 8 bits 202 | const uint8_t STHValue = (uint8_t)floor(uint8_t(ma_current * STALL_CURRENT_CONSTANT - 1)); 203 | SetParam(L6470_STALL_TH, STHValue < STALL_TH_MAX ? STHValue : STALL_TH_MAX); 204 | } 205 | 206 | // Enable or disable the low-speed optimization option. If enabling, 207 | // the other 12 bits of the register will be automatically zero. 208 | // When disabling, the value will have to be explicitly written by 209 | // the user with a SetParam() call. See the datasheet for further 210 | // information about low-speed optimization. 211 | void L64XX::SetLowSpeedOpt(const boolean enable) { 212 | Xfer(dSPIN_SET_PARAM | L6470_MIN_SPEED); 213 | Param(enable ? 0x1000 : 0, 13); 214 | } 215 | 216 | // dSPIN_RUN sets the motor spinning in a direction (defined by the constants 217 | // dSPIN_FWD and dSPIN_REV). Maximum speed and minimum speed are defined 218 | // by the L6470_MAX_SPEED and L6470_MIN_SPEED registers; exceeding the L6470_FS_SPD value 219 | // will switch the device into full-step mode. 220 | // The SpdCalc() function is provided to convert steps/s values into 221 | // appropriate integer values for this function. 222 | void L64XX::run(const uint8_t dir, const float spd) { 223 | uint32_t speedVal = SpdCalc(spd); 224 | Xfer(dSPIN_RUN | dir); 225 | if (speedVal > 0xFFFFF) speedVal = 0xFFFFF; 226 | Xfer(uint8_t(speedVal >> 16)); 227 | Xfer(uint8_t(speedVal >> 8)); 228 | Xfer(uint8_t(speedVal)); 229 | } 230 | 231 | // dSPIN_STEP_CLOCK puts the device in external step clocking mode. When active, 232 | // pin 25, STCK, becomes the step clock for the device, and steps it in 233 | // the direction (set by the dSPIN_FWD and dSPIN_REV constants) imposed by the call 234 | // of this function. Motion commands (dSPIN_RUN, dSPIN_MOVE, etc) will cause the device 235 | // to exit step clocking mode. 236 | void L64XX::Step_Clock(const uint8_t dir) { 237 | Xfer(dSPIN_STEP_CLOCK | dir); 238 | } 239 | 240 | // dSPIN_MOVE will send the motor n_step steps (size based on step mode) in the 241 | // direction imposed by dir (dSPIN_FWD or dSPIN_REV constants may be used). The motor 242 | // will accelerate according the acceleration and deceleration curves, and 243 | // will run at L6470_MAX_SPEED. Stepping mode will adhere to L6470_FS_SPD value, as well. 244 | void L64XX::move(const long n_step) { 245 | const uint8_t dir = n_step >= 0 ? dSPIN_FWD : dSPIN_REV; 246 | Xfer(dSPIN_MOVE | dir); // Set direction 247 | long n_stepABS = abs(n_step); 248 | if (n_stepABS > 0x3FFFFF) n_stepABS = 0x3FFFFF; 249 | Xfer(uint8_t(n_stepABS >> 16)); 250 | Xfer(uint8_t(n_stepABS >> 8)); 251 | Xfer(uint8_t(n_stepABS)); 252 | } 253 | 254 | // dSPIN_GOTO operates much like dSPIN_MOVE, except it produces absolute motion instead 255 | // of relative motion. The motor will be moved to the indicated position 256 | // in the shortest possible fashion. 257 | void L64XX::goTo(long pos) { 258 | Xfer(dSPIN_GOTO); 259 | if (pos > 0x3FFFFF) pos = 0x3FFFFF; 260 | Xfer(uint8_t(pos >> 16)); 261 | Xfer(uint8_t(pos >> 8)); 262 | Xfer(uint8_t(pos)); 263 | } 264 | 265 | // Same as dSPIN_GOTO, but with user constrained rotational direction. 266 | void L64XX::goTo_DIR(const uint8_t dir, long pos) { 267 | Xfer(dSPIN_GOTO_DIR | dir); 268 | if (pos > 0x3FFFFF) pos = 0x3FFFFF; 269 | Xfer(uint8_t(pos >> 16)); 270 | Xfer(uint8_t(pos >> 8)); 271 | Xfer(uint8_t(pos)); 272 | } 273 | 274 | // GoUntil will set the motor running with direction dir (dSPIN_REV or 275 | // dSPIN_FWD) until a falling edge is detected on the SW pin. Depending 276 | // on bit SW_MODE in L64XX_CONFIG, either a hard stop or a soft stop is 277 | // performed at the falling edge, and depending on the value of 278 | // act (either RESET or COPY) the value in the L6470_ABS_POS register is 279 | // either RESET to 0 or COPY-ed into the L6470_MARK register. 280 | void L64XX::goUntil(const uint8_t act, const uint8_t dir, uint32_t spd) { 281 | Xfer(dSPIN_GO_UNTIL | act | dir); 282 | if (spd > 0x3FFFFF) spd = 0x3FFFFF; 283 | Xfer(uint8_t(spd >> 16)); 284 | Xfer(uint8_t(spd >> 8)); 285 | Xfer(uint8_t(spd)); 286 | } 287 | 288 | // Similar in nature to GoUntil, ReleaseSW produces motion at the 289 | // higher of two speeds: the value in L6470_MIN_SPEED or 5 steps/s. 290 | // The motor continues to run at this speed until a rising edge 291 | // is detected on the switch input, then a hard stop is performed 292 | // and the L6470_ABS_POS register is either COPY-ed into L6470_MARK or RESET to 293 | // 0, depending on whether RESET or COPY was passed to the function 294 | // for act. 295 | void L64XX::releaseSW(const uint8_t act, const uint8_t dir) { 296 | Xfer(dSPIN_RELEASE_SW | act | dir); 297 | } 298 | 299 | // GoHome is equivalent to GoTo(0), but requires less time to send. 300 | // Note that no direction is provided; motion occurs through shortest 301 | // path. If a direction is required, use GoTo_DIR(). 302 | void L64XX::goHome() { Xfer(dSPIN_GO_HOME); } 303 | 304 | // GoMark is equivalent to GoTo(L6470_MARK), but requires less time to send. 305 | // Note that no direction is provided; motion occurs through shortest 306 | // path. If a direction is required, use GoTo_DIR(). 307 | void L64XX::goMark() { Xfer(dSPIN_GO_MARK); } 308 | 309 | void L64XX::setMark(long value) { 310 | Xfer(L6470_MARK); 311 | if (value > 0x3FFFFF) value = 0x3FFFFF; 312 | if (value < -0x3FFFFF) value = -0x3FFFFF; 313 | Xfer(uint8_t(value >> 16)); 314 | Xfer(uint8_t(value >> 8)); 315 | Xfer(uint8_t(value)); 316 | } 317 | 318 | void L64XX::setMark() { 319 | long value = getPos(); 320 | Xfer(L6470_MARK); 321 | if (value > 0x3FFFFF) value = 0x3FFFFF; 322 | if (value < -0x3FFFFF) value = -0x3FFFFF; 323 | Xfer(uint8_t(value >> 16)); 324 | Xfer(uint8_t(value >> 8)); 325 | Xfer(uint8_t(value)); 326 | } 327 | 328 | // Sets the L6470_ABS_POS register to 0, effectively declaring the current 329 | // position to be "HOME". 330 | void L64XX::setAsHome() { Xfer(dSPIN_RESET_POS); } 331 | 332 | // Reset device to power up conditions. Equivalent to toggling the STBY 333 | // pin or cycling power. 334 | void L64XX::resetDev() { Xfer(dSPIN_RESET_DEVICE); } 335 | 336 | // Bring the motor to a halt using the deceleration curve. 337 | void L64XX::softStop() { Xfer(dSPIN_SOFT_STOP); } 338 | 339 | // Stop the motor right away. No deceleration. 340 | void L64XX::hardStop() { Xfer(dSPIN_HARD_STOP); } 341 | 342 | // Decelerate the motor and disengage 343 | void L64XX::softFree() { Xfer(dSPIN_SOFT_HIZ); } 344 | 345 | // Disengage the motor immediately with no deceleration. 346 | void L64XX::free() { Xfer(dSPIN_HARD_HIZ); } 347 | 348 | // Fetch and return the 16-bit value in the L64XX_STATUS register. Resets 349 | // any warning flags and exits any error states. Using GetParam() 350 | // to read L64XX_STATUS does not clear these values. 351 | int16_t L64XX::getStatus() { 352 | Xfer(dSPIN_GET_STATUS); 353 | return Xfer(0) << 8 | Xfer(0); 354 | } 355 | 356 | // The value in the L6470_ACC register is [(steps/s/s)*(tick^2)]/(2^-40) where tick is 357 | // 250ns (datasheet value)- 0x08A on boot. 358 | // Multiply desired steps/s/s by .137438 to get an appropriate value for this register. 359 | // This is a 12-bit value, so we need to make sure the value is at or below 0xFFF. 360 | uint32_t L64XX::AccCalc(const float stepsPerSecPerSec) { 361 | uint32_t temp = (uint32_t)(stepsPerSecPerSec * 0.137438); 362 | return temp < 0x00000FFF ? temp : 0x00000FFF; 363 | } 364 | 365 | uint32_t L64XX::DecCalc(float stepsPerSecPerSec) { 366 | // The calculation for L6470_DEC is the same as for L6470_ACC. Value is 0x08A on boot. 367 | // This is a 12-bit value, so we need to make sure the value is at or below 0xFFF. 368 | uint32_t temp = (uint32_t)(stepsPerSecPerSec * 0.137438); 369 | return temp < 0x00000FFF ? temp : 0x00000FFF; 370 | } 371 | 372 | uint32_t L64XX::MaxSpdCalc(const float stepsPerSec) { 373 | // The value in the MAX_SPD register is [(steps/s)*(tick)]/(2^-18) where tick is 374 | // 250ns (datasheet value)- 0x041 on boot. 375 | // Multiply desired steps/s by .065536 to get an appropriate value for this register 376 | // This is a 10-bit value, so we need to make sure it remains at or below 0x3FF 377 | uint32_t temp = (uint32_t)(stepsPerSec * .065536); 378 | return temp < 0x000003FF ? temp : 0x000003FF; 379 | } 380 | 381 | uint32_t L64XX::MinSpdCalc(const float stepsPerSec) { 382 | // The value in the MIN_SPD register is [(steps/s)*(tick)]/(2^-24) where tick is 383 | // 250ns (datasheet value)- 0x000 on boot. 384 | // Multiply desired steps/s by 4.1943 to get an appropriate value for this register 385 | // This is a 12-bit value, so we need to make sure the value is at or below 0xFFF. 386 | uint32_t temp = (uint32_t)(stepsPerSec * 4.1943); 387 | return temp < 0x00000FFF ? temp : 0x00000FFF; 388 | } 389 | 390 | uint32_t L64XX::FSCalc(const float stepsPerSec) { 391 | // The value in the L6470_FS_SPD register is ([(steps/s)*(tick)]/(2^-18))-0.5 where tick is 392 | // 250ns (datasheet value)- 0x027 on boot. 393 | // Multiply desired steps/s by .065536 and subtract .5 to get an appropriate value for this register 394 | // This is a 10-bit value, so we need to make sure the value is at or below 0x3FF. 395 | uint32_t temp = (uint32_t)(stepsPerSec * .065536 - .5); 396 | return temp < 0x000003FF ? temp : 0x000003FF; 397 | } 398 | 399 | uint32_t L64XX::IntSpdCalc(const float stepsPerSec) { 400 | // The value in the L6470_INT_SPD register is [(steps/s)*(tick)]/(2^-24) where tick is 401 | // 250ns (datasheet value)- 0x408 on boot. 402 | // Multiply desired steps/s by 4.1943 to get an appropriate value for this register 403 | // This is a 14-bit value, so we need to make sure the value is at or below 0x3FFF. 404 | uint32_t temp = (uint32_t)(stepsPerSec * 4.1943); 405 | return temp < 0x00003FFF ? temp : 0x00003FFF; 406 | } 407 | 408 | uint32_t L64XX::SpdCalc(const float stepsPerSec) { 409 | // When issuing dSPIN_RUN command, the 20-bit speed is [(steps/s)*(tick)]/(2^-28) where tick is 410 | // 250ns (datasheet value). 411 | // Multiply desired steps/s by 67.106 to get an appropriate value for this register 412 | // This is a 20-bit value, so we need to make sure the value is at or below 0xFFFFF. 413 | uint32_t temp = (uint32_t)(stepsPerSec * 67.106); 414 | return temp < 0x000FFFFF ? temp : 0x000FFFFF; 415 | } 416 | 417 | uint32_t L64XX::Param(uint32_t value, const uint8_t bit_len) { 418 | // Generalization of the subsections of the register read/write functionality. 419 | // We want the end user to just write the value without worrying about length, 420 | // so we pass a bit length parameter from the calling function. 421 | const uint8_t uint8_t_len = (bit_len + 7) / 8; 422 | // Ensure the value has no spurious bits set, and apply limit. 423 | uint32_t mask = 0xFFFFFFFF >> (32 - bit_len); 424 | if (value > mask) value = mask; 425 | // The following three if statements handle the various possible uint8_t length 426 | // transfers- it'll be no less than 1 but no more than 3 uint8_ts of data. 427 | // L64XX::Xfer() sends a uint8_t out through SPI and returns a uint8_t received 428 | // over SPI- when calling it, we typecast a shifted version of the masked 429 | // value, then we shift the received value back by the same amount and 430 | // store it until return time. 431 | uint32_t ret_val = 0; 432 | switch (uint8_t_len) { 433 | case 3: ret_val = long(Xfer(uint8_t(value >> 16))) << 16; 434 | case 2: ret_val |= long(Xfer(uint8_t(value >> 8))) << 8; 435 | case 1: ret_val |= Xfer(uint8_t(value)); 436 | default: break; 437 | } 438 | //Serial.println(ret_val, HEX); 439 | 440 | // Return the received values. Mask off any unnecessary bits, just for 441 | // the sake of thoroughness- we don't EXPECT to see anything outside 442 | // the bit length range but better to be safe than sorry. 443 | return ret_val & mask; 444 | } 445 | 446 | uint8_t L64XX::Xfer(uint8_t data) { 447 | 448 | if (pin_SCK < 0) { // External SPI 449 | return uint8_t( 450 | position ? helper.transfer(data, pin_SS, position) // ... in a chain 451 | : helper.transfer(data, pin_SS) // ... not chained 452 | ); 453 | } 454 | 455 | // if pin_SCK is set use internal soft SPI. 456 | 457 | if (position == 0) { // Internal soft SPI, not in a chain 458 | if (pin_SS >= 0) digitalWrite(pin_SS, LOW); // Allow external code to control SS_PIN 459 | uint8_t bits = 8; 460 | do { 461 | digitalWrite(pin_SCK, LOW); 462 | digitalWrite(pin_MOSI, data & 0x80); 463 | delay(0); // 10 cycles @ 84mhz 464 | digitalWrite(pin_SCK, HIGH); 465 | data <<= 1; // little setup time 466 | data |= (digitalRead(pin_MISO) != 0); 467 | } while (--bits); 468 | delay(0); // 10 cycles @ 84mhz 469 | if (pin_SS >= 0) digitalWrite(pin_SS, HIGH); 470 | return data; 471 | } 472 | else { // internal soft SPI SPI, and in a chain 473 | #define CMD_NOP 0 474 | uint8_t out_data = 0; 475 | uint8_t return_data = 0; 476 | digitalWrite(pin_SS, LOW); 477 | for (uint8_t i = chain[0]; i >= 1; i--) { 478 | out_data = (chain[i] == position ? data : CMD_NOP); 479 | uint8_t bits = 8; 480 | do { 481 | digitalWrite(pin_SCK, LOW); 482 | digitalWrite(pin_MOSI, out_data & 0x80); 483 | delay(0); // 10 cycles @ 84mhz 484 | digitalWrite(pin_SCK, HIGH); 485 | out_data <<= 1; // little setup time 486 | out_data |= (digitalRead(pin_MISO) != 0); 487 | } while (--bits); 488 | delay(0); // 10 cycles @ 84mhz 489 | if (chain[i] == position) return_data = out_data; 490 | } 491 | digitalWrite(pin_SS, HIGH); 492 | return return_data; 493 | } 494 | return 0; // never executed but prevents a compiler warning 495 | } 496 | 497 | void L64XX::SetParam(const uint8_t param, const uint32_t value) { 498 | Xfer(dSPIN_SET_PARAM | param); 499 | ParamHandler(param, value); 500 | } 501 | 502 | // Read from the various registers in the dSPIN chip. 503 | uint32_t L64XX::GetParam(const uint8_t param) { 504 | Xfer(dSPIN_GET_PARAM | param); 505 | return ParamHandler(param, 0); 506 | } 507 | 508 | long L64XX::convert(uint32_t val) { 509 | // Convert 22bit 2s comp to signed long 510 | const int16_t MSB = val >> 21; 511 | val >>= 11; 512 | val <<= 11; 513 | if (MSB == 1) val |= 0b11111111111000000000000000000000; 514 | return val; 515 | } 516 | 517 | // Much of the functionality between "get parameter" and "set parameter" is 518 | // very similar, so we deal with that by putting all of it in one function 519 | // here to save memory space and simplify the program. 520 | 521 | // ParamHandler() does not list the powerSTEP01 current mode registers because 522 | // these registers are the same length as their voltage mode counterparts. 523 | 524 | uint32_t L64XX::ParamHandler(const uint8_t param, const uint32_t value) { 525 | // This switch structure handles the appropriate action for each register. 526 | // This is necessary since not all registers are of the same length, either 527 | // bit-wise or uint8_t-wise, so we want to make sure we mask out any spurious 528 | // bits and do the right number of transfers. That is handled by the dSPIN_Param() 529 | // function, in most cases, but for 1-uint8_t or smaller transfers, we call 530 | // Xfer() directly. 531 | 532 | // These defines help handle the register address differences between L6470, L6480 & powerSTEP01 533 | #define L64XX_CONFIG_A 0x18 // L6470: L64XX_CONFIG, L6480 & powerSTEP01: L6470_GATECFG1 534 | #define L64XX_STATUS_A 0x19 // L6470: L64XX_STATUS, L6480 & powerSTEP01: L6470_GATECFG2 535 | #define L64XX_CONFIG_B 0x1A // L6480 & powerSTEP01: L64XX_CONFIG 536 | #define L64XX_STATUS_B 0x1B // L6480 & powerSTEP01: L64XX_STATUS 537 | 538 | switch (param) { 539 | // L6470_ABS_POS is the current absolute offset from home. It is a 22 bit number expressed 540 | // in two's complement. At power up, this value is 0. It cannot be written when 541 | // the motor is running, but at any other time, it can be updated to change the 542 | // interpreted position of the motor. 543 | case L6470_ABS_POS: return Param(value, 22); 544 | 545 | // L6470_EL_POS is the current electrical position in the step generation cycle. It can 546 | // be set when the motor is not in motion. Value is 0 on power up. 547 | case L6470_EL_POS: return Param(value, 9); 548 | 549 | // L6470_MARK is a second position other than 0 that the motor can be told to go to. As 550 | // with L6470_ABS_POS, it is 22-bit two's complement. Value is 0 on power up. 551 | case L6470_MARK: return Param(value, 22); 552 | 553 | // L6470_SPEED contains information about the current speed. It is read-only. It does 554 | // NOT provide direction information. 555 | case L6470_SPEED: return Param(0, 20); 556 | 557 | // L6470_ACC and L6470_DEC set the acceleration and deceleration rates. Set L6470_ACC to 0xFFF 558 | // to get infinite acceleration/decelaeration- there is no way to get infinite 559 | // deceleration w/o infinite acceleration (except the HARD STOP command). 560 | // Cannot be written while motor is running. Both default to 0x08A on power up. 561 | // AccCalc() and DecCalc() functions exist to convert steps/s/s values into 562 | // 12-bit values for these two registers. 563 | case L6470_ACC: 564 | case L6470_DEC: return Param(value, 12); 565 | 566 | // L6470_MAX_SPEED is just what it says- any command which attempts to set the speed 567 | // of the motor above this value will simply cause the motor to turn at this 568 | // speed. Value is 0x041 on power up. 569 | // MaxSpdCalc() function exists to convert steps/s value into a 10-bit value 570 | // for this register. 571 | case L6470_MAX_SPEED: return Param(value, 10); 572 | 573 | // L6470_MIN_SPEED controls two things- the activation of the low-speed optimization 574 | // feature and the lowest speed the motor will be allowed to operate at. LSPD_OPT 575 | // is the 13th bit, and when it is set, the minimum allowed speed is automatically 576 | // set to zero. This value is 0 on startup. 577 | // MinSpdCalc() function exists to convert steps/s value into a 12-bit value for this 578 | // register. SetLowSpeedOpt() function exists to enable/disable the optimization feature. 579 | case L6470_MIN_SPEED: return Param(value, 12); 580 | 581 | // L6470_FS_SPD register contains a threshold value above which microstepping is disabled 582 | // and the dSPIN operates in full-step mode. Defaults to 0x027 on power up. 583 | // FSCalc() function exists to convert steps/s value into 10-bit integer for this 584 | // register. 585 | case L6470_FS_SPD: return Param(value, 10); 586 | 587 | // KVAL is the maximum voltage of the PWM outputs. These 8-bit values are ratiometric 588 | // representations: 255 for full output voltage, 128 for half, etc. Default is 0x29. 589 | // The implications of different KVAL settings is too complex to dig into here, but 590 | // it will usually work to max the value for dSPIN_RUN, L6470_ACC, and L6470_DEC. Maxing the value for 591 | // HOLD may result in excessive power dissipation when the motor is not running. 592 | case L6470_KVAL_HOLD: return Xfer(uint8_t(value)); 593 | case L6470_KVAL_RUN: return Xfer(uint8_t(value)); 594 | case L6470_KVAL_ACC: return Xfer(uint8_t(value)); 595 | case L6470_KVAL_DEC: return Xfer(uint8_t(value)); 596 | 597 | // L6470_INT_SPD, L6470_ST_SLP, L6470_FN_SLP_ACC and L6470_FN_SLP_DEC are all related to the back EMF 598 | // compensation functionality. Please see the datasheet for details of this 599 | // function- it is too complex to discuss here. Default values seem to work 600 | // well enough. 601 | case L6470_INT_SPD: return Param(value, 14); 602 | case L6470_ST_SLP: return Xfer(uint8_t(value)); 603 | case L6470_FN_SLP_ACC: return Xfer(uint8_t(value)); 604 | case L6470_FN_SLP_DEC: return Xfer(uint8_t(value)); 605 | 606 | // L6470_K_THERM is motor winding thermal drift compensation. Please see the datasheet 607 | // for full details on operation- the default value should be okay for most users. 608 | case L6470_K_THERM: return Xfer(uint8_t(value) & 0x0F); 609 | 610 | // L6470_ADC_OUT is a read-only register containing the result of the ADC measurements. 611 | // This is less useful than it sounds; see the datasheet for more information. 612 | case L6470_ADC_OUT: return Xfer(0); 613 | 614 | // Set the overcurrent threshold. Ranges from 375mA to 6A in steps of 375mA. 615 | // A set of defined constants is provided for the user's convenience. Default 616 | // value is 3.375A- 0x08. This is a 4-bit value. 617 | case L6470_OCD_TH: return Xfer(uint8_t(value) & OCD_TH_MAX); 618 | // Stall current threshold. Defaults to 0x40, or 2.03A. Value is from 31.25mA to 619 | // 4A in 31.25mA steps. This is a 7-bit value. 620 | case L6470_STALL_TH: return Xfer(uint8_t(value) & STALL_TH_MAX); 621 | 622 | // L6470_STEP_MODE controls the microstepping settings, as well as the generation of an 623 | // output signal from the dSPIN. Bits 2:0 control the number of microsteps per 624 | // step the part will generate. Bit 7 controls whether the BUSY/SYNC pin outputs 625 | // a BUSY signal or a step synchronization signal. Bits 6:4 control the frequency 626 | // of the output signal relative to the full-step frequency; see datasheet for 627 | // that relationship as it is too complex to reproduce here. 628 | // Most likely, only the microsteps per step value will be needed; there is a set 629 | // of constants provided for ease of use of these values. 630 | case L6470_STEP_MODE: return Xfer(uint8_t(value)); 631 | 632 | // L6470_ALARM_EN controls which alarms will cause the FLAG pin to fall. A set of constants 633 | // is provided to make this easy to interpret. By default, ALL alarms will trigger the 634 | // FLAG pin. 635 | case L6470_ALARM_EN: return Xfer(uint8_t(value)); 636 | 637 | // GATECFG1 & GATECFG2 - L6480 and powerSTEP01 only, these registers control the slew rate and off time of the 638 | // bridge power FETs. 639 | 640 | // L64XX_CONFIG contains some assorted configuration bits and fields. A fairly comprehensive 641 | // set of reasonably self-explanatory constants is provided, but users should refer 642 | // to the datasheet before modifying the contents of this register to be certain they 643 | // understand the implications of their modifications. Value on boot is 0x2E88; this 644 | // can be a useful way to verify proper start up and operation of the dSPIN chip. 645 | case L64XX_CONFIG_A: if (L6470_status_layout) return Param(value, 16); // L6470 CONFIG register 646 | else return Param(uint16_t(value & 0x07ff), 11); // L6480 & powerSTEP01 GATECFG1 register 647 | case L64XX_CONFIG_B: if (L6470_status_layout) return Param(value, 16); // L6480 & powerSTEP01 CONFIG register 648 | 649 | 650 | // L64XX_STATUS contains read-only information about the current condition of the chip. A 651 | // comprehensive set of constants for masking and testing this register is provided, but 652 | // users should refer to the datasheet to ensure that they fully understand each one of 653 | // the bits in the register. 654 | case L64XX_STATUS_A: if (L6470_status_layout) return Param(value, 16); // L46470 STATUS register 655 | else return Xfer(uint8_t(value)); // L6480 & powerSTEP01 GATECFG2 register 656 | case L64XX_STATUS_B: return Param(0, 16); // L6480 & powerSTEP01 STATUS register 657 | 658 | } 659 | return Xfer(uint8_t(value)); 660 | } 661 | -------------------------------------------------------------------------------- /src/L6470.h: -------------------------------------------------------------------------------- 1 | //////////////////////////////////////////////////////////////////// 2 | // // 3 | // ORIGINAL CODE 12 Dec 2011 Mike Hord, SparkFun Electronics // 4 | // // 5 | // LIBRARY Created by Adam Meyer (@ameyer) of bildr 18 Aug 2012 // 6 | // Released as MIT license // 7 | // // 8 | // Changes: // 9 | // Scott Lahteine (@thinkyhead) - Cleanup 06 Mar 2018 // 10 | // Bob Kuhn (@bob-the-kuhn) - Chain / SPI 06 Jan 2019 // 11 | // Scott Lahteine (@thinkyhead) - L64XXHelper 01 Mar 2019 // 12 | // // 13 | //////////////////////////////////////////////////////////////////// 14 | 15 | #pragma once 16 | 17 | #ifndef _L6470_H_ 18 | #define _L6470_H_ 19 | 20 | /** 21 | * This library is aimed at the L6470 but it also can be used 22 | * for other L647x devices, L648x devices and the powerSTEP01. 23 | * 24 | * Page numbers are for the L6470 data sheet. 25 | */ 26 | 27 | #include 28 | 29 | #define L6470_LIBRARY_VERSION 0x000700 30 | 31 | //#define SCK 10 // Wire this to the CSN pin 32 | //#define MOSI 11 // Wire this to the SDI pin 33 | //#define MISO 12 // Wire this to the SDO pin 34 | //#define SS_PIN 16 // Wire this to the CK pin 35 | #define PIN_RESET 6 // Wire this to the STBY line 36 | #define PIN_BUSYN 4 // Wire this to the BSYN line 37 | 38 | #define STAT1 14 // Hooked to an LED on the test jig 39 | #define STAT2 15 // Hooked to an LED on the test jig 40 | #define SWITCH 8 // Hooked to the switch input and a pB on the jig 41 | 42 | // Constant definitions for L6470 overcurrent thresholds. Write these values to 43 | // register dSPIN_OCD_TH to set the level at which an overcurrent even occurs. 44 | #define OCD_TH_375mA 0x00 45 | #define OCD_TH_750mA 0x01 46 | #define OCD_TH_1125mA 0x02 47 | #define OCD_TH_1500mA 0x03 48 | #define OCD_TH_1875mA 0x04 49 | #define OCD_TH_2250mA 0x05 50 | #define OCD_TH_2625mA 0x06 51 | #define OCD_TH_3000mA 0x07 52 | #define OCD_TH_3375mA 0x08 53 | #define OCD_TH_3750mA 0x09 54 | #define OCD_TH_4125mA 0x0A 55 | #define OCD_TH_4500mA 0x0B 56 | #define OCD_TH_4875mA 0x0C 57 | #define OCD_TH_5250mA 0x0D 58 | #define OCD_TH_5625mA 0x0E 59 | #define OCD_TH_6000mA 0x0F 60 | 61 | // L6470_STEP_MODE option values. 62 | // First comes the "microsteps per step" options... 63 | #define STEP_MODE_STEP_SEL 0x07 // Mask for these bits only. 64 | #define STEP_SEL_1 0x00 65 | #define STEP_SEL_1_2 0x01 66 | #define STEP_SEL_1_4 0x02 67 | #define STEP_SEL_1_8 0x03 68 | #define STEP_SEL_1_16 0x04 69 | #define STEP_SEL_1_32 0x05 70 | #define STEP_SEL_1_64 0x06 71 | #define STEP_SEL_1_128 0x07 72 | 73 | // ...next, define the SYNC_EN bit. When set, the BUSYN pin will instead 74 | // output a clock related to the full-step frequency as defined by the 75 | // SYNC_SEL bits below. 76 | #define STEP_MODE_SYNC_EN 0x80 // Mask for this bit 77 | #define SYNC_EN 0x80 78 | 79 | // ...last, define the SYNC_SEL modes. The clock output is defined by 80 | // the full-step frequency and the value in these bits- see the datasheet 81 | // for a matrix describing that relationship (page 46). 82 | #define STEP_MODE_SYNC_SEL 0x70 83 | #define SYNC_SEL_1_2 0x00 84 | #define SYNC_SEL_1 0x10 85 | #define SYNC_SEL_2 0x20 86 | #define SYNC_SEL_4 0x30 87 | #define SYNC_SEL_8 0x40 88 | #define SYNC_SEL_16 0x50 89 | #define SYNC_SEL_32 0x60 90 | #define SYNC_SEL_64 0x70 91 | 92 | // Bit names for the L6470_ALARM_EN register. 93 | // Each of these bits defines one potential alarm condition. 94 | // When one of these conditions occurs and the respective bit in L6470_ALARM_EN is set, 95 | // the FLAG pin will go low. The register must be queried to determine which event 96 | // caused the alarm. 97 | #define ALARM_EN_OVERCURRENT 0x01 98 | #define ALARM_EN_THERMAL_SHUTDOWN 0x02 99 | #define ALARM_EN_THERMAL_WARNING 0x04 100 | #define ALARM_EN_UNDER_VOLTAGE 0x08 101 | #define ALARM_EN_STALL_DET_A 0x10 102 | #define ALARM_EN_STALL_DET_B 0x20 103 | #define ALARM_EN_SW_TURN_ON 0x40 104 | #define ALARM_EN_WRONG_NPERF_CMD 0x80 105 | 106 | // L64XX_CONFIG register renames. 107 | 108 | // Oscillator options. 109 | // The dSPIN needs to know what the clock frequency is because it uses that for some 110 | // calculations during operation. 111 | #define CONFIG_OSC_SEL 0x000F // Mask for this bit field. 112 | #define CONFIG_INT_16MHZ 0x0000 // Internal 16MHz, no output 113 | #define CONFIG_INT_16MHZ_OSCOUT_2MHZ 0x0008 // Default; internal 16MHz, 2MHz output 114 | #define CONFIG_INT_16MHZ_OSCOUT_4MHZ 0x0009 // Internal 16MHz, 4MHz output 115 | #define CONFIG_INT_16MHZ_OSCOUT_8MHZ 0x000A // Internal 16MHz, 8MHz output 116 | #define CONFIG_INT_16MHZ_OSCOUT_16MHZ 0x000B // Internal 16MHz, 16MHz output 117 | #define CONFIG_EXT_8MHZ_XTAL_DRIVE 0x0004 // External 8MHz crystal 118 | #define CONFIG_EXT_16MHZ_XTAL_DRIVE 0x0005 // External 16MHz crystal 119 | #define CONFIG_EXT_24MHZ_XTAL_DRIVE 0x0006 // External 24MHz crystal 120 | #define CONFIG_EXT_32MHZ_XTAL_DRIVE 0x0007 // External 32MHz crystal 121 | #define CONFIG_EXT_8MHZ_OSCOUT_INVERT 0x000C // External 8MHz crystal, output inverted 122 | #define CONFIG_EXT_16MHZ_OSCOUT_INVERT 0x000D // External 16MHz crystal, output inverted 123 | #define CONFIG_EXT_24MHZ_OSCOUT_INVERT 0x000E // External 24MHz crystal, output inverted 124 | #define CONFIG_EXT_32MHZ_OSCOUT_INVERT 0x000F // External 32MHz crystal, output inverted 125 | 126 | // Configure the functionality of the external switch input 127 | #define CONFIG_SW_MODE 0x0010 // Mask for this bit. 128 | #define CONFIG_SW_HARD_STOP 0x0000 // Default; hard stop motor on switch. 129 | #define CONFIG_SW_USER 0x0010 // Tie to the GoUntil and ReleaseSW 130 | // commands to provide jog function. 131 | // See page 25 of datasheet. 132 | 133 | // Configure the motor voltage compensation mode (see page 34 of datasheet) 134 | #define CONFIG_EN_VSCOMP 0x0020 // Mask for this bit. 135 | #define CONFIG_VS_COMP_DISABLE 0x0000 // Disable motor voltage compensation. 136 | #define CONFIG_VS_COMP_ENABLE 0x0020 // Enable motor voltage compensation. 137 | 138 | // Configure overcurrent detection event handling 139 | #define CONFIG_OC_SD 0x0080 // Mask for this bit. 140 | #define CONFIG_OC_SD_DISABLE 0x0000 // Bridges do NOT shutdown on OC detect 141 | #define CONFIG_OC_SD_ENABLE 0x0080 // Bridges shutdown on OC detect 142 | 143 | // Configure the slew rate of the power bridge output 144 | // L6470 145 | #define CONFIG_POW_SR 0x0300 // Mask for this bit field. 146 | #define CONFIG_POW_SR_BIT 8 // starting bit of this field 147 | #define CONFIG_SR_320V_us 0x0000 // 320V/us 148 | #define CONFIG_SR_75V_us 0x0100 // 75V/us 149 | #define CONFIG_SR_110V_us 0x0200 // 110V/us 150 | #define CONFIG_SR_260V_us 0x0300 // 260V/us 151 | 152 | // L6480 & powerSTEP01 153 | #define CONFIG1_SR 0x00FF // Mask for this bit field. 154 | #define CONFIG1_SR_220V_us 0x006C 155 | #define CONFIG1_SR_400V_us 0x0087 156 | #define CONFIG1_SR_520V_us 0x00A6 157 | #define CONFIG1_SR_980V_us 0x00E2 158 | #define CONFIG2_SR_220V_us 0x10 159 | #define CONFIG2_SR_400V_us 0x10 160 | #define CONFIG2_SR_520V_us 0x10 161 | #define CONFIG2_SR_980V_us 0x30 162 | 163 | 164 | // L6480 & powerSTEP01 VCC setting 165 | #define PWR_VCC_7_5V 0 166 | #define PWR_VCC_15V 0x0100 167 | 168 | // Integer divisors for PWM sinewave generation 169 | // See page 32 of the datasheet for more information on this. 170 | #define CONFIG_F_PWM_DEC 0x1C00 // mask for this bit field 171 | #define CONFIG_PWM_MUL_0_625 (0x00<<10) 172 | #define CONFIG_PWM_MUL_0_75 (0x01<<10) 173 | #define CONFIG_PWM_MUL_0_875 (0x02<<10) 174 | #define CONFIG_PWM_MUL_1 (0x03<<10) 175 | #define CONFIG_PWM_MUL_1_25 (0x04<<10) 176 | #define CONFIG_PWM_MUL_1_5 (0x05<<10) 177 | #define CONFIG_PWM_MUL_1_75 (0x06<<10) 178 | #define CONFIG_PWM_MUL_2 (0x07<<10) 179 | 180 | // Multiplier for the PWM sinewave frequency 181 | #define CONFIG_F_PWM_INT 0xE000 // mask for this bit field. 182 | #define CONFIG_PWM_DIV_1 (0x00<<13) 183 | #define CONFIG_PWM_DIV_2 (0x01<<13) 184 | #define CONFIG_PWM_DIV_3 (0x02<<13) 185 | #define CONFIG_PWM_DIV_4 (0x03<<13) 186 | #define CONFIG_PWM_DIV_5 (0x04<<13) 187 | #define CONFIG_PWM_DIV_6 (0x05<<13) 188 | #define CONFIG_PWM_DIV_7 (0x06<<13) 189 | 190 | // Status register bit renames- read-only bits conferring information about the 191 | // device to the user. 192 | #define STATUS_HIZ 0x0001 // high when bridges are in HiZ mode 193 | #define STATUS_BUSY 0x0002 // mirrors BUSY pin 194 | #define STATUS_SW_F 0x0004 // low when switch open, high when closed 195 | #define STATUS_SW_EVN 0x0008 // active high, set on switch falling edge, 196 | // cleared by reading L64XX_STATUS 197 | #define STATUS_DIR 0x0010 // Indicates current motor direction. 198 | // High is dSPIN_FWD, Low is dSPIN_REV. 199 | 200 | 201 | // Status register motor status field 202 | #define STATUS_MOT_STATUS 0x0060 // field mask 203 | #define STATUS_MOT_STATUS_STOPPED (0x0000<<5) // Motor stopped 204 | #define STATUS_MOT_STATUS_ACCELERATION (0x0001<<5) // Motor accelerating 205 | #define STATUS_MOT_STATUS_DECELERATION (0x0002<<5) // Motor decelerating 206 | #define STATUS_MOT_STATUS_CONST_SPD (0x0003<<5) // Motor at constant speed 207 | 208 | // Register address redefines. 209 | // See the Param_Handler() function for more info about these. 210 | #define L6470_ABS_POS 0x01 211 | #define L6470_EL_POS 0x02 212 | #define L6470_MARK 0x03 213 | #define L6470_SPEED 0x04 214 | #define L6470_ACC 0x05 215 | #define L6470_DEC 0x06 216 | #define L6470_MAX_SPEED 0x07 217 | #define L6470_MIN_SPEED 0x08 218 | #define L6470_FS_SPD 0x15 219 | #define L6470_KVAL_HOLD 0x09 220 | #define L6470_KVAL_RUN 0x0A 221 | #define L6470_KVAL_ACC 0x0B 222 | #define L6470_KVAL_DEC 0x0C 223 | #define L6470_INT_SPD 0x0D 224 | #define L6470_ST_SLP 0x0E 225 | #define L6470_FN_SLP_ACC 0x0F 226 | #define L6470_FN_SLP_DEC 0x10 227 | #define L6470_K_THERM 0x11 228 | #define L6470_ADC_OUT 0x12 229 | #define L6470_OCD_TH 0x13 230 | #define L6470_STALL_TH 0x14 231 | #define L6470_STEP_MODE 0x16 232 | #define L6470_ALARM_EN 0x17 233 | #define L6470_GATECFG1 0x18 // L6480 & powerSTEP01 only 234 | #define L6470_GATECFG2 0x19 // L6480 & powerSTEP01 only 235 | 236 | #define PWR_TVAL_HOLD 0X09 // powerSTEP01 current mode register names 237 | #define PWR_TVAL_RUN 0X0A 238 | #define PWR_TVAL_ACC 0X0B 239 | #define PWR_TVAL_DEC 0X0C 240 | #define PWR_T_FAST 0X0E 241 | #define PWR_TON_MIN 0X0F 242 | #define PWR_TOFF_MIN 0X10 243 | 244 | // dSPIN commands 245 | #define dSPIN_NOP 0x00 246 | #define dSPIN_SET_PARAM 0x00 247 | #define dSPIN_GET_PARAM 0x20 248 | #define dSPIN_RUN 0x50 249 | #define dSPIN_STEP_CLOCK 0x58 250 | #define dSPIN_MOVE 0x40 251 | #define dSPIN_GOTO 0x60 252 | #define dSPIN_GOTO_DIR 0x68 253 | #define dSPIN_GO_UNTIL 0x82 254 | #define dSPIN_RELEASE_SW 0x92 255 | #define dSPIN_GO_HOME 0x70 256 | #define dSPIN_GO_MARK 0x78 257 | #define dSPIN_RESET_POS 0xD8 258 | #define dSPIN_RESET_DEVICE 0xC0 259 | #define dSPIN_SOFT_STOP 0xB0 260 | #define dSPIN_HARD_STOP 0xB8 261 | #define dSPIN_SOFT_HIZ 0xA0 262 | #define dSPIN_HARD_HIZ 0xA8 263 | #define dSPIN_GET_STATUS 0xD0 264 | 265 | // dSPIN direction options 266 | #define dSPIN_FWD 0x01 267 | #define dSPIN_REV 0x00 268 | 269 | // dSPIN action options 270 | #define dSPIN_ACTION_RESET 0x00 271 | #define dSPIN_ACTION_COPY 0x01 272 | 273 | typedef uint16_t _pin_t; 274 | 275 | class L64XXHelper { 276 | protected: 277 | friend class L64XX; 278 | static inline void spi_init() { } 279 | static inline uint8_t transfer(uint8_t data, const _pin_t ss_pin) { } 280 | static inline uint8_t transfer(uint8_t data, const _pin_t ss_pin, const uint8_t chain_position) { } 281 | }; 282 | 283 | extern L64XXHelper nullHelper; 284 | 285 | class L64XX { 286 | public: 287 | _pin_t pin_SS = -1, 288 | pin_SCK = -1, 289 | pin_MOSI = -1, 290 | pin_MISO = -1, 291 | pin_RESET = -1, 292 | pin_BUSYN = -1; 293 | 294 | static uint8_t chain[21]; 295 | // [0] - number of drivers in chain 296 | // [1]... axis index for first device in the chain (closest to MOSI) 297 | 298 | uint8_t axis_index; 299 | uint8_t position = 0; // 0 - not part of a chain 300 | 301 | // This object must be supplied by the client 302 | static L64XXHelper &helper; 303 | 304 | static inline void set_helper(L64XXHelper & _helper) { helper = _helper; } 305 | 306 | void init(); 307 | 308 | inline void init(const _pin_t ss_pin) { pin_SS = ss_pin; } 309 | inline void init(const _pin_t ss_pin, L64XXHelper &_helper) { 310 | pin_SS = ss_pin; 311 | set_helper(_helper); 312 | } 313 | 314 | void set_pins(const _pin_t SCK, const _pin_t MOSI, const _pin_t MISO, const _pin_t RESET, const _pin_t BUSYN); 315 | void set_chain_info(const uint8_t axis_index, const uint8_t position); 316 | 317 | void setMicroSteps(int16_t microSteps); 318 | void setMaxSpeed(const int16_t speed); 319 | void setMinSpeed(const int16_t speed); 320 | void setAcc(const float acceleration); 321 | void setDec(const float deceleration); 322 | void setOverCurrent(float ma_current); 323 | void setThresholdSpeed(const float threshold); 324 | void setStallCurrent(float ma_current); 325 | 326 | uint32_t ParamHandler(const uint8_t param, const uint32_t value); 327 | void SetLowSpeedOpt(boolean enable); 328 | 329 | void run(const uint8_t dir, const float spd); 330 | void Step_Clock(const uint8_t dir); 331 | 332 | void goHome(); 333 | void setAsHome(); 334 | 335 | void goMark(); 336 | void move(const long n_step); 337 | void goTo(long pos); 338 | void goTo_DIR(const uint8_t dir, long pos); 339 | void goUntil(const uint8_t act, const uint8_t dir, uint32_t spd); 340 | 341 | boolean isBusy(); 342 | 343 | void releaseSW(const uint8_t act, const uint8_t dir); 344 | 345 | float getSpeed(); 346 | long getPos(); 347 | void setMark(); 348 | void setMark(long value); 349 | 350 | void resetPos(); 351 | void resetDev(); 352 | void softStop(); 353 | void hardStop(); 354 | void softFree(); 355 | void free(); 356 | int16_t getStatus(); 357 | 358 | void SetParam(const uint8_t param, const uint32_t value); 359 | uint32_t GetParam(const uint8_t param); 360 | 361 | // L6470 placeholders may be overridden by sub-classes 362 | static constexpr uint8_t OCD_TH_MAX = 15; 363 | static constexpr uint8_t STALL_TH_MAX = 127; 364 | static constexpr float OCD_CURRENT_CONSTANT_INV = 375; // mA per count 365 | static constexpr float OCD_CURRENT_CONSTANT = 1.0f / OCD_CURRENT_CONSTANT_INV; // counts per mA 366 | static constexpr float STALL_CURRENT_CONSTANT_INV = 31.25; // mA per count 367 | static constexpr float STALL_CURRENT_CONSTANT = 1.0f / STALL_CURRENT_CONSTANT_INV; // counts per mA 368 | 369 | static constexpr uint8_t L64XX_CONFIG = 0x18; 370 | static constexpr uint8_t L64XX_STATUS = 0x19; 371 | 372 | static constexpr bool L6470_status_layout = true; 373 | static constexpr uint16_t STATUS_NOTPERF_CMD = 0x0080; // Last command not performed. 374 | static constexpr uint16_t STATUS_WRONG_CMD = 0x0100; // Last command not valid. 375 | static constexpr uint16_t STATUS_CMD_ERR = 0x0180; // Command error 376 | static constexpr uint16_t STATUS_UVLO = 0x0200; // Undervoltage lockout is active 377 | static constexpr uint16_t STATUS_TH_WRN = 0x0400; // Thermal warning 378 | static constexpr uint16_t STATUS_TH_SD = 0x0800; // Thermal shutdown 379 | static constexpr uint16_t STATUS_OCD = 0x1000; // Overcurrent detected 380 | static constexpr uint16_t STATUS_STEP_LOSS_A = 0x2000; // Stall detected on A bridge 381 | static constexpr uint16_t STATUS_STEP_LOSS_B = 0x4000; // Stall detected on B bridge 382 | static constexpr uint16_t STATUS_SCK_MOD = 0x8000; // Step clock mode is active 383 | 384 | private: 385 | long convert(uint32_t val); 386 | 387 | uint32_t AccCalc(const float stepsPerSecPerSec); 388 | uint32_t DecCalc(const float stepsPerSecPerSec); 389 | uint32_t MaxSpdCalc(const float stepsPerSec); 390 | uint32_t MinSpdCalc(const float stepsPerSec); 391 | uint32_t FSCalc(const float stepsPerSec); 392 | uint32_t IntSpdCalc(const float stepsPerSec); 393 | uint32_t SpdCalc(const float stepsPerSec); 394 | uint32_t Param(uint32_t value, const uint8_t bit_len); 395 | uint8_t Xfer(uint8_t data); 396 | uint8_t Xfer(uint8_t data, _pin_t ss_pin, uint8_t position); 397 | 398 | static inline void spi_init_noop() { } 399 | static inline uint8_t transfer_noop(uint8_t data, const _pin_t ss_pin) { } 400 | static inline uint8_t chain_transfer_noop(uint8_t data, const _pin_t ss_pin, const uint8_t chain_position) { } 401 | }; 402 | 403 | class L6470 : public L64XX { 404 | public: 405 | L6470(const _pin_t ss_pin) { init(ss_pin); } 406 | L6470(const _pin_t ss_pin, L64XXHelper &_helper) { init(ss_pin, _helper); } 407 | }; 408 | 409 | class L6480_Base : public L64XX { 410 | public: 411 | static constexpr uint8_t L64XX_CONFIG = 0x1A; 412 | static constexpr uint8_t L64XX_STATUS = 0x1B; 413 | 414 | static constexpr bool L6470_status_layout = false; 415 | static constexpr uint16_t STATUS_WRONG_CMD = 0x0080; // Last command not valid. 416 | static constexpr uint16_t STATUS_CMD_ERR = 0x0080; // Command error 417 | static constexpr uint16_t STATUS_UVLO = 0x0200; // Undervoltage lockout is active 418 | static constexpr uint16_t UVLO_ADC = 0x0400; // ADC undervoltage event 419 | static constexpr uint16_t STATUS_TH_WRN = 0x0800; // Thermal warning 420 | static constexpr uint16_t STATUS_TH_SD = 0x1000; // Thermal shutdown 421 | static constexpr uint16_t STATUS_OCD = 0x2000; // Overcurrent detected 422 | static constexpr uint16_t STATUS_STEP_LOSS_A = 0x4000; // Stall detected on A bridge 423 | static constexpr uint16_t STATUS_STEP_LOSS_B = 0x8000; // Stall detected on B bridge 424 | static constexpr uint16_t STATUS_SCK_MOD = 0x0100; // Step clock mode is active 425 | }; 426 | 427 | class L6480 : public L6480_Base { 428 | public: 429 | L6480(const _pin_t ss_pin) { init(ss_pin); } 430 | L6480(const _pin_t ss_pin, L64XXHelper &_helper) { init(ss_pin, _helper); } 431 | 432 | static constexpr uint8_t OCD_TH_MAX = 31; 433 | static constexpr uint8_t STALL_TH_MAX = 31; 434 | static constexpr float OCD_CURRENT_CONSTANT_INV = 31.25; // mA per count 435 | static constexpr float OCD_CURRENT_CONSTANT = 1.0f / OCD_CURRENT_CONSTANT_INV; // counts per mA 436 | static constexpr float STALL_CURRENT_CONSTANT_INV = 31.25; // mA per count 437 | static constexpr float STALL_CURRENT_CONSTANT = 1.0f / STALL_CURRENT_CONSTANT_INV; // counts per mA 438 | }; 439 | 440 | class powerSTEP01 : public L6480_Base { 441 | public: 442 | powerSTEP01(const _pin_t ss_pin) { init(ss_pin); } 443 | powerSTEP01(const _pin_t ss_pin, L64XXHelper &_helper) { init(ss_pin, _helper); } 444 | 445 | static constexpr uint8_t OCD_TH_MAX = 31; 446 | static constexpr uint8_t STALL_TH_MAX = 31; 447 | static constexpr float OCD_CURRENT_CONSTANT = 0.001; // counts per mA (empirically derived for powerSTEP01) 448 | static constexpr float OCD_CURRENT_CONSTANT_INV = 1000; // mA per count (empirically derived for powerSTEP01) 449 | static constexpr float STALL_CURRENT_CONSTANT = 0.005; // counts per mA (empirically derived for powerSTEP01) 450 | static constexpr float STALL_CURRENT_CONSTANT_INV = 200; // mA per count (empirically derived for powerSTEP01) 451 | //static constexpr float POWERSTEP_AVERAGE_RDS = 0.016; // Ohms - L648x use external FETs so this may be user modified 452 | //static constexpr float OCD_CURRENT_CONSTANT = (POWERSTEP_AVERAGE_RDS/0.03125)/1000; // counts per mA (calc per data sheet - definitely wrong) 453 | //static constexpr float OCD_CURRENT_CONSTANT_INV = (1000 * 0.03125)/(POWERSTEP_AVERAGE_RDS); // mA per count (calc per data sheet - definitely wrong) 454 | //static constexpr float STALL_CURRENT_CONSTANT = OCD_CURRENT_CONSTANT; // counts per mA (calc per data sheet - definitely wrong) 455 | //static constexpr float STALL_CURRENT_CONSTANT_INV = OCD_CURRENT_CONSTANT_INV; // mA per count (calc per data sheet - definitely wrong) 456 | }; 457 | 458 | #endif // _L6470_H_ 459 | --------------------------------------------------------------------------------