Самое интересное начинается, когда мы применяем полученные знания для решения конкретных задач. За время работы с 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. Естественно, они все у меня сохранились, и я полез в их...
|