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.
()
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\$