ISR выполняется дважды, если данные поступают через SoftwareSerial
Я использую Arduino Pro Mini для получения информации GPS с чипа u-blox NEO-M8N и отображения ее на OLED-дисплее Adafruit. NEO-M8N имеет выход PPS (импульс в секунду), который согласован с секундой GPS. Поскольку UART и десериализация медленные, я использую сигнал PPS, чтобы сделать отображение времени более точным. Сигнал PPS (импульс времени) и сообщение UART приходят следующим образом:
На моем Arduino SoftwareSerial RX/TX находится на выводе 7/8 (но используется только вывод 7, поскольку Arduino не имеет сообщения для отправки в NEO-M8N), а прерывание (PPS) находится на выводе 2. Если я просто подключу вывод 7, дисплей будет обновляться раз в секунду, как и предполагалось; если я просто подключу вывод 2, время увеличится на одну секунду в секунду, как и предполагалось. НО если подключить их оба, ((
время увеличится более чем на одну секунду в секунду)
и (
processGPS()
иногда потеряет синхронизацию с предстоящим сообщением UBX(формат сообщения u-blox)))
.
А вот и весь код:
#include <SoftwareSerial.h>
#include <Adafruit_SSD1306.h>
#define OLED_RESET 9
Adafruit_SSD1306 display(OLED_RESET);
#if (SSD1306_LCDHEIGHT != 64)
#error("Height incorrect, please fix Adafruit_SSD1306.h!");
#endif
// Подключите GPS RX/TX к контактам 7 и 8 Arduino
SoftwareSerial S_Serial = SoftwareSerial(7,8);
int intpin = 2;
volatile int state = LOW;
const unsigned char UBX_HEADER[] = { 0xB5, 0x62 };
struct NAV_PVT {
unsigned char cls;
unsigned char id;
unsigned short len;
unsigned long iTOW;
unsigned short year;
byte month;
byte day;
byte hour;
byte miniute;
byte sec;
unsigned char valid;
unsigned long tAcc;
long nano;
byte fixType;
unsigned char flag;
unsigned char flag2;
byte numSV;
long lon;
long lat;
long height;
long hMSL;
unsigned long hAcc;
unsigned long vAcc;
long velN;
long velE;
long velD;
long gSpeed;
long headMot;
unsigned long sAcc;
unsigned long headAcc;
unsigned short pDop;
byte reserved1[6];
long headVeh;
byte reserved2[4];
};
NAV_PVT pvt;
void calcChecksum(unsigned char* CK) {
memset(CK, 0, 2);
for (int i = 0; i < (int)sizeof(NAV_PVT); i++) {
CK[0] += ((unsigned char*)(&pvt))[i];
CK[1] += CK[0];
}
}
bool processGPS() {
static int fpos = 0;
static unsigned char checksum[2];
const int payloadSize = sizeof(NAV_PVT);
while ( S_Serial.available() ) {
byte c = S_Serial.read();
if ( fpos < 2 ) { //СИНХРОНИЗАЦИЯ
if ( c == UBX_HEADER[fpos] )
fpos++;
else
fpos = 0;
}
else {
if ( (fpos-2) < payloadSize )
((unsigned char*)(&pvt))[fpos-2] = c;
fpos++;
if ( fpos == (payloadSize+2) ) {
calcChecksum(checksum);
}
else if ( fpos == (payloadSize+3) ) {
if ( c != checksum[0] )
fpos = 0;
}
else if ( fpos == (payloadSize+4) ) {
fpos = 0;
if ( c == checksum[1] ) {
return true;
}
}
else if ( fpos > (payloadSize+4) ) {
fpos = 0;
}
}
}
return false;
}
void pps()
{
state=!state;
}
void PrintScreen()
{
display.clearDisplay();
//Размер 2
display.setTextSize(2);
display.setTextColor(WHITE);
display.setCursor(0,0);
float sik = (pvt.gSpeed / 100) / 3.6f;
if (sik<100)
display.print(" ");
if (sik<10)
display.print(" ");
display.print(sik,1);//СкоростьВКф
display.setCursor(84,0);
if (pvt.numSV<10)
display.print(" ");
display.print(pvt.numSV);//SV
//Размер 1
display.setTextSize(1);
display.setCursor(60,8);
display.print("km/h");
display.setCursor(63,0);
display.print("CHL");
display.setCursor(108,8);
display.print("SVs");
display.setCursor(108,0);
switch (pvt.fixType){
case 0:
display.print("N.F");
break;
case 2:
display.print("2D");
break;
case 3:
display.print("3D");
if ((byte)pvt.flag & 2)
display.print("+");
break;
default:
display.print("INV");
break;
}
long temp;
//Строка 1
display.setCursor(0,16);
if (pvt.lat >= 0)
temp = pvt.lat;
else
temp = -pvt.lat;
if (temp >= 1000000000)
display.print("lat:");
else
if (temp >= 100000000)
display.print("lat: ");
else
display.print("lat: ");
display.print(temp / 10000000);display.print(".");
long temp2 = 1000000;
temp = temp % 10000000;
while ((temp / temp2 == 0)&&(temp2>1))
{
display.print("0");
temp2 /= 10;
}
display.print(temp);
if (pvt.lat >= 0)
display.print("N");
else
display.print("S");
temp = 10000;
while (((pvt.pDop / temp) == 0)&&(temp>1))
{
display.print(" ");
temp /= 10;
}
display.println(pvt.pDop);
//Строка2
if (pvt.lon >= 0)
temp = pvt.lon;
else
temp = -pvt.lon;
if (temp >= 1000000000)
display.print("lon:");
else
if (temp >= 100000000)
display.print("lon: ");
else
display.print("lon: ");
display.print(temp / 10000000);display.print(".");
temp2 = 1000000;
temp = temp % 10000000;
while ((temp / temp2 == 0)&&(temp2>1))
{
display.print("0");
temp2 /= 10;
}
display.print(temp);
if (pvt.lon >= 0)
display.print("E");
else
display.print("W");
display.println(" HEAD");
//Строка3
display.print("hMSL:");
if (pvt.hMSL < 0)
pvt.hMSL = -pvt.hMSL;
temp = 10000000;
while (((pvt.hMSL / temp) == 0)&&(temp>1000))
{
display.print(" ");
temp /= 10;
}
display.print((pvt.hMSL % 100000000) / 1000);display.print(".");
if ((pvt.hMSL % 1000) < 100)
display.print("0");
if ((pvt.hMSL % 1000) < 10)
display.print("0");
display.print(pvt.hMSL % 1000);
display.print("|");
temp = pvt.headMot / 100000;
if (temp < 100)
display.print(" ");
if (temp < 10)
display.print(" ");
display.print(temp);display.print(".");
temp2 = (pvt.headMot%100000)/1000;
if (temp2<0)
temp2 = -temp2;
if (temp2<10)
display.print("0");
display.println(temp2);
//Строка4
display.print("h,vAcc:");
if (pvt.hAcc >= 100000)
display.print(" >100");
else
{
display.print(pvt.hAcc / 1000);display.print(".");
if (pvt.hAcc % 1000 < 100)
display.print("0");
if (pvt.hAcc % 1000 < 10)
display.print("0");
display.print(pvt.hAcc % 1000);
}
display.print(",");
if (pvt.vAcc >= 100000)
display.print(" >100");
else
{
display.print(pvt.vAcc / 1000);display.print(".");
if (pvt.vAcc%1000 < 100)
display.print("0");
if (pvt.vAcc%1000 < 10)
display.print("0");
display.print(pvt.vAcc%1000);
}
display.println("m");
//Строка5
display.print("V:");
if (pvt.gSpeed < 1000000)
display.print(" ");
if (pvt.gSpeed < 100000)
display.print(" ");
if (pvt.gSpeed < 10000)
display.print(" ");
display.print((pvt.gSpeed % 10000000)/1000);display.print(".");
if (pvt.gSpeed % 1000 < 100)
display.print("0");
if (pvt.gSpeed % 1000 < 10)
display.print("0");
display.print(pvt.gSpeed % 1000);
display.print("m/s");
if (pvt.sAcc < 1000000)
{
if (pvt.sAcc < 100000)
display.print(" ");
if (pvt.sAcc < 10000)
display.print(" ");
display.print("~");
display.print(pvt.sAcc / 1000);display.print(".");
if (pvt.sAcc % 1000 < 100)
display.print("0");
if (pvt.sAcc % 1000 < 10)
display.print("0");
display.println(pvt.sAcc % 1000);
}
else
display.println(" ~>100");
//Строка6
if ((pvt.year<10000)&&(pvt.year>=1000))
display.print(pvt.year);
else
display.print("????");
display.print("/");
if ((pvt.month<=12)&&(pvt.month>=1))
{
if (pvt.month < 10)
display.print("0");
display.print(pvt.month);
}
else
display.print("??");
display.print("/");
if ((pvt.day<=31)&&(pvt.day>=1))
{
if (pvt.day < 10)
display.print("0");
display.print(pvt.day);
}
else
display.print("??");
if (pvt.valid & 0x04)
display.print("UTC");
else
display.print("INV");
if ((pvt.hour<=23)&&(pvt.hour>=0))
{
if (pvt.hour < 10)
display.print("0");
display.print(pvt.hour);
}
else
display.print("??");
display.print(":");
if ((pvt.miniute<=59)&&(pvt.miniute>=0))
{
if (pvt.miniute < 10)
display.print("0");
display.print(pvt.miniute);
}
else
display.print("??");
display.print(":");
if ((pvt.sec<=60)&&(pvt.sec>=0))
{
if (pvt.sec < 10)
display.print("0");
display.print(pvt.sec);
}
else
display.print("??");
display.display();
}
void PrintSec()
{
pvt.sec = (pvt.sec + 1)%60;
display.fillRect(114,56,12,8,BLACK);
display.setCursor(114,56);
if (pvt.sec<10)
display.print("0");
display.println(pvt.sec);
display.display();
state = LOW;
}
void setup()
{
S_Serial.begin(9600);
pinMode(intpin, INPUT);
attachInterrupt(digitalPinToInterrupt(intpin), pps, RISING);
// по умолчанию мы будем генерировать высокое напряжение из линии 3,3 В внутри! (здорово!)
display.begin(SSD1306_SWITCHCAPVCC, 0x3C);
// инициализация выполнена
}
void loop() {
if (state == HIGH)
PrintSec();
if ( processGPS() )
PrintScreen();
}
Я обнаружил похожую проблему здесь (Процедура обслуживания прерываний выполняется дважды ATmega88), но поскольку ISR выполняется только один раз, если я подключаю только контакт 2, я думаю, что причина может быть в чем-то другом.
Это проблема программного обеспечения или оборудования? Как ее исправить?
@HL. Chen, 👍2
2 ответа
Если я правильно вас понял, у вас есть 2 способа увеличить количество секунд,
когда программный последовательный порт получает данные
Когда вывод PPM становится высоким
И когда каждый из них запускается по отдельности, все работает, но когда вы используете их вместе, секунды увеличиваются на 2 каждый раз?
Не означает ли это, что ваша программа должна использовать только один из них для увеличения секунд?
1) Во-первых, откладывание PrintSec
для цикла
через изменчивое состояние
, которое устанавливается ISR, на самом деле занимает больше времени для обновления дисплея. Вы могли бы также просто проверить пин в цикле
:
void setup()
{
S_Serial.begin(9600);
pinMode(intpin, INPUT);
// по умолчанию мы будем генерировать высокое напряжение из линии 3,3 В внутри! (здорово!)
display.begin(SSD1306_SWITCHCAPVCC, 0x3C);
}
void loop() {
bool state = digitalRead(intPin);
static bool oldState = false;
// Следите за изменениями
if (oldState != state) {
oldState = state;
// ПОДЪЕМНЫЙ край?
if (state)
PrintSec();
}
if ( processGPS() )
PrintScreen();
}
2) «Но я могу пропустить PPS!» — воскликните вы. Плохая новость в том, что если вы не можете закончить
PrintScreen
вовремя, чтобы вернуться к тестированию PIN-кода, то вы пытаетесь сделать слишком много.
Возможно, вы захотите рассмотреть инкрементные обновления отображения. Если у вас есть предыдущие части (например, дополнительная структура NAV_PVT), вы можете сравнить каждую часть, чтобы увидеть, изменилась ли она. Если изменилась, то можно потратить время на ее написание. Просто добавьте дополнительный тест к каждой части:
NAV_PVT pvt, prevPVT;
static bool firstTime = true;
.
.
.
void PrintScreen()
{
if (firstTime)
display.clearDisplay();
//Размер 2
display.setTextSize(2);
display.setTextColor(WHITE);
// Обновить скорость?
if (firstTime || (pvt.gSpeed != prevPVT.gSpeed)) {
display.setCursor(0,0);
float sik = (pvt.gSpeed / 100) / 3.6f;
if (sik<100)
display.print(" ");
if (sik<10)
display.print(" ");
display.print(sik,1);//СкоростьВКф
}
В конце PrintScreen
сделайте следующее:
display.display();
prevPVT = pvt;
firstTime = false;
}
Для большинства обновлений будет записано только поле секунд
.
Очистка экрана и отрисовка меток, вероятно, может выполняться только один раз в настройке
.
3) Я бы предложил более эффективную и надежную программную последовательную библиотеку.
SoftwareSerial
отключает все прерывания на все время 10-битного символа после получения стартового бита. Это делает его уязвимым и навязчивым для других прерываний, таких как PinChange и SPI. Ужас. Если вы можете использовать контакты 8 и 9, AltSoftSerial — лучший выбор.
Если вы не можете использовать эти контакты и не используете порт USB для подключения к хосту, можете ли вы использовать последовательный вход (контакт 0) для GPS? GPS придется отключить для программирования USB, но он может оставаться подключенным, если вы используете ISP.
4) Вам нужен более надежный способ перезапустить кадрирование пакетов UBX, даже если символы потеряны. Я бы предложил что-то вроде этого:
void loop() {
if (state == HIGH) {
PrintSec();
fpos = 0; // скоро появится новый пакет, отбрасываем старый частичный пакет
}
if ( processGPS() )
PrintScreen();
}
По крайней мере, это позволит вам оставаться в синхронизации. Без этого завершение пакета может не произойти до тех пор, пока не произойдет 2-й PPS (и не начнет прибывать 2-й пакет). Может быть, это объясняет симптомы, которые вы видите?
- Как остановить SoftwareSerial от получения данных и повторно включить его в какой-то другой момент?
- Почему необходимо использовать ключевое слово volatile для глобальных переменных при обработке прерываний в ардуино?
- Серийное прерывание
- Влияет ли `millis()` на длинные ISR?
- Как прервать функцию цикла и перезапустить ее?
- Отправка команд PUBX на плату GPS (Ublox NEO-6M) через SoftwareSerial
- Мусор последовательного монитора Arduino Pro Mini
- Аппаратное прерывание срабатывает случайным образом