Note-11.arduino的volatile修饰符和中断

描述

Volatile是一个变量修饰符(关键字),它通常被用在变量的数据类型之前,用来修改编译器和后续程序处理变量的方式。声明volatile变量是对编译器的一个指令。

编译器是将C/C++代码转换为机器代码的软件,而机器代码是Arduino中Atmega芯片的真正执行的指令。

具体来说,它指示编译器从RAM而不是从存储寄存器加载变量,存储寄存器是存储和操作程序变量的临时内存位置。在某些条件下,存储在寄存器中的变量的值可能不准确。

Volatile指示编译器从RAM而不是从存储寄存器加载变量

只要变量的值可以被它所在代码段之外的东西(比如并发执行的线程)更改,就应该将其声明为volatile。在Arduino中,唯一可能发生这种情况的地方是与中断相关的代码段,称为中断服务例程。

int 或 long volatiles

如果volatile变量大于一个字节(例如16位int或32位long类型),那么一个8位的微控制器不能一步读取该volatile变量。这意味着当主代码段(例如loop)读取变量的前8位时,中断可能已经改变了第二个8位的值。这将使得变量产生随机值。

对策:在读取变量时,需要禁用中断,这样就不会在读取时改变对应位的值。有几种方法可以做到这一点:

  • noInterrupts():禁用中断,可以使用interrupts()重新启用。
    • 中断允许某些重要的任务在后台发生,并默认启用。
    • 当中断被禁用时,一些功能将无法工作,传入的通信可能会被忽略。
    • 中断可能会稍微打乱代码的时序,因此可能对特别关键的代码部分需要禁用中断。

void setup() {}

void loop() {

noInterrupts();

// 关键的,时间敏感的代码禁用中断

interrupts();

// 其他代码开启中断

}

  • 使用ATOMIC_BLOCK宏。要访问大于微控制器8位数据总线大小的变量,可以使用ATOMIC_BLOCK宏。宏确保在原子操作中读取变量,也就是说,在读取它的时候,它的内容不能被改变。

#include // 包含ATOMIC_BLOCK宏库.

volatile int input_from_interrupt;

// loop()函数中代码段

ATOMIC_BLOCK(ATOMIC_RESTORESTATE) {

// 中断阻塞代码(连续的原子操作不会被中断)

int result = input_from_interrupt;

}

Arduino C中volatile总结

  • 告诉编译器被修饰的变量不能被优化
  • 被修饰变量的读和写是直接通过内存,不能通过寄存器或缓存
  • 中断中的变量尽量使用volatile修饰

volatile示例

  • volatile修饰符确保对state的更改在loop()中立即可见。
  • 如果没有volatile修饰符,state可能会在进入函数时被加载到寄存器中,直到函数结束时才会更新。

// 如果输入发生改变则闪烁LED

volatile byte changed = 0;

void setup() {

pinMode(LED_BUILTIN, OUTPUT);

attachInterrupt(digitalPinToInterrupt(2), toggle, CHANGE);

}

void loop() {

if (changed == 1) {

// toggle()函数在中断服务程序中调用

// 将changed 重置为0

changed = 0;

// LED闪烁 200 ms

digitalWrite(LED_BUILTIN, HIGH);

delay(200);

digitalWrite(LED_BUILTIN, LOW);

}

}

// 中断服务程序ISR

void toggle() {

changed = 1;

}

attachInterrupt()函数

语法

attachInterrupt(digitalPinToInterrupt(pin), ISR, mode)

  • interrupt:中断号,非引脚号,因此通常使用digitalPinToInterrupt(pin)获取。
  • ISR:中断服务程序,当发生中断时调用,不能接受任何形参,也不返回任何值。
  • mode:定义中断触发的方式
    • LOW:引脚低电平时触发中断(Due,Zero支持高电平触发)
    • CHANGE:引脚状态发生改变时触发中断,从高到低或从低到高发生变化。
    • RISING:从低到高变化时触发中断
    • FALLING:从高到低变化时触发中断

描述

attachInterrupt()的第一个参数是一个中断号。通常应该使用digitalPinToInterrupt(pin)来将实际的数字引脚转换为特定的中断号码。例如,如果连接到pin 3,使用digitalPinToInterrupt(3)作为attachInterrupt()的第一个参数。

应该使用digitalPinToInterrupt(pin)来将实际的数字引脚转换为特定的中断号码

arduino主板支持硬件中断的引脚

arduino主板支持硬件中断的引脚

使用中断

在微控制器程序中,中断可用于事件自动触发,并有助于解决定时问题。比如旋转编码器或监视用户的输入。

如果想确保一个程序总是能从旋转编码器捕获脉冲,不会漏掉一个脉冲,那么如果需要做其他事情就会非常棘手,因为这个程序需要不断地轮询编码器的传感器,以便在脉冲发生时准确捕获。其他传感器也有类似的情况,比如试图捕捉点击的声音传感器,或试图捕捉硬币掉落的红外传感器(光中断器)。对于这些情况,使用中断可以释放微控制器来完成一些其他工作,同时不丢失输入。

关于中断服务程序ISR(About Interrupt Service Routines)

ISR是一种特殊的函数,它有一些其他函数没有的独特限制。ISR不能有任何参数,不能返回任何值。

  • 一般来说,ISR应该尽可能的短和快。
  • 如果示例包含多个isr,那么一次只能运行一个,其他中断将在当前中断结束后执行,其顺序取决于它们的优先级。
  • millis()依赖于中断计数,所以它永远不会在ISR内递增。
  • delay()需要中断才能工作,所以如果在ISR内部调用,它将不起作用。
  • Micros()开始能正常工作,但在1-2毫秒后开始表现不稳定。
  • delayMicroseconds()不使用任何计数器,所以它将正常工作。
  • 通常全局变量用于在ISR和主程序之间传递数据。为了确保ISR和主程序之间共享的变量被正确地更新,将它们声明为volatile。

示例代码

const byte ledPin = 13;

const byte interruptPin = 2;

volatile byte state = LOW;

void setup() {

pinMode(ledPin, OUTPUT);

pinMode(interruptPin, INPUT_PULLUP);

attachInterrupt(digitalPinToInterrupt(interruptPin), blink, CHANGE);

}

void loop() {

digitalWrite(ledPin, state);

}

void blink() {

state = !state;

}

发表评论
留言与评论(共有 0 条评论) “”
   
验证码:

相关文章

推荐文章