This week, we were asked to implement a user interface (UI) that can interact with hardware.
For that, I will use QtDesigner to create the interface for my project, which needs to be capable of
controlling the speed, direction, and start of two independent step motors, turning on and off a
12-volt fan
and display the temperature of the extruder. In this instance even though there will be a section
called extruder
it will work to turn ON or OFF an LED.
GROUP
PAGE.
Qt Designer
Qt is an open-source framework for designing graphical user interfaces (GUIs) that can be easily
implemented in cross-platform applications. While Qt is primarily based on C++, it can also be used
with other programming languages, such as Python, through bindings like PySide and QtPy. Qt Designer
is
a GUI design tool that uses a drag-and-drop approach.
It includes a wide variety of pre-designed widgets, allowing users to focus on the creative
aspects of interface design rather than the underlying code.
Templates
When opening Qt Designer it will show the next window,
where one can create a new form based on different Templates,
open an specified one or open a recently worked form.
Main toolbar
Contains the most common actions in the program:
Creating, saving and opening forms.
Editing various factors including widgets.
Setting and breaking layouts.
Widget Box
Organizes available widgets by category.
widgets like Frames, Push Buttons, Sliders, Group Boxes and
LCD Numbers will be used on the interface.
Form
Serves as the canvas where you design your GUI by
dragging and dropping widgets onto it.
Object inspector
Displays the hierarchical relationship of all widgets in the design.
A right-click on a widget opens a menu where one can modify this
hierarchy.
Property Editor
Shows the properties of a selected widget,
allowing you to modify them as needed. This includes options such as
size, position, text content, color, font, alignment, and more.
StyleSheet
Doing a right-click on a widget and selecting
change styleSheet, will open a window. In this
window you can modify the appearance and interactions
of the widgets with a language similar to CSS. When
a parent styleSheet is changed it reflects on it's
children, unless their styleSheet specifies otherwise.
My design
For the interface I decided that there should be 5 buttons, 1 for the toggle of each device (2
motors, a fan and an LED) and one to update the speed of both motors. Two sliders with a minimum
value of 600 and a maximum value of 8000 will be implemented to control the speed of the motors. The
next video shows visually the interface and highlights the different components. their use and their
stylesheet:
The Code
The interface will be saved as a .ui file, which can be converted to a .py file. To do this, either
PyQt6 or PySide needs to be implemented as a Python package through pip. I will be using PyQt6,
which can be installed with pip install PyQt6. Then, in the CMD, one can convert
the file using the following command: pyuic6 -x "name.ui" -o "name.py"
It is recommended not to modify the generated .py file because if changes are made to the interface
and it needs to be converted again, either the .py file will be overwritten (if "name.py" is the
same as the filename) or the changes made will need to be added manually to the new file. This can
be easily solved by creating a different .py file and importing all the elements that compose the
interface into it.
I decided to create a python code that could communicate the actions in the interface to an Arduino
code though serial communication, the codes and a more detailed break down can be found below:
ARDUINO
In this part of the code, I define the pins based on their function to be used
on their code.
// Pin definitions for Motor 1
#define STEP_PIN_1 4
#define DIR_PIN_1 3
#define ENABLE_PIN_1 5
// Pin definitions for Motor 2
#define STEP_PIN_2 8
#define DIR_PIN_2 7
#define ENABLE_PIN_2 9
// Pin definitions for FAN
#define FAN_PIN 10
// Pin definitions for LED
#define LED_PIN 6
ARDUINO
The following code set ups the variables that will control the speed between
steps, toggle the motors, fan and the extruder LED.
// Variables
unsigned long previousMicros1 = 0; // Store the time of the last step update for Motor 1
unsigned long previousMicros2 = 0; // Store the time of the last step update for Motor 2
int speed1 = 4000; // Time interval between steps for Motor 1 (in microseconds)
int speed2 = 4000; // Time interval between steps for Motor 2 (in microseconds)
bool stepState1 = false; // Step pin state for Motor 1 (HIGH or LOW)
bool stepState2 = false; // Step pin state for Motor 2 (HIGH or LOW)
bool enable1 = true;
bool enable2 = true;
int command = 0;
int fanMode = 1;
int fan = 0;
int ledMode = 1;
int led = 0;
ARDUINO
void setup() is the main configuration of the program. It initializes serial
communication at 9600 baud and configures the pins for two motors, a fan, and an
LED. It sets the direction, step, and enable pins for both motors as outputs and
enables the drivers. It also sets the initial direction for both motors to HIGH,
and configures the fan and LED pins as outputs, initially setting them to LOW.
void setup() {
Serial.begin(9600);
// Set up the pins for Motor 1
pinMode(STEP_PIN_1, OUTPUT);
pinMode(DIR_PIN_1, OUTPUT);
pinMode(ENABLE_PIN_1, OUTPUT);
digitalWrite(ENABLE_PIN_1, HIGH); // Enable the driver for Motor 1
// Set up the pins for Motor 2
pinMode(STEP_PIN_2, OUTPUT);
pinMode(DIR_PIN_2, OUTPUT);
pinMode(ENABLE_PIN_2, OUTPUT);
digitalWrite(ENABLE_PIN_2, HIGH); // Enable the driver for Motor 2
// Set the initial direction for both motors
digitalWrite(DIR_PIN_1, HIGH);
digitalWrite(DIR_PIN_2, HIGH);
// Set up for FAN
pinMode(FAN_PIN, OUTPUT);
digitalWrite(FAN_PIN, LOW);
// Set up for LED
pinMode(LED_PIN, OUTPUT);
digitalWrite(LED_PIN, LOW);
}
ARDUINO
The following loop function performs the next tasks in a cycle:
Retrieves the current time in microseconds.
Controls Motor 1 by toggling its step pin state if the elapsed time since
the last toggle meets or exceeds the value of speed1.
Controls Motor 2 by toggling its step pin state if the elapsed time since
the last toggle meets or exceeds the value of speed2.
Checks for available serial data and reads several integer commands from the
serial buffer.
Prints the received commands and parameters to the serial monitor.
Executes the Motors, Fan, and LED functions with the received parameters.
void loop() {
unsigned long currentMicros = micros();
// Controls Motor 1
if (currentMicros - previousMicros1 >= speed1) {
previousMicros1 = currentMicros;
// Toggles the step pin state
stepState1 = !stepState1;
digitalWrite(STEP_PIN_1, stepState1);
}
// Controls Motor 2
if (currentMicros - previousMicros2 >= speed2) {
previousMicros2 = currentMicros;
// Toggles the step pin state
stepState2 = !stepState2;
digitalWrite(STEP_PIN_2, stepState2);
}
// Reed Serial commands
if (Serial.available() > 0) {
//char command = Serial.read();
// Save on variables Serial commands
command = Serial.parseInt();
speed1 = Serial.parseInt();
speed2 = Serial.parseInt();
fan = Serial.parseInt();
led = Serial.parseInt();
//ledMode = Serial.parseInt();
//int x = Serial.parseInt();
Serial.println(command);
Serial.println(speed1);
Serial.println(speed2);
Serial.println(fanMode);
Serial.println(ledMode);
This function called Motors is composed of a switch case, which depending on the
number provided will change the direction of an specific motor or toggle its
state (START/STOP). In case number 8 it is designed to break out of the
function, this is because functions are called constantly so it has the option
of not affecting the motors.
void Motors(int command) {
switch (command) {
case 7:
// Move Motor 1 backwards
digitalWrite(DIR_PIN_1, LOW);
//Serial.println("Q");
break;
case 2:
// Move Motor 1 forwards
digitalWrite(DIR_PIN_1, HIGH);
Serial.println("W");
break;
case 3:
// Move Motor 2 backwards
digitalWrite(DIR_PIN_2, LOW);
Serial.println("A");
break;
case 4:
// Move Motor 2 forwards
digitalWrite(DIR_PIN_2, HIGH);
Serial.println("S");
break;
case 6:
// Toggle Motor 2 RUN/STOP
enable2=!enable2;
digitalWrite(ENABLE_PIN_2, enable2);
Serial.print("MOTOR 2 =");
Serial.println(enable1);
break;
case 5:
// Toggle Motor 1 RUN/STOP
enable1=!enable1;
digitalWrite(ENABLE_PIN_1, enable1);
Serial.print("MOTOR 1 =");
Serial.println(enable1);
break;
// Case 8 is for not changing things.
case 8:
break;
default:
break;
}
}
ARDUINO
The function called fanfuncion() is in charge of turning on and off the fan if
the input is a 1.
Similarly ledfuncion() will turn on or off an LED if its input is a 1.
In this part, the libraries are imported with all the classes from the QTpy.py
file. Then an instance of the class Main is instantiated pulling all the
classes made in QTpy.py, then a serial connection is established. Then the
initial variables are defined for their following use:
command: Represents the action that a motor will take (directly connected to
the switch case in the Arduino code), it will be reset to 8 in most
functions because its respective case breaks from the function.
speed1 and speed2: Represents the corresponding speed for each motor.
fanvalue and ledvalue: Represents a binary value, when 1 they will toggle
the state of their respective devices. Will be reset to 0 constantly, as it
is a neutral state.
x: A variable reserved to store what is read in the serial communication.
#IMPORT LIBRARIES AND INTERFACE--------------------
from QTpy import *
import serial
This set of functions correspond to the control of Motor 1, the values in
self.command correspond to the respective switch cases in the Arduino Code. In
"motor1_move" the text of the button changes to represent what will happen when
next clicked. In "motor1_speed" the LCD screen is updated to display the actual
value.
#DIRECTION MOTOR 1---------------------------------
def motor1_right(self):
self.command = 7
self.send()
self.command = 8 #Reset to neutral value
def motor1_left(self):
self.command = 2
self.send()
self.command = 8 #Reset to neutral value
#TOGGLE MOTOR 1 START/STOP---------------------------------
def motor1_move(self):
self.command = 5
self.send()
if self.B3.styleSheet() == "background-color: rgb(136, 4, 6);""font: 8pt 'Roboto';""border-radius: 10px;":
self.B3.setStyleSheet("background-color: rgb(8, 96, 211);""font: 8pt 'Roboto';""border-radius: 10px;")
self.B3.setText("START") #change button text to START
else:
self.B3.setText("STOP") #change button text to STOP
self.B3.setStyleSheet("background-color: rgb(136, 4, 6);""font: 8pt 'Roboto';""border-radius: 10px;")
self.command = 8 #Reset to neutral value
#CHANGE MOTOR 1 Speed---------------------------------
def motor1_speed(self):
self.speed1=self.slider1.value()
self.Slider1_value.display(self.speed1) #display on LCD speed value
PYTHON
This set of functions correspond to the control of Motor 2, the values in
self.command correspond to the respective switch cases in the Arduino Code. In
"motor2_move" the text of the button changes to represent what will happen when
next clicked. In "motor2_speed" the LCD screen is updated to display the actual
value.
#DIRECTION MOTOR 2---------------------------------
def motor2_right(self):
self.command = 3
self.send()
self.command = 8 #Reset to neutral value
def motor2_left(self):
self.command = 4
self.send()
self.command = 8 #Reset to neutral value
#TOGGLE MOTOR 2 START/STOP---------------------------------
def motor2_move(self):
self.command = 6
self.send()
if self.B6.styleSheet() == "background-color: rgb(136, 4, 6);""font: 8pt 'Roboto';""border-radius: 10px;":
self.B6.setText("START") #change button text to START
self.B6.setStyleSheet("background-color: rgb(8, 96, 211);""font: 8pt 'Roboto';""border-radius: 10px;")
else:
self.B6.setStyleSheet("background-color: rgb(136, 4, 6);""font: 8pt 'Roboto';""border-radius: 10px;")
self.B6.setText("STOP") #change button text to STOP
self.command = 8 #Reset to neutral value
#CHANGE MOTOR 2 Speed---------------------------------
def motor2_speed(self):
self.speed2=self.slider2.value()
self.Slider1_value_2.display(self.speed2) #display on LCD speed value
PYTHON
The function "fanstate" changes the state of the fan through serial
communication through the function send(), it then changes the text of the
button "fan" so its corresponds with its next posible action. Similarly
"ledstate" uses the function "send" to change the state of the LED and then
updates its corresponding button text, it also reads calls the function "leer"
which is explained later.
#TOGGLE ON/OFF LED---------------------------------
def ledstate(self):
self.ledvalue = 1
self.send()
self.ledvalue = 0 #Reset to neutral value
if self.BLED.styleSheet() == "background-color: rgb(136, 4, 6);""font: 8pt 'Roboto';""border-radius: 10px;":
self.BLED.setStyleSheet("background-color: rgb(8, 96, 211);""font: 8pt 'Roboto';""border-radius: 10px;")
self.BLED.setText("ON") #change button text to ON
else:
self.BLED.setText("OFF") #change button text to OFF
self.BLED.setStyleSheet("background-color: rgb(136, 4, 6);""font: 8pt 'Roboto';""border-radius: 10px;")
self.leer()
PYTHON
The function send constructs a comma separated list in a string format, which is
then encoded, printed and sent to the microcontroller. The function leer
(translated as read) reads the serial communication to change the Extruder LCD
in the interface to display if the LED is ON or OFF-
#READ SERIAL TO INDICATE IF EXTRUDER ON OR OFF-----------------
def leer(self):
self.x=self.serial.readline()
self.TEM.display(int(self.x)) #change LCD Screen
PYTHON
This code is guaranteed to run when the application starts, it creates an
instance of the class that is shown as it own window.
Before creating the PCB, I decided to test the code by creating a simple circuit on a breadboard that
can demonstrate that the different code coordinate to make the two motors work as instructed.
Here you can see a simple diagram of the connections and the pins that will be used to connect the
microcontroller to the board with drivers.
Three PCBs were designed to communicate with each other and an external device (in this case, an
Arduino). The main PCB has a driver for each motor. Due to time constraints, the LED will not be
tested as the fan follows a similar logic of only changing states. PCB NEMA
Test
This video shows the operation of all the components in tandem:
PCB
designing
I designed a simple board with output for some pins. I used the RP2040 microcontroller from SID
Studio. This board is basic, with output for the pins I use and some more if needed for future
practices.PCB
manufacturing
For the manufacturing process, you can visit WEEK 4 to see more about the manufacturing.
FINAL
Here, I used the board I made with my board to place the motor drivers. By using a motor and the
interface, I changed the direction of the motor.