使用 Servo 和 SoftwareSerial 时出现定时器冲突问题

Having timer collision issue with using Servo and SoftwareSerial

我在 Arduino Nano 板上使用 Servo.h 和 SoftwareSerial.h 时遇到定时器冲突问题。现在我需要 2 对串行引脚,通过在我的笔记本电脑上使用 NFC 模块和 Arduino 的串行监视器。

如果我得到的信息没有错的话,Nano board 中有三个定时器(timer0,timer1,timer2)。我听说 timer1 是 16 位定时器,Servo.h 和 SoftwareSerial.h 在 Nano 板上同时使用 timer1,这就是为什么他们无法避免定时器冲突问题。

然而我需要同时使用这两个头文件而不会发生计时器冲突。遇到这种情况,我该怎么办?我是否必须修改 Servo.h 文件才能不使用 timer1?

因为我对伺服电机所做的一切都是控制 angular 位置。

因此,在这个项目中使用 16 位定时器是没有用的,除非我使用 PWM 控制。

所以,在这一点上,我想使用 timer0 或 timer2(都是 8 位定时器)而不是使用 timer1。否则,Servo 和 Software 头文件中的 timer1 将发生冲突。 以下是我使用的源代码。

const unsigned char wake[24]={
  0x55, 0x55, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0x03, 0xfd, 0xd4, 0x14, 0x01, 0x17, 0x00};//wake up NFC module
const unsigned char firmware[9]={
  0x00, 0x00, 0xFF, 0x02, 0xFE, 0xD4, 0x02, 0x2A, 0x00};//
const unsigned char tag[11]={
  0x00, 0x00, 0xFF, 0x04, 0xFC, 0xD4, 0x4A, 0x01, 0x00, 0xE1, 0x00};//detecting tag command
const unsigned char std_ACK[25] = {
  0x00, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0x00, 0x00, 0xFF, 0x0C, 
0xF4, 0xD5, 0x4B, 0x01, 0x01, 0x00, 0x04, 0x08, 0x04, 0x00, 0x00, 0x00, 0x00, 0x4b, 0x00};
unsigned char old_id[5];

unsigned char receive_ACK[25];//Command receiving buffer
//int inByte = 0;               //incoming serial byte buffer

#if defined(ARDUINO) && ARDUINO >= 100
#include "Arduino.h"
#define print1Byte(args) mySerial.write(args)
#define print1lnByte(args)  mySerial.write(args),mySerial.println()
#else
#include "WProgram.h"
#define print1Byte(args) mySerial.print(args,BYTE)
#define print1lnByte(args)  mySerial.println(args,BYTE)
#endif

#include <Servo.h>
#include <NeoSWSerial.h>

NeoSWSerial mySerial(5,6);

volatile uint32_t newlines = 0UL;

Servo sv;

int pos1=0; //initial value = 93 degree
int pos2=180;
int sw1 = 4;

static void handleRxChar( uint8_t c )
    {
      if (c == '\n')
        newlines++;
    }

void setup(){
  mySerial.attachInterrupt( handleRxChar );
  pinMode(sw1, INPUT_PULLUP);
  sv.attach(9);
  Serial.begin(9600);  // open serial with PC
  mySerial.begin(9600);  //open serial1 with device
  //Serial2.begin(115200);
  wake_card();
  delay(100);
  read_ACK(15);
  delay(100);
  display(15);
}

void loop(){
  send_tag(); 
  read_ACK(25);
  delay(100);
  if (!cmp_id ()) {   //nfc tag
    if (test_ACK ()) {
      display (25);
      sv.write(pos1);
      delay(2500);
      sv.write(pos2);
    }
  }
  else if (cmp_id()){   // switch
    if(digitalRead(sw1) == LOW){
      sv.write(pos1);   // waits 15ms for the servo to reach the position
      }
    else if(digitalRead(sw1) == HIGH){
      sv.write(pos2);
    }
  }
  copy_id ();
}

void copy_id (void) {//save old id
  int ai, oi;
  for (oi=0, ai=19; oi<5; oi++,ai++) {
    old_id[oi] = receive_ACK[ai];
  }
}

char cmp_id (void){//return true if find id is old
  int ai, oi;
  for (oi=0,ai=19; oi<5; oi++,ai++) {
    if (old_id[oi] != receive_ACK[ai])
      return 0;
  }
  return 1;
}

int test_ACK (void) {// return true if receive_ACK accord with std_ACK
  int i;
  for (i=0; i<19; i++) {
    if (receive_ACK[i] != std_ACK[i])
      return 0;
  }
  return 1;
}

void send_id (void) {//send id to PC
  int i;
  Serial.print ("ID: ");
  for (i=19; i<= 23; i++) {
    Serial.print (receive_ACK[i], HEX);
    Serial.print (" ");
  }
  Serial.println ();
}

void UART1_Send_Byte(unsigned char command_data){//send byte to device
  print1Byte(command_data);
#if defined(ARDUINO) && ARDUINO >= 100
  mySerial.flush();// complete the transmission of outgoing serial data 
#endif
} 

void UART_Send_Byte(unsigned char command_data){//send byte to PC
  Serial.print(command_data,HEX);
  Serial.print(" ");
} 

void read_ACK(unsigned char temp){//read ACK into reveive_ACK[]
  unsigned char i;
  for(i=0;i<temp;i++) {
    receive_ACK[i]= mySerial.read();
  }
}

void wake_card(void){//send wake[] to device
  unsigned char i;
  for(i=0;i<24;i++) //send command
    UART1_Send_Byte(wake[i]);
}

void firmware_version(void){//send fireware[] to device
  unsigned char i;
  for(i=0;i<9;i++) //send command
    UART1_Send_Byte(firmware[i]);
}

void send_tag(void){//send tag[] to device
  unsigned char i;
  for(i=0;i<11;i++) //send command
    UART1_Send_Byte(tag[i]);
}

void display(unsigned char tem){//send receive_ACK[] to PC
  unsigned char i;
  for(i=0;i<tem;i++) //send command
    UART_Send_Byte(receive_ACK[i]);
  Serial.println();
}

总结

我在使用 Servo.h 和 SoftwareSerial.h 时遇到计时器冲突问题。

他们同时共享 timer1。为了避免这种碰撞问题并使这两者正常工作,我应该怎么做?我应该对源代码做些什么吗,比如添加几行代码或修改那些头文件?

通常,我会建议 AltSoftSerial 作为 SoftwareSerial 的替代方案(阅读更多 here),但它也与伺服库的 TIMER1 使用冲突。它只能用于两个特定的引脚。

我认为我的 NeoSWSerial 可以解决问题。它重新使用 micros() 时钟 (TIMER0) 和 Pin Change 中断来实现软件串行端口。这将其限制为波特率 9600、19200 和 38400,但它比 SoftwareSerial 效率高得多。它可以用在任意两个引脚上。


更新

我不建议在 115200 使用软件串行端口,因为它在 38400 以上可能不可靠。您可以向 NFC 模块发送波特率配置命令以将其设置为较低的速率。

顺便说一句,如果你正在发送信息(不仅仅是接收),所有软件串口库在传输过程中都禁用中断,except AltSoftSerial...你可以使用。请注意这一点,因为当您在 NeoSWSerial.

上传输时,它可能会影响您的伺服器

此外,请确保您使用的是伺服的 PWM 引脚之一。如果伺服库正在使用软件创建 PWM 信号(就像软件串行端口一样),CPU 将没有时间做其他事情。

NFC模块放在硬件串口上可能会更好,Serial。对于调试打印,使用 NeoSWSerial 连接到 TTL 串行到 USB 转换器。然后打开该转换器的 COM 端口上的串行监视器。稍后删除调试,因为传输禁用中断。

还有其他带有附加 UART 的开发板。例如,Arduino Leo (ATMega32U4 MCU) 有一个额外的串行端口 Serial1,您可以将其用于 NFC。 Serial 仍可用于调试打印。