Arduino 系列:Arduino Due 系列 (基于 ATSAM3X8E)_(8).定时器与中断
定时器与中断
定时器和中断是嵌入式系统中非常重要的概念,它们在Arduino Due中也有广泛的应用。定时器用于精确控制时间间隔,而中断则用于在特定事件发生时中断当前的程序执行,转而执行特定的中断服务例程(ISR)。本节将详细介绍Arduino Due中的定时器和中断的原理和使用方法,并通过具体的代码示例来说明它们的应用。
定时器
定时器的工作原理
定时器是一种硬件模块,用于产生精确的时间间隔。Arduino Due基于ATSAM3X8E微控制器,提供了多个定时器模块。这些定时器可以用于各种定时任务,例如生成定时脉冲、控制LED闪烁、实现延时功能等。
ATSAM3X8E中的定时器主要分为以下几种:
TC(Timer Counter):通用定时器/计数器,适用于简单的定时任务。
TCC(Timer Counter with Capture/Compare):具有捕获/比较功能的定时器,适用于更复杂的定时和计数任务。
RTC(Real-Time Clock):实时时钟,用于长时间计时和日历功能。
定时器的配置
在Arduino Due中,使用定时器通常需要以下步骤:
-
选择定时器模块:根据任务需求选择合适的定时器模块。
-
配置定时器模式:设置定时器的工作模式,例如波形生成模式、捕获模式等。
-
设置定时器时钟源:选择定时器的时钟源,可以是内部时钟或外部时钟。
-
设置定时器周期:计算并设置定时器的周期,使其在特定的时间间隔内产生中断。
-
启用定时器中断:配置中断并启用定时器中断。
-
编写中断服务例程:定义中断发生时执行的代码。
代码示例:使用TC定时器生成1秒中断
下面是一个使用TC定时器生成1秒中断的示例代码。我们将使用TC0定时器来实现这个功能。
// 定义定时器通道
#define TC_CHANNEL 0
// 定义定时器时钟源频率
#define TC_CLOCK_SOURCE 48000000 // 48 MHz
// 定义定时器周期(1秒)
#define TC_PERIOD 1000000 // 1秒 = 1000000微秒
// 定义定时器中断服务例程
void TC0_Handler() {
// 清除中断标志
TC_GetStatus(TC3, TC_CHANNEL);
// 执行中断处理代码
static uint32_t count = 0;
count++;
Serial.print("中断次数: ");
Serial.println(count);
}
void setup() {
// 初始化串口
Serial.begin(115200);
// 配置TC0定时器
pmc_set_writeprotect(false); // 取消写保护
pmc_enable_periph_clk(ID_TC3); // 启用TC3时钟
// 选择波形生成模式
TcChannel *tcChannel = TC_ChannelPtr(TC3, TC_CHANNEL);
TC_Configure(tcChannel, TC_CMR_WAVE | TC_CMR_WAVSEL_UP | TC_CMR_TCCLKS_TIMER_CLOCK1);
// 设置定时器周期
uint32_t prescaler = 2; // 预分频器
uint32_t period = TC_PERIOD * prescaler * (TC_CLOCK_SOURCE / 8);
TC_SetRC(tcChannel, period);
// 启用中断
TC_EnableIt(tcChannel, TC_IER_CPCS);
NVIC_EnableIRQ(TC3_IRQn); // 启用中断
NVIC_SetPriority(TC3_IRQn, 0); // 设置中断优先级
// 启动定时器
TC_Start(tcChannel);
}
void loop() {
// 主程序
// 在这里可以执行其他任务
}
代码解释
-
定义定时器通道和时钟源频率:
-
TC_CHANNEL
:选择TC0定时器的通道。 -
TC_CLOCK_SOURCE
:定时器的时钟源频率,为48 MHz。 -
TC_PERIOD
:设置定时器周期为1秒,即1000000微秒。 -
定义中断服务例程:
-
TC0_Handler
:这是一个中断服务例程,当定时器产生中断时,会调用这个函数。 -
TC_GetStatus(TC3, TC_CHANNEL)
:清除定时器的中断标志,确保中断可以再次触发。 -
count
:静态变量,用于记录中断次数。 -
初始化串口:
Serial.begin(115200)
:初始化串口通信,波特率为115200。-
配置TC0定时器:
-
pmc_set_writeprotect(false)
:取消写保护,允许对定时器寄存器进行写操作。 -
pmc_enable_periph_clk(ID_TC3)
:启用TC3定时器的时钟。 -
TC_Configure
:配置定时器的工作模式,选择波形生成模式(TC_CMR_WAVE
)、上升沿波形(TC_CMR_WAVSEL_UP
)和时钟源(TC_CMR_TCCLKS_TIMER_CLOCK1
)。 -
设置定时器周期:
-
prescaler
:预分频器,设置为2。 -
period
:计算定时器周期,单位为计数器时钟周期。 -
TC_SetRC
:设置定时器的周期。 -
启用中断:
-
TC_EnableIt
:启用定时器的中断。 -
NVIC_EnableIRQ
:启用定时器中断。 -
NVIC_SetPriority
:设置中断的优先级。 -
启动定时器:
TC_Start
:启动定时器。
中断
中断的工作原理
中断是一种机制,当特定事件发生时(例如定时器溢出、外部输入信号变化等),中断请求会被发送到处理器,处理器会暂停当前的程序执行,转而执行中断服务例程(ISR)。中断处理完成后,处理器会返回到被中断的任务继续执行。
在Arduino Due中,中断可以分为以下几类:
外部中断:由外部输入信号触发。
定时器中断:由定时器模块触发。
ADC中断:由模拟数字转换器触发。
UART中断:由串口通信模块触发。
中断的配置
在Arduino Due中,配置中断通常需要以下步骤:
-
启用中断:使用
NVIC_EnableIRQ
函数启用特定的中断。 -
设置中断优先级:使用
NVIC_SetPriority
函数设置中断的优先级。 -
编写中断服务例程:定义中断发生时执行的代码。
-
注册中断服务例程:在
setup
函数中注册中断服务例程。
代码示例:使用外部中断检测按钮状态
下面是一个使用外部中断检测按钮状态的示例代码。我们将使用外部中断0来检测按钮的按下事件。
// 定义按钮引脚
#define BUTTON_PIN 2
// 定义中断服务例程
void extInterrupt0() {
static uint32_t count = 0;
count++;
Serial.print("按钮按下次数: ");
Serial.println(count);
}
void setup() {
// 初始化串口
Serial.begin(115200);
// 配置按钮引脚
pinMode(BUTTON_PIN, INPUT_PULLUP);
// 启用外部中断
attachInterrupt(BUTTON_PIN, extInterrupt0, FALLING);
// 设置中断优先级
NVIC_SetPriority(EXTINT_IRQn, 0);
}
void loop() {
// 主程序
// 在这里可以执行其他任务
}
代码解释
-
定义按钮引脚:
BUTTON_PIN
:定义按钮连接的引脚,这里选择引脚2。-
定义中断服务例程:
-
extInterrupt0
:这是一个中断服务例程,当按钮按下时,会调用这个函数。 -
count
:静态变量,用于记录按钮按下的次数。 -
初始化串口:
Serial.begin(115200)
:初始化串口通信,波特率为115200。-
配置按钮引脚:
pinMode(BUTTON_PIN, INPUT_PULLUP)
:将引脚2配置为输入模式,并启用内部上拉电阻。-
启用外部中断:
-
attachInterrupt(BUTTON_PIN, extInterrupt0, FALLING)
:将引脚2的下降沿中断(按钮按下)与extInterrupt0
中断服务例程关联。 -
NVIC_SetPriority(EXTINT_IRQn, 0)
:设置外部中断的优先级。 -
主程序:
loop
:主程序中可以执行其他任务,中断服务例程会在按钮按下时被调用。
定时器和中断的综合应用
综合应用示例:使用定时器和中断控制LED闪烁
下面是一个使用定时器和中断控制LED闪烁的综合示例代码。我们将使用TC定时器生成1秒中断,并在中断服务例程中切换LED的状态。
// 定义LED引脚
#define LED_PIN 13
// 定义定时器通道
#define TC_CHANNEL 0
// 定义定时器时钟源频率
#define TC_CLOCK_SOURCE 48000000 // 48 MHz
// 定义定时器周期(500毫秒)
#define TC_PERIOD 500000 // 500毫秒 = 500000微秒
// 定义定时器中断服务例程
void TC0_Handler() {
// 清除中断标志
TC_GetStatus(TC3, TC_CHANNEL);
// 切换LED状态
digitalWrite(LED_PIN, !digitalRead(LED_PIN));
}
void setup() {
// 初始化串口
Serial.begin(115200);
// 配置LED引脚
pinMode(LED_PIN, OUTPUT);
digitalWrite(LED_PIN, LOW);
// 配置TC0定时器
pmc_set_writeprotect(false); // 取消写保护
pmc_enable_periph_clk(ID_TC3); // 启用TC3时钟
// 选择波形生成模式
TcChannel *tcChannel = TC_ChannelPtr(TC3, TC_CHANNEL);
TC_Configure(tcChannel, TC_CMR_WAVE | TC_CMR_WAVSEL_UP | TC_CMR_TCCLKS_TIMER_CLOCK1);
// 设置定时器周期
uint32_t prescaler = 2; // 预分频器
uint32_t period = TC_PERIOD * prescaler * (TC_CLOCK_SOURCE / 8);
TC_SetRC(tcChannel, period);
// 启用中断
TC_EnableIt(tcChannel, TC_IER_CPCS);
NVIC_EnableIRQ(TC3_IRQn); // 启用中断
NVIC_SetPriority(TC3_IRQn, 0); // 设置中断优先级
// 启动定时器
TC_Start(tcChannel);
}
void loop() {
// 主程序
// 在这里可以执行其他任务
}
代码解释
-
定义LED引脚:
LED_PIN
:定义LED连接的引脚,这里选择引脚13。-
定义定时器通道和时钟源频率:
-
TC_CHANNEL
:选择TC0定时器的通道。 -
TC_CLOCK_SOURCE
:定时器的时钟源频率,为48 MHz。 -
TC_PERIOD
:设置定时器周期为500毫秒,即500000微秒。 -
定义中断服务例程:
-
TC0_Handler
:这是一个中断服务例程,当定时器产生中断时,会调用这个函数。 -
digitalWrite(LED_PIN, !digitalRead(LED_PIN))
:切换LED的状态。 -
初始化串口:
Serial.begin(115200)
:初始化串口通信,波特率为115200。-
配置LED引脚:
-
pinMode(LED_PIN, OUTPUT)
:将引脚13配置为输出模式。 -
digitalWrite(LED_PIN, LOW)
:初始状态将LED关闭。 -
配置TC0定时器:
-
pmc_set_writeprotect(false)
:取消写保护,允许对定时器寄存器进行写操作。 -
pmc_enable_periph_clk(ID_TC3)
:启用TC3定时器的时钟。 -
TC_Configure
:配置定时器的工作模式,选择波形生成模式(TC_CMR_WAVE
)、上升沿波形(TC_CMR_WAVSEL_UP
)和时钟源(TC_CMR_TCCLKS_TIMER_CLOCK1
)。 -
设置定时器周期:
-
prescaler
:预分频器,设置为2。 -
period
:计算定时器周期,单位为计数器时钟周期。 -
TC_SetRC
:设置定时器的周期。 -
启用中断:
-
TC_EnableIt
:启用定时器的中断。 -
NVIC_EnableIRQ
:启用定时器中断。 -
NVIC_SetPriority
:设置中断的优先级。 -
启动定时器:
TC_Start
:启动定时器。
通过上述代码,Arduino Due会在每500毫秒产生一次中断,并在中断服务例程中切换LED的状态,从而实现LED的闪烁效果。
定时器和中断的高级应用
高级应用示例:使用多个定时器实现多任务调度
在某些应用场景中,可能需要使用多个定时器来实现多任务调度。下面是一个使用两个定时器(TC0和TC1)分别控制两个LED闪烁的示例代码。
// 定义LED引脚
#define LED1_PIN 13
#define LED2_PIN 12
// 定义定时器通道
#define TC_CHANNEL0 0
#define TC_CHANNEL1 1
// 定义定时器时钟源频率
#define TC_CLOCK_SOURCE 48000000 // 48 MHz
// 定义定时器周期
#define TC_PERIOD1 500000 // 500毫秒 = 500000微秒
#define TC_PERIOD2 1000000 // 1000毫秒 = 1000000微秒
// 定义定时器0中断服务例程
void TC0_Handler() {
// 清除中断标志
TC_GetStatus(TC3, TC_CHANNEL0);
// 切换LED1状态
digitalWrite(LED1_PIN, !digitalRead(LED1_PIN));
}
// 定义定时器1中断服务例程
void TC1_Handler() {
// 清除中断标志
TC_GetStatus(TC3, TC_CHANNEL1);
// 切换LED2状态
digitalWrite(LED2_PIN, !digitalRead(LED2_PIN));
}
void setup() {
// 初始化串口
Serial.begin(115200);
// 配置LED引脚
pinMode(LED1_PIN, OUTPUT);
pinMode(LED2_PIN, OUTPUT);
digitalWrite(LED1_PIN, LOW);
digitalWrite(LED2_PIN, LOW);
// 配置TC0定时器
pmc_set_writeprotect(false); // 取消写保护
pmc_enable_periph_clk(ID_TC3); // 启用TC3时钟
// 选择波形生成模式
TcChannel *tcChannel0 = TC_ChannelPtr(TC3, TC_CHANNEL0);
TC_Configure(tcChannel0, TC_CMR_WAVE | TC_CMR_WAVSEL_UP | TC_CMR_TCCLKS_TIMER_CLOCK1);
// 设置定时器周期
uint32_t prescaler = 2; // 预分频器
uint32_t period1 = TC_PERIOD1 * prescaler * (TC_CLOCK_SOURCE / 8);
TC_SetRC(tcChannel0, period1);
// 启用中断
TC_EnableIt(tcChannel0, TC_IER_CPCS);
NVIC_EnableIRQ(TC3_IRQn); // 启用中断
NVIC_SetPriority(TC3_IRQn, 0); // 设置中断优先级
// 配置TC1定时器
TcChannel *tcChannel1 = TC_ChannelPtr(TC3, TC_CHANNEL1);
TC_Configure(tcChannel1, TC_CMR_WAVE | TC_CMR_WAVSEL_UP | TC_CMR_TCCLKS_TIMER_CLOCK1);
// 设置定时器周期
uint32_t period2 = TC_PERIOD2 * prescaler * (TC_CLOCK_SOURCE / 8);
TC_SetRC(tcChannel1, period2);
// 启用中断
TC_EnableIt(tcChannel1, TC_IER_CPCS);
NVIC_EnableIRQ(TC4_IRQn); // 启用中断
NVIC_SetPriority(TC4_IRQn, 1); // 设置中断优先级
// 启动定时器
TC_Start(tcChannel0);
TC_Start(tcChannel1);
}
void loop() {
// 主程序
// 在这里可以执行其他任务
}
代码解释
-
定义LED引脚:
-
LED1_PIN
:定义LED1连接的引脚,这里选择引脚13。 -
LED2_PIN
:定义LED2连接的引脚,这里选择引脚12。 -
定义定时器通道和时钟源频率:
-
TC_CHANNEL0
:选择TC0定时器的通道。 -
TC_CHANNEL1
:选择TC1定时器的通道。 -
TC_CLOCK_SOURCE
:定时器的时钟源频率,为48 MHz。 -
TC_PERIOD1
:设置定时器0的周期为500毫秒,即500000微秒。 -
TC_PERIOD2
:设置定时器1的周期为1秒,即1000000微秒。 -
定义定时器中断服务例程:
-
TC0_Handler
:这是一个中断服务例程,当定时器0产生中断时,会调用这个函数。在这个函数中,我们切换LED1的状态。 -
TC1_Handler
:这是另一个中断服务例程,当定时器1产生中断时,会调用这个函数。在这个函数中,我们切换LED2的状态。 -
初始化串口:
Serial.begin(115200)
:初始化串口通信,波特率为115200。-
配置LED引脚:
-
pinMode(LED1_PIN, OUTPUT)
:将引脚13配置为输出模式。 -
pinMode(LED2_PIN, OUTPUT)
:将引脚12配置为输出模式。 -
digitalWrite(LED1_PIN, LOW)
:初始状态将LED1关闭。 -
digitalWrite(LED2_PIN, LOW)
:初始状态将LED2关闭。 -
配置TC0定时器:
-
pmc_set_writeprotect(false)
:取消写保护,允许对定时器寄存器进行写操作。 -
pmc_enable_periph_clk(ID_TC3)
:启用TC3定时器的时钟。 -
TC_Configure(tcChannel0, TC_CMR_WAVE | TC_CMR_WAVSEL_UP | TC_CMR_TCCLKS_TIMER_CLOCK1)
:配置定时器的工作模式,选择波形生成模式(TC_CMR_WAVE
)、上升沿波形(TC_CMR_WAVSEL_UP
)和时钟源(TC_CMR_TCCLKS_TIMER_CLOCK1
)。 -
prescaler
:预分频器,设置为2。 -
period1
:计算定时器0的周期,单位为计数器时钟周期。 -
TC_SetRC(tcChannel0, period1)
:设置定时器0的周期。 -
TC_EnableIt(tcChannel0, TC_IER_CPCS)
:启用定时器0的中断。 -
NVIC_EnableIRQ(TC3_IRQn)
:启用定时器3的中断。 -
NVIC_SetPriority(TC3_IRQn, 0)
:设置定时器3中断的优先级。 -
TC_Start(tcChannel0)
:启动定时器0。 -
配置TC1定时器:
-
TC_Configure(tcChannel1, TC_CMR_WAVE | TC_CMR_WAVSEL_UP | TC_CMR_TCCLKS_TIMER_CLOCK1)
:配置定时器的工作模式,选择波形生成模式(TC_CMR_WAVE
)、上升沿波形(TC_CMR_WAVSEL_UP
)和时钟源(TC_CMR_TCCLKS_TIMER_CLOCK1
)。 -
period2
:计算定时器1的周期,单位为计数器时钟周期。 -
TC_SetRC(tcChannel1, period2)
:设置定时器1的周期。 -
TC_EnableIt(tcChannel1, TC_IER_CPCS)
:启用定时器1的中断。 -
NVIC_EnableIRQ(TC4_IRQn)
:启用定时器4的中断。 -
NVIC_SetPriority(TC4_IRQn, 1)
:设置定时器4中断的优先级。 -
TC_Start(tcChannel1)
:启动定时器1。 -
主程序:
loop
:主程序中可以执行其他任务,中断服务例程会在定时器中断发生时被调用。
通过上述代码,Arduino Due会在每500毫秒和1秒分别产生一次中断,并在对应的中断服务例程中切换两个LED的状态,从而实现两个LED的闪烁效果。
总结
定时器和中断是嵌入式系统中非常重要的概念,它们在Arduino Due中也有广泛的应用。通过配置和使用定时器,可以实现精确的时间控制和延时功能。通过配置和使用中断,可以在特定事件发生时快速响应并执行相应的处理代码。本节通过具体的代码示例,详细介绍了如何在Arduino Due中使用定时器和中断,希望能够帮助读者更好地理解和应用这些概念。
进一步学习
定时器模式:了解更多关于不同定时器模式的详细信息,例如波形生成模式、捕获模式等。
中断优先级:深入学习中断优先级的设置和管理,了解如何处理多个中断请求的情况。
多任务调度:探索如何使用多个定时器和中断实现更复杂的多任务调度和实时控制功能。
通过不断学习和实践,读者可以掌握更多关于定时器和中断的高级应用技巧,进一步提升嵌入式系统的开发能力。
作者:kkchenkx