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

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

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

Нажмите на изображение для увеличения
Название: Управление Arduino из Windows Forms приложения C# 3.jpg
Просмотров: 524
Размер:	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
Комментарии
 
Новые блоги и статьи
Скрипты Tampermonkey для CyberForum, ChatGPT, Claude и пр.
Jin X 06.09.2026
Скрипты Tampermonkey для CyberForum, ChatGPT, Claude и пр. Работая с форумом и нейросетями в браузере часто хочется что-то подкорректировать или добавить какого-то функционала. Ниже прикреплён. . .
Программа опроса у.з. расходомера SLS-720F
Argus19 02.09.2026
Программа опроса у. з. расходомера SLS-720F Программа опрашивает один раз в минуту три ультразвуковых расходомера SLS-720F через интерфейс RS-485 по протоколу Modbus RTU. Опрашиваются регистры. . .
Hyper-V: Компьютер должен поддерживать доверенный платформенный модуль 2.0.
Maks 31.08.2026
При установке Windows 11 на виртуальную машину Hyper-V 2-го поколения вылезла такая ошибка: Решение: в параметрах виртуальной машины, в разделе "Безопасность" (Security) активировать флаг. . .
Архитектура биовида Стива в Майнкрафте: Зачем бонобо кубический каннибализм
anaschu 30.08.2026
Кубический Вагинокапитализм в Minecraft: Математический инвариант ОДУ и рок Стивов-бонобо Главная задача разработанной «Модели Всего» — наглядно продемонстрировать наличие системной «судьбы». . .
Оттачиваю умение писать js программы.
russiannick 30.08.2026
Проектом выходного дня стало написание Книги шифров Виженера. Итогом стала версия 200, синий туман. Синий туман назван так, потому что замораживает текст под собой. Нажатие синих кнопок управляют. . .
мат медиц модель 30. презентация проекта
anaschu 27.08.2026
хоп хоп хоп хидахоп, а я кладую))
Как у меня протекала болезнь
zorxor 27.08.2026
Здравствуйте, друзья! Эта запись блога предназначена именно для вас - для моих дорогих друзей, которые знали меня лично. Чтобы ответить на вопрос - а что же со мной произошло на самом деле? Я учился. . .
Нашел вот забавное видео о измерениях. Лучшее что я видел на эту тему
kumehtar 26.08.2026
ILETXiw9bMQ Основная суть и тезисы по измерениям: 0D (Нулевое измерение): точка, не имеющая длины, ширины, высоты или объема. Объект не может перемещаться в 0D. 1D (Первое измерение):. . .
КиберФорум - форум программистов, компьютерный форум, программирование
Powered by vBulletin
Copyright ©2000 - 2026, CyberForum.ru