1
\$\begingroup\$

I'm using an Atmel ATMega8 microcontroller to control a stepper motor. (Datasheet).

Timer1 (a 16-bit hardware counter) is used in CTC mode to modulate an IO pin to provide the stepping signal to the motor controller. This part works fine and I'm able to adjust the frequency of stepping with decent granularity.

I've configured Timer1's Compare Match A interrupt (TIMER1_COMPA_vect below) to fire each time the IO pin toggles (both rising and falling edges). This also works correctly.

Timer1 has a minimum frequency that it will operate at, below this frequency I simply want to kill the timer altogether. For this purpose I am attempting to disable the Timer1 clock source. See the TIMER_1_HALT macro in the source code below.

According to the datasheet, setting TTCR1B bits CS12, CS11 and CS10 all to zero should completely disable the timer and thus prevent the stepping signal from reaching the motor driver.

Problem is that setting these clock selection bits to zero, as prescribed in the datasheet, does not appear to prevent Timer1 from running. I'm still seeing the blue LED flash at 600 Hz, and the motor keeps running at the original speed it started at, indicating that the hardware timer is still operating.

Below is a bare-bones version of the project that demonstrates the problem.

#define F_CPU (8000000)

#include <avr/io.h>
#include <avr/interrupt.h>

#define RED_LED_PB6      (1 << PB6)
#define GREEN_LED_PD6    (1 << PD6)
#define BLUE_LED_PB7     (1 << PB7)

#define MOTOR_ENABLE_PB2 (1 << PB2)
#define MOTOR_STEP_PB1   (1 << PB1)
#define MOTOR_MS1_PC0    (1 << PC0)
#define MOTOR_MS2_PC1    (1 << PC1)
#define MOTOR_DIR_PD7    (1 << PD7)

#define GLOBAL_INTERRUPTS_ON  asm("sei\r\n");
#define GLOBAL_INTERRUPTS_OFF asm("cli\r\n");

volatile unsigned char forward_flag = 1;
float mapped_freq = 0;

const float MOTOR_MAX_FREQ = 30000.0f;
const float MOTOR_MIN_FREQ = 300.0f;

void Init_IO()
{
  DDRB |= RED_LED_PB6
       | BLUE_LED_PB7;

  DDRD |= GREEN_LED_PD6;

  DDRB |= MOTOR_ENABLE_PB2;
  DDRD |= MOTOR_DIR_PD7;
  
  DDRB |= MOTOR_STEP_PB1; //enable output for the step signal
}

#define TIMER_1_HALT TCCR1B &= ~(1 << CS12) | (1 << CS11) | (1 << CS10);
#define TIMER_1_RUN  TCCR1B |=  (0 << CS12) | (0 << CS11) | (1 << CS10);

void Init_Timer1()
{
  TCCR1A = (0 << WGM11) | (0 << WGM10)   //CTC mode
         | (0 << COM1A1)| (1 << COM1A0); //toggle OC1A pin on compare match
 
  TCCR1B = (0 << WGM13) |(1 << WGM12)    //CTC mode
         //| (0 << CS12) | (0 << CS11) | (1 << CS10) //clock div by 1 (e.g. no prescaler)
         ;

  OCR1AH = 0xff; //16-bit register pair governs the output frequency
  OCR1AL = 0xff;
  
  //enable the Timer1 compare match A interrupt
  TIMSK |= (1 << OCIE1A);
}

ISR(TIMER1_COMPA_vect)
{
  PORTB |= BLUE_LED_PB7;
  PORTB &= ~BLUE_LED_PB7;
}

void Init_Timer2()
{
  //CTC mode, clock divided by 32
  TCCR2 |= (1 << WGM21) | (0 << WGM20) | (0 << CS22) | (1 << CS21) | (1 << CS20);
  OCR2 = 248; //the ISR will fire at about 1kHz
  TIMSK |= (1 << OCIE2); //enable the compare interrupt
}

volatile unsigned char timer2_wait_milliseconds = 0;
ISR(TIMER2_COMP_vect)
{
  if(timer2_wait_milliseconds) //if non-zero
    timer2_wait_milliseconds--; //decrement (stops at zero and doesn't wrap around)
}

void Wait_ms(unsigned char ms)
{
  //MAXIMUM wait is 255 milliseconds
  timer2_wait_milliseconds = ms;
  while(timer2_wait_milliseconds)
  {}
}

void Set_Motor_Frequency(float hz)
{
  if(hz >= 0.0f) //hz is positive
  {
    if(hz < MOTOR_MIN_FREQ)
    {
      //hold the motor
      TIMER_1_HALT
    }
    else
    {
      //frequency is POSITIVE and within valid range, so update Timer1
      
      mapped_freq = ((((float)F_CPU) / hz) / 2.0f) - 1; //scale the frequency to fit Timer1

      OCR1AH = (signed int)mapped_freq >> 8;
      OCR1AL = (signed int)mapped_freq & 0x00ff;

      PORTD |= MOTOR_DIR_PD7; //set forward direction
      forward_flag = 1;
      TIMER_1_RUN
    }
  }
  else //hz is negative
  {
    if(hz > -MOTOR_MIN_FREQ)
    {
      //hold the motor
      TIMER_1_HALT
    }
    else
    {
      //frequency is NEGATIVE and within valid range, so update Timer1
      
      mapped_freq = ((((float)F_CPU) / -hz) / 2.0f) - 1; //scale the frequency to fit Timer1

      OCR1AH = (signed int)mapped_freq >> 8;
      OCR1AL = (signed int)mapped_freq & 0x00ff;

      PORTD &= ~MOTOR_DIR_PD7; //set reverse direction
      forward_flag = 0;
      TIMER_1_RUN
    }
  }
}

int main(void)
{
  OSCCAL = 0xA4;  //tuned for 8 MHz exactly

  Init_IO();
  Init_Timer1(); //stepper motor instantaneous frequency generator
  Init_Timer2(); //simple millisecond delay timer for general use

  GLOBAL_INTERRUPTS_ON

  PORTB |= MOTOR_ENABLE_PB2;

  Set_Motor_Frequency(300.0f); //set the motor going slowly (works fine)
  Wait_ms(250);
  Set_Motor_Frequency(0.0f); //SHOULD stop the motor but it doesn't
  Wait_ms(250);
  
  while(1)
  {}
}

When the system powers up, the motor turns slowly but never stops, even though I'm setting the speed to zero 250 milliseconds later. I've tried other speeds too such as 600.0, but it seems that Timer1 doesn't want to stop, it just keeps going at whatever speed I start it at.

Why doesn't Timer1 stop triggering its interrupt when I set it's clock selection bits to 0?

I'm doing something boneheaded but I'm unable to fathom it.

\$\endgroup\$
5
  • 1
    \$\begingroup\$ Try to add a bracket () to the ~(1 << CS12) | (1 << CS11) | (1 << CS10); macro. Because now it seems that you are only clearing the CS12 bit. So the correct way to do it is this: ~((1 << CS12) | (1 << CS11) | (1 << CS10)); \$\endgroup\$
    – G36
    Commented Apr 30, 2023 at 16:46
  • \$\begingroup\$ @G36, the devil is in the detail! Yes, you are right, now it works. Make this into an answer and I'll accept it. Thank you, this was driving me crazy. Sometimes you just need another pair of eyes! \$\endgroup\$
    – Wossname
    Commented Apr 30, 2023 at 17:00
  • 1
    \$\begingroup\$ What type of motor driver you are using? \$\endgroup\$
    – G36
    Commented Apr 30, 2023 at 17:46
  • \$\begingroup\$ @G36, it's an EasyDriver, link: "schmalzhaus.com/EasyDriver" Version v44. I've used these for a few years on and off. I've attached a small heatsink to mine, just in case. \$\endgroup\$
    – Wossname
    Commented Apr 30, 2023 at 18:04
  • 1
    \$\begingroup\$ Thank for the info. \$\endgroup\$
    – G36
    Commented Apr 30, 2023 at 18:40

1 Answer 1

2
\$\begingroup\$

Looking into your code we can see that here:

#define TIMER_1_HALT TCCR1B &= ~(1 << CS12) | (1 << CS11) | (1 << CS10);

You forgot to add a bracket () into ~(1 << CS12) | (1 << CS11) | (1 << CS10)

Because now you are only clearing the CS12 bit in the TCCR1B register.

The correct way to do it looks like this:

 ~((1 << CS12) | (1 << CS11) | (1 << CS10));
\$\endgroup\$
0

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.