[PA-03b] Control RGB Led with slider Qt

I used the code of Mr.Vannevar Morgan and modified Arduino code in order to display RGB value on LCD I2C. Please check this link to see his youtube tutorial and github code
youtube: https://www.youtube.com/watch?v=5uuFlSzzYsc

github: https://github.com/vannevar-morgan/Qt-RGB-LED

The idea is similiar with the [PA-03a], you send command to Arduino and Arduino does correspondingly. Since we have 3 values of R-G-B so to discriminate them, we have to add prefix ‘r’ for Red, ‘g’ for Green and ‘b’ for Blue before brightness value. Here is my Arduino code:


/*
 * Date: Tue, 09/05/2017
 * Desc: Receive command from python to adjust RGB color
 * Convert message into integer, PWM on pin 9, 10, 11
 * Give feedback message syntax (R,G,B) displayed on LCD
 */
#include <Wire.h>
#include <LCD.h>
#include <LiquidCrystal_I2C.h>

#define I2C_ADDR 0x3F 
#define BACKLIGHT_PIN 3
#define En_pin 2
#define Rw_pin 1
#define Rs_pin 0
#define D4_pin 4
#define D5_pin 5
#define D6_pin 6
#define D7_pin 7
LiquidCrystal_I2C lcd(I2C_ADDR,En_pin,Rw_pin,Rs_pin,D4_pin,D5_pin,D6_pin,D7_pin);

#define red_led 9
#define green_led 10
#define blue_led 11
int rgb_val[3];

void setup()
{
 delay(1000);
 // Initialize pinMode
 pinMode(red_led, OUTPUT);
 pinMode(green_led, OUTPUT);
 pinMode(blue_led, OUTPUT);
 setPwmFrequency(red_led, 8); //Set pin 9's PWM frequency to 3906 Hz (31250/8 = 3906)
 setPwmFrequency(green_led, 8);
 setPwmFrequency(blue_led, 8);
 rgb_val[0] = 0; // This is the only way
 rgb_val[1] = 100;
 rgb_val[2] = 0;
 analogWrite(red_led, rgb_val[0]);
 analogWrite(green_led, rgb_val[1]);
 analogWrite(blue_led, rgb_val[2]);

// Initial LCD
 lcd.begin(16,2);
 lcd.setBacklightPin(BACKLIGHT_PIN,POSITIVE);
 lcd.setBacklight(HIGH);
 lcd_display_command(rgb_val);
 lcd.setCursor(0,0);
 lcd.print("Initial R-G-B ");
 
 // Initialize serial
 Serial.begin(9600);
}

void loop()
{
 if (Serial.available()){
 char led_specifier = Serial.read();
 int led_brightness = Serial.parseInt();
 write_leds(led_specifier, led_brightness);
 }
}

void write_leds(char led, int brightness)
{
 if (led == 'r'){
 analogWrite(red_led, brightness);
 rgb_val[0] = brightness;
 lcd_display_command(rgb_val);
 return;
 }
 if (led == 'g'){
 analogWrite(green_led, brightness);
 rgb_val[1] = brightness;
 lcd_display_command(rgb_val);
 return;
 }
 if (led == 'b'){
 analogWrite(blue_led, brightness);
 rgb_val[2] = brightness;
 lcd_display_command(rgb_val);
 return;
 } 
 return;
}

// change the frequency of PWM
void setPwmFrequency(int pin, int divisor) {
 byte mode;
 if(pin == 5 || pin == 6 || pin == 9 || pin == 10) {
 switch(divisor) {
 case 1: mode = 0x01; break;
 case 8: mode = 0x02; break;
 case 64: mode = 0x03; break;
 case 256: mode = 0x04; break;
 case 1024: mode = 0x05; break;
 default: return;
 }
 if(pin == 5 || pin == 6) {
 TCCR0B = TCCR0B & 0b11111000 | mode;
 } else {
 TCCR1B = TCCR1B & 0b11111000 | mode;
 }
 } else if(pin == 3 || pin == 11) {
 switch(divisor) {
 case 1: mode = 0x01; break;
 case 8: mode = 0x02; break;
 case 32: mode = 0x03; break;
 case 64: mode = 0x04; break;
 case 128: mode = 0x05; break;
 case 256: mode = 0x06; break;
 case 1024: mode = 0x07; break;
 default: return;
 }
 TCCR2B = TCCR2B & 0b11111000 | mode;
 }
}
// display on LCD 16x2
void lcd_display_command(int rgb_value[3]){
 lcd.clear();
 lcd.print("Color code R-G-B");
 lcd.setCursor(0,1);
 String content = String(rgb_value[0]) + "-" + String(rgb_value[1]) + "-" + String(rgb_value[2]);
 lcd.print(content);
}

There are some need-improve things. First, at the initializing state, if you are not happy with my R-G-B value = [0, 100, 0] and you want to change this, you have to change the initial value of the slider. So you need code to update the initial state at the beginning of Qt.

Second, the LCD seems to fade when you are dragging the slider (if you click, LCD displays quickly) so I think there is some uncertainty when you deal with the function slider_value_change. I will follow up this tutorial (maybe feedback message with time logging, yeah that is good, yay me)

Third, only one channel value (Red, Green or Blue) is sent at a time. It is not practical in the real life because you have to send a data block of 3 values. For example you have a color pallet and you pick a point on the pallet, the RGB value will change all of 3 value. I am thinking about sending a number (with binary from for enhancing transfer speed)


(256^2)*R + (256^1)*G + (256^0)*B


and a prefix (like modbus: one byte address, one byte value). So this tutorial will be suspended for further improvement.

[PA-03a] Create GUI with Qt

In this tutorial, we focus on building a GUI that gives command to Arduino and Arduino will display the received command, turn Led on-off and give feedback message. The main content will how to use Qt to make GUI. Easy thing on first, here is the code of Arduino
pyqt_blink_feedback.ino


/*
 * Date: Fri, 05/05/2017
 * Desc: Blink Led 13 when receiving command
 * and give feedback message
 * also display lcd i2c command
 */
#include <Wire.h>
#include <LCD.h>
#include <LiquidCrystal_I2C.h>

#define I2C_ADDR 0x3F 
#define BACKLIGHT_PIN 3
#define En_pin 2
#define Rw_pin 1
#define Rs_pin 0
#define D4_pin 4
#define D5_pin 5
#define D6_pin 6
#define D7_pin 7
LiquidCrystal_I2C lcd(I2C_ADDR,En_pin,Rw_pin,Rs_pin,D4_pin,D5_pin,D6_pin,D7_pin);

// Used for pin status
int ledPin = 13;

// Used for serial 
char incomingChar;

void setup() {
 // Initial process
 delay(1000);
 pinMode(ledPin, OUTPUT); // declare LED as output
 digitalWrite(ledPin, LOW);
 Serial.begin(9600);
 Serial.println("Ready for command.");

lcd.begin(16,2);
 lcd.setBacklightPin(BACKLIGHT_PIN,POSITIVE);
 lcd.setBacklight(HIGH);
 lcd.clear();
 lcd.print("Waiting command:");
}

void loop(){
 // when characters arrive over the serial port...
 if (Serial.available()){
 // wait a bit for the entire message to arrive
 delay(100);
 while (Serial.available() > 0) {
 incomingChar = Serial.read();
 lcd_display_command(String(incomingChar));
 if (incomingChar == '1'){ 
 digitalWrite(ledPin, HIGH);
 Serial.println("Led is turn on."); 
 } else if (incomingChar == '0'){
 digitalWrite(ledPin, LOW);
 Serial.println("Led is turn off."); 
 } else {
 Serial.println("Error.");
 }
 // Post processing, clear incomingChar
 incomingChar = ' ';
 }
 }
}

void lcd_display_command(String content){
 lcd.clear();
 lcd.print("Received command");
 lcd.setCursor(0,1);
 lcd.print(content);
}

Step 1: download PyQt 4.10 x32 at SourceForge URL: https://sourceforge.net/projects/pyqt/files/PyQt4/PyQt-4.10/PyQt4-4.10-gpl-Py2.7-Qt4.8.4-x32.exe/download and install to your computer

Step 2: create folder pyqt_blink_command

Open Qt designer, New Form will show up --> pick Dialog without Button --> Create.

Step 2.1: modify the windowTitle of Dialog into Control Led with feedback

Step 2.2: create Text Browser --> Drag and Drop TextBrowser (Display Widget) to Dialog

Step 2.3: create Button --> Drag and Drop PushButton to Dialog --> change Text to Turn on (because initially Led 13 is off)

Step 2.4: save form with name ArduinoLed.ui in folder pyqt_blink_command

Step 3: cmd --> change directory to folder pyqt_blink_command --> convert file .ui to file .py


pyuic4 -x ArduinoLed.ui -o ArduinoLedUI.py

Right click on ArduinoLedUI.py --> Edit with IDLE. We can Run this code for testing first.

Step 4: modify code that is converted from .ui, including: import serial library, write function for button and display the feedback message

Step 4.1: import the serial library at the beginning of the code

[modified ArduinoLedUI.py]


# -*- coding: utf-8 -*-

# Form implementation generated from reading ui file 'ArduinoLed.ui'
#
# Created: Fri May 05 14:43:31 2017
# by: PyQt4 UI code generator 4.10
#
# WARNING! All changes made in this file will be lost!

import sys, time, serial
from PyQt4 import QtCore, QtGui

try:
 _fromUtf8 = QtCore.QString.fromUtf8
except AttributeError:
 def _fromUtf8(s):
 return s

try:
 _encoding = QtGui.QApplication.UnicodeUTF8
 def _translate(context, text, disambig):
 return QtGui.QApplication.translate(context, text, disambig, _encoding)
except AttributeError:
 def _translate(context, text, disambig):
 return QtGui.QApplication.translate(context, text, disambig)

class Ui_Dialog(object):
 def setupUi(self, Dialog):
 # declaration for serial port and initialize variable
 self.LedStatus = 0 # Led status showing on-off
 self.arduino = serial.Serial('COM3', 9600)
 txtArduino = self.arduino.readline()
 txt = "Arduino> " + txtArduino
 #
 Dialog.setObjectName(_fromUtf8("Dialog"))
 Dialog.resize(401, 223)
 self.textBrowser = QtGui.QTextBrowser(Dialog)
 self.textBrowser.setGeometry(QtCore.QRect(10, 10, 381, 141))
 self.textBrowser.setObjectName(_fromUtf8("textBrowser"))
 # Set initial content of Text Browser
 self.textBrowser.append(txt)
 #
 self.pushButton = QtGui.QPushButton(Dialog)
 self.pushButton.setGeometry(QtCore.QRect(280, 160, 111, 41))
 self.pushButton.setObjectName(_fromUtf8("pushButton"))

self.retranslateUi(Dialog)
 QtCore.QMetaObject.connectSlotsByName(Dialog)
 # Set the event for button when being clicked
 QtCore.QObject.connect(self.pushButton,QtCore.SIGNAL("clicked()"), self.LedControl)

def retranslateUi(self, Dialog):
 Dialog.setWindowTitle(_translate("Dialog", "Control Led with feedback", None))
 self.pushButton.setText(_translate("Dialog", "Turn on", None))

# Function for exit
 def quit(self):
 sys.exit(app.exec_())

# Function for clicking event
 def LedControl(self):
 if self.LedStatus==0:
 self.LedStatus = 1
 self.pushButton.setText('Turn off')
 self.arduino.write('1')
 txtArduino = self.arduino.readline()
 else:
 self.LedStatus = 0
 self.pushButton.setText('Turn on')
 self.arduino.write('0')
 txtArduino = self.arduino.readline()
 txt = "Arduino> " + txtArduino
 self.textBrowser.append(txt)

if __name__ == "__main__":
 import sys
 app = QtGui.QApplication(sys.argv)
 Dialog = QtGui.QDialog()
 ui = Ui_Dialog()
 ui.setupUi(Dialog)
 Dialog.show()
 sys.exit(app.exec_())

Tabspace will determine the structure of python code so be careful when copying and pasting the following code to your project.

LCD I2C display serial message

This testing module is used to display message received via serial on LCD with I2C driver. The purpose of this module is when you use 2 Arduino boards for broadcasting, one transmitter and one receiver. This module is used by the receiver to test the sending message. And this is the one-step-ahead for module nRF24L01, LoRa, Bluetooth or GSM testing. I give you 2 ways to display: direct display without buffering and buffering display in case you want to modified the message you receive (transfer message to number, interpreting the data, for instance, analog vaule 512 will equal 28ºC and calculating, subtracting offset …)
direct display without buffering


/*
 * Date: Wed, 03/05/2017
 * Desc: Receive message from serial
 * Display to LCD I2c
 * direct display to LCD without buffering
*/
#include <Wire.h>
#include <LCD.h>
#include <LiquidCrystal_I2C.h>

#define I2C_ADDR 0x3F 
#define BACKLIGHT_PIN 3
#define En_pin 2
#define Rw_pin 1
#define Rs_pin 0
#define D4_pin 4
#define D5_pin 5
#define D6_pin 6
#define D7_pin 7
LiquidCrystal_I2C lcd(I2C_ADDR,En_pin,Rw_pin,Rs_pin,D4_pin,D5_pin,D6_pin,D7_pin);

void setup()
{
 Serial.begin(9600);
 lcd.begin(16,2);
 lcd.setBacklightPin(BACKLIGHT_PIN,POSITIVE);
 lcd.setBacklight(HIGH);
 lcd.clear();
 lcd.print("Waiting message:");
}

void loop()
{
 // when characters arrive over the serial port...
 if (Serial.available()) {
 // wait a bit for the entire message to arrive
 delay(100);
 // clear the screen
 lcd.clear();
 // read all the available characters
 while (Serial.available() > 0) {
 // display each character to the LCD WITHOUT BUFFERING
 lcd.write(Serial.read());
 }
 }
}

 

buffering display


/*
 * Date: Wed, 03/05/2017
 * Desc: Receive message from serial
 * Display to LCD I2c
 * direct display to LCD with buffering
*/
#include <Wire.h>
#include <LCD.h>
#include <LiquidCrystal_I2C.h>

#define I2C_ADDR 0x3F 
#define BACKLIGHT_PIN 3
#define En_pin 2
#define Rw_pin 1
#define Rs_pin 0
#define D4_pin 4
#define D5_pin 5
#define D6_pin 6
#define D7_pin 7
LiquidCrystal_I2C lcd(I2C_ADDR,En_pin,Rw_pin,Rs_pin,D4_pin,D5_pin,D6_pin,D7_pin);

// used for serial buffering
#define TERM_CHAR '\n'
#define BUF_LEN 16
int i = 0;
char incomingChar; 
char buf[BUF_LEN];

void setup()
{
 Serial.begin(9600);
 lcd.begin(16,2);
 lcd.setBacklightPin(BACKLIGHT_PIN,POSITIVE);
 lcd.setBacklight(HIGH);
 lcd.clear();
 lcd.print("Waiting message:");
}

void loop()
{
 // when characters arrive over the serial port...
 if (Serial.available()) {
 // wait a bit for the entire message to arrive
 delay(100);
 // clean buffer and index
 memset(buf,'\0',BUF_LEN); 
 i = 0; 
 // read all the available characters
 while (Serial.available() > 0) {
 incomingChar = (char) Serial.read();
 if(incomingChar != TERM_CHAR && i != BUF_LEN) 
 buf[i++] = incomingChar;
 else
 break;
 delay(1); // wait for another byte 
 }
 // clear the screen and display message
 lcd.clear();
 lcd.print("Serial message:");
 lcd.setCursor(0,1); // 2nd line 
 lcd.write(buf);
 }
}

you can make the different BUF_LEN (but maximum value will be 128)

I2C device scanner

In case you do not know exactly the address of I2C driver, you can use I2C_scanner to detect the address of device connected to SDA and SCL


/*
 * I2C address scanner
 */
 
#include <Wire.h>
 
void setup()
{
 Wire.begin();
 
 Serial.begin(9600);
 while (!Serial); // Leonardo: wait for serial monitor
 Serial.println("\nI2C Scanner");
}
 
 
void loop()
{
 byte error, address;
 int nDevices;
 
 Serial.println("Scanning...");
 
 nDevices = 0;
 for(address = 1; address < 127; address++ )
 {
 // The i2c_scanner uses the return value of
 // the Write.endTransmisstion to see if
 // a device did acknowledge to the address.
 Wire.beginTransmission(address);
 error = Wire.endTransmission();
 
 if (error == 0)
 {
 Serial.print("I2C device found at address 0x");
 if (address<16)
 Serial.print("0");
 Serial.print(address,HEX);
 Serial.println(" !");
 
 nDevices++;
 }
 else if (error==4)
 {
 Serial.print("Unknown error at address 0x");
 if (address<16)
 Serial.print("0");
 Serial.println(address,HEX);
 } 
 }
 if (nDevices == 0)
 Serial.println("No I2C devices found\n");
 else
 Serial.println("done\n");
 
 delay(5000); // wait 5 seconds for next scan
}

[PA-03] Python gives command to Arduino

This lesson will show you how to build a control form with 2 buttons to turn on and turn off Led on pin 13 of Arduino. How to do that? Simply Python send character ‘H’ and ‘L’ when pressing the corresponding button via COM port, then Arduino will read the sending character and do the corresponding command. I make a feedback response for Arduino when the command is done so the mechanism is more reliable.

Step 1: gui_command_toggle.ino


/*
 * Date: Tue, 02/05/2017
 * Desc: Receive command from Python
 * Do the command and give message feedback
 */
#define LED 13

void setup() {
 // Initial process
 pinMode(LED, OUTPUT);
 digitalWrite(LED, LOW);
 Serial.begin(9600);

Serial.println("Led is initially turn off. Staring ...");
 Serial.println("--------------------------------------");
}

void loop() {
 if (Serial.available()) {
 char c = Serial.read();
 if (c == 'H') {
 digitalWrite(LED, HIGH);
 Serial.println("Led is turn on");
 }
 else if (c == 'L') {
 digitalWrite(LED, LOW);
 Serial.println("Led is turn off");
 }
 }
}

Step 2: gui_toggle.py


from Tkinter import *
import serial
import time

def led_on():
 arduinoData.write(b'H') # Send with byte format, b

def led_off():
 arduinoData.write(b'L')

arduinoData = serial.Serial('com3', 9600)

# create form
led_control_window = Tk()
led_control_window.title("Control led 13 on Uno")
led_control_window.geometry("300x200") # Size of form

# create UI control
but_1 = Button(led_control_window, text="led_on", command=led_on)
but_1.place(x=100, y=140, width=90, height=30)
but_1.pack() # stick it to form
but_2 = Button(led_control_window, text="led_off", command=led_off)
but_2.place(x=300, y=340, width=190, height=130)
but_2.pack()

# run the GUI
led_control_window.mainloop()

while True:
 if (arduinoData.inWaiting()>0):
 myData = arduinoData.readline()
 print myData

The outcome is not as good as expected. Only when the GUI is closed, Arduino sends back the message. I should not use 2 buttons because there is a chance that I hit turn on when the Led is already turn on.

PA03_gui_totalPA03_gui_without_control

It is a not good result. Maybe I will build a text field to receive the feedback message from Arduino next time. The index of next section will be [PA-03a].

 

Note: Tkinter with capital "T"

[PA-02] Send data from Arduino via pyserial

Step 1: install pyserial: go to https://pypi.python.org/pypi/pyserial/2.7 download version pyserial-2.7.win32.exe and install to your computer.

Step 2: load the following code to the Arduino, this code will send variable count to the computer via serial port and blink led on pin 13 (1 sec ON, 1 sec OFF to prove that Uno is still working)


/*
 * Arduino and Python Serial Port
 * Date: Thu, 27/04/2017
 * Desc: Serial port testing
 * Arduino counting and sending count via serial port COM3
 * Blink Led 13 to prove alive
 */
int cnt;
void setup() {
 // Initialize variable, procedure and state
 cnt = 0;
 Serial.begin(9600);
 pinMode(13, OUTPUT);
 digitalWrite(13, LOW);
 // Foreword
 Serial.println("Counting demo");
 Serial.println("-------------");
}

void loop() { 
 // and sending count
 Serial.print("I am counting to ");
 Serial.print(cnt);
 Serial.println(" Mississippi.");
 // Counting
 cnt++;
 // Blink Led 13
 digitalWrite(13, HIGH);
 delay(1000);
 digitalWrite(13, LOW);
 delay(1000);
}

Now you can go to: Tools --> Serial Monitor to check the data that Arduino sent to PC.

Step 3: paste the following code in VIDLE VPython


import serial #Import Serial Library

arduinoSerialData = serial.Serial('COM3', 9600)

while(1==1):
 if (arduinoSerialData.inWaiting()>0):
 myData = arduinoSerialData.readline()
 print myData

Remember to change to COM port that is suitable in your computer (mine is 'COM3'). You can find your COM port at the bottom-right of your Arduino IDE.  The baud rate will be 9600 at default.

PA02_determine_COM_port

What happens if the variable int cnt reach the maximum positive value (in Arduino code I initiate variable cnt = 32760 in void setup)? Here comes the result:

PA02_integer_max_value

You can press Ctrl+C (corrupt) to terminal the running program. It will help for the next connection without prompting some alarm.

Beware of the variable type and its range and how microcontroller processes variable limit. You do not want to save too much money to bank account and then your account amount will be negative or zero.

 

That is one-way ticket, from Arduino to PC. Next time, I will show you the other way, from PC to Arduino.