C# приложения для управления Arduino из под Windows

Тема в разделе "Глядите, что я сделал", создана пользователем issaom, 7 дек 2017.

  1. issaom

    issaom Гуру

    Сюда буду выкладывать приложения под Windows для управления Arduino. Может кому пригодятся куски кода или все приложение )))
    Первый проект - Windows Servo Tester. Разработано на C# в бесплатной версии Visual Studio community от мелкософта.
    Для передачи данных используется самопальный протокол на ASCII который подойдет для очень многих проектов ;-)

    Код (C++):
    const char StaPack = '#';              // Признак начала пакета данных
    const char EndPack = '|';                                  // Признак окончания пакета данных
    const uint16_t TimeOut = 500;                       // Отвалились через это время mls

    #include <Servo.h>      //используем библиотеку для работы с сервоприводом
    Servo servo;                  //объявляем переменную servo типа Servo

    void setup() {
      Serial.begin(9600);
      pinMode(13, OUTPUT);
      servo.attach(10);     //сервопривод на порту 10
      servo.write(0);
    }

    byte hexToByte (String StrControlHex) {         //0 : 255 (1 байт)

      uint8_t  HEX16 = 0;   // число 16-е из символа
      uint8_t  exp16 = 1;   // степень числа 16
      uint8_t  decBy = 0;   // число 10-е расчитанное без знака

      StrControlHex.remove(0, 2);           //отрезаем управляющие символы (2шт)
      int i = StrControlHex.indexOf('|');   //определяем дилнну строки
      if (i == -1) return 0;                          //фигня пришла а не байт
      if (i > 2)   return 0;                             //фигня пришла а не байт
      StrControlHex.remove(i, 1);           //отрезаем управляющие символы ('|')

      for (int j = StrControlHex.length() - 1; j >= 0; j--) {

        HEX16 = StrControlHex.charAt(j);

        if (HEX16 >= 48 && HEX16 <= 57) HEX16 = map(HEX16, 48, 57, 0, 9);
        if (HEX16 >= 65 && HEX16 <= 70) HEX16 = map(HEX16, 65, 70, 10, 15);
        if (HEX16 >= 97 && HEX16 <= 102) HEX16 = map(HEX16, 97, 102, 10, 15);

        decBy = decBy + HEX16 * exp16;
        exp16 = exp16 * 16;

      }
      return decBy;                         //возвращаем десятичное число 1 байт без знака
    }

    void loop() {

    clearPack:                                // переходим сюда если приняли мусор /отвалились по таймауту/

      char   IncomChar;
      String StrControl = "";

      while (Serial.available() > 0) {            // выгребаем все байты которые пришли в буфер Serial

        IncomChar = Serial.read();

        if (IncomChar == StaPack) {           // пришел символ начала посылки данных
          StrControl += IncomChar;             // плюсуем пришедший символ к строке
          unsigned long currentTime = millis(); // записываем текущее время

          ReceptionPacket:                    // начало приема пакета

          if (Serial.available() > 0) {
            IncomChar = Serial.read();
            StrControl += IncomChar;          // плюсуем пришедший символ к строке
            if (IncomChar == EndPack) break;  // вываливаемся из цикла выгребания байт если приняли весь пакет
          }

          if (millis() - currentTime >= TimeOut) goto clearPack;  // отвалились по TimeOut

          goto ReceptionPacket;               // переход в начало приема пакета

        }

      }                                       // конец цикла по выгребанию байт

      if (StrControl != "") {

      //Serial.println(StrControl);           //печатаем принятый пакет отладка

      switch (StrControl.charAt(1)) {       //применяем полученую строку
        case 'w':
          digitalWrite(13, 0);
          break;
        case 'x':
          digitalWrite(13, 1);
          break;
        case 'A':
          servo.write(hexToByte(StrControl));
          break;
      }
      }
    }
    Примеры команд (можно и из терминала посылать)
    #x| - включить светодиод на 13м пине
    #w|- выключить светодиод на 13м пине
    #Af|- повернуть сервопривод на 15 градусов
    #A5а|- повернуть сервопривод на 90 градусов

    Байт передается в 16-й системе счисления двумя символами.

    Внешний вид:

    upload_2017-12-7_1-12-51.png

    Исходники:
    https://yadi.sk/d/Zsp8FM1t3QPE3T

    Код главной формы в C#

    Код (C++):
    using System;
    using System.Collections.Generic;
    using System.ComponentModel;
    using System.Data;
    using System.Drawing;
    using System.Linq;
    using System.Text;
    using System.Windows.Forms;
    using System.IO.Ports;

    namespace WindowsServoTester
    {
        public partial class Form1 : Form
        {
            bool isConnected = false;
            SerialPort port;
            public Form1()
            {
                InitializeComponent();
            }

            private void button1_Click(object sender, EventArgs e)
            {
                comboBox1.Items.Clear();
                // Получаем список COM портов доступных в системе
                string[] portnames = SerialPort.GetPortNames();
                // Проверяем есть ли доступные
                if (portnames.Length == 0)
                {
                    MessageBox.Show("COM PORT not found");
                }
                foreach (string s in portnames)
                {
                    //добавляем доступные COM порты в список          
                    comboBox1.Items.Add(s);
                }
            }

            private void connectToArduino()
            {
                isConnected = true;
                string selectedPort = comboBox1.GetItemText(comboBox1.SelectedItem);
                port = new SerialPort(selectedPort, 9600, Parity.None, 8, StopBits.One);
                port.Open();
                button2.Text = "Disconnect";
            }

            private void disconnectFromArduino()
            {
                isConnected = false;
                port.Close();
                button2.Text = "Connect";
            }

            private void button2_Click(object sender, EventArgs e)
            {
                if (!isConnected)
                {
                    connectToArduino();
                }
                else
                {
                    disconnectFromArduino();
                }
            }

            private void button3_Click(object sender, EventArgs e)
            {
                port.Write("#x|");
            }

            private void button4_Click(object sender, EventArgs e)
            {
                port.Write("#w|");
            }

            private void trackBar1_Scroll(object sender, EventArgs e)
            {
                String UgolServo = "#A";
                UgolServo = UgolServo + Convert.ToString(trackBar1.Value, 16);
                UgolServo = UgolServo + "|";
                port.Write(UgolServo);
                label1.Text = String.Format("Угол сервопривода: {0}", trackBar1.Value);
            }
        }
    }
     
    т.е. несложные практические задачи можно решать за вечер даже сильно не разбираясь в программировании

    Краткая видео-демонстрация работы:

     
    Последнее редактирование: 7 дек 2017
    zsm, shpock, БАРМАТОГРАФ и 3 другим нравится это.
  2. Tomasina

    Tomasina Сушитель лампочек Модератор

    :mad:
     
  3. Tomasina

    Tomasina Сушитель лампочек Модератор

    Я имею в виду построение фразы.
     
    Толик Иванов нравится это.
  4. REMBOV

    REMBOV Воскреситель Матрёшек

    научите?
     
  5. issaom

    issaom Гуру

    С наступающим 2018 всех - сделал программу для управления робо-рукой на 4-х сервоприводах. Теперь можно управлять манипулятором прямо с компьютера и нарезать задания которые можно сохранять в файл не переписывая код в Aрдуино.



    Интерфейс программы:

    Windows robot arm.JPG

    Код для Arduino:

    Код (C++):
    const char StaPack = '#';                // Признак начала пакета данных
    const char EndPack = '|';                // Признак окончания пакета данных
    const uint16_t TimeOut = 500;            // Отвалились через это время mls

    #include <Servo.h>      //используем библиотеку для работы с сервоприводом
    Servo servo1;           //объявляем переменную servo типа Servo
    Servo servo2;           //объявляем переменную servo типа Servo
    Servo servo3;           //объявляем переменную servo типа Servo
    Servo servo4;           //объявляем переменную servo типа Servo

    void setup() {
     
    Serial.begin(9600);
    pinMode(4, OUTPUT);   //led
    //pinMode(5, OUTPUT); //na
    servo1.attach(11); // grapple
    servo2.attach(10); // up-down
    servo3.attach(9);  // forward backward
    servo4.attach(6);  // rotation

    servo1.write(35);
    servo2.write(75);
    servo3.write(30);
    servo4.write(90);

    }

    byte hexToByte (String StrControlHex) {         //0 : 255 (1 байт)

      uint8_t  HEX16 = 0;   // число 16-е из символа
      uint8_t  exp16 = 1;   // степень числа 16
      uint8_t  decBy = 0;   // число 10-е расчитанное без знака

      StrControlHex.remove(0, 2);           //отрезаем управляющие символы (2шт)
      int i = StrControlHex.indexOf('|');   //определяем дилнну строки
      if (i == -1) return 0;                //фигня пришла а не байт
      if (i > 2)   return 0;                //фигня пришла а не байт
      StrControlHex.remove(i, 1);           //отрезаем управляющие символы ('|')

      for (int j = StrControlHex.length() - 1; j >= 0; j--) {

        HEX16 = StrControlHex.charAt(j);

        if (HEX16 >= 48 && HEX16 <= 57) HEX16 = map(HEX16, 48, 57, 0, 9);
        if (HEX16 >= 65 && HEX16 <= 70) HEX16 = map(HEX16, 65, 70, 10, 15);
        if (HEX16 >= 97 && HEX16 <= 102) HEX16 = map(HEX16, 97, 102, 10, 15);

        decBy = decBy + HEX16 * exp16;
        exp16 = exp16 * 16;

      }
      return decBy;                         //возвращаем десятичное число 1 байт без знака
    }


    int16_t hexToInt16 (String StrControlHex) {      // -32768 : 32767 (2 байта)

      uint8_t  HEX16 = 0;    // число 16-е из символа
      uint16_t exp16 = 1;    // степень числа 16
      int16_t  dec16 = 0;    // число 10-е расчитанное со знаком

      StrControlHex.remove(0, 2);           //отрезаем управляющие символы (2шт)
      int i = StrControlHex.indexOf('|');   //определяем дилнну строки
      if (i == -1) return 0;                //фигня пришла а не 2 байта
      if (i > 4)   return 0;                //фигня пришла а не 2 байта
      StrControlHex.remove(i, 1);           //отрезаем управляющие символы ('|')

      for (int j = StrControlHex.length() - 1; j >= 0; j--) {

        HEX16 = StrControlHex.charAt(j);

        if (HEX16 >= 48 && HEX16 <= 57) HEX16 = map(HEX16, 48, 57, 0, 9);
        if (HEX16 >= 65 && HEX16 <= 70) HEX16 = map(HEX16, 65, 70, 10, 15);
        if (HEX16 >= 97 && HEX16 <= 102) HEX16 = map(HEX16, 97, 102, 10, 15);

        dec16 = dec16 + HEX16 * exp16;
        exp16 = exp16 * 16;
      }
      return dec16;                         //возвращаем десятичное число int16_t со знаком
    }


    int32_t hexToInt32 (String StrControlHex) {      // -2147483648 : 2147483647 (4 байта)

      uint8_t  HEX16 = 0;    // число 16-е из символа
      uint32_t exp16 = 1;    // степень числа 16
      int32_t  dec32 = 0;    // число 10-е расчитанное со знаком

      StrControlHex.remove(0, 2);           //отрезаем управляющие символы (2шт)
      int i = StrControlHex.indexOf('|');   //определяем дилнну строки
      if (i == -1) return 0;               //фигня пришла а не 4 байта
      if (i > 8)   return 0;               //фигня пришла а не 4 байта
      StrControlHex.remove(i, 1);          //отрезаем управляющие символы ('|')

      for (int j = StrControlHex.length() - 1; j >= 0; j--) {

        HEX16 = StrControlHex.charAt(j);

        if (HEX16 >= 48 && HEX16 <= 57) HEX16 = map(HEX16, 48, 57, 0, 9);
        if (HEX16 >= 65 && HEX16 <= 70) HEX16 = map(HEX16, 65, 70, 10, 15);
        if (HEX16 >= 97 && HEX16 <= 102) HEX16 = map(HEX16, 97, 102, 10, 15);

        dec32 = dec32 + HEX16 * exp16;
        exp16 = exp16 * 16;
      }
      return dec32;                         //возвращаем десятичное число int32_t со знаком
    }

    void loop() {

    clearPack:                                // переходим сюда если приняли мусор /отвалились по таймауту/

      char   IncomChar;
      String StrControl = "";

      while (Serial.available() > 0) {        // выгребаем все байты которые пришли в буфер Serial

        IncomChar = Serial.read();

        if (IncomChar == StaPack) {             // пришел символ начала посылки данных
          StrControl += IncomChar;              // плюсуем пришедший символ к строке
          unsigned long currentTime = millis(); // записываем текущее время

          ReceptionPacket:                      // начало приема пакета

          if (Serial.available() > 0) {
            IncomChar = Serial.read();
            StrControl += IncomChar;            // плюсуем пришедший символ к строке
            if (IncomChar == EndPack) break;    // вываливаемся из цикла выгребания байт если приняли весь пакет
          }

          if (millis() - currentTime >= TimeOut) goto clearPack;  // отвалились по TimeOut

          goto ReceptionPacket;             // переход в начало приема пакета

        }

      }                                     // конец цикла по выгребанию байт

      if (StrControl != "") {
     
      //Serial.println(StrControl);         // печатаем принятый пакет отладка

      switch (StrControl.charAt(1)) {       // применяем полученую строку
        case 'w':
          digitalWrite(4, 0);
          break;
        case 'x':
          digitalWrite(4, 1);
          break;
        case 'A':
          servo1.write(hexToByte(StrControl));
          break;
         case 'B':
         servo2.write(hexToByte(StrControl));
         break;
         case 'C':
         servo3.write(hexToByte(StrControl));
         break;
         case 'D':
         servo4.write(hexToByte(StrControl));
         break;
      }
      }
    }


     
    Исходники программы C#:

    WindowsControlRobotArmV1.zip
     

    Вложения:

    ИгорьК нравится это.
  6. issaom

    issaom Гуру

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

    изучаемС.jpg

    Например в последней программе потребовалось сохранять данные в файл. В книге этому посвящена отдельная глава (48 страниц) – т.е. описано много различных способов. Я ознакомился со всеми и выбрал тот, который показался на мой взгляд наиболее удобным и оптимальным. Даже если я для Вас разжую именно этот один конкретный способ - при решении Вашей задачи он Вам может просто тупо не подойти. Книги наше все – никакие Ютубы, форумы и тематические сайты Вам их не заменят если вы преследуете цели больше чем просто мигать светодиодом….

    книги.jpg
     
    sendsay, IvanUA и ИгорьК нравится это.
  7. ИгорьК

    ИгорьК Гуру

    Для поколения web 2.0 это открытие.
     
    sendsay и REMBOV нравится это.
  8. Tolik

    Tolik Нуб

    У Вас есть исходный код который шел в прошивке Arduino Uno с завода для данной руки.
    Игрался разными прошивками и не могу вернуть как было.

    На сайте производителя код выдает ошибку компиляции на этапе проверки.
    http://cdn.sinoning.com/code/snarm.zip

    Заранее спасибо.
     

    Вложения:

    • Ошибка.jpg
      Ошибка.jpg
      Размер файла:
      178,5 КБ
      Просмотров:
      268
    • me_arm3.0.ino
      Размер файла:
      2,3 КБ
      Просмотров:
      193
  9. Tolik

    Tolik Нуб

    Вопрос выше отпал.
    Получил код от производителя.

    Хотел уточнить каким образом Вы калибровали сервопривода мри монтаже? Какие начальные координаты?
    Т.е. при сборке первую шестерню можно вставить под любым углом поворота по отношению к сервоприводу.
    От этого зависит в последующем координаты положения рычага руки и заданные данные в программе по углу поворота сервопривода.
     
  10. zsm

    zsm Гик

    Спасибо за примеры, а где скорость порта указана в исходнике?
    Можете примером поделится как отобразить значения с arduino в окне?
     
  11. всем привет, прикольно конечно с компа мышкой и клавой рулить железками в реальном времени, а можно так открывать ком порт чтоб микроконтроллер не перезапускал прошивку? а то в некоторых приложениях это наверно будет нежелательно а то и вовсе не допустимо?
     
  12. DetSimen

    DetSimen Guest

    Нет.
     
    baby_in_Arduino нравится это.
  13. KindMan

    KindMan Гик

    Почему нет то, Деда? Перерезать дорожку от DTR к Reset'у, точнее перемычкой заменить, и ставить только на время прошивки.
     
  14. DetSimen

    DetSimen Guest

    Ну так он же спрашивал то:
    А не "что надо перерезать, чтобы... "
    или "Как сделать так, чтобы..."
    :)
     
  15. На виндовсе решал задачку немного похожую (давно правда было, в прошлой жизни, сейчас стараюсь не использовать ОС которой непонятно как управлять).
    Пользователь запускал приложение, которое работало с последовательным портом, а потом не выгружая приложения выполнялась смена пользователя (смена учетной записи).
    Другой пользователь тоже запускал приложение и получал ошибку (потому как порт занят), заказчику это очень не нравилось.
    Я предложил заставить пользователя завершать приложение перед сменой учетной записи, получил ответ: никто завершать не будет, пользователь уходя с рабочего места заблокировал экран и ушел, а другой пользователь под своей учетной записью выполнил вход и должен иметь возможность работать в приложении с использованием последовательного порта.
    Решение простое, сделал шлюз из ip в последовательный порт, а приложение минимально переделал для работы через этот шлюз.
    Поскольку железо подключалось к usb, то найти в девайсах нужный порт с именем содержащим CH340 или CP210x и т.п. не составило труда, подключили устройство, определили соответствующий порт, открыли его и больше не закрываем.
     
    parovoZZ нравится это.
  16. hulitolku

    hulitolku Нуб

    А почему си шарп, а не питон?