Note: All the files here


A time-lapse camera slider!

In this week’s assignment I will continue to collaborate on the time-lapse camera dolly with this team:

All the documentation related with the machine is available under the machine design documentation.

I will simply detail here, my main focus on this assignment within the group for this week, which was the development of an usable code for the machine using two endstops and an ISR.


An interrupt is a way microcontrollers have to let you respond to “external” events while doing something else in the meantime. I have documented it way more than this during the embedded programming week The current memory status and flags is saved and once the current machine line is done in the execution, the workflow is handed over to the defined function. This function, should follow the lines below (from Nick Gammon’s site):

  • Keep it short
  • Don’t use delay ()
  • Don’t do serial prints
  • Make variables shared with the main code volatile
  • Variables shared with main code may need to be protected by “critical sections” (see below)
  • Don’t try to turn interrupts off or on

For this assigmnent, we will be using an Arduino Uno with an ATMEGA328P-PU, which has a wide range of digital pins to be used. However, not all the pins can be used for an Interrupt with the attachInterrupt() function as stated in this link, and the hat we are using is definitively not using only those (2 and 3). Therefore, we would need to use the ISR in a different way, and this way is to work with the actual registers and define our own ISR.


For the ATMEGA328, below there is a list of available interrupts, in order of priority:

 1  Reset 
 2  External Interrupt Request 0  (pin D2)          (INT0_vect)
 3  External Interrupt Request 1  (pin D3)          (INT1_vect)
 4  Pin Change Interrupt Request 0 (pins D8 to D13) (PCINT0_vect)
 5  Pin Change Interrupt Request 1 (pins A0 to A5)  (PCINT1_vect)
 6  Pin Change Interrupt Request 2 (pins D0 to D7)  (PCINT2_vect)
 7  Watchdog Time-out Interrupt                     (WDT_vect)
 8  Timer/Counter2 Compare Match A                  (TIMER2_COMPA_vect)
 9  Timer/Counter2 Compare Match B                  (TIMER2_COMPB_vect)
10  Timer/Counter2 Overflow                         (TIMER2_OVF_vect)
11  Timer/Counter1 Capture Event                    (TIMER1_CAPT_vect)
12  Timer/Counter1 Compare Match A                  (TIMER1_COMPA_vect)
13  Timer/Counter1 Compare Match B                  (TIMER1_COMPB_vect)
14  Timer/Counter1 Overflow                         (TIMER1_OVF_vect)
15  Timer/Counter0 Compare Match A                  (TIMER0_COMPA_vect)
16  Timer/Counter0 Compare Match B                  (TIMER0_COMPB_vect)
17  Timer/Counter0 Overflow                         (TIMER0_OVF_vect)
18  SPI Serial Transfer Complete                    (SPI_STC_vect)
19  USART Rx Complete                               (USART_RX_vect)
20  USART, Data Register Empty                      (USART_UDRE_vect)
21  USART, Tx Complete                              (USART_TX_vect)
22  ADC Conversion Complete                         (ADC_vect)
23  EEPROM Ready                                    (EE_READY_vect)
24  Analog Comparator                               (ANALOG_COMP_vect)
25  2-wire Serial Interface  (I2C)                  (TWI_vect)
26  Store Program Memory Ready                      (SPM_READY_vect)

The D2 and D3 pins are the ones mentioned in the Arduino documentation. However, we will be using the pins D8 to D13 with the PCINT0_vect services. For that we will have to write our own strategy, and to make it right we used the one from grbl, first by defining an interrupt function on the PCINT0_vect pins:

#define LIMIT_INT_vect   PCINT0_vect 

ISR(LIMIT_INT_vect) // DEFAULT: Limit pin change interrupt process. 

uint8_t state = limits_get_state();
  switch (state) {
      case 1:
        dir = false;
      case 0:
        dir = true;

This function checks the status of the pins whenever triggered via the function limits_get_state():

uint8_t limits_get_state()
  uint8_t limit_state = 2;

  if (digitalRead(9)==HIGH && digitalRead(10)==LOW){
    limit_state = 1;
  if (digitalRead(9) == LOW && digitalRead(10)==HIGH) {
    limit_state = 0;

  // The first time we home, we don't do it again
  if (!home){
    home = true;

This surely can be done in a much more efficient way, but it serves our purposes: we are checking everytime an ISR is coming on the digital pins of PCINT0vect which one of the two are HIGH, either the pin 9 or the pin 10. In case of not having done the _homing yet, we set the home flag to true, to change the movement settings from fast to slow. The variable contaning which direction is the motor turning is changed accordinly and is sent in the main loop:

void loop(){

  //While not homed, move fast!
  while (home==false) {
    move(dir,X_DIR, X_STP, stepsFast, delayTimeFast);

  //Now move nicely
  move(dir, X_DIR, X_STP, stepsSlow, delayTimeSlow); //X, Clockwise


Finally, as a remark, the registers are enabled in the setup() function as defined below:

#define LIMIT_DDR        DDRB
#define LIMIT_PIN        PINB
#define LIMIT_PORT       PORTB
#define X_LIMIT_BIT      1  // Uno Digital Pin 9
#define Y_LIMIT_BIT      2  // Uno Digital Pin 10
#define LIMIT_MASK ((1<<X_LIMIT_BIT)|(1<<Y_LIMIT_BIT)) // All limit bits
#define LIMIT_INT        PCIE0  // Pin change interrupt enable pin
#define LIMIT_PCMSK      PCMSK0 // Pin change interrupt register

void limits_init() 
  LIMIT_DDR &= ~(LIMIT_MASK); // Set as input pins

  LIMIT_PORT |= (LIMIT_MASK);  // Enable internal pull-up resistors. Normal high operation.

  LIMIT_PCMSK |= LIMIT_MASK; // Enable specific pins of the Pin Change Interrupt
  PCICR |= (1 << LIMIT_INT); // Enable Pin Change Interrupt

And the final results can be checked below!