单片机与定时器/计数器的应用实例

技术深度剖析 2020-07-28 ⋅ 28 阅读

在单片机(Microcontroller)的应用中,定时器/计数器(Timer/Counter)是一种非常重要的设备。它可以用来测量时间、生成精确的延迟、实现时序控制等功能。本文将介绍一些定时器/计数器在实际应用中的使用案例,并探讨其更多的用途。

1. 定时功能

定时功能是定时器/计数器最常见的应用之一。在许多实际场景中,我们需要按照预定的时间间隔执行某些操作。比如,我们想让一个LED灯每隔1秒闪烁一次,或者让一个电机每隔10毫秒旋转一次。这时候,定时器/计数器就可以派上用场了。

以传统的8位单片机ATmega328P为例,它内部拥有3个定时器/计数器,分别是Timer/Counter0、Timer/Counter1和Timer/Counter2。通过适当地设置定时器/计数器的配置寄存器,我们可以控制其计数频率和定时时间。代码示例如下:

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

#define F_CPU 16000000UL  // CPU频率为16MHz
#define LED_PIN  PB5      // LED引脚位于引脚B5上

void init_timer() {
  TCCR1B |= (1 << CS12) | (1 << CS10);  // 设置定时器1的分频为1024
  TIMSK1 |= (1 << TOIE1);               // 开启定时器1溢出中断
  sei();                               // 允许中断
}

void delay_ms(uint16_t ms) {
  while (ms--) {
    TCNT1 = 49912;                        // 重载定时器1计数值,产生1ms的延迟
    while (!(TIFR1 & (1 << TOV1)));       // 等待定时器1溢出
    TIFR1 |= (1 << TOV1);                // 清除定时器1溢出标志位
  }
}

int main() {
  DDRB |= (1 << LED_PIN);  // 设置LED引脚为输出

  init_timer();           // 初始化定时器1
  while (1) {
    PORTB ^= (1 << LED_PIN);    // LED引脚取反
    delay_ms(1000);             // 1秒延迟
  }

  return 0;
}

上述代码使用了ATmega328P的Timer/Counter1来实现精确的1秒定时功能。通过设置定时器1的分频为1024以及重载计数值49912,就可以实现每1毫秒产生一次定时器溢出中断,从而累积到1秒。在定时器1溢出中断中,我们将LED引脚取反,实现闪烁效果。

注意,以上代码使用了avr-libc库,因此需要进行相应的头文件和链接库的引入。另外,本文只是简单示范了定时器的应用,实际使用中还需要考虑更多的细节,例如定时器溢出时间的误差,定时器中断处理的优先级等等。

2. 定时采样

除了定时功能,定时器/计数器还可以用于定时采样。在许多实时控制系统中,需要周期性地对传感器数据进行采样,并根据采样值进行相应的控制。定时器/计数器可以帮助我们实现这一功能。

假设我们有一个温度传感器,每隔10毫秒采集一次温度值,并通过串口发送给上位机。我们可以使用定时器/计数器来控制采样频率,并在定时器溢出中断中执行数据采集和发送操作。示例代码如下:

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

#define F_CPU 16000000UL  // CPU频率为16MHz

volatile uint16_t temperature = 0;   // 存储温度值

void init_timer() {
  TCCR1B |= (1 << CS12) | (1 << CS10);  // 设置定时器1的分频为1024
  TIMSK1 |= (1 << TOIE1);               // 开启定时器1溢出中断
  sei();                               // 允许中断
}

void init_uart() {
  UBRR0H = 0;
  UBRR0L = 103;                                // 设置波特率为9600
  UCSR0B |= (1 << TXEN0) | (1 << RXEN0);       // 开启发送和接收
  UCSR0C |= (1 << UCSZ00) | (1 << UCSZ01);     // 设置数据位为8位
}

void transmit_uart(uint16_t data) {
  while (!(UCSR0A & (1 << UDRE0)));   // 等待发送缓冲器为空
  UDR0 = data;                       // 发送数据
}

ISR(TIMER1_OVF_vect) {
  temperature = read_temperature();  // 读取温度值
  transmit_uart(temperature);        // 发送温度值
}

int main() {
  init_timer();   // 初始化定时器1
  init_uart();    // 初始化串口
  
  while (1) {
    // 在主循环中处理其他任务
  }

  return 0;
}

需要注意的是,上述代码中的read_temperature()函数应该由具体的传感器模块提供,用于读取实际的温度值。发送函数transmit_uart()可以根据具体的串口驱动库进行修改。

3. 时序控制

时序控制是单片机应用中另一个常见的使用案例。通过定时器/计数器的设置,我们可以实现复杂的时序控制逻辑。例如,让一个步进电机按照指定的速度和步数旋转,或者让LED灯依次点亮不同的颜色。

以步进电机控制为例,假设我们有一个四相四线步进电机,如何以指定的速度和步数控制它的旋转?这就需要使用定时器/计数器来控制电机的驱动信号产生。

例如,我们使用Timer/Counter2作为定时器,并设置其分频为64。通过调整OCR2A寄存器的值,可以改变驱动信号的频率,从而控制步进电机的旋转速度。示例代码如下:

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

#define F_CPU 16000000UL          // CPU频率为16MHz
#define MOTOR_PULSE_PIN  PB5      // 步进电机脉冲引脚位于引脚B5上
#define MOTOR_DIR_PIN    PB4      // 步进电机方向引脚位于引脚B4上
#define STEPS_PER_REV    200      // 步进电机每转一圈需要的脉冲数
#define SPEED            1000     // 步进电机的旋转速度,单位:RPM

volatile uint16_t pulse_count = 0;   // 步进电机脉冲计数器
volatile uint16_t step_count = 0;    // 步进电机步数计数器

void init_timer() {
  TCCR2A |= (1 << COM2A0) | (1 << WGM21);   // 配置定时器2的工作模式为CTC
  TCCR2B |= (1 << CS22);                    // 设置定时器2的分频为64
  TIMSK2 |= (1 << OCIE2A);                  // 开启定时器2比较匹配A中断
  OCR2A = (F_CPU / 64) / (STEPS_PER_REV * SPEED / 60) - 1;   // 计算比较匹配A值
  sei();                                    // 允许中断
}

ISR(TIMER2_COMPA_vect) {
  if (step_count < STEPS_PER_REV) {
    PORTB ^= (1 << MOTOR_PULSE_PIN);  // 脉冲引脚取反
    pulse_count++;                    // 脉冲计数器加1
    if (pulse_count == 2) {
      PORTB ^= (1 << MOTOR_DIR_PIN);  // 每连续两个脉冲,方向引脚取反
      pulse_count = 0;                // 重置脉冲计数器
      step_count++;                   // 步数计数器加1
    }
  }
}

int main() {
  DDRB |= (1 << MOTOR_PULSE_PIN) | (1 << MOTOR_DIR_PIN);  // 设置脉冲和方向引脚为输出
  
  init_timer();                                          // 初始化定时器2

  while (1) {
    // 在主循环中处理其他任务
  }

  return 0;
}

上述代码使用Timer/Counter2作为定时器,并设置其工作模式为CTC。根据步进电机的旋转速度和步数,我们可以计算出比较匹配值,从而控制脉冲信号的频率。在比较匹配A中断中,我们反转脉冲引脚,并根据步数来判断是否需要反转方向引脚。

需要注意的是,上述代码中的步进电机驱动信号的产生只是简单示意,实际上还需要考虑如何将驱动信号和步进电机的线圈接口起来。

结论

本文介绍了单片机与定时器/计数器的应用实例。定时功能、定时采样和时序控制是它们的常见应用场景。通过合理配置定时器/计数器的参数,我们可以实现精确的定时控制、周期性的数据采集和复杂的时序控制逻辑。希望读者能够借助此文提供的案例和代码,更好地理解和应用定时器/计数器。


全部评论: 0

    我有话说: