Использование контроллера Arduino для прерываний

В данной стать я приведу пример использования arduino контроллера для вызова прерываний программы на C#.

Стоит отметить, что в WindowsForms присутствует элемент Timer который включается и выполняет код через определенный промежуток времени.



Я решил реализовать подобное используя микроконтроллер Arduino UNO.

Для этого я запрограммировал контроллер так, чтобы он через определённый промежуток времени отправлял в последовательный порт данные, вызывая прерывание программы.

Я считаю, что данная статья будет интересна тем, кто занимается программированием микроконтроллеров, так как в ней приведен пример одного из вариантов использования микроконтроллеров.

void setup(){
Serial.begin(9600);//Открыть последовательный порт
}
int str=0;
int interval=0;
String text;
void loop()
{


	while(str==0)
	 str=Serial.parseInt();//Ждать прихода данных
  if (str==1)//Если поступила команда запуска таймера
  {
	 while(interval==0)
	   interval=Serial.parseInt();//получить интервал
	 text=String(interval);
	 text+="!";
	 while(str!=2){
		delay(interval);//Подождать указанный промежуток времени 
		
		Serial.println(text);//Отправить данные в последовательный порт
		
		 str=Serial.parseInt();//Ждать следующей комнды
	 }
	 interval=0;
  }

}

Также контроллер принимает команды запуска(1) и остановки(2) таймера. (Аналог методов start и stop для таймера windowsforms).

После получения команды запуска котроллер ждет получения промежутка времени через которое данные будут отправляться в последовательный порт.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Threading;
using System.IO.Ports;
using System.IO;
using System.Net;
namespace ConsoleApp33
{
    class Program
    {
        private static SerialPort ard = new SerialPort("COM13", 9600);//Название порта к которому подключен контроллер и скорость обмена данными с портом.
        private static Thread stop_thread = new Thread(Stop);
        private static bool sost = true;
       [MTAThread]
        static void Main(string[] args)
        {
           
            SerialDataReceivedEventHandler handler = new SerialDataReceivedEventHandler(Serial_interrupt);//Метод выполняемый при приходе данных
            ard.DataReceived += handler;
            //Задать интервал задержки
            int interval = int.Parse(Console.ReadLine());
            //Открыть последовательный порт
            while (ard.IsOpen != true)
                ard.Open();
            //Запустить контроллер
            ard.WriteLine("1");
            Thread.Sleep(10);
            ard.WriteLine(interval.ToString());
            Console.WriteLine(ard.ReadLine());
            //Запуск потока остановки контроллера
            stop_thread.IsBackground = true;
         
            stop_thread.Start();
         
            while (sost) Thread.Sleep(1);//Приостановить поток
        }
        public enum Comand
        {
          Start = 1,//Запуск таймера
         Stop = 2   //Остановка таймера
        }
        private static System.DateTime dateTime;
        public static void Serial_interrupt(object sender,SerialDataReceivedEventArgs e)
        {
            //Вывести дату и время в консоль
            dateTime = DateTime.Now;
            Console.WriteLine(dateTime.ToString());
        }
        public static void Stop()
        {
            if ((int)Console.ReadKey().Key == 27)//Если нажата клавиша Esc выключить контроллер
            {
                ard.WriteLine(Comand.Stop.ToString());//Выклюить контроллер
                sost = false;//Завершить Main
            }
            Thread.Sleep(10);
        }
      
    }
}

Программа на C# открывает соединение с портом и при приходе данных возникает событие

ard.DataReceived += handler;

вызывающее метод Serial_interrupt(), который выводит в консоль дату и время компьютера.

Также присутствует поток stop_thread который завершает программу и отправляет на контроллер команду выключения когда нажимается клавиша Esc.

После запуска программа будет ожидать введения пользователем интервала времени через которое контроллер будет отправлять данные, затем до нажатия клавиши Esc программа будет выводить через указанный промежуток времени дату и время в консоль.

Всем спасибо за внимание.
Ads
AdBlock has stolen the banner, but banners are not teeth — they will be back

More

Comments 27

    +2
    … а где светодиоды?
      +2
      Ваша претензия несправедлива: в Arduino UNO есть светодиод TX, который будет подмигивать при передаче по Serial.
        +1
        Целью статьи я ставил показать возможность использовать микроконтроллер не только для управления электронными компонентами.
          0

          Ну, цели вы не достигли. Применение ардуино тут вообще ничего не даёт. С тем же успехом вы могли просто положить его рядом на столе.

        0

        Эх, был бы шарп и мк в мои 17 лет, какую бы фигню только несотворил.

          0
          МК Intel 8051 появился аккурат в год вашего рождения. Что касается Шарпа — то на нем единственном свет клином не сошелся.
          +3

          Картинка с троллейбусом уже была?

            0
            Не было. И с баяном тоже.
            +7
            Думал, будут использованы прерывания ардуино (https://www.arduino.cc/reference/en/language/functions/external-interrupts/attachinterrupt/), а не while(true) {delay(x)}
              0

              Не очень понятно, зачем [MTAThread]? А использование Thread.Sleep это же лютый code smell, нет?

                +5
                да но это ведь не прерывание… это стандартное событие получения данных…
                  • UFO just landed and posted this here
                      +1
                      Рядом не лежало.

                      Событие обрабатывается «когда руки дойдут» (если у вас не RTOS, конечно, но там тоже не так все просто)

                      Прерывание обрабатывается как только оно возникает. Писать обработчик прерываний тоже некоторый опыт нужен — из нынешнего поколения фреймворков у кого он есть? Кто знает как работать с прерыванием RTC? Ась?

                      В общем и целом, кабы я был экзаменатором, а вы студентом — послал бы вас учить WinAPI с написанием учебнойпрограммы на голом API без никаких библиотек и фреймворков. Просто для понимания как оно работает внутри.
                    +3

                    Справедливости ради, в статье расписан обработчик событий, а не обработчик прерываний.


                    Прерывание — это процесс БЕЗУСЛОВНО переключающий ваш МК на другую задачу, и возобновляющий прерванную программу с того места где она прервалась.


                    Вот есть у вас Thread.Sleep(10); или delay() не суть важно. Увеличьте их значения до заоблачных, и попробуйте что вышло. Ваша программа ожидаемо застопорится на этой функции и больше не станет обрабатывать никакие нажатия или данные на последовательном порту. В случае с прерыванием было бы пофигу.


                    void setup()
                    {
                    pinMode(13, OUTPUT);
                    attachInterrupt(0, lol, CHANGE); // ЕМНИП, нулевое прерывание подвязывается ко второму пину.
                    }
                    
                    void loop()
                    {
                    delay(100500); // Здесь программа намертво подвисает
                    }
                    
                    void lol()
                    {
                    digitalWrite(13, LOW);
                    delay(1000); // Делайчик не сработает, а введен для красоты, чтоб было видно что я моргаю светодиодом
                    digitalWrite(13, HIGH);
                    delay(1000);
                    }
                    

                    Как только эта программа стартует — она сразу же "зависает", исполняя delay(100500) и ей пофигу на любые данные на последовательном порту, rx\tx и тд. Но стоит пину 2 поменять свое состояние — delay(100500) прервется, светодиод моргнет, и подпрограмма возвратится досиживать delay на оставшееся время.


                    Как-то так.

                      0
                      Давно не работал с AVR, но насколько помню у них при входе в обработчик прерывания происходит маскирования остальных прерываний. Если в начале void lol() не выполнить SEI, то из за запрета прерываний от таймера delay() будет висеть бесконечно.
                      +1
                      Вот это — private static bool sost = true; — надо исправить: добавить volatile. Вот так: private static volatile bool sost = true;
                      Иначе у оптимизатора, если он вдруг захочет соптимизировать ваш код, появится искушение вынести sost как инвариант вот из этого цикла:
                      while (sost) Thread.Sleep(1);//Приостановить поток
                      И тогда ваша программа по Esc не закончится.
                        +1
                        Стоит отметить, что в WindowsForms присутствует элемент Timer который включается и выполняет код через определенный промежуток времени.


                        Поколение фреймворков…

                        Таймер — это элемент WinAPI. Он всего лишь посылает сообщение WM_TIMER в очередь сообщений приложения через заданные промежутки времени. А выполнение кода обеспечивается диспетчером сообщений приложения в том случае, когда на это сообщение назначен соответствующий обработчик.

                        Прерыванием это ни в кокей мере не является поскольку диспетчер сообщений периодически вызывается самим приложением. И если он ушло в длительный цикл обработки чего-либо без периодического вызова диспетчера сообщений, то код обработчика таймера вызываться не будет до тех пор, пока приложение не выйдет из цикла (равно как и не будут вызываться все остальные обработчики — перемещение окна, изменение его размеров, нажатие кнопок и т.п.)

                        Так работает Windows и это надо понимать. Если у вас есть длинные циклы, то лучше бы внутри них периодически вызывать диспетчер сообщений (что-то типа метода ProcessMessages() или аналогичного)

                        Равно как и в коде потока периодически вызывать функцию, отдающую таймслайсы системе (Sleep, WaitObject и т.п.)

                        А настоящие аппаратные прерывания в Windows пользователю недоступны (разве что на уровне драйверов, но это отдельная тема).

                        Так что тут уже правильно сказали — ваша программа будет генерировать события, не более. А уж когда это событие будет обработано…
                          +4
                          Код с обеих сторон настолько отвратительный, что я просто пройду мимо, дабы не портить себе настроение его чтением…
                            0
                            От комментариев по содержанию откажусь, но интересная мысль. Мне вот вспомнилась задача по синхронизации и обновлении данных на двух железках, одна из которых комп на win. Там был RealTime процесс который требовал обновления данных чаще чем 10ms. Так вот внешняя железка весьма бы помогла для выдачи тактов.
                              0
                              В arduino есть
                              delayMicroseconds(int microsecond);
                              приостанавливающая поток на указанное количество микросекунд.
                                0
                                Суть той проблематики в несколько другой плоскости была. Задержки менее 30-15 мс реализуемые на WinApi на самом деле таковыми не являются, ОС накладывает ограничения на квант времени и если надо что-то делать быстрее чем этот квант, то это те еще танцы с бубном.
                                  0
                                  Никаких там танцев с бубном нет. Достаточно использовать Multimedia Timer. Он работает с точностью 1ms (обычный таймер — порядка 16ms). Все это неплохо документировано.

                                  Проверено личным опытом.
                                0
                                Вы о чём? Тут негарантированная задержка генерации, потом миллисекунда на передачу каждого байта с рейтом 9600, потом буфер операционной системы, событие операционной системы, потом переключение процессов, и только потом обработка. На каждом шаге в операционной системе вносятся ещё и случайные ошибки.
                                  0
                                  Я про возможность использовать внешний микроконтроллер, для выдачи дискретных сигналов (тактов) на разнородные устройства. И по этим тактам синхронизировать две управляющие системы между собой.
                                  У меня задача была скорее в отсутствии бюджета и необходимости распределять вычислительные ресурсы между тем что есть. По факту управляющему устройству необходимо было в достаточно быстром процессе считать вес падающего столба исходя из типа продукта и скорости его подачи с поправкой на накопленную ошибку и т.д и т.п. там много нюансов было. и запись сторонней метки времени по счетному дискретному входу весьма помогло бы.
                                    0
                                    Это хорошо сработает под какой-нибудь RTOS, но не под Windows. Там приход байта в порт сгенерирует событие, не более того. А дальше уже все будет зависеть от того, когда вы проверите наличие события и обратит на него внимание.

                                    Я бы сделал Multimedia Timer, который вызывает callback функцию через заданный промежуток времени и из этой функции уже рассылал бы другим устройствам синхрометку.

                                    А вообще, из моего опыта, пытаться поднять реалтайм на Win задача неблагодарная. Не та это система
                                      0
                                      Вообще говоря есть ещё такая штука: en.wikipedia.org/wiki/RTX_(operating_system)
                                      Но это явно не предмет этой дискуссии.

                              Only users with full accounts can post comments. Log in, please.