├── LICENSE ├── Pin.h ├── PinGroup.h ├── README.md ├── SimpleStepper.cpp ├── SimpleStepper.h ├── TimerOne.cpp ├── TimerOne.h ├── config └── known_16bit_timers.h ├── examples └── basic-example │ └── basic-example.ino └── keywords.txt /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Evert Chin 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Pin.h: -------------------------------------------------------------------------------- 1 | /** 2 | @file Pin.h 3 | @author Alec Fenichel 4 | @brief Fast operations on Arduino I/O pins 5 | */ 6 | 7 | 8 | #include "Arduino.h" 9 | 10 | 11 | #pragma once 12 | 13 | 14 | // ################################# Defines ################################# 15 | 16 | #define DDR_HIGH (*_DDR |= _offset) ///< Set the DDR register to HIGH for the pin 17 | #define DDR_TOGGLE (*_DDR ^= _offset) ///< Set the DDR register to the inverse for the pin 18 | #define DDR_LOW (*_DDR &= _ioffset) ///< Set the DDR register to LOW for the pin 19 | 20 | #define PORT_HIGH (*_PORT |= _offset) ///< Set the PORT register to HIGH for the pin 21 | #define PORT_TOGGLE (*_PORT ^= _offset) ///< Set the PORT register to the inverse for the pin 22 | #define PORT_LOW (*_PORT &= _ioffset) ///< Set the PORT register to LOW for the pin 23 | 24 | #define DDR_ON (*_DDR & _offset) ///< Get the DDR register for the pin (HIGH, LOW) with other pins forced to 0 25 | #define DDR_OFF (*_DDR | _ioffset) ///< Get the DDR register for the pin (HIGH, LOW) with other pins forced to 1 26 | 27 | #define PORT_ON (*_PORT & _offset) ///< Get the PORT register for the pin (HIGH, LOW) with other pins forced to 0 28 | #define PORT_OFF (*_PORT | _ioffset) ///< Get the PORT register for the pin (HIGH, LOW) with other pins forced to 1 29 | 30 | #define PIN_ON (*_PIN & _offset) ///< Get the PIN register for the pin (HIGH, LOW) with other pins forced to 0 31 | #define PIN_OFF (*_PIN | _ioffset) ///< Get the PIN register for the pin (HIGH, LOW) with other pins forced to 1 32 | 33 | 34 | /** 35 | Class for fast operations on Arduino I/O pins 36 | 37 | @author Alec Fenichel 38 | */ 39 | class Pin { 40 | public: 41 | // ################################# Constructors ################################# 42 | 43 | /** 44 | Arduino supported board constructor 45 | 46 | @param number pin number written on board 47 | */ 48 | Pin(uint8_t number) { 49 | _number = number; 50 | _offset = digitalPinToBitMask(_number); 51 | _ioffset = ~_offset; 52 | _timer = digitalPinToTimer(_number); 53 | _PIN = portInputRegister(digitalPinToPort(_number)); 54 | _PORT = portOutputRegister(digitalPinToPort(_number)); 55 | _DDR = portModeRegister(digitalPinToPort(_number)); 56 | } 57 | 58 | /** 59 | Custom board constructor 60 | 61 | getAnalogValue() and setDutyCycle(int value) not supported 62 | 63 | @param number pin number written on board 64 | @param offset bit mask used to access pin in registers 65 | @param timer timer for pin 66 | @param PIN input register for pin 67 | @param PORT data register for pin 68 | @param DDR data direction register for pin 69 | */ 70 | Pin(uint8_t number, uint8_t offset, uint8_t timer, volatile uint8_t* PIN, volatile uint8_t* PORT, volatile uint8_t* DDR) { 71 | _number = number; 72 | _offset = offset; 73 | _ioffset = ~_offset; 74 | _timer = timer; 75 | _PIN = PIN; 76 | _PORT = PORT; 77 | _DDR = DDR; 78 | } 79 | 80 | 81 | // ################################# Operators ################################# 82 | 83 | /** 84 | Get the value of the pin from the PIN register 85 | 86 | @return true if the value of the pin is HIGH, false otherwise 87 | */ 88 | operator bool() const { 89 | return bool(PIN_ON); 90 | } 91 | 92 | /** 93 | Set the pin state 94 | 95 | @param state the state of the pin (HIGH, LOW) 96 | */ 97 | Pin& operator =(uint8_t state) { 98 | uint8_t oldSREG = SREG; 99 | cli(); 100 | if (state == LOW) { 101 | PORT_LOW; 102 | } else { 103 | PORT_HIGH; 104 | } 105 | SREG = oldSREG; 106 | 107 | return *this; 108 | } 109 | 110 | 111 | // ################################# Getters ################################# 112 | 113 | /** 114 | Get the pin number 115 | 116 | @return pin number 117 | */ 118 | uint8_t getNumber() { 119 | return _number; 120 | } 121 | 122 | /** 123 | Get the pin offset 124 | 125 | @return pin offset 126 | */ 127 | uint8_t getOffset() { 128 | return _offset; 129 | } 130 | 131 | /** 132 | Get the inverse pin offset 133 | 134 | @return inverse pin offset 135 | */ 136 | uint8_t getInverseOffset() { 137 | return _ioffset; 138 | } 139 | 140 | /** 141 | Get the pin timer 142 | 143 | @return pin timer 144 | */ 145 | uint8_t getTimer() { 146 | return _timer; 147 | } 148 | 149 | /** 150 | Get a pointer to the PIN register 151 | 152 | @return pointer to the PIN register 153 | */ 154 | volatile uint8_t* getPIN() { 155 | return _PIN; 156 | } 157 | 158 | /** 159 | Get a pointer to the PORT register 160 | 161 | @return pointer to the PORT register 162 | */ 163 | volatile uint8_t* getPORT() { 164 | return _PORT; 165 | } 166 | 167 | /** 168 | Get a pointer to the DDR register 169 | 170 | @return pointer to the DDR register 171 | */ 172 | volatile uint8_t* getDDR() { 173 | return _DDR; 174 | } 175 | 176 | /** 177 | Get the mode of the pin from the DDR register 178 | 179 | @return mode of the pin (OUTPUT, INPUT) 180 | */ 181 | uint8_t getMode() { 182 | if (DDR_ON) { 183 | return OUTPUT; 184 | } else { 185 | return INPUT; 186 | } 187 | } 188 | 189 | /** 190 | Get the state of the pin from the PORT register 191 | 192 | @return state of the pin (HIGH, LOW) 193 | */ 194 | uint8_t getState() { 195 | if (PORT_ON) { 196 | return HIGH; 197 | } else { 198 | return LOW; 199 | } 200 | } 201 | 202 | /** 203 | Get the value of the pin from the PIN register 204 | 205 | @return value of the pin (HIGH, LOW) 206 | */ 207 | uint8_t getValue() { 208 | if (PIN_ON) { 209 | return HIGH; 210 | } else { 211 | return LOW; 212 | } 213 | } 214 | 215 | /** 216 | Get the analog value of the pin 217 | 218 | @return analog value of the pin (0-1023) 219 | */ 220 | uint16_t getAnalogValue() { 221 | return analogRead(_number); 222 | } 223 | 224 | 225 | // ################################# Setters ################################# 226 | 227 | // #################### Generic #################### 228 | 229 | /** 230 | Set the pin mode and pin state 231 | 232 | @param mode the mode of the pin (OUTPUT, INPUT) 233 | @param state the state of the pin (HIGH, LOW) 234 | */ 235 | void set(uint8_t mode, uint8_t state) { 236 | uint8_t oldSREG = SREG; 237 | cli(); 238 | if (mode == INPUT) { 239 | DDR_LOW; 240 | } else { 241 | DDR_HIGH; 242 | } 243 | if (state == LOW) { 244 | PORT_LOW; 245 | } else { 246 | PORT_HIGH; 247 | } 248 | SREG = oldSREG; 249 | } 250 | 251 | /** 252 | Set the pin mode 253 | 254 | @param mode the mode of the pin (OUTPUT, INPUT) 255 | */ 256 | void setMode(uint8_t mode) { 257 | uint8_t oldSREG = SREG; 258 | cli(); 259 | if (mode == INPUT) { 260 | DDR_LOW; 261 | } else { 262 | DDR_HIGH; 263 | } 264 | SREG = oldSREG; 265 | } 266 | 267 | /** 268 | Set the pin state 269 | 270 | @param state the state of the pin (HIGH, LOW) 271 | */ 272 | void setState(uint8_t state) { 273 | uint8_t oldSREG = SREG; 274 | cli(); 275 | if (state == LOW) { 276 | PORT_LOW; 277 | } else { 278 | PORT_HIGH; 279 | } 280 | SREG = oldSREG; 281 | } 282 | 283 | // #################### Input #################### 284 | 285 | /** 286 | Set the pin mode to input 287 | */ 288 | void setInput() { 289 | uint8_t oldSREG = SREG; 290 | cli(); 291 | DDR_LOW; 292 | SREG = oldSREG; 293 | } 294 | 295 | /** 296 | Set the pin pullup resistor to on 297 | */ 298 | void setPullupOn() { 299 | uint8_t oldSREG = SREG; 300 | cli(); 301 | PORT_HIGH; 302 | SREG = oldSREG; 303 | } 304 | 305 | /** 306 | Set the pin pullup resistor to off 307 | */ 308 | void setPullupOff() { 309 | uint8_t oldSREG = SREG; 310 | cli(); 311 | PORT_LOW; 312 | SREG = oldSREG; 313 | } 314 | 315 | /** 316 | Set the pin mode to input and the pin pullup resistor to on 317 | */ 318 | void setInputPullupOn() { 319 | uint8_t oldSREG = SREG; 320 | cli(); 321 | DDR_LOW; 322 | PORT_HIGH; 323 | SREG = oldSREG; 324 | } 325 | 326 | /** 327 | Set the pin mode to input and the pin pullup resistor to off 328 | */ 329 | void setInputPullupOff() { 330 | uint8_t oldSREG = SREG; 331 | cli(); 332 | DDR_LOW; 333 | PORT_LOW; 334 | SREG = oldSREG; 335 | } 336 | 337 | // #################### Output #################### 338 | 339 | /** 340 | Set the pin mode to output 341 | */ 342 | void setOutput() { 343 | uint8_t oldSREG = SREG; 344 | cli(); 345 | DDR_HIGH; 346 | SREG = oldSREG; 347 | } 348 | 349 | /** 350 | Set the pin output to HIGH 351 | */ 352 | void setHigh() { 353 | uint8_t oldSREG = SREG; 354 | cli(); 355 | PORT_HIGH; 356 | SREG = oldSREG; 357 | } 358 | 359 | /** 360 | Set the pin output to LOW 361 | */ 362 | void setLow() { 363 | uint8_t oldSREG = SREG; 364 | cli(); 365 | PORT_LOW; 366 | SREG = oldSREG; 367 | } 368 | 369 | /** 370 | Set the pin mode to output and the pin output to HIGH 371 | */ 372 | void setOutputHigh() { 373 | uint8_t oldSREG = SREG; 374 | cli(); 375 | DDR_HIGH; 376 | PORT_HIGH; 377 | SREG = oldSREG; 378 | } 379 | 380 | /** 381 | Set the pin mode to output and the pin output to LOW 382 | */ 383 | void setOutputLow() { 384 | uint8_t oldSREG = SREG; 385 | cli(); 386 | DDR_HIGH; 387 | PORT_LOW; 388 | SREG = oldSREG; 389 | } 390 | 391 | /** 392 | Set the PWM duty cycle 393 | 394 | @param value the duty cycle (0-255) 395 | */ 396 | void setDutyCycle(int value) { 397 | analogWrite(_number,value); 398 | } 399 | 400 | 401 | // ################################# Utilities ################################# 402 | 403 | // #################### Toggle #################### 404 | 405 | /** 406 | Toggle the pin mode (OUTPUT -> INPUT, INPUT -> OUTPUT) 407 | */ 408 | void toggleMode() { 409 | uint8_t oldSREG = SREG; 410 | cli(); 411 | DDR_TOGGLE; 412 | SREG = oldSREG; 413 | } 414 | 415 | /** 416 | Toggle the pin state (HIGH -> LOW, LOW -> HIGH) 417 | */ 418 | void toggleState() { 419 | uint8_t oldSREG = SREG; 420 | cli(); 421 | PORT_TOGGLE; 422 | SREG = oldSREG; 423 | } 424 | 425 | 426 | // #################### RC Timer #################### 427 | 428 | /** 429 | Set the pin mode to input and decrement a counter until the pin goes HIGH or the counter reaches 0 then set the pin mode to output and return the counter value 430 | 431 | @param count the initial value for the counter to start at (0-65535) 432 | 433 | @return the value remaining on the counter when the pin state went to HIGH or 0 if the counter reached 0 434 | */ 435 | volatile unsigned int rcTimer(volatile unsigned int count) { 436 | // ######## C ######## 437 | /* 438 | pin.setInput(); 439 | for (; count > 0; count--) { 440 | if ((pin.getValue() == HIGH) || (count == 0)) { 441 | pin.setOutput(); 442 | return count; 443 | } 444 | } 445 | */ 446 | 447 | // ######## Assembly ######## 448 | uint8_t status; 449 | asm volatile ( 450 | // Save interupt status and disable interupts 451 | "in %[status], __SREG__ \n\t" // Store current interupt status in variable 's' 452 | "cli \n\t" // Disable interupts 453 | 454 | // Set Pin to input mode to start charging capacitor 455 | "ld __tmp_reg__, %a[_DDR] \n\t" // Load the DDR register into r0 (__tmp_reg__) 456 | "and __tmp_reg__, %[_ioffset] \n\t" // Apply the bit mask (offset) to r0 (__tmp_reg__) 457 | "st %a[_DDR], __tmp_reg__ \n\t" // Store r0 (__tmp_reg__) in the DDR register 458 | 459 | // Count time before Pin becomes high 460 | "loop%=: \n\t" // Label for looping 461 | //"ld __tmp_reg__,%a[_PIN] \n\t" // Load the PIN register into r0 (__tmp_reg__) 462 | "and __tmp_reg__, %[_offset] \n\t" // Apply the bit mask (offset) to r0 (__tmp_reg__) 463 | "brne end%= \n\t" // End the loop if r0 (__tmp_reg__) is not equal to zero by branching to label 'end' 464 | "dec %[count] \n\t" // Decrement the value of 'count' by one 465 | "brne loop%= \n\t" // If the value of 'count' is not equal to zero continue the loop by branching to label 'loop' 466 | 467 | // Done counting 468 | "end%=: \n\t" // Label for ending loop 469 | // Set Pin to output mode to start discharging capacitor 470 | "ld __tmp_reg__, %a[_DDR] \n\t" // Load the DDR register into r0 (__tmp_reg__) 471 | "or __tmp_reg__, %[_offset] \n\t" // Apply the bit mask (offset) to r0 (__tmp_reg__) 472 | "st %a[_DDR], __tmp_reg__ \n\t" // Store r0 (__tmp_reg__) in the PORT register 473 | 474 | // Restore interupt status 475 | "out __SREG__, %[status] \n\t" // Load interupt status from variable 's' 476 | 477 | // Outputs 478 | : 479 | [count] "+r" (count), // The value the counter was at when the pin went high 480 | [status] "=&r" (status) // The interupt status 481 | 482 | // Inputs 483 | : 484 | [_DDR] "e" (_DDR), // The address of the DDR register for the pin 485 | [_PIN] "e" (_PIN), // The address of the PIN register for the pin 486 | [_offset] "r" (_offset), // The bit mask used to access pin in registers 487 | [_ioffset] "r" (_ioffset) // The inverse bit mask used to access pin in registers 488 | ); 489 | 490 | return count; 491 | } 492 | 493 | private: 494 | // Variables 495 | uint8_t _number; 496 | uint8_t _offset; 497 | uint8_t _ioffset; 498 | uint8_t _timer; 499 | volatile uint8_t* _PIN; 500 | volatile uint8_t* _PORT; 501 | volatile uint8_t* _DDR; 502 | }; 503 | -------------------------------------------------------------------------------- /PinGroup.h: -------------------------------------------------------------------------------- 1 | /** 2 | @file PinGroup.h 3 | @author Alec Fenichel 4 | @brief Simultaneous operations on Arduino I/O pins 5 | */ 6 | 7 | 8 | #include "Pin.h" 9 | 10 | #pragma once 11 | 12 | 13 | /** 14 | Class for simultaneous operations on Arduino I/O pins 15 | 16 | @author Alec Fenichel 17 | */ 18 | class PinGroup { 19 | public: 20 | // ################################# Constructors ################################# 21 | template 22 | PinGroup(Pin (&pins)[N]) { 23 | _offset = pins[0].getOffset(); 24 | _PIN = pins[0].getPIN(); 25 | _PORT = pins[0].getPORT(); 26 | _DDR = pins[0].getDDR(); 27 | _numbers[0] = pins[0].getNumber(); 28 | _valid = true; 29 | 30 | for (int i = 1; i < N; i++) { 31 | if (_DDR != pins[i].getDDR()) { 32 | _valid = false; 33 | } 34 | _offset |= pins[i].getOffset(); 35 | _numbers[i] = pins[i].getNumber(); 36 | } 37 | _ioffset = ~_offset; 38 | } 39 | 40 | // ################################# Operators ################################# 41 | 42 | /** 43 | Compare the value of the pin 44 | 45 | @param value the state of the pin (HIGH, LOW) 46 | 47 | @return true if the value of all of the pins are equal to the value passed in, false otherwise 48 | */ 49 | bool operator ==(uint8_t value) { 50 | uint8_t status = *_PIN; 51 | if ((status & _offset) == _offset) { 52 | return (value == HIGH); 53 | } else if ((status | _ioffset) == _ioffset) { 54 | return (value == LOW); 55 | } else { 56 | return false; 57 | } 58 | } 59 | 60 | /** 61 | Compare the value of the pin 62 | 63 | @param value the state of the pin (HIGH, LOW) 64 | 65 | @return true if the value of all of the pins are not equal to the value passed in, false otherwise 66 | */ 67 | bool operator !=(uint8_t value) { 68 | uint8_t status = *_PIN; 69 | if ((status & _offset) == _offset) { 70 | return (value == LOW); 71 | } else if ((status | _ioffset) == _ioffset) { 72 | return (value == HIGH); 73 | } else { 74 | return false; 75 | } 76 | } 77 | 78 | /** 79 | Set the pin state 80 | 81 | @param state the state of the pin (HIGH, LOW) 82 | */ 83 | PinGroup& operator =(uint8_t state) { 84 | uint8_t oldSREG = SREG; 85 | cli(); 86 | if (state == LOW) { 87 | PORT_LOW; 88 | } else { 89 | PORT_HIGH; 90 | } 91 | SREG = oldSREG; 92 | 93 | return *this; 94 | } 95 | 96 | // ################################# Getters ################################# 97 | 98 | /** 99 | Get the pin numbers 100 | 101 | @return array of pin numbers 102 | */ 103 | uint8_t* getNumbers() { 104 | return _numbers; 105 | } 106 | 107 | /** 108 | Get the pin offset 109 | 110 | @return pin offset 111 | */ 112 | uint8_t getOffset() { 113 | return _offset; 114 | } 115 | 116 | /** 117 | Get the inverse pin offset 118 | 119 | @return inverse pin offset 120 | */ 121 | uint8_t getInverseOffset() { 122 | return _ioffset; 123 | } 124 | 125 | /** 126 | Get a pointer to the PIN register 127 | 128 | @return pointer to the PIN register 129 | */ 130 | volatile uint8_t* getPIN() { 131 | return _PIN; 132 | } 133 | 134 | /** 135 | Get a pointer to the PORT register 136 | 137 | @return pointer to the PORT register 138 | */ 139 | volatile uint8_t* getPORT() { 140 | return _PORT; 141 | } 142 | 143 | /** 144 | Get a pointer to the DDR register 145 | 146 | @return pointer to the DDR register 147 | */ 148 | volatile uint8_t* getDDR() { 149 | return _DDR; 150 | } 151 | 152 | /** 153 | Get the mode of the pin from the DDR register 154 | 155 | @return mode of the pin (OUTPUT, INPUT, -1) 156 | */ 157 | uint8_t getMode() { 158 | uint8_t status = *_DDR; 159 | if ((status & _offset) == _offset) { 160 | return OUTPUT; 161 | } else if ((status | _ioffset) == _ioffset) { 162 | return INPUT; 163 | } else { 164 | return -1; 165 | } 166 | } 167 | 168 | /** 169 | Get the state of the pin from the PORT register 170 | 171 | @return state of the pin (HIGH, LOW, -1) 172 | */ 173 | uint8_t getState() { 174 | uint8_t status = *_PORT; 175 | if ((status & _offset) == _offset) { 176 | return HIGH; 177 | } else if ((status | _ioffset) == _ioffset) { 178 | return LOW; 179 | } else { 180 | return -1; 181 | } 182 | } 183 | 184 | /** 185 | Get the value of the pin from the PIN register 186 | 187 | @return value of the pin (HIGH, LOW, -1) 188 | */ 189 | uint8_t getValue() { 190 | uint8_t status = *_PIN; 191 | if ((status & _offset) == _offset) { 192 | return HIGH; 193 | } else if ((status | _ioffset) == _ioffset) { 194 | return LOW; 195 | } else { 196 | return -1; 197 | } 198 | } 199 | 200 | /** 201 | Check the group to ensure all pins use the same registers 202 | 203 | @return true if the pins in the group all use the same registers, false otherwise 204 | */ 205 | bool isValid() { 206 | return _valid; 207 | } 208 | 209 | 210 | // ################################# Setters ################################# 211 | 212 | // #################### Generic #################### 213 | 214 | /** 215 | Set the pin mode and pin state 216 | 217 | @param mode the mode of the pin (OUTPUT, INPUT) 218 | @param state the state of the pin (HIGH, LOW) 219 | */ 220 | void set(uint8_t mode, uint8_t state) { 221 | uint8_t oldSREG = SREG; 222 | cli(); 223 | if (mode == INPUT) { 224 | DDR_LOW; 225 | } else { 226 | DDR_HIGH; 227 | } 228 | if (state == LOW) { 229 | PORT_LOW; 230 | } else { 231 | PORT_HIGH; 232 | } 233 | SREG = oldSREG; 234 | } 235 | 236 | /** 237 | Set the pin mode 238 | 239 | @param mode the mode of the pin (OUTPUT, INPUT) 240 | */ 241 | void setMode(uint8_t mode) { 242 | uint8_t oldSREG = SREG; 243 | cli(); 244 | if (mode == INPUT) { 245 | DDR_LOW; 246 | } else { 247 | DDR_HIGH; 248 | } 249 | SREG = oldSREG; 250 | } 251 | 252 | /** 253 | Set the pin state 254 | 255 | @param state the state of the pin (HIGH, LOW) 256 | */ 257 | void setState(uint8_t state) { 258 | uint8_t oldSREG = SREG; 259 | cli(); 260 | if (state == LOW) { 261 | PORT_LOW; 262 | } else { 263 | PORT_HIGH; 264 | } 265 | SREG = oldSREG; 266 | } 267 | 268 | // #################### Input #################### 269 | 270 | /** 271 | Set the pin mode to input 272 | */ 273 | void setInput() { 274 | uint8_t oldSREG = SREG; 275 | cli(); 276 | DDR_LOW; 277 | SREG = oldSREG; 278 | } 279 | 280 | /** 281 | Set the pin pullup resistor to on 282 | */ 283 | void setPullupOn() { 284 | uint8_t oldSREG = SREG; 285 | cli(); 286 | PORT_HIGH; 287 | SREG = oldSREG; 288 | } 289 | 290 | /** 291 | Set the pin pullup resistor to off 292 | */ 293 | void setPullupOff() { 294 | uint8_t oldSREG = SREG; 295 | cli(); 296 | PORT_LOW; 297 | SREG = oldSREG; 298 | } 299 | 300 | /** 301 | Set the pin mode to input and the pin pullup resistor to on 302 | */ 303 | void setInputPullupOn() { 304 | uint8_t oldSREG = SREG; 305 | cli(); 306 | DDR_LOW; 307 | PORT_HIGH; 308 | SREG = oldSREG; 309 | } 310 | 311 | /** 312 | Set the pin mode to input and the pin pullup resistor to off 313 | */ 314 | void setInputPullupOff() { 315 | uint8_t oldSREG = SREG; 316 | cli(); 317 | DDR_LOW; 318 | PORT_LOW; 319 | SREG = oldSREG; 320 | } 321 | 322 | // #################### Output #################### 323 | 324 | /** 325 | Set the pin mode to output 326 | */ 327 | void setOutput() { 328 | uint8_t oldSREG = SREG; 329 | cli(); 330 | DDR_HIGH; 331 | SREG = oldSREG; 332 | } 333 | 334 | /** 335 | Set the pin output to HIGH 336 | */ 337 | void setHigh() { 338 | uint8_t oldSREG = SREG; 339 | cli(); 340 | PORT_HIGH; 341 | SREG = oldSREG; 342 | } 343 | 344 | /** 345 | Set the pin output to LOW 346 | */ 347 | void setLow() { 348 | uint8_t oldSREG = SREG; 349 | cli(); 350 | PORT_LOW; 351 | SREG = oldSREG; 352 | } 353 | 354 | /** 355 | Set the pin mode to output and the pin output to HIGH 356 | */ 357 | void setOutputHigh() { 358 | uint8_t oldSREG = SREG; 359 | cli(); 360 | DDR_HIGH; 361 | PORT_HIGH; 362 | SREG = oldSREG; 363 | } 364 | 365 | /** 366 | Set the pin mode to output and the pin output to LOW 367 | */ 368 | void setOutputLow() { 369 | uint8_t oldSREG = SREG; 370 | cli(); 371 | DDR_HIGH; 372 | PORT_LOW; 373 | SREG = oldSREG; 374 | } 375 | 376 | 377 | // ################################# Utilities ################################# 378 | 379 | // #################### Toggle #################### 380 | 381 | /** 382 | Toggle the pin mode (OUTPUT -> INPUT, INPUT -> OUTPUT) 383 | */ 384 | void toggleMode() { 385 | uint8_t oldSREG = SREG; 386 | cli(); 387 | DDR_TOGGLE; 388 | SREG = oldSREG; 389 | } 390 | 391 | /** 392 | Toggle the pin state (HIGH -> LOW, LOW -> HIGH) 393 | */ 394 | void toggleState() { 395 | uint8_t oldSREG = SREG; 396 | cli(); 397 | PORT_TOGGLE; 398 | SREG = oldSREG; 399 | } 400 | 401 | private: 402 | // Variables 403 | uint8_t _numbers[8]; 404 | uint8_t _offset; 405 | uint8_t _ioffset; 406 | bool _valid; 407 | volatile uint8_t* _PIN; 408 | volatile uint8_t* _PORT; 409 | volatile uint8_t* _DDR; 410 | }; 411 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | # Simple Stepper    [![Badge License]][License] 3 | 4 | *A **Minimal** / **Fast** / **Simple** **Arduino** library.* 5 | 6 |
7 | 8 | 9 | Relies on `Timer1 Interrupts` and `Direct Port Manipulations` via
10 | the Pin library to produce extremely smooth stepping signal. 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 |
19 | 20 | ## Features 21 | 22 | - Doesn't block as long as you have processing power left. 23 | - Modifyable tick rate for slower / faster motor speed. 24 | 25 |
26 |
27 | 28 | ## Unsupported 29 | 30 | - Acceleration / Deceleration 31 | 32 |
33 |
34 | 35 | ## Usage 36 | 37 | Simply provide the `STEP` & `DIR` pin numbers, steps, direction
38 | and tick rate, the rest is pretty much self-explanatory. 39 | 40 |
41 |
42 | 43 | ## Tested 44 | 45 | With an **[Arduino Nano 3.0]**, it is able to drive a `1.8° Nema 17`
46 | motor with `1 / 128` microsteps with reasonable speed with still
47 | have processing power left for the rest of your code. 48 | 49 | 50 |
51 |
52 | 53 | ## Installation 54 | 55 |
56 | 57 | 1. **[Download]** or **Clone** this repository. 58 | 59 | #### Clone 60 | 61 | ```sh 62 | git clone https://github.com/megablue/SimpleStepper 63 | ``` 64 | 65 |
66 | 67 | 2. Move the ***cloned*** / ***extracted*** folder to: 68 | 69 | ``` 70 | Arduino/lib/targets/libraries 71 | ``` 72 | 73 | ### Arduino Folder 74 | 75 | *You can find the folder under:* 76 | 77 | ![Badge Windows]
78 | `C:\Users\\Documents\Arduino` 79 | 80 | ![Badge MacOS]
81 | `/Users//Documents/Arduino` 82 | 83 | ![Badge Linux]
84 | `/home//Arduino` 85 | 86 |
87 | 88 | 89 | 90 | 91 | [Arduino Nano 3.0]: https://docs.arduino.cc/hardware/nano 92 | 93 | [Download]: https://github.com/megablue/SimpleStepper/archive/refs/heads/master.zip 94 | [License]: LICENSE 95 | 96 | [Badge License]: https://img.shields.io/badge/License-MIT-yellow.svg?style=for-the-badge 97 | [Badge Windows]: https://img.shields.io/badge/Windows-0078D6?style=for-the-badge&logo=Windows&logoColor=white 98 | [Badge Linux]: https://img.shields.io/badge/Linux-10B981?style=for-the-badge&logo=Linux&logoColor=white 99 | [Badge MacOS]: https://img.shields.io/badge/MacOS-lightgray?style=for-the-badge&logo=MacOS&logoColor=white 100 | -------------------------------------------------------------------------------- /SimpleStepper.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | @file SimpleStepper.cpp 3 | @author Evert Chin 4 | @brief Fast and Simple Stepper Driver 5 | 6 | * This file may be redistributed under the terms of the MIT license. 7 | * A copy of this license has been included with this distribution in the file LICENSE. 8 | */ 9 | 10 | #include "SimpleStepper.h" 11 | #include "TimerOne.h" 12 | 13 | SimpleStepper *SimpleStepper::firstInstance; 14 | 15 | SimpleStepper::SimpleStepper(uint8_t dirpin, uint8_t steppin){ 16 | if(!firstInstance){ 17 | firstInstance = this; 18 | } 19 | 20 | this->dirPin = Pin(dirpin); 21 | this->stepPin = Pin(steppin); 22 | } 23 | 24 | void SimpleStepper::init(){ 25 | dirPin.setOutput(); 26 | stepPin.setOutput(); 27 | Timer1.initialize(); 28 | Timer1.attachInterrupt(SimpleStepper::ticking); 29 | Timer1.stop(); 30 | this->pause(); 31 | } 32 | 33 | void SimpleStepper::setPulse(long pulse){ 34 | Timer1.setPeriod(pulse); 35 | } 36 | 37 | bool SimpleStepper::step(long steps, uint8_t direction){ 38 | if(this->isStepping()){ 39 | return false; 40 | } 41 | 42 | this->ticksRemaining = steps * 2; //converting steps to ticks 43 | 44 | if(direction == HIGH){ 45 | this->dirPin.setHigh(); 46 | } else { 47 | this->dirPin.setLow(); 48 | } 49 | 50 | return true; 51 | } 52 | 53 | bool SimpleStepper::step(long steps, uint8_t direction, long pulse){ 54 | if(this->isStepping()){ 55 | return false; 56 | } 57 | 58 | this->resume(); 59 | this->setPulse(pulse); 60 | 61 | return this->step(steps, direction); 62 | } 63 | 64 | long SimpleStepper::getRemainingSteps(){ 65 | return ticksRemaining/2; 66 | } 67 | 68 | //returns the remaining steps 69 | long SimpleStepper::stop(){ 70 | //each step = 2 ticks 71 | long stepsRemaining = this->getRemainingSteps(); 72 | 73 | Timer1.stop(); 74 | 75 | if(ticksRemaining & 1){ 76 | ticksRemaining = 1; 77 | } else{ 78 | ticksRemaining = 0; 79 | } 80 | 81 | Timer1.start(); 82 | 83 | return stepsRemaining; 84 | } 85 | 86 | void SimpleStepper::pause(){ 87 | paused = true; 88 | Timer1.stop(); 89 | } 90 | 91 | void SimpleStepper::resume(){ 92 | if(paused){ 93 | Timer1.start(); 94 | paused = false; 95 | } 96 | } 97 | 98 | bool SimpleStepper::isStepping(){ 99 | return (ticksRemaining > 0); 100 | } 101 | 102 | bool SimpleStepper::isStopped(){ 103 | return (ticksRemaining <= 0); 104 | } 105 | 106 | bool SimpleStepper::isPaused(){ 107 | return paused; 108 | } 109 | 110 | void SimpleStepper::ticking(){ 111 | if(firstInstance->ticksRemaining > 0){ 112 | //generate high/low signal for the stepper driver 113 | firstInstance->stepPin.toggleState(); 114 | --firstInstance->ticksRemaining; 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /SimpleStepper.h: -------------------------------------------------------------------------------- 1 | /** 2 | @file SimpleStepper.h 3 | @author Evert Chin 4 | @brief Fast and Simple Stepper Driver 5 | 6 | * This file may be redistributed under the terms of the MIT license. 7 | * A copy of this license has been included with this distribution in the file LICENSE. 8 | */ 9 | #ifndef SIMPLE_STEPPER_BASE_H 10 | #define SIMPLE_STEPPER_BASE_H 11 | #include 12 | #include "Pin.h" 13 | 14 | /* 15 | * Simple Stepper class. 16 | */ 17 | class SimpleStepper { 18 | public: 19 | volatile long ticksRemaining; // remaining ticks, 2 ticks = 1 pulse = 1 microstep/step 20 | 21 | protected: 22 | /* for some stupid reason the Pin class requires initialization */ 23 | Pin dirPin = Pin(1000); 24 | Pin stepPin = dirPin; 25 | bool paused; 26 | 27 | public: 28 | SimpleStepper(uint8_t dirpin, uint8_t steppin); 29 | void init(); 30 | void setPulse(long pulse); 31 | bool step(long steps, uint8_t direction); 32 | bool step(long steps, uint8_t direction, long pulse); 33 | long getRemainingSteps(); 34 | long stop(); 35 | void pause(); 36 | void resume(); 37 | bool isStepping(); 38 | bool isStopped(); 39 | bool isPaused(); 40 | static void ticking(); 41 | 42 | private: 43 | static SimpleStepper *firstInstance; 44 | }; 45 | #endif // SIMPLE_STEPPER_BASE_H 46 | -------------------------------------------------------------------------------- /TimerOne.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Interrupt and PWM utilities for 16 bit Timer1 on ATmega168/328 3 | * Original code by Jesse Tane for http://labs.ideo.com August 2008 4 | * Modified March 2009 by Jérôme Despatis and Jesse Tane for ATmega328 support 5 | * Modified June 2009 by Michael Polli and Jesse Tane to fix a bug in setPeriod() which caused the timer to stop 6 | * Modified Oct 2009 by Dan Clemens to work with timer1 of the ATMega1280 or Arduino Mega 7 | * Modified April 2012 by Paul Stoffregen 8 | * Modified again, June 2014 by Paul Stoffregen 9 | * Modified July 2017 by Stoyko Dimitrov - added support for ATTiny85 except for the PWM functionality 10 | * 11 | * This is free software. You can redistribute it and/or modify it under 12 | * the terms of Creative Commons Attribution 3.0 United States License. 13 | * To view a copy of this license, visit http://creativecommons.org/licenses/by/3.0/us/ 14 | * or send a letter to Creative Commons, 171 Second Street, Suite 300, San Francisco, California, 94105, USA. 15 | * 16 | */ 17 | 18 | #include "TimerOne.h" 19 | 20 | TimerOne Timer1; // preinstatiate 21 | 22 | unsigned short TimerOne::pwmPeriod = 0; 23 | unsigned char TimerOne::clockSelectBits = 0; 24 | void (*TimerOne::isrCallback)() = TimerOne::isrDefaultUnused; 25 | 26 | // interrupt service routine that wraps a user defined function supplied by attachInterrupt 27 | #if defined (__AVR_ATtiny85__) 28 | ISR(TIMER1_COMPA_vect) 29 | { 30 | Timer1.isrCallback(); 31 | } 32 | #elif defined(__AVR__) 33 | ISR(TIMER1_OVF_vect) 34 | { 35 | Timer1.isrCallback(); 36 | } 37 | 38 | #elif defined(__arm__) && defined(CORE_TEENSY) 39 | void ftm1_isr(void) 40 | { 41 | uint32_t sc = FTM1_SC; 42 | #ifdef KINETISL 43 | if (sc & 0x80) FTM1_SC = sc; 44 | #else 45 | if (sc & 0x80) FTM1_SC = sc & 0x7F; 46 | #endif 47 | Timer1.isrCallback(); 48 | } 49 | 50 | #endif 51 | 52 | void TimerOne::isrDefaultUnused() 53 | { 54 | } 55 | -------------------------------------------------------------------------------- /TimerOne.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Interrupt and PWM utilities for 16 bit Timer1 on ATmega168/328 3 | * Original code by Jesse Tane for http://labs.ideo.com August 2008 4 | * Modified March 2009 by Jérôme Despatis and Jesse Tane for ATmega328 support 5 | * Modified June 2009 by Michael Polli and Jesse Tane to fix a bug in setPeriod() which caused the timer to stop 6 | * Modified April 2012 by Paul Stoffregen - portable to other AVR chips, use inline functions 7 | * Modified again, June 2014 by Paul Stoffregen - support Teensy 3.x & even more AVR chips 8 | * Modified July 2017 by Stoyko Dimitrov - added support for ATTiny85 except for the PWM functionality 9 | * 10 | * 11 | * This is free software. You can redistribute it and/or modify it under 12 | * the terms of Creative Commons Attribution 3.0 United States License. 13 | * To view a copy of this license, visit http://creativecommons.org/licenses/by/3.0/us/ 14 | * or send a letter to Creative Commons, 171 Second Street, Suite 300, San Francisco, California, 94105, USA. 15 | * 16 | */ 17 | 18 | #ifndef TimerOne_h_ 19 | #define TimerOne_h_ 20 | 21 | #if defined(ARDUINO) && ARDUINO >= 100 22 | #include "Arduino.h" 23 | #else 24 | #include "WProgram.h" 25 | #endif 26 | 27 | #include "config/known_16bit_timers.h" 28 | #if defined (__AVR_ATtiny85__) 29 | #define TIMER1_RESOLUTION 256UL // Timer1 is 8 bit 30 | #elif defined(__AVR__) 31 | #define TIMER1_RESOLUTION 65536UL // Timer1 is 16 bit 32 | #else 33 | #define TIMER1_RESOLUTION 65536UL // assume 16 bits for non-AVR chips 34 | #endif 35 | 36 | // Placing nearly all the code in this .h file allows the functions to be 37 | // inlined by the compiler. In the very common case with constant values 38 | // the compiler will perform all calculations and simply write constants 39 | // to the hardware registers (for example, setPeriod). 40 | 41 | 42 | class TimerOne 43 | { 44 | 45 | #if defined (__AVR_ATtiny85__) 46 | public: 47 | //**************************** 48 | // Configuration 49 | //**************************** 50 | void initialize(unsigned long microseconds=1000000) __attribute__((always_inline)) { 51 | TCCR1 = _BV(CTC1); //clear timer1 when it matches the value in OCR1C 52 | TIMSK |= _BV(OCIE1A); //enable interrupt when OCR1A matches the timer value 53 | setPeriod(microseconds); 54 | } 55 | void setPeriod(unsigned long microseconds) __attribute__((always_inline)) { 56 | const unsigned long cycles = microseconds * ratio; 57 | if (cycles < TIMER1_RESOLUTION) { 58 | clockSelectBits = _BV(CS10); 59 | pwmPeriod = cycles; 60 | } else 61 | if (cycles < TIMER1_RESOLUTION * 2UL) { 62 | clockSelectBits = _BV(CS11); 63 | pwmPeriod = cycles / 2; 64 | } else 65 | if (cycles < TIMER1_RESOLUTION * 4UL) { 66 | clockSelectBits = _BV(CS11) | _BV(CS10); 67 | pwmPeriod = cycles / 4; 68 | } else 69 | if (cycles < TIMER1_RESOLUTION * 8UL) { 70 | clockSelectBits = _BV(CS12); 71 | pwmPeriod = cycles / 8; 72 | } else 73 | if (cycles < TIMER1_RESOLUTION * 16UL) { 74 | clockSelectBits = _BV(CS12) | _BV(CS10); 75 | pwmPeriod = cycles / 16; 76 | } else 77 | if (cycles < TIMER1_RESOLUTION * 32UL) { 78 | clockSelectBits = _BV(CS12) | _BV(CS11); 79 | pwmPeriod = cycles / 32; 80 | } else 81 | if (cycles < TIMER1_RESOLUTION * 64UL) { 82 | clockSelectBits = _BV(CS12) | _BV(CS11) | _BV(CS10); 83 | pwmPeriod = cycles / 64UL; 84 | } else 85 | if (cycles < TIMER1_RESOLUTION * 128UL) { 86 | clockSelectBits = _BV(CS13); 87 | pwmPeriod = cycles / 128; 88 | } else 89 | if (cycles < TIMER1_RESOLUTION * 256UL) { 90 | clockSelectBits = _BV(CS13) | _BV(CS10); 91 | pwmPeriod = cycles / 256; 92 | } else 93 | if (cycles < TIMER1_RESOLUTION * 512UL) { 94 | clockSelectBits = _BV(CS13) | _BV(CS11); 95 | pwmPeriod = cycles / 512; 96 | } else 97 | if (cycles < TIMER1_RESOLUTION * 1024UL) { 98 | clockSelectBits = _BV(CS13) | _BV(CS11) | _BV(CS10); 99 | pwmPeriod = cycles / 1024; 100 | } else 101 | if (cycles < TIMER1_RESOLUTION * 2048UL) { 102 | clockSelectBits = _BV(CS13) | _BV(CS12); 103 | pwmPeriod = cycles / 2048; 104 | } else 105 | if (cycles < TIMER1_RESOLUTION * 4096UL) { 106 | clockSelectBits = _BV(CS13) | _BV(CS12) | _BV(CS10); 107 | pwmPeriod = cycles / 4096; 108 | } else 109 | if (cycles < TIMER1_RESOLUTION * 8192UL) { 110 | clockSelectBits = _BV(CS13) | _BV(CS12) | _BV(CS11); 111 | pwmPeriod = cycles / 8192; 112 | } else 113 | if (cycles < TIMER1_RESOLUTION * 16384UL) { 114 | clockSelectBits = _BV(CS13) | _BV(CS12) | _BV(CS11) | _BV(CS10); 115 | pwmPeriod = cycles / 16384; 116 | } else { 117 | clockSelectBits = _BV(CS13) | _BV(CS12) | _BV(CS11) | _BV(CS10); 118 | pwmPeriod = TIMER1_RESOLUTION - 1; 119 | } 120 | OCR1A = pwmPeriod; 121 | OCR1C = pwmPeriod; 122 | TCCR1 = _BV(CTC1) | clockSelectBits; 123 | } 124 | 125 | //**************************** 126 | // Run Control 127 | //**************************** 128 | void start() __attribute__((always_inline)) { 129 | TCCR1 = 0; 130 | TCNT1 = 0; 131 | resume(); 132 | } 133 | void stop() __attribute__((always_inline)) { 134 | TCCR1 = _BV(CTC1); 135 | } 136 | void restart() __attribute__((always_inline)) { 137 | start(); 138 | } 139 | void resume() __attribute__((always_inline)) { 140 | TCCR1 = _BV(CTC1) | clockSelectBits; 141 | } 142 | 143 | //**************************** 144 | // PWM outputs 145 | //**************************** 146 | //Not implemented yet for ATTiny85 147 | //TO DO 148 | 149 | //**************************** 150 | // Interrupt Function 151 | //**************************** 152 | void attachInterrupt(void (*isr)()) __attribute__((always_inline)) { 153 | isrCallback = isr; 154 | TIMSK |= _BV(OCIE1A); 155 | } 156 | void attachInterrupt(void (*isr)(), unsigned long microseconds) __attribute__((always_inline)) { 157 | if(microseconds > 0) setPeriod(microseconds); 158 | attachInterrupt(isr); 159 | } 160 | void detachInterrupt() __attribute__((always_inline)) { 161 | //TIMSK = 0; // Timer 0 and Timer 1 both use TIMSK register so setting it to 0 will override settings for Timer1 as well 162 | TIMSK &= ~_BV(OCIE1A); 163 | } 164 | static void (*isrCallback)(); 165 | static void isrDefaultUnused(); 166 | 167 | private: 168 | static unsigned short pwmPeriod; 169 | static unsigned char clockSelectBits; 170 | static const byte ratio = (F_CPU)/ ( 1000000 ); 171 | 172 | #elif defined(__AVR__) 173 | public: 174 | //**************************** 175 | // Configuration 176 | //**************************** 177 | void initialize(unsigned long microseconds=1000000) __attribute__((always_inline)) { 178 | TCCR1B = _BV(WGM13); // set mode as phase and frequency correct pwm, stop the timer 179 | TCCR1A = 0; // clear control register A 180 | setPeriod(microseconds); 181 | } 182 | void setPeriod(unsigned long microseconds) __attribute__((always_inline)) { 183 | const unsigned long cycles = (F_CPU / 2000000) * microseconds; 184 | if (cycles < TIMER1_RESOLUTION) { 185 | clockSelectBits = _BV(CS10); 186 | pwmPeriod = cycles; 187 | } else 188 | if (cycles < TIMER1_RESOLUTION * 8) { 189 | clockSelectBits = _BV(CS11); 190 | pwmPeriod = cycles / 8; 191 | } else 192 | if (cycles < TIMER1_RESOLUTION * 64) { 193 | clockSelectBits = _BV(CS11) | _BV(CS10); 194 | pwmPeriod = cycles / 64; 195 | } else 196 | if (cycles < TIMER1_RESOLUTION * 256) { 197 | clockSelectBits = _BV(CS12); 198 | pwmPeriod = cycles / 256; 199 | } else 200 | if (cycles < TIMER1_RESOLUTION * 1024) { 201 | clockSelectBits = _BV(CS12) | _BV(CS10); 202 | pwmPeriod = cycles / 1024; 203 | } else { 204 | clockSelectBits = _BV(CS12) | _BV(CS10); 205 | pwmPeriod = TIMER1_RESOLUTION - 1; 206 | } 207 | ICR1 = pwmPeriod; 208 | TCCR1B = _BV(WGM13) | clockSelectBits; 209 | } 210 | 211 | //**************************** 212 | // Run Control 213 | //**************************** 214 | void start() __attribute__((always_inline)) { 215 | TCCR1B = 0; 216 | TCNT1 = 0; // TODO: does this cause an undesired interrupt? 217 | resume(); 218 | } 219 | void stop() __attribute__((always_inline)) { 220 | TCCR1B = _BV(WGM13); 221 | } 222 | void restart() __attribute__((always_inline)) { 223 | start(); 224 | } 225 | void resume() __attribute__((always_inline)) { 226 | TCCR1B = _BV(WGM13) | clockSelectBits; 227 | } 228 | 229 | //**************************** 230 | // PWM outputs 231 | //**************************** 232 | void setPwmDuty(char pin, unsigned int duty) __attribute__((always_inline)) { 233 | unsigned long dutyCycle = pwmPeriod; 234 | dutyCycle *= duty; 235 | dutyCycle >>= 10; 236 | if (pin == TIMER1_A_PIN) OCR1A = dutyCycle; 237 | #ifdef TIMER1_B_PIN 238 | else if (pin == TIMER1_B_PIN) OCR1B = dutyCycle; 239 | #endif 240 | #ifdef TIMER1_C_PIN 241 | else if (pin == TIMER1_C_PIN) OCR1C = dutyCycle; 242 | #endif 243 | } 244 | void pwm(char pin, unsigned int duty) __attribute__((always_inline)) { 245 | if (pin == TIMER1_A_PIN) { pinMode(TIMER1_A_PIN, OUTPUT); TCCR1A |= _BV(COM1A1); } 246 | #ifdef TIMER1_B_PIN 247 | else if (pin == TIMER1_B_PIN) { pinMode(TIMER1_B_PIN, OUTPUT); TCCR1A |= _BV(COM1B1); } 248 | #endif 249 | #ifdef TIMER1_C_PIN 250 | else if (pin == TIMER1_C_PIN) { pinMode(TIMER1_C_PIN, OUTPUT); TCCR1A |= _BV(COM1C1); } 251 | #endif 252 | setPwmDuty(pin, duty); 253 | TCCR1B = _BV(WGM13) | clockSelectBits; 254 | } 255 | void pwm(char pin, unsigned int duty, unsigned long microseconds) __attribute__((always_inline)) { 256 | if (microseconds > 0) setPeriod(microseconds); 257 | pwm(pin, duty); 258 | } 259 | void disablePwm(char pin) __attribute__((always_inline)) { 260 | if (pin == TIMER1_A_PIN) TCCR1A &= ~_BV(COM1A1); 261 | #ifdef TIMER1_B_PIN 262 | else if (pin == TIMER1_B_PIN) TCCR1A &= ~_BV(COM1B1); 263 | #endif 264 | #ifdef TIMER1_C_PIN 265 | else if (pin == TIMER1_C_PIN) TCCR1A &= ~_BV(COM1C1); 266 | #endif 267 | } 268 | 269 | //**************************** 270 | // Interrupt Function 271 | //**************************** 272 | void attachInterrupt(void (*isr)()) __attribute__((always_inline)) { 273 | isrCallback = isr; 274 | TIMSK1 = _BV(TOIE1); 275 | } 276 | void attachInterrupt(void (*isr)(), unsigned long microseconds) __attribute__((always_inline)) { 277 | if(microseconds > 0) setPeriod(microseconds); 278 | attachInterrupt(isr); 279 | } 280 | void detachInterrupt() __attribute__((always_inline)) { 281 | TIMSK1 = 0; 282 | } 283 | static void (*isrCallback)(); 284 | static void isrDefaultUnused(); 285 | 286 | private: 287 | // properties 288 | static unsigned short pwmPeriod; 289 | static unsigned char clockSelectBits; 290 | 291 | 292 | 293 | 294 | 295 | 296 | #elif defined(__arm__) && defined(CORE_TEENSY) 297 | 298 | #if defined(KINETISK) 299 | #define F_TIMER F_BUS 300 | #elif defined(KINETISL) 301 | #define F_TIMER (F_PLL/2) 302 | #endif 303 | 304 | public: 305 | //**************************** 306 | // Configuration 307 | //**************************** 308 | void initialize(unsigned long microseconds=1000000) __attribute__((always_inline)) { 309 | setPeriod(microseconds); 310 | } 311 | void setPeriod(unsigned long microseconds) __attribute__((always_inline)) { 312 | const unsigned long cycles = (F_TIMER / 2000000) * microseconds; 313 | // A much faster if-else 314 | // This is like a binary serch tree and no more than 3 conditions are evaluated. 315 | // I haven't checked if this becomes significantly longer ASM than the simple ladder. 316 | // It looks very similar to the ladder tho: same # of if's and else's 317 | 318 | /* 319 | // This code does not work properly in all cases :( 320 | // https://github.com/PaulStoffregen/TimerOne/issues/17 321 | if (cycles < TIMER1_RESOLUTION * 16) { 322 | if (cycles < TIMER1_RESOLUTION * 4) { 323 | if (cycles < TIMER1_RESOLUTION) { 324 | clockSelectBits = 0; 325 | pwmPeriod = cycles; 326 | }else{ 327 | clockSelectBits = 1; 328 | pwmPeriod = cycles >> 1; 329 | } 330 | }else{ 331 | if (cycles < TIMER1_RESOLUTION * 8) { 332 | clockSelectBits = 3; 333 | pwmPeriod = cycles >> 3; 334 | }else{ 335 | clockSelectBits = 4; 336 | pwmPeriod = cycles >> 4; 337 | } 338 | } 339 | }else{ 340 | if (cycles > TIMER1_RESOLUTION * 64) { 341 | if (cycles > TIMER1_RESOLUTION * 128) { 342 | clockSelectBits = 7; 343 | pwmPeriod = TIMER1_RESOLUTION - 1; 344 | }else{ 345 | clockSelectBits = 7; 346 | pwmPeriod = cycles >> 7; 347 | } 348 | } 349 | else{ 350 | if (cycles > TIMER1_RESOLUTION * 32) { 351 | clockSelectBits = 6; 352 | pwmPeriod = cycles >> 6; 353 | }else{ 354 | clockSelectBits = 5; 355 | pwmPeriod = cycles >> 5; 356 | } 357 | } 358 | } 359 | */ 360 | if (cycles < TIMER1_RESOLUTION) { 361 | clockSelectBits = 0; 362 | pwmPeriod = cycles; 363 | } else 364 | if (cycles < TIMER1_RESOLUTION * 2) { 365 | clockSelectBits = 1; 366 | pwmPeriod = cycles >> 1; 367 | } else 368 | if (cycles < TIMER1_RESOLUTION * 4) { 369 | clockSelectBits = 2; 370 | pwmPeriod = cycles >> 2; 371 | } else 372 | if (cycles < TIMER1_RESOLUTION * 8) { 373 | clockSelectBits = 3; 374 | pwmPeriod = cycles >> 3; 375 | } else 376 | if (cycles < TIMER1_RESOLUTION * 16) { 377 | clockSelectBits = 4; 378 | pwmPeriod = cycles >> 4; 379 | } else 380 | if (cycles < TIMER1_RESOLUTION * 32) { 381 | clockSelectBits = 5; 382 | pwmPeriod = cycles >> 5; 383 | } else 384 | if (cycles < TIMER1_RESOLUTION * 64) { 385 | clockSelectBits = 6; 386 | pwmPeriod = cycles >> 6; 387 | } else 388 | if (cycles < TIMER1_RESOLUTION * 128) { 389 | clockSelectBits = 7; 390 | pwmPeriod = cycles >> 7; 391 | } else { 392 | clockSelectBits = 7; 393 | pwmPeriod = TIMER1_RESOLUTION - 1; 394 | } 395 | 396 | uint32_t sc = FTM1_SC; 397 | FTM1_SC = 0; 398 | FTM1_MOD = pwmPeriod; 399 | FTM1_SC = FTM_SC_CLKS(1) | FTM_SC_CPWMS | clockSelectBits | (sc & FTM_SC_TOIE); 400 | } 401 | 402 | //**************************** 403 | // Run Control 404 | //**************************** 405 | void start() __attribute__((always_inline)) { 406 | stop(); 407 | FTM1_CNT = 0; 408 | resume(); 409 | } 410 | void stop() __attribute__((always_inline)) { 411 | FTM1_SC = FTM1_SC & (FTM_SC_TOIE | FTM_SC_CPWMS | FTM_SC_PS(7)); 412 | } 413 | void restart() __attribute__((always_inline)) { 414 | start(); 415 | } 416 | void resume() __attribute__((always_inline)) { 417 | FTM1_SC = (FTM1_SC & (FTM_SC_TOIE | FTM_SC_PS(7))) | FTM_SC_CPWMS | FTM_SC_CLKS(1); 418 | } 419 | 420 | //**************************** 421 | // PWM outputs 422 | //**************************** 423 | void setPwmDuty(char pin, unsigned int duty) __attribute__((always_inline)) { 424 | unsigned long dutyCycle = pwmPeriod; 425 | dutyCycle *= duty; 426 | dutyCycle >>= 10; 427 | if (pin == TIMER1_A_PIN) { 428 | FTM1_C0V = dutyCycle; 429 | } else if (pin == TIMER1_B_PIN) { 430 | FTM1_C1V = dutyCycle; 431 | } 432 | } 433 | void pwm(char pin, unsigned int duty) __attribute__((always_inline)) { 434 | setPwmDuty(pin, duty); 435 | if (pin == TIMER1_A_PIN) { 436 | *portConfigRegister(TIMER1_A_PIN) = PORT_PCR_MUX(3) | PORT_PCR_DSE | PORT_PCR_SRE; 437 | } else if (pin == TIMER1_B_PIN) { 438 | *portConfigRegister(TIMER1_B_PIN) = PORT_PCR_MUX(3) | PORT_PCR_DSE | PORT_PCR_SRE; 439 | } 440 | } 441 | void pwm(char pin, unsigned int duty, unsigned long microseconds) __attribute__((always_inline)) { 442 | if (microseconds > 0) setPeriod(microseconds); 443 | pwm(pin, duty); 444 | } 445 | void disablePwm(char pin) __attribute__((always_inline)) { 446 | if (pin == TIMER1_A_PIN) { 447 | *portConfigRegister(TIMER1_A_PIN) = 0; 448 | } else if (pin == TIMER1_B_PIN) { 449 | *portConfigRegister(TIMER1_B_PIN) = 0; 450 | } 451 | } 452 | 453 | //**************************** 454 | // Interrupt Function 455 | //**************************** 456 | void attachInterrupt(void (*isr)()) __attribute__((always_inline)) { 457 | isrCallback = isr; 458 | FTM1_SC |= FTM_SC_TOIE; 459 | NVIC_ENABLE_IRQ(IRQ_FTM1); 460 | } 461 | void attachInterrupt(void (*isr)(), unsigned long microseconds) __attribute__((always_inline)) { 462 | if(microseconds > 0) setPeriod(microseconds); 463 | attachInterrupt(isr); 464 | } 465 | void detachInterrupt() __attribute__((always_inline)) { 466 | FTM1_SC &= ~FTM_SC_TOIE; 467 | NVIC_DISABLE_IRQ(IRQ_FTM1); 468 | } 469 | static void (*isrCallback)(); 470 | static void isrDefaultUnused(); 471 | 472 | private: 473 | // properties 474 | static unsigned short pwmPeriod; 475 | static unsigned char clockSelectBits; 476 | 477 | #undef F_TIMER 478 | 479 | #endif 480 | }; 481 | 482 | extern TimerOne Timer1; 483 | 484 | #endif 485 | 486 | -------------------------------------------------------------------------------- /config/known_16bit_timers.h: -------------------------------------------------------------------------------- 1 | #ifndef known_16bit_timers_header_ 2 | #define known_16bit_timers_header_ 3 | 4 | // Wiring-S 5 | // 6 | #if defined(__AVR_ATmega644P__) && defined(WIRING) 7 | #define TIMER1_A_PIN 5 8 | #define TIMER1_B_PIN 4 9 | #define TIMER1_ICP_PIN 6 10 | 11 | // Teensy 2.0 12 | // 13 | #elif defined(__AVR_ATmega32U4__) && defined(CORE_TEENSY) 14 | #define TIMER1_A_PIN 14 15 | #define TIMER1_B_PIN 15 16 | #define TIMER1_C_PIN 4 17 | #define TIMER1_ICP_PIN 22 18 | #define TIMER1_CLK_PIN 11 19 | #define TIMER3_A_PIN 9 20 | #define TIMER3_ICP_PIN 10 21 | 22 | // Teensy++ 2.0 23 | #elif defined(__AVR_AT90USB1286__) && defined(CORE_TEENSY) 24 | #define TIMER1_A_PIN 25 25 | #define TIMER1_B_PIN 26 26 | #define TIMER1_C_PIN 27 27 | #define TIMER1_ICP_PIN 4 28 | #define TIMER1_CLK_PIN 6 29 | #define TIMER3_A_PIN 16 30 | #define TIMER3_B_PIN 15 31 | #define TIMER3_C_PIN 14 32 | #define TIMER3_ICP_PIN 17 33 | #define TIMER3_CLK_PIN 13 34 | 35 | // Teensy 3.0 36 | // 37 | #elif defined(__MK20DX128__) 38 | #define TIMER1_A_PIN 3 39 | #define TIMER1_B_PIN 4 40 | #define TIMER1_ICP_PIN 4 41 | 42 | // Teensy 3.1 / Teensy 3.2 43 | // 44 | #elif defined(__MK20DX256__) 45 | #define TIMER1_A_PIN 3 46 | #define TIMER1_B_PIN 4 47 | #define TIMER1_ICP_PIN 4 48 | #define TIMER3_A_PIN 32 49 | #define TIMER3_B_PIN 25 50 | #define TIMER3_ICP_PIN 32 51 | 52 | // Teensy 3.5 / Teensy 3.6 53 | // 54 | #elif defined(__MK64FX512__) || defined(__MK66FX1M0__) 55 | #define TIMER1_A_PIN 3 56 | #define TIMER1_B_PIN 4 57 | #define TIMER1_ICP_PIN 4 58 | #define TIMER3_A_PIN 29 59 | #define TIMER3_B_PIN 30 60 | #define TIMER3_ICP_PIN 29 61 | 62 | // Teensy-LC 63 | // 64 | #elif defined(__MKL26Z64__) 65 | #define TIMER1_A_PIN 16 66 | #define TIMER1_B_PIN 17 67 | #define TIMER1_ICP_PIN 17 68 | #define TIMER3_A_PIN 3 69 | #define TIMER3_B_PIN 4 70 | #define TIMER3_ICP_PIN 4 71 | 72 | // Arduino Mega 73 | // 74 | #elif defined(__AVR_ATmega1280__) || defined(__AVR_ATmega2560__) 75 | #define TIMER1_A_PIN 11 76 | #define TIMER1_B_PIN 12 77 | #define TIMER1_C_PIN 13 78 | #define TIMER3_A_PIN 5 79 | #define TIMER3_B_PIN 2 80 | #define TIMER3_C_PIN 3 81 | #define TIMER4_A_PIN 6 82 | #define TIMER4_B_PIN 7 83 | #define TIMER4_C_PIN 8 84 | #define TIMER4_ICP_PIN 49 85 | #define TIMER5_A_PIN 46 86 | #define TIMER5_B_PIN 45 87 | #define TIMER5_C_PIN 44 88 | #define TIMER3_ICP_PIN 48 89 | #define TIMER3_CLK_PIN 47 90 | 91 | // Arduino Leonardo, Yun, etc 92 | // 93 | #elif defined(__AVR_ATmega32U4__) 94 | #define TIMER1_A_PIN 9 95 | #define TIMER1_B_PIN 10 96 | #define TIMER1_C_PIN 11 97 | #define TIMER1_ICP_PIN 4 98 | #define TIMER1_CLK_PIN 12 99 | #define TIMER3_A_PIN 5 100 | #define TIMER3_ICP_PIN 13 101 | 102 | // Uno, Duemilanove, LilyPad, etc 103 | // 104 | #elif defined (__AVR_ATmega168__) || defined (__AVR_ATmega328P__) 105 | #define TIMER1_A_PIN 9 106 | #define TIMER1_B_PIN 10 107 | #define TIMER1_ICP_PIN 8 108 | #define TIMER1_CLK_PIN 5 109 | 110 | // Sanguino 111 | // 112 | #elif defined(__AVR_ATmega644P__) || defined(__AVR_ATmega644__) 113 | #define TIMER1_A_PIN 13 114 | #define TIMER1_B_PIN 12 115 | #define TIMER1_ICP_PIN 14 116 | #define TIMER1_CLK_PIN 1 117 | 118 | // Wildfire - Wicked Devices 119 | // 120 | #elif defined(__AVR_ATmega1284P__) && defined(WILDFIRE_VERSION) && WILDFIRE_VERSION >= 3 121 | #define TIMER1_A_PIN 5 // PD5 122 | #define TIMER1_B_PIN 8 // PD4 123 | #define TIMER1_ICP_PIN 6 // PD6 124 | #define TIMER1_CLK_PIN 23 // PB1 125 | #define TIMER3_A_PIN 12 // PB6 126 | #define TIMER3_B_PIN 13 // PB7 127 | #define TIMER3_ICP_PIN 9 // PB5 128 | #define TIMER3_CLK_PIN 0 // PD0 129 | #elif defined(__AVR_ATmega1284P__) && defined(WILDFIRE_VERSION) && WILDFIRE_VERSION < 3 130 | #define TIMER1_A_PIN 5 // PD5 131 | #define TIMER1_B_PIN 4 // PD4 132 | #define TIMER1_ICP_PIN 6 // PD6 133 | #define TIMER1_CLK_PIN 15 // PB1 134 | #define TIMER3_A_PIN 12 // PB6 135 | #define TIMER3_B_PIN 13 // PB7 136 | #define TIMER3_ICP_PIN 11 // PB5 137 | #define TIMER3_CLK_PIN 0 // PD0 138 | 139 | // Mighty-1284 - Maniacbug 140 | // 141 | #elif defined(__AVR_ATmega1284P__) 142 | #define TIMER1_A_PIN 12 // PD5 143 | #define TIMER1_B_PIN 13 // PD4 144 | #define TIMER1_ICP_PIN 14 // PD6 145 | #define TIMER1_CLK_PIN 1 // PB1 146 | #define TIMER3_A_PIN 6 // PB6 147 | #define TIMER3_B_PIN 7 // PB7 148 | #define TIMER3_ICP_PIN 5 // PB5 149 | #define TIMER3_CLK_PIN 8 // PD0 150 | 151 | #endif 152 | 153 | #endif 154 | -------------------------------------------------------------------------------- /examples/basic-example/basic-example.ino: -------------------------------------------------------------------------------- 1 | /** 2 | @example basic-example.ino 3 | @file basic-example.ino 4 | @author Evert Chin 5 | @brief Fast and Simple Stepper Driver 6 | 7 | * This file may be redistributed under the terms of the MIT license. 8 | * A copy of this license has been included with this distribution in the file LICENSE. 9 | */ 10 | 11 | //example of driving a nema 17 stepper motor with A4988 12 | #include "SimpleStepper.h" 13 | 14 | #define PIN_STEP 9 // stepper step pin 15 | #define PIN_DIR 10 // stepper dir pin 16 | #define MOTORSTEPS 3200 // 360/1.8 = 200 full steps * 16 microsteps = number of steps per revolution 17 | #define CLOCKWISE 1 // Rotation of the stepper motor, reverse if it is swapped 18 | #define ANTICW 0 // Rotation of the stepper motor, reverse if it is swapped 19 | 20 | SimpleStepper stepper(PIN_DIR, PIN_STEP); 21 | uint8_t counter = 0; 22 | 23 | void setup() { 24 | //initialize the object 25 | stepper.init(); 26 | } 27 | 28 | void loop() { 29 | 30 | //once the stepper finished stepping to remaining ticks/steps 31 | if(stepper.isStopped()){ 32 | 33 | //conter is even number 34 | if(counter % 2 == 0){ 35 | //do a full rotation clockwise at 20rpm 36 | stepper.step(MOTORSTEPS, CLOCKWISE, rpmToTickInterval(20)); 37 | } else { 38 | //do a full rotation clockwise at 10rpm 39 | stepper.step(MOTORSTEPS, ANTICW, rpmToTickInterval(10)); 40 | } 41 | 42 | ++counter; 43 | } 44 | 45 | if(counter == 100){ 46 | //stop whatever the stepper is doing 47 | stepper.stop(); 48 | } 49 | } 50 | 51 | //rpm to stepper tick in micro seconds 52 | long rpmToTickInterval(long targetRPM){ 53 | // rotation per sec = targetRPM/60 54 | float stepsPerSecond = (float) targetRPM/60 * MOTORSTEPS; 55 | long pulseInMicroseconds = (long) (1000000L/stepsPerSecond) / 2; 56 | 57 | return pulseInMicroseconds; 58 | } 59 | -------------------------------------------------------------------------------- /keywords.txt: -------------------------------------------------------------------------------- 1 | SimpleStepper KEYWORD1 2 | Interrupt KEYWORD2 3 | Timer1 KEYWORD2 4 | Fast KEYWORD2 5 | StepperDriver KEYWORD2 6 | 1/128 KEYWORD2 7 | Microsteps KEYWORD2 8 | highspeed KEYWORD2 9 | --------------------------------------------------------------------------------