Форум программистов, компьютерный форум, киберфорум
Wired
Войти
Регистрация
Восстановить пароль
Блоги Сообщество Поиск Заказать работу  

Управление Arduino из Windows Forms приложения C#. Примеры применения

Запись от Wired размещена 30.07.2025 в 19:29. Обновил(-а) Wired 30.07.2025 в 19:37
Показов 4565 Комментарии 0

Нажмите на изображение для увеличения
Название: Управление Arduino из Windows Forms приложения C# 3.jpg
Просмотров: 395
Размер:	71.3 Кб
ID:	11024
Самое интересное начинается, когда мы применяем полученные знания для решения конкретных задач. За время работы с Arduino я реализовал десятки проектов, где управление осуществлялось через C# приложение, и хочу поделиться наиболее интересными и полезными примерами.

Все части статьи:
Управление Arduino из Windows Forms приложения C#. Подключение Arduino и создание приложения
Управление Arduino из Windows Forms приложения C#. Программирование Arduino и отправка команд, датчики
Управление Arduino из Windows Forms приложения C#. Примеры применения

Управление светодиодами и RGB-подсветкой



Начнем с классики - управления светодиодами. Это не просто учебный пример, а база для создания систем индикации, декоративного освещения или даже световых шоу. Вот как можно реализовать RGB-контроллер:

На стороне Arduino:

C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
const int redPin = 9;    // PWM-пин для красного
const int greenPin = 10; // PWM-пин для зеленого
const int bluePin = 11;  // PWM-пин для синего
 
void setup() {
  Serial.begin(9600);
  pinMode(redPin, OUTPUT);
  pinMode(greenPin, OUTPUT);
  pinMode(bluePin, OUTPUT);
}
 
void loop() {
  if (Serial.available() > 0) {
    String command = Serial.readStringUntil(';');
    
    if (command.startsWith("RGB:")) {
      // Формат команды: RGB:R,G,B;
      command = command.substring(4); // Удаляем "RGB:"
      
      int firstComma = command.indexOf(',');
      int secondComma = command.indexOf(',', firstComma + 1);
      
      if (firstComma != -1 && secondComma != -1) {
        int r = command.substring(0, firstComma).toInt();
        int g = command.substring(firstComma + 1, secondComma).toInt();
        int b = command.substring(secondComma + 1).toInt();
        
        analogWrite(redPin, r);
        analogWrite(greenPin, g);
        analogWrite(bluePin, b);
        
        Serial.println("OK");
      }
    }
  }
}
А вот соответствующий код в C# для создания интерфейса выбора цвета:

C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
private void colorPicker_ColorChanged(object sender, EventArgs e)
{
    Color selectedColor = colorPicker.Color;
    
    // Преобразуем цвет в значения ШИМ (0-255)
    int red = selectedColor.R;
    int green = selectedColor.G;
    int blue = selectedColor.B;
    
    // Отправляем команду на Arduino
    string command = $"RGB:{red},{green},{blue};";
    _arduinoController.SendCommand(command);
    
    // Обновляем превью выбранного цвета
    panelColorPreview.BackColor = selectedColor;
}

Управление моторами и сервоприводами



Добавление моторов и сервоприводов существенно расширяет возможности проектов на Arduino. Я использовал это для создания робота, управляемого через Windows-приложение:

C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
#include <Servo.h>
 
// Пины для управления моторами
const int leftMotorPin1 = 2;
const int leftMotorPin2 = 3;
const int rightMotorPin1 = 4;
const int rightMotorPin2 = 5;
 
// Пин для сервопривода "головы"
const int servoPin = 9;
 
Servo headServo;
int servoPosition = 90; // Центральное положение
 
void setup() {
  Serial.begin(9600);
  
  // Настройка пинов моторов
  pinMode(leftMotorPin1, OUTPUT);
  pinMode(leftMotorPin2, OUTPUT);
  pinMode(rightMotorPin1, OUTPUT);
  pinMode(rightMotorPin2, OUTPUT);
  
  // Инициализация сервопривода
  headServo.attach(servoPin);
  headServo.write(servoPosition);
}
 
void loop() {
  if (Serial.available() > 0) {
    String command = Serial.readStringUntil(';');
    
    if (command.startsWith("MOVE:")) {
      // Формат: MOVE:direction,speed;
      command = command.substring(5);
      int commaIndex = command.indexOf(',');
      
      if (commaIndex != -1) {
        String direction = command.substring(0, commaIndex);
        int speed = command.substring(commaIndex + 1).toInt();
        
        if (direction == "FORWARD") {
          moveForward(speed);
        } else if (direction == "BACKWARD") {
          moveBackward(speed);
        } else if (direction == "LEFT") {
          turnLeft(speed);
        } else if (direction == "RIGHT") {
          turnRight(speed);
        } else if (direction == "STOP") {
          stopMotors();
        }
        
        Serial.println("OK");
      }
    } else if (command.startsWith("SERVO:")) {
      // Формат: SERVO:angle;
      int angle = command.substring(6).toInt();
      
      // Ограничиваем угол диапазоном 0-180
      angle = constrain(angle, 0, 180);
      
      headServo.write(angle);
      servoPosition = angle;
      
      Serial.println("OK");
    }
  }
}
 
void moveForward(int speed) {
  // Реализация движения вперед
  digitalWrite(leftMotorPin1, HIGH);
  digitalWrite(leftMotorPin2, LOW);
  digitalWrite(rightMotorPin1, HIGH);
  digitalWrite(rightMotorPin2, LOW);
}
 
// ... другие функции для движения ...
В C# приложении для этого я реализовал джойстик с помощью элемента PictureBox и событий мыши:

C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
private void pictureBoxJoystick_MouseDown(object sender, MouseEventArgs e)
{
    _joystickActive = true;
    UpdateJoystickPosition(e.X, e.Y);
}
 
private void pictureBoxJoystick_MouseMove(object sender, MouseEventArgs e)
{
    if (_joystickActive)
        UpdateJoystickPosition(e.X, e.Y);
}
 
private void pictureBoxJoystick_MouseUp(object sender, EventArgs e)
{
    _joystickActive = false;
    ResetJoystick();
    
    // Отправляем команду остановки
    _arduinoController.SendCommand("MOVE:STOP,0;");
}
 
private void UpdateJoystickPosition(int x, int y)
{
    // Рассчитываем смещение от центра
    int centerX = pictureBoxJoystick.Width / 2;
    int centerY = pictureBoxJoystick.Height / 2;
    
    int deltaX = x - centerX;
    int deltaY = centerY - y; // Инвертируем Y, так как в GDI+ ось Y направлена вниз
    
    // Нормализуем значения до диапазона -100..100
    float normalizedX = (float)deltaX / centerX * 100;
    float normalizedY = (float)deltaY / centerY * 100;
    
    // Ограничиваем значения
    normalizedX = Math.Max(-100, Math.Min(100, normalizedX));
    normalizedY = Math.Max(-100, Math.Min(100, normalizedY));
    
    // Определяем направление и скорость
    string direction;
    int speed;
    
    if (Math.Abs(normalizedY) > Math.Abs(normalizedX)) {
        // Движение вперед/назад
        direction = normalizedY > 0 ? "FORWARD" : "BACKWARD";
        speed = (int)Math.Abs(normalizedY);
    } else {
        // Поворот влево/вправо
        direction = normalizedX < 0 ? "LEFT" : "RIGHT";
        speed = (int)Math.Abs(normalizedX);
    }
    
    // Отправляем команду
    _arduinoController.SendCommand($"MOVE:{direction},{speed};");
    
    // Обновляем положение индикатора джойстика
    UpdateJoystickIndicator(x, y);
}

Метеостанция с записью данных



Один из самых полезных проектов, который я реализовал - домашняя метеостанция с датчиками температуры, влажности и атмосферного давления. Она не только показывает текущие значения, но и строит графики и прогнозирует погоду на основе изменения давления.

На стороне Arduino:

C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
#include <Wire.h>
#include <Adafruit_Sensor.h>
#include <Adafruit_BME280.h>
#include <DHT.h>
 
#define DHTPIN 2
#define DHTTYPE DHT22
 
Adafruit_BME280 bme;
DHT dht(DHTPIN, DHTTYPE);
 
unsigned long lastReadTime = 0;
const unsigned long readInterval = 5000; // Считываем каждые 5 секунд
 
void setup() {
  Serial.begin(9600);
  
  // Инициализация датчиков
  if (!bme.begin(0x76)) {
    Serial.println("ERROR:BME280_NOT_FOUND;");
  }
  
  dht.begin();
}
 
void loop() {
  // Обработка команд
  if (Serial.available() > 0) {
    String command = Serial.readStringUntil(';');
    
    if (command == "READ") {
      sendSensorData();
    }
  }
  
  // Периодическое считывание датчиков
  unsigned long currentTime = millis();
  if (currentTime - lastReadTime >= readInterval) {
    sendSensorData();
    lastReadTime = currentTime;
  }
}
 
void sendSensorData() {
  // Считываем данные с BME280
  float tempBME = bme.readTemperature();
  float humBME = bme.readHumidity();
  float pressure = bme.readPressure() / 100.0F; // гПа
  
  // Считываем данные с DHT22
  float tempDHT = dht.readTemperature();
  float humDHT = dht.readHumidity();
  
  // Проверяем значения на корректность
  if (isnan(tempDHT) || isnan(humDHT)) {
    Serial.println("ERROR:DHT_READ_FAILED;");
  } else {
    Serial.print("TEMP_DHT:");
    Serial.print(tempDHT);
    Serial.println(";");
    
    Serial.print("HUM_DHT:");
    Serial.print(humDHT);
    Serial.println(";");
  }
  
  if (isnan(tempBME) || isnan(humBME) || isnan(pressure)) {
    Serial.println("ERROR:BME_READ_FAILED;");
  } else {
    Serial.print("TEMP_BME:");
    Serial.print(tempBME);
    Serial.println(";");
    
    Serial.print("HUM_BME:");
    Serial.print(humBME);
    Serial.println(";");
    
    Serial.print("PRESSURE:");
    Serial.print(pressure);
    Serial.println(";");
  }
}
В C# приложении я реализовал не только отображение текущих данных, но и прогнозирование погоды на основе барометрической тенденции:

C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
private WeatherTrend CalculateWeatherTrend(List<float> pressureHistory)
{
    if (pressureHistory.Count < 3)
        return WeatherTrend.Stable;
    
    // Получаем последние 3 часа измерений (при интервале 5 минут)
    var recentReadings = pressureHistory.Skip(Math.Max(0, pressureHistory.Count - 36)).ToList();
    
    // Находим линейный тренд
    float slope = CalculateLinearRegressionSlope(recentReadings);
    
    // Классифицируем тренд
    if (slope > 0.5f)
        return WeatherTrend.RisingFast; // Быстрый рост - улучшение погоды
    else if (slope > 0.2f)
        return WeatherTrend.Rising; // Умеренный рост - постепенное улучшение
    else if (slope > -0.2f)
        return WeatherTrend.Stable; // Стабильно
    else if (slope > -0.5f)
        return WeatherTrend.Falling; // Умеренное падение - ухудшение погоды
    else
        return WeatherTrend.FallingFast; // Быстрое падение - вероятны осадки
}
 
private float CalculateLinearRegressionSlope(List<float> values)
{
    int n = values.Count;
    float sumX = 0;
    float sumY = 0;
    float sumXY = 0;
    float sumXX = 0;
    
    for (int i = 0; i < n; i++)
    {
        float x = i;
        float y = values[i];
        
        sumX += x;
        sumY += y;
        sumXY += x * y;
        sumXX += x * x;
    }
    
    return (n * sumXY - sumX * sumY) / (n * sumXX - sumX * sumX);
}

Система "умного дома" с контролем освещения



Ещё один пример - система управления освещением в квартире. Arduino подключается к реле, которые коммутируют светильники, а приложение на C# позволяет управлять освещением, настраивать расписание и автоматизацию.

На стороне Arduino:

C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
const int relayPins[] = {2, 3, 4, 5, 6, 7}; // Пины для реле
const int numRelays = 6; // Количество реле
 
// Состояния реле
bool relayStates[6] = {false, false, false, false, false, false};
 
void setup() {
  Serial.begin(9600);
  
  // Настройка пинов реле
  for (int i = 0; i < numRelays; i++) {
    pinMode(relayPins[i], OUTPUT);
    digitalWrite(relayPins[i], HIGH); // Реле с активным LOW
  }
}
 
void loop() {
  if (Serial.available() > 0) {
    String command = Serial.readStringUntil(';');
    
    if (command.startsWith("RELAY:")) {
      // Формат: RELAY:index,state;
      command = command.substring(6);
      int commaIndex = command.indexOf(',');
      
      if (commaIndex != -1) {
        int relayIndex = command.substring(0, commaIndex).toInt();
        int state = command.substring(commaIndex + 1).toInt();
        
        if (relayIndex >= 0 && relayIndex < numRelays) {
          // Инвертируем значение, так как реле с активным LOW
          digitalWrite(relayPins[relayIndex], state == 0 ? HIGH : LOW);
          relayStates[relayIndex] = (state != 0);
          
          // Отправляем подтверждение
          Serial.print("RELAY_ACK:");
          Serial.print(relayIndex);
          Serial.print(",");
          Serial.print(state);
          Serial.println(";");
        }
      }
    } else if (command == "STATUS") {
      // Отправляем текущее состояние всех реле
      for (int i = 0; i < numRelays; i++) {
        Serial.print("RELAY_STATUS:");
        Serial.print(i);
        Serial.print(",");
        Serial.print(relayStates[i] ? "1" : "0");
        Serial.println(";");
      }
    }
  }
}
А в приложении на C# реализована интерактивная схема квартиры:

C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
private void InitializeRoomLayout()
{
    // Создаем интерактивную карту комнат
    _roomPanels = new Dictionary<string, Panel>();
    
    // Гостиная
    var livingRoomPanel = new Panel
    {
        Location = new Point(50, 50),
        Size = new Size(200, 150),
        BackColor = Color.LightGray,
        BorderStyle = BorderStyle.FixedSingle
    };
    livingRoomPanel.Click += (s, e) => ShowRoomControls("LivingRoom");
    
    var livingRoomLabel = new Label
    {
        Text = "Гостиная",
        AutoSize = true,
        Location = new Point(70, 70)
    };
    livingRoomPanel.Controls.Add(livingRoomLabel);
    
    _roomPanels.Add("LivingRoom", livingRoomPanel);
    panelFloorPlan.Controls.Add(livingRoomPanel);
    
    // Добавляем другие комнаты аналогично...
}
 
private void ShowRoomControls(string roomName)
{
    panelRoomControls.Controls.Clear();
    
    // Заголовок комнаты
    var roomTitle = new Label
    {
        Text = GetRoomDisplayName(roomName),
        Font = new Font(Font.FontFamily, 12, FontStyle.Bold),
        AutoSize = true,
        Location = new Point(10, 10)
    };
    panelRoomControls.Controls.Add(roomTitle);
    
    // Получаем список света в этой комнате
    var lightsInRoom = _lightingConfig.Where(l => l.Room == roomName).ToList();
    
    int y = 50;
    foreach (var light in lightsInRoom)
    {
        // Создаем переключатель для света
        var checkbox = new CheckBox
        {
            Text = light.Name,
            Location = new Point(20, y),
            Tag = light.RelayIndex,
            Checked = _relayStates[light.RelayIndex]
        };
        checkbox.CheckedChanged += LightSwitch_CheckedChanged;
        
        panelRoomControls.Controls.Add(checkbox);
        y += 30;
    }
    
    // Добавляем кнопку "Включить все"
    var btnAllOn = new Button
    {
        Text = "Включить все",
        Location = new Point(20, y + 10),
        Tag = roomName
    };
    btnAllOn.Click += (s, e) => SetAllLightsInRoom(roomName, true);
    
    // Добавляем кнопку "Выключить все"
    var btnAllOff = new Button
    {
        Text = "Выключить все",
        Location = new Point(130, y + 10),
        Tag = roomName
    };
    btnAllOff.Click += (s, e) => SetAllLightsInRoom(roomName, false);
    
    panelRoomControls.Controls.Add(btnAllOn);
    panelRoomControls.Controls.Add(btnAllOff);
}
 
private void LightSwitch_CheckedChanged(object sender, EventArgs e)
{
    var checkbox = (CheckBox)sender;
    int relayIndex = (int)checkbox.Tag;
    bool state = checkbox.Checked;
    
    // Отправляем команду на Arduino
    _arduinoController.SendCommand($"RELAY:{relayIndex},{(state ? 1 : 0)};");
    
    // Обновляем состояние в нашем кэше
    _relayStates[relayIndex] = state;
    
    // Обновляем индикатор на плане
    UpdateRoomIndicator(GetRoomByRelayIndex(relayIndex), HasActiveLights(GetRoomByRelayIndex(relayIndex)));
}
 
private void SetAllLightsInRoom(string roomName, bool state)
{
    var lightsInRoom = _lightingConfig.Where(l => l.Room == roomName).ToList();
    
    foreach (var light in lightsInRoom)
    {
        _arduinoController.SendCommand($"RELAY:{light.RelayIndex},{(state ? 1 : 0)};");
        _relayStates[light.RelayIndex] = state;
        
        // Обновляем UI-элементы
        foreach (CheckBox checkbox in panelRoomControls.Controls.OfType<CheckBox>())
        {
            if ((int)checkbox.Tag == light.RelayIndex)
                checkbox.Checked = state;
        }
    }
    
    // Обновляем индикатор на плане
    UpdateRoomIndicator(roomName, state);
}

Система автоматического полива растений



Нередко я сталкивался с проблемой ухода за растениями во время отпуска. После нескольких "погибших" кактусов (кто бы мог подумать, что даже они требуют внимания), я решил автоматизировать систему полива с помощью Arduino. Вот базовый скетч для такой системы:

C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
const int moistureSensorPin = A0;
const int pumpRelayPin = 7;
const int waterLevelPin = 8;
 
// Пороговые значения влажности почвы (в процентах)
const int dryThreshold = 30;   // Ниже этого значения - сухая почва
const int wetThreshold = 70;   // Выше этого значения - влажная почва
 
void setup() {
  Serial.begin(9600);
  pinMode(pumpRelayPin, OUTPUT);
  pinMode(waterLevelPin, INPUT_PULLUP);
  
  // Выключаем насос при запуске
  digitalWrite(pumpRelayPin, LOW);
}
 
void loop() {
  if (Serial.available() > 0) {
    String command = Serial.readStringUntil(';');
    
    if (command == "CHECK") {
      sendSensorData();
    } else if (command == "PUMP_ON") {
      activatePump(true);
    } else if (command == "PUMP_OFF") {
      activatePump(false);
    }
  }
}
 
void sendSensorData() {
  // Считываем значение с датчика влажности почвы
  int moistureRaw = analogRead(moistureSensorPin);
  
  // Преобразуем в проценты (зависит от конкретного датчика)
  // Для многих датчиков влажности: 0 - мокрый, 1023 - сухой
  int moisturePercent = map(moistureRaw, 1023, 0, 0, 100);
  
  // Проверяем уровень воды в резервуаре
  bool waterLevelOK = digitalRead(waterLevelPin) == LOW;
  
  // Отправляем данные
  Serial.print("MOISTURE:");
  Serial.print(moisturePercent);
  Serial.println(";");
  
  Serial.print("WATER_LEVEL:");
  Serial.print(waterLevelOK ? "OK" : "LOW");
  Serial.println(";");
}
 
void activatePump(bool turnOn) {
  // Проверяем уровень воды перед включением насоса
  bool waterLevelOK = digitalRead(waterLevelPin) == LOW;
  
  if (turnOn && !waterLevelOK) {
    Serial.println("ERROR:LOW_WATER_LEVEL;");
    return;
  }
  
  digitalWrite(pumpRelayPin, turnOn ? HIGH : LOW);
  Serial.print("PUMP:");
  Serial.print(turnOn ? "ON" : "OFF");
  Serial.println(";");
}
В Windows Forms приложении я создал наглядную визуализацию с индикаторами влажности и таймерами полива:

C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
private void SetupPlantMonitoring()
{
    // Таймер для регулярной проверки датчиков
    _monitoringTimer = new System.Windows.Forms.Timer
    {
        Interval = 30000, // Проверка каждые 30 секунд
        Enabled = true
    };
    _monitoringTimer.Tick += MonitoringTimer_Tick;
    
    // Создаем элементы интерфейса для каждого растения
    foreach (var plant in _plants)
    {
        CreatePlantPanel(plant);
    }
}
 
private void MonitoringTimer_Tick(object sender, EventArgs e)
{
    if (!_arduinoController.IsConnected)
        return;
        
    _arduinoController.SendCommand("CHECK;");
}
 
private void CreatePlantPanel(Plant plant)
{
    // Создаем панель для растения
    Panel plantPanel = new Panel
    {
        Width = 180,
        Height = 220,
        BorderStyle = BorderStyle.FixedSingle,
        Margin = new Padding(10)
    };
    
    // Добавляем изображение растения
    PictureBox plantImage = new PictureBox
    {
        Width = 100,
        Height = 100,
        SizeMode = PictureBoxSizeMode.Zoom,
        Image = plant.Image,
        Location = new Point(40, 10)
    };
    
    // Добавляем название
    Label nameLabel = new Label
    {
        Text = plant.Name,
        TextAlign = ContentAlignment.MiddleCenter,
        Width = 160,
        Location = new Point(10, 115)
    };
    
    // Индикатор влажности
    ProgressBar moistureBar = new ProgressBar
    {
        Minimum = 0,
        Maximum = 100,
        Width = 160,
        Location = new Point(10, 140)
    };
    moistureBar.Tag = plant.Id; // Для идентификации
    _moistureBars.Add(plant.Id, moistureBar);
    
    // Кнопка ручного полива
    Button waterButton = new Button
    {
        Text = "Полить",
        Width = 80,
        Location = new Point(50, 170)
    };
    waterButton.Click += (s, e) => WaterPlant(plant.Id);
    
    // Добавляем элементы на панель
    plantPanel.Controls.Add(plantImage);
    plantPanel.Controls.Add(nameLabel);
    plantPanel.Controls.Add(moistureBar);
    plantPanel.Controls.Add(waterButton);
    
    // Добавляем панель в контейнер
    flowLayoutPanelPlants.Controls.Add(plantPanel);
}
 
private void WaterPlant(string plantId)
{
    _arduinoController.SendCommand("PUMP_ON;");
    
    // Запускаем таймер для отключения насоса через 5 секунд
    System.Threading.Timer pumpTimer = null;
    pumpTimer = new System.Threading.Timer(state => 
    {
        _arduinoController.SendCommand("PUMP_OFF;");
        pumpTimer.Dispose();
    }, null, 5000, Timeout.Infinite);
    
    LogAction($"Полив растения {plantId} запущен");
}

Система контроля доступа с RFID



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

C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
#include <SPI.h>
#include <MFRC522.h>
 
#define RST_PIN 9
#define SS_PIN 10
 
MFRC522 rfid(SS_PIN, RST_PIN);
 
// Буфер для хранения ID метки
byte lastReadCardID[4] = {0, 0, 0, 0};
bool cardPresent = false;
 
void setup() {
  Serial.begin(9600);
  SPI.begin();
  rfid.PCD_Init();
}
 
void loop() {
  // Проверяем наличие команд
  if (Serial.available() > 0) {
    String command = Serial.readStringUntil(';');
    
    if (command == "GET_STATUS") {
      sendCardStatus();
    }
  }
  
  // Проверяем наличие карты
  if (!rfid.PICC_IsNewCardPresent())
    return;
    
  // Считываем серийный номер карты
  if (!rfid.PICC_ReadCardSerial())
    return;
    
  // Сохраняем ID карты
  for (byte i = 0; i < 4; i++) {
    lastReadCardID[i] = rfid.uid.uidByte[i];
  }
  
  cardPresent = true;
  
  // Отправляем информацию о карте
  sendCardInfo();
  
  // Останавливаем шифрование PICC
  rfid.PICC_HaltA();
  // Останавливаем шифрование PCD
  rfid.PCD_StopCrypto1();
}
 
void sendCardStatus() {
  if (cardPresent) {
    Serial.println("CARD_PRESENT:YES;");
  } else {
    Serial.println("CARD_PRESENT:NO;");
  }
}
 
void sendCardInfo() {
  Serial.print("CARD_ID:");
  for (byte i = 0; i < 4; i++) {
    Serial.print(lastReadCardID[i] < 0x10 ? "0" : "");
    Serial.print(lastReadCardID[i], HEX);
  }
  Serial.println(";");
  
  // Сбрасываем флаг присутствия карты после отправки
  cardPresent = false;
}
В C# я создал приложение для управления пользователями и их правами доступа:

C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
public class AccessSystem
{
    private Dictionary<string, User> _authorizedUsers = new Dictionary<string, User>();
    private List<AccessLog> _accessLogs = new List<AccessLog>();
    
    public void AddUser(User user)
    {
        _authorizedUsers[user.CardId] = user;
        SaveUsers();
    }
    
    public bool CheckAccess(string cardId)
    {
        if (_authorizedUsers.TryGetValue(cardId, out User user))
        {
            LogAccess(cardId, user.Name, true);
            return true;
        }
        
        LogAccess(cardId, "Unknown", false);
        return false;
    }
    
    private void LogAccess(string cardId, string userName, bool granted)
    {
        var log = new AccessLog
        {
            CardId = cardId,
            UserName = userName,
            Timestamp = DateTime.Now,
            AccessGranted = granted
        };
        
        _accessLogs.Add(log);
        SaveLogs();
    }
    
    // Отображение логов доступа на форме
    public void DisplayLogs(DataGridView gridView)
    {
        var bindingSource = new BindingSource();
        bindingSource.DataSource = _accessLogs;
        gridView.DataSource = bindingSource;
    }
}
Эти примеры демонстрируют, насколько гибкой может быть связка Arduino + Windows Forms для создания практических решений под конкретные задачи. Удивительно, но даже простые проекты часто оказываются невероятно полезными в повседневной жизни.

Автоматизация тестирования аппаратных прототипов через C# интерфейс



Тестирование - одно из самых узких мест в разработке электронных устройств. Помню, как мы с командой разрабатывали систему "умных" дверных замков и вручную проверяли каждый прототип. Это занимало часы, а результаты были неконсистентными: один инженер мог пропустить то, что другой всегда проверял. Именно тогда я понял - нам нужна автоматизация.

Создание автоматических тестов для аппаратных решений на Arduino через C# интерфейс дает массу преимуществ: повторяемость, скорость и обьективность. Представьте, что вместо ручной проверки каждого модуля вы запускаете программу, которая проводит десятки или сотни тестов, собирает результаты и генерирует отчет. Звучит заманчиво, правда?

Организация тестовых сценариев



Начать стоит с продумывания архитектуры тестового фреймворка. Я обычно использую подход, похожий на устройство NUnit или MSTest, но адаптированный для работы с физическими устройствами:

C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
public abstract class ArduinoTestCase
{
  protected IArduinoController Controller { get; private set; }
  
  public string Name { get; set; }
  public string Description { get; set; }
  
  private List<TestResult> _results = new List<TestResult>();
  
  public void Initialize(IArduinoController controller)
  {
      Controller = controller;
  }
  
  public abstract Task<bool> RunTestAsync();
  
  protected void LogResult(string stepName, bool passed, string message = null)
  {
      _results.Add(new TestResult
      {
          TestName = Name,
          StepName = stepName,
          Passed = passed,
          Message = message,
          Timestamp = DateTime.Now
      });
  }
  
  public IReadOnlyList<TestResult> GetResults() => _results;
}
Наследуя от этого базового класса, я создаю конкретные тесты для проверки различных аспектов прототипа:

C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
public class LEDFunctionalityTest : ArduinoTestCase
{
  public LEDFunctionalityTest()
  {
      Name = "Проверка функциональности светодиода";
      Description = "Тест включает и выключает светодиод, проверяя корректность обратной связи";
  }
  
  public override async Task<bool> RunTestAsync()
  {
      try
      {
          // Шаг 1: Включаем светодиод
          LogResult("Включение светодиода", true, "Отправка команды...");
          string response = await Controller.SendCommandAsync("LED:13,1;");
          bool step1 = response.Contains("OK");
          LogResult("Проверка ответа на включение", step1, response);
          
          // Небольшая пауза между командами
          await Task.Delay(500);
          
          // Шаг 2: Выключаем светодиод
          LogResult("Выключение светодиода", true, "Отправка команды...");
          response = await Controller.SendCommandAsync("LED:13,0;");
          bool step2 = response.Contains("OK");
          LogResult("Проверка ответа на выключение", step2, response);
          
          return step1 && step2;
      }
      catch (Exception ex)
      {
          LogResult("Исключение в тесте", false, ex.Message);
          return false;
      }
  }
}

Запуск наборов тестов



Для запуска нескольких тестов последовательно создаем менеджер тестирования:

C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
public class TestManager
{
  private List<ArduinoTestCase> _tests = new List<ArduinoTestCase>();
  private IArduinoController _controller;
  
  public event EventHandler<TestResultEventArgs> TestCompleted;
  public event EventHandler<EventArgs> AllTestsCompleted;
  
  public TestManager(IArduinoController controller)
  {
      _controller = controller;
  }
  
  public void AddTest(ArduinoTestCase test)
  {
      _tests.Add(test);
  }
  
  public async Task RunAllTestsAsync()
  {
      foreach (var test in _tests)
      {
          test.Initialize(_controller);
          bool result = await test.RunTestAsync();
          
          TestCompleted?.Invoke(this, new TestResultEventArgs
          {
              TestName = test.Name,
              Passed = result,
              Results = test.GetResults()
          });
      }
      
      AllTestsCompleted?.Invoke(this, EventArgs.Empty);
  }
}

Стресс-тестирование и длительные тесты



Особенно полезны автоматические тесты для проверки граничных ситуаций и длительной работы устройств. Для этого я создаю специальные типы тестов:

C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
public class StressTest : ArduinoTestCase
{
  private int _iterations;
  
  public StressTest(int iterations = 100)
  {
      _iterations = iterations;
      Name = $"Стресс-тест ({iterations} итераций)";
      Description = "Многократно отправляет команды для проверки стабильности";
  }
  
  public override async Task<bool> RunTestAsync()
  {
      int successCount = 0;
      
      for (int i = 0; i < _iterations; i++)
      {
          try
          {
              // Отправляем команду включения
              string response = await Controller.SendCommandAsync("LED:13,1;");
              await Task.Delay(50);
              
              // Отправляем команду выключения
              response = await Controller.SendCommandAsync("LED:13,0;");
              await Task.Delay(50);
              
              successCount++;
              
              // Логируем промежуточный результат каждые 10 итераций
              if (i % 10 == 0)
              {
                  LogResult($"Итерация {i}", true, $"Успешно: {successCount}/{i+1}");
              }
          }
          catch (Exception ex)
          {
              LogResult($"Ошибка на итерации {i}", false, ex.Message);
          }
      }
      
      double successRate = (double)successCount / _iterations * 100;
      LogResult("Итоговый результат", successRate > 95, 
              $"Успешно выполнено {successRate:F1}% операций");
      
      return successRate > 95; // Тест считается успешным, если >95% операций прошли нормально
  }
}

Визуализация результатов тестирования



Важной частью автоматизации является удобное представление результатов. Я реализовал простую, но информативную панель тестов в Windows Forms:

C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
private void InitializeTestingPanel()
{
  // Создаем таблицу для отображения результатов
  dataGridViewTests.Columns.Add("TestName", "Название теста");
  dataGridViewTests.Columns.Add("Status", "Статус");
  dataGridViewTests.Columns.Add("Details", "Детали");
  
  // Настраиваем форматирование ячеек в зависимости от статуса
  dataGridViewTests.CellFormatting += (s, e) => {
      if (e.ColumnIndex == 1) // Колонка статуса
      {
          if (e.Value?.ToString() == "Пройден")
              e.CellStyle.BackColor = Color.LightGreen;
          else if (e.Value?.ToString() == "Провален")
              e.CellStyle.BackColor = Color.LightPink;
          else if (e.Value?.ToString() == "Выполняется")
              e.CellStyle.BackColor = Color.LightYellow;
      }
  };
  
  // Настраиваем обработчики событий от менеджера тестов
  _testManager.TestCompleted += (s, e) => {
      this.Invoke((MethodInvoker)delegate {
          int rowIndex = FindTestRow(e.TestName);
          if (rowIndex >= 0)
          {
              dataGridViewTests.Rows[rowIndex].Cells[1].Value = e.Passed ? "Пройден" : "Провален";
              
              // Добавляем кнопку для просмотра деталей
              DataGridViewButtonCell detailsButton = 
                  new DataGridViewButtonCell { Value = "Просмотр" };
              dataGridViewTests.Rows[rowIndex].Cells[2] = detailsButton;
              
              // Сохраняем результаты для отображения в деталях
              dataGridViewTests.Rows[rowIndex].Tag = e.Results;
          }
      });
  };
}

Генерация отчетов о тестировании



После завершения всех тестов я генерирую детальный отчет, который можно сохранить для дальнейшего анализа:

C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
private void GenerateTestReport()
{
  StringBuilder report = new StringBuilder();
  report.AppendLine("=== Отчет о тестировании аппаратного прототипа ===");
  report.AppendLine($"Дата: {DateTime.Now}");
  report.AppendLine($"Устройство: {_arduinoController.PortName}");
  report.AppendLine();
  
  int totalTests = dataGridViewTests.Rows.Count;
  int passedTests = 0;
  
  foreach (DataGridViewRow row in dataGridViewTests.Rows)
  {
      string testName = row.Cells[0].Value?.ToString();
      string status = row.Cells[1].Value?.ToString();
      
      if (status == "Пройден")
          passedTests++;
          
      report.AppendLine($"Тест: {testName}");
      report.AppendLine($"Статус: {status}");
      
      // Добавляем детали, если они есть
      if (row.Tag is IReadOnlyList<TestResult> results)
      {
          report.AppendLine("Детали:");
          foreach (var result in results)
          {
              report.AppendLine($"  - {result.StepName}: {(result.Passed ? "OK" : "ОШИБКА")}");
              if (!string.IsNullOrEmpty(result.Message))
                  report.AppendLine($"    {result.Message}");
          }
      }
      
      report.AppendLine();
  }
  
  report.AppendLine($"Итого: {passedTests}/{totalTests} тестов пройдено успешно");
  
  // Сохраняем отчет в файл
  string reportPath = $"Test_Report_{DateTime.Now:yyyy-MM-dd_HH-mm-ss}.txt";
  File.WriteAllText(reportPath, report.ToString());
  
  MessageBox.Show($"Отчет о тестировании сохранен в файл {reportPath}", 
                 "Тестирование завершено", MessageBoxButtons.OK, MessageBoxIcon.Information);
}

Практическое применение



На практике я часто использую такую систему для регресионного тестирования. Каждый раз, когда вносятся изменения в прошивку Arduino или в схему устройства, запускается полный набор тестов для проверки, что ничего не сломалось.

Особено ценна такая автоматизация при разработке серийных устройств. Например, в одном из проектов мы делали систему управления теплицей на базе Arduino Mega, и каждая плата проходила автоматический тест, проверяющий все датчики, реле и коммуникационные модули. Это позволило существенно повысить качество и уменьшить количество брака.

Автоматизация тестирования превратила мой подход к разработке с Arduino. Теперь я могу быстро экспериментировать с новыми идеями, не боясь сломать существующую функциональность. А клиентам я демонстрирую не только работающее устройство, но и результаты всеобъемлющего тестирования, что существенно повышает их доверие к продукту.

Создание приложения на C# Windows Forms, взаимодействующего с БД MS SQL Server. Полезные примеры
Добрый день. Делаю курсовой на тему создания приложения на C# Windows Forms, взаимодействующего с...

Примеры применения указателей на функции
Всем доброго дня! С тем как использовать указатели на функции, вроде разобрался. Но так и не...

Как элементы Windows Forms использовать для грамотного расположения двух таблиц Windows Forms?
Как элементы Windows Forms использовать для грамотного расположения двух таблиц Windows Forms? ...

Нужен перевод кода с С# Windows Forms в C++ Windows Forms
Нужно конвертировать(перевести код) в С++ Windows Forms using System; using...


Заключение: следующие шаги



Вот мы и прошли весь путь от простейшего моргания светодиодом до создания полноценных систем с автоматическим тестированием. Связка Arduino + C# открывает поистине безграничные возможности для творчества и практического применения. Но что же дальше? Как развиваться в этом направлении?

Первое, что я советую - изучить другие протоколы связи. UART хорош для начала, но в серьезных проектах могут понадобиться более продвинутые технологии. Освойте Bluetooth для беспроводной связи с Arduino - это откроет новые горизонты для мобильных решений. Попробуйте работу через Ethernet или WiFi модули - с ними можно создавать устройства, доступные из любой точки мира.

Для тех, кто всерьёз заинтересовался IoT, следующим шагом может стать интеграция с облачными платформами. Azure IoT Hub, AWS IoT или даже самописные решения на основе MQTT брокеров позволят вам создавать распределенные системы с централизованым управлением. Я сам был удивлен, насколько просто оказалось подключить свою домашнюю метеостанцию к облаку и получать уведомления о критической температуре, даже находясь в отпуске. Стоит также обратить внимание на более производительные микроконтроллеры. ESP32, например, обладает значительно большей мощностью и функциональностью чем классический Arduino, но при этом совместим с тем же подходом к программированию. Raspberry Pi - еще один шаг вперед, позволяющий запускать .NET Core прямо на устройстве, хотя это уже совсем другая история.

Конверт Virtual-Key Codes в System.Windows.Forms.Keys и System.Windows.Forms.Keys в Virtual-Key Codes
Есть Virtual-Key Codes https://docs.microsoft.com/ru-ru/windows/desktop/inputdev/virtual-key-codes...

Нужен таймер. Не удаётся преобразовать преобразовать из "System.Windows.Forms.Timer" в "System.Windows.Forms.Control"
private static void TimeLable(Form current) { int i; int tk; ...

Примеры кода програм windows forms
Гуглится это дело в рунете довольно вяло. Может быть кто не постесняется покидать сюда...

Windows Forms и Arduino Uno
Добрый вечер! Имеется приложение Windows Forms и простая программа на Arduino. Суть программы...

Общие примеры использования микроконтроллера. Примеры использования различных видов адресации и некоторых команд в прогр
Подходом к делению является представление делителя в виде суммы чисел, являющихся дробными...

Arduino UNO. Как работать c RFID-сканнером и Arduino на одном Serial-порту?
Рас уж тут речь зашла об ардуине и многопоточности COM порта, думаю могу обратиться именно сюда за...

Модуль распознавания речи + Arduino Pro mini + Arduino MP3-Sheild
Список компонентов: 1).Модуль распознавания речи.(напряжение питания от 4,5 до 5,5 Вольт DC)...

Arduino uno + arduino ethernet + delphi для чайников
Доброго времени суток. У меня такая задача нужно реализовать программу на Delphi которая...

Ошибка при загрузке кода в Arduino Uno (Китай) - Arduino
В Диспетчере устройств Arduino определяется, как USB-SERIAL CH340 (COM5). При попытке залить...

Arduino обмен данные между Arduino
Доброго времени суток, писал код обмен данных между двумя ардуинкой, отправляю из одной ардуинку...

Вывод из php "echo" в arduino ethernet + arduino mega 2560
Всем здравствуйте. Недавно потребовалось передавать данные с Arduino ethernet + arduino mega 2560 в...

Код для Windows Forms не работает в Web Forms?
В том году я делал лабораторки по Winforms. Естественно, они все у меня сохранились, и я полез в их...

Надоела реклама? Зарегистрируйтесь и она исчезнет полностью.
Всего комментариев 0
Комментарии
 
Новые блоги и статьи
Нейросеть на алгоритме "эстафета хвоста" как перспектива.
Hrethgir 06.05.2026
На десерт, когда запущу сервер. Статья тут https:/ / habr. com/ ru/ articles/ 1030914/ . Автор я сам, нейросеть только помогает в вопросах которые мне не известны - не знаю людей которые знали-бы. . .
Асинхронный приём данных из COM-порта
Argus19 01.05.2026
Асинхронный приём данных из COM-порта Купил на aliexpress термопринтер QR701. Он оказался странным. Поключил к Arduino Nano. Был очень удивлён. Наотрез отказывается печатать русские буквы. Чтобы. . .
попытка написать игровой сервер на C++
pyirrlicht 29.04.2026
попытка написать игровой сервер на плюсах с открытым бесконечным миром. возможно получится прикрутить интерпретатор питон для кастомизации игровой логики. что есть на текущий момент:. . .
Контроль уникальности выбранного документа-основания при изменении реквизита
Maks 28.04.2026
Алгоритм из решения ниже разработан на примере нетипового документа "ЗаявкаНаРемонтСпецтехники", разработанного в КА2. Задача: уведомлять пользователя, если указанная заявка (документ-основание). . .
Благородство как наказание
Maks 24.04.2026
У хорошего человека отношения с женщинами всегда складываются трудно. А я человек хороший. Заявляю без тени смущения, потому что гордиться тут нечем. От хорошего человека ждут соответствующего. . .
Валидация и контроль данных табличной части документа перед записью
Maks 22.04.2026
Алгоритм из решения ниже реализован на примере нетипового документа, разработанного в КА2. Задача: контроль и валидация данных табличной части документа перед записью с учетом регламента компании. . .
Отчёт о затраченных материалах за определенный период с макетом печатной формы
Maks 21.04.2026
Отчёт из решения ниже размещён в конфигурации КА2. Задача: разработка отчёта по затраченным материалам за определённый период, с возможностью вывода печатной формы отчёта с шапкой и подвалом. В. . .
Отчёт о спецтехнике находящейся в ремонте
Maks 20.04.2026
Отчёт из решения ниже размещен в конфигурации КА2. Задача: отобразить спецтехнику, которая на данный момент находится в ремонте. Есть нетиповой документ "Заявка на ремонт спецтехники" который. . .
КиберФорум - форум программистов, компьютерный форум, программирование
Powered by vBulletin
Copyright ©2000 - 2026, CyberForum.ru