Week 14 . Interface and application programming¶
Group Assignment¶
Group assignment-ի ընթացքում մեր նպատակը տարբեր գործիքների համեմատումն էր, որոնք օգտագործվում են միկրոկոնտրոլերի հետ user interface ստեղծելու և Serial communication իրականացնելու համար։ Պետք էր փորձարկել տարբեր ծրագրեր և հասկանալ, թե դրանցից որն է ավելի հարմար և արդյունավետ հետագա նախագծերի համար։
Gevorg-ի հետ միասին ընտրեցինք երեք տարբեր գործիքներ՝ Processing + G4P, Python + Tkinter և Node-RED։ Բոլոր ծրագրերում օգտագործեցինք նույն առաջադրանքը՝ համակարգչից կառավարել XIAO RP2040 board-ի LED-ը Serial communication-ի միջոցով։
| Tool | Processing + G4P | Python + Tkinter (VS Code) | Node-RED |
|---|---|---|---|
| Language | Java-like | Python | Visual + JavaScript |
| Installation | Processing IDE + G4P library | VS Code + Python + pyserial |
npm + dashboard and serial modules |
| Interface Type | Desktop GUI | Desktop GUI | Browser dashboard |
| Serial Communication | Built-in Serial library | pyserial library |
Serial nodes |
| Similar to Arduino | Yes, very similar structure | Partially similar | No |
| Difficulty Level | Medium | Medium | Easy |
| Time to Working Result | ~30 minutes | ~25 minutes | ~15 minutes |
| Main Advantage | Easy to understand Serial communication | Flexible and simple Python code | Fast setup and visual workflow |
| Main Disadvantage | GUI must be written manually | Requires threading for stable Serial reading | Part of the logic is hidden inside nodes |
After comparing these tools during the group assignment, I decided to continue working with Processing for my individual assignment. Since its structure is very similar to Arduino IDE, it was easier to understand the Serial communication process and build the interface step by step.
The comparison also helped me better understand how different environments handle GUI creation, Serial communication, and interaction with the microcontroller. Each tool approached the same task differently, but all of them were able to communicate with the XIAO RP2040 board successfully.
You can find the complete group assignment documentation here: Group Assignment Page
After completing the group assignment, I moved on to developing my own individual interface project.
const int LED_PIN = 25;
bool ledState = false;
int brightness = 255;
String inputBuffer = "";
void setup() {
Serial.begin(9600);
pinMode(LED_PIN, OUTPUT);
analogWrite(LED_PIN, 0);
delay(1000);
sendState();
}
void loop() {
while (Serial.available()) {
char c = (char)Serial.read();
if (c == '\n') {
processCommand(inputBuffer);
inputBuffer = "";
} else if (c != '\r') {
inputBuffer += c;
}
}
}
void processCommand(String cmd) {
cmd.trim();
if (cmd == "LED_ON") {
ledState = true;
analogWrite(LED_PIN, 0);
} else if (cmd == "LED_OFF") {
ledState = false;
analogWrite(LED_PIN, brightness);
}
sendState();
}
void sendState() {
if (ledState)
Serial.println("STATE:ON:" + String(brightness));
else
Serial.println("STATE:OFF:0");
}
import processing.serial.*;
import g4p_controls.*;
final String PORT_NAME = "COM13";
Serial port;
GButton btnToggle;
GLabel lblStatus;
boolean ledOn = false;
void setup() {
size(400, 280);
G4P.setGlobalColorScheme(GCScheme.BLUE_SCHEME);
port = new Serial(this, PORT_NAME, 9600);
port.bufferUntil('\n');
btnToggle = new GButton(this, 140, 110, 120, 45, "Turn LED ON");
lblStatus = new GLabel(this, 80, 175, 240, 30, "Waiting for board...");
lblStatus.setTextAlign(GAlign.CENTER, GAlign.MIDDLE);
}
void draw() {
background(245);
fill(60);
textAlign(CENTER);
textSize(17);
text("XIAO RP2040 — LED Control", width / 2, 65);
}
void handleButtonEvents(GButton button, GEvent event) {
if (button == btnToggle && event == GEvent.CLICKED) {
ledOn = !ledOn;
port.write(ledOn ? "LED_ON\n" : "LED_OFF\n");
btnToggle.setText(ledOn ? "Turn LED OFF" : "Turn LED ON");
}
}
void serialEvent(Serial p) {
String msg = trim(p.readStringUntil('\n'));
if (msg != null) {
lblStatus.setText(msg);
println("From board: " + msg);
}
}
#include <Arduino.h>
int ldrPin = A2;
int ledPin = D1;
void setup() {
Serial.begin(9600);
pinMode(ledPin, OUTPUT);
}
void loop() {
int ldrValue = analogRead(ldrPin);
Serial.print("LDR Value: ");
Serial.println(ldrValue);
if (ldrValue > 500) {
digitalWrite(ledPin, LOW); // LED ON
}
else {
digitalWrite(ledPin, HIGH); // LED OFF
}
delay(500);
}
Individual assignment¶
Installing and Setting Up the G4P Library and GUI Builder¶
For my project, I needed to create a convenient and visually clear graphical user interface through which it would be possible to control the system and display data. For this purpose, I downloaded the G4P library in the Processing environment.
First, I opened Sketch → Import Library → Manage Libraries… in Processing to access the Library Manager.
Then, in the opened Contribution Manager window, I typed G4P in the search field, selected the G4P library, and clicked Install.
After the installation was completed, the G4P library was successfully installed and ready to be used in my program.
The G4P library also includes the G4P GUI Builder tool, which allows the interface to be created visually. With it, different elements such as buttons, text fields, and sliders can be easily added, while the program automatically generates the corresponding code. This makes the GUI creation process faster and more convenient, which is why I also installed G4P GUI Builder from the Tools section of the Contribution Manager.
After installing the G4P library and the G4P GUI Builder tool, I proceeded with the work and started developing the graphical user interface and implementing the main part of the project.
The assignment was to write a program for my embedded board that would connect the user with the input and/or output devices of the system.
However, during the process, a problem occurred when I connected the DS3231 sensor to my board. The power LED on the module did not turn on, which indicated that the sensor was not receiving power.
Because of that, I started checking all the connections to make sure that VCC and GND were correctly connected.
Later, I connected the DS3231 module separately on a breadboard, and it worked properly—the power LED turned on. This confirmed that the sensor itself was functioning correctly.
Therefore, I concluded that the issue was related to the connections on my designed board. Most likely, during soldering, some of the pins were accidentally shorted, which prevented proper power delivery to the sensor.
After solving the issue related to the DS3231 module and checking all the connections, I moved on to the software implementation stage. At this stage, I wrote code in the Arduino IDE environment to establish communication between the computer and the RTC (Real Time Clock) module using Serial communication.
First, the necessary libraries were included in the program: Wire.h and RTClib.h. The Wire.h library is used for I2C communication, while RTClib.h makes it easier to work with the DS3231 module and retrieve or modify the current time.
#include <Wire.h>
#include "RTClib.h"
RTC_DS3231 rtc;
void setup () {
Serial.begin(9600);
if (!rtc.begin()) {
Serial.println("Couldn't find RTC");
while (1);
}
// If RTC lost power, set it to compile time
if (rtc.lostPower()) {
rtc.adjust(DateTime(F(__DATE__), F(__TIME__)));
}
}
void loop () {
DateTime now = rtc.now();
Serial.print(now.year(), DEC);
Serial.print('/');
Serial.print(now.month(), DEC);
Serial.print('/');
Serial.print(now.day(), DEC);
Serial.print(" ");
Serial.print(now.hour(), DEC);
Serial.print(':');
Serial.print(now.minute(), DEC);
Serial.print(':');
Serial.print(now.second(), DEC);
Serial.println();
delay(1000);
}
The Arduino code retrieved the current time from the DS3231 module and sent it through the Serial port.
After that, I created a new sketch in the Processing environment, opened the G4P GUI Builder, and created a button named TIME. The button was designed so that when pressed, the current time would be displayed in the program. Then, I included the necessary libraries in Processing: G4P controls and the SimpleDateFormat and Date classes used for handling time and date.
Next, Serial communication was implemented between Processing and Arduino. For this purpose, the processing.serial.* library was included, making it possible to read the data sent from Arduino. The program continuously received text data through the Serial port and stored it in a variable so it could later be displayed in the graphical interface.
import g4p_controls.*;
import java.text.SimpleDateFormat;
import java.util.Date;
GButton btn;
String currentTime = "";
void setup() {
size(500, 300, JAVA2D);
// TIME կոճակ
btn = new GButton(this, 180, 70, 140, 40);
btn.setText("TIME");
textAlign(CENTER, CENTER);
textSize(32);
}
void draw() {
background(220);
fill(0);
// Ժամի ցուցադրում
text(currentTime, width/2, 180);
}
// Կոճակի event
void handleButtonEvents(GButton button, GEvent event) {
if (button == btn && event == GEvent.CLICKED) {
currentTime =
new SimpleDateFormat("HH:mm:ss").format(new Date());
}
}
Then, an event handler was added for the created TIME button, making it possible to display the current time from the RTC module on the screen whenever the button was pressed.
public void button2_click1(GButton source, GEvent event) {
String now =
new SimpleDateFormat("dd/MM/yyyy HH:mm:ss")
.format(new Date());
label1.setText(now);
}
Քանի որ իմ Final Project-ը ներառում է երեք տարբեր սենսոր՝ DHT11 ջերմաստիճանի և խոնավության սենսոր, DS3231 RTC ժամացույցի մոդուլ և LDR լուսային սենսոր, անհրաժեշտ էր ստեղծել ծրագիր, որը կկարողանա միաժամանակ կարդալ բոլոր սենսորների տվյալները և փոխանցել դրանք համակարգչին։ Այդ նպատակով Arduino IDE միջավայրում գրվեց հետևյալ կոդը, որը յուրաքանչյուր սենսորից ստացված տվյալները ուղարկում է Serial Monitor-ի միջոցով։
Ծրագիրը նախ ներմուծում է անհրաժեշտ գրադարանները (Wire.h, RTClib.h, DHT.h), այնուհետև սահմանվում են սենսորների միացման pin-երը։ setup() ֆունկցիայի մեջ կատարվում է Serial կապի և սենսորների սկզբնական ակտիվացումը, իսկ loop() ֆունկցիայի ընթացքում պարբերաբար ընթերցվում են ջերմաստիճանի, խոնավության, ժամի և լուսավորության արժեքները։
Ստացված տվյալները Serial-ի միջոցով ուղարկվում են մեկ տողի տեսքով՝ ստորակետերով բաժանված, որպեսզի հետագայում հնարավոր լինի հեշտությամբ մշակել դրանք Processing միջավայրում։
#include <Wire.h>
#include "RTClib.h"
#include "DHT.h"
#define DHTPIN 26
#define DHTTYPE DHT11
#define LDR_PIN 27
DHT dht(DHTPIN, DHTTYPE);
RTC_DS3231 rtc;
void setup() {
Serial.begin(9600);
dht.begin();
rtc.begin();
}
void loop() {
float temp = dht.readTemperature();
float hum = dht.readHumidity();
DateTime now = rtc.now();
int lightValue = analogRead(LDR_PIN);
Serial.print(temp);
Serial.print(",");
Serial.print(hum);
Serial.print(",");
Serial.print(now.hour());
Serial.print(":");
Serial.print(now.minute());
Serial.print(":");
Serial.print(now.second());
Serial.print(",");
Serial.println(lightValue);
delay(1000);
}
Հիմա անցնենք Processing ծրագրին․
Ծրագրի սկզբում ներմուծեցի անհրաժեշտ գրադարանները՝ g4p_controls գրաֆիկական էլեմենտների ստեղծման համար, processing.serial՝ Arduino-ի հետ սերիական կապ հաստատելու համար, ինչպես նաև java.awt.Font՝ տեքստերի չափն ու տեսքը փոփոխելու նպատակով։
import g4p_controls.*;
import g4p_controls.*;
import processing.serial.*;
import java.awt.Font;
Այնուհետև ստեղծեցի փոփոխականներ, որոնց մեջ պահվում էին Arduino-ից եկող տվյալները՝ ջերմաստիճանը (temp), խոնավությունը (hum), ժամանակը (tim) և լուսավորության սենսորի արժեքը (light)։ Սկզբնական արժեքները նշանակված էին "---" տեսքով, որպեսզի մինչև տվյալների ստանալը պատուհանում դատարկ տեղեր չլինեն։
String temp = "---";
String hum = "---";
String tim = "---";
String light = "---";
setup() ֆունկցիայի մեջ ստեղծեցի ծրագրի հիմնական պատուհանը (500x300): Դրանից հետո ավելացրեցի չորս կոճակ՝
TIME — ժամանակի տվյալների համար,
TEMPERATURE — ջերմաստիճանի արժեքի համար,
HUMIDITY — խոնավության արժեքի համար,
PHOTORESISTOR — լուսավորության սենսորի տվյալների համար։
Յուրաքանչյուր կոճակի դիմաց ստեղծեցի համապատասխան Label, որտեղ պետք է ցուցադրվեին տվյալները։ Labels-ների տառատեսակը փոխեցի ավելի մեծ և հաստ (Arial Bold, 20), որպեսզի արժեքները հեշտ ընթեռնելի լինեն։
Կոճակներին տրվեցին տարբեր գունային սխեմաներ, ինչը ինտերֆեյսը դարձրեց ավելի տեսողական և հարմար օգտագործման համար։
Arduino-ի հետ կապ հաստատելու համար օգտագործեցի Serial կապը․
myPort = new Serial(this, Serial.list()[0], 9600);
Այստեղ ծրագիրը միանում է առաջին հասանելի COM պորտին և աշխատում է 9600 baud rate արագությամբ։
serialEvent() ֆունկցիան ընդունում է Arduino-ից եկող տվյալները, բաժանում դրանք մասերի և պահում համապատասխան փոփոխականներում՝ ջերմաստիճան, խոնավություն, ժամանակ և լուսավորություն։
void serialEvent(Serial myPort) {
String data = myPort.readStringUntil('\n');
if (data == null) return;
data = trim(data);
String[] parts = split(data, ',');
if (parts.length == 4) {
temp = parts[0];
hum = parts[1];
tim = parts[2];
light = parts[3];
}
}
handleButtonEvents() ֆունկցիան ապահովում է կոճակների աշխատանքը․ յուրաքանչյուր կոճակ սեղմելիս ցուցադրվում է համապատասխան սենսորի արժեքը։
void handleButtonEvents(GButton button, GEvent event) {
if (button == b1) {
timeLab.setText(tim);
}
if (button == b2) {
tempLab.setText(temp);
}
if (button == b3) {
humLab.setText(hum);
}
if (button == b4) {
lightLab.setText(light);
}
}
Այսպիսով ստեղծվեց պարզ գրաֆիկական համակարգ, որի միջոցով հնարավոր դարձավ առանձին դիտել Arduino-ից ստացվող տարբեր տվյալները։
Ահա սա էլ ամբողջական սկեչի կոդը․
import g4p_controls.*;
import processing.serial.*;
import java.awt.Font;
Serial myPort;
// data
String temp = "---";
String hum = "---";
String tim = "---";
String light = "---";
// labels
GLabel tempLab, humLab, timeLab, lightLab;
// buttons
GButton b1, b2, b3, b4;
void setup() {
size(500, 300);
// background
background(255, 230, 230);
// buttons
b1 = new GButton(this, 80, 60, 130, 30, "TIME");
b2 = new GButton(this, 80, 110, 130, 30, "TEMPERATURE");
b3 = new GButton(this, 80, 160, 130, 30, "HUMIDITY");
b4 = new GButton(this, 80, 210, 130, 30, "PHOTORESISTOR");
// labels
timeLab = new GLabel(this, 260, 60, 180, 30);
tempLab = new GLabel(this, 260, 110, 180, 30);
humLab = new GLabel(this, 260, 160, 180, 30);
lightLab = new GLabel(this, 260, 210, 180, 30);
// font
Font f = new Font("Arial", Font.BOLD, 20);
timeLab.setFont(f);
tempLab.setFont(f);
humLab.setFont(f);
lightLab.setFont(f);
// default text
timeLab.setText("---");
tempLab.setText("---");
humLab.setText("---");
lightLab.setText("---");
// color schemes
b1.setLocalColorScheme(GCScheme.BLUE_SCHEME);
b2.setLocalColorScheme(GCScheme.GREEN_SCHEME);
b3.setLocalColorScheme(GCScheme.RED_SCHEME);
b4.setLocalColorScheme(GCScheme.CYAN_SCHEME);
println(Serial.list());
// COM port
myPort = new Serial(this, Serial.list()[0], 9600);
myPort.bufferUntil('\n');
}
void draw() {
background(255, 230, 230);
}
// SERIAL
void serialEvent(Serial myPort) {
String data = myPort.readStringUntil('\n');
if (data == null) return;
data = trim(data);
String[] parts = split(data, ',');
if (parts.length == 4) {
temp = parts[0];
hum = parts[1];
tim = parts[2];
light = parts[3];
}
}
// BUTTONS
void handleButtonEvents(GButton button, GEvent event) {
if (button == b1) {
timeLab.setText(tim);
}
if (button == b2) {
tempLab.setText(temp);
}
if (button == b3) {
humLab.setText(hum);
}
if (button == b4) {
lightLab.setText(light);
}
}