GalacticBox Anything unexpected

Introduction to Machine Learning Concepts

This is a work in progress. More will be added soon!

The Problem Setting

A typical learning problem considers a set of unknown data samples and tries to predict the data's properties. If each sample is more than a single number (a multi-dimensional entry aka multivariate data), it is said to have several attributes or features.

We can separate common learning problems in two large categories: supervised learning and unsupervised learning.

Supervised learning

In supervised learning, the data set comes with additional attributes, a piece of information that determines the properties we want to predict. Problems can be either classification or regression.

In classification problems, samples belong to two or more classes. We want to learn from already labeled data how to predict the class of unlabeled data. An example of classification problem would be handwritten letter recognition. Another way to think of classification is as a discrete form of supervised learning where one has a limited number of categories and for each of the n samples provided, one tries to label them with the correct category or class.

In regression cases, the desired output consists of one or more continuous variables. An example would be the prediction of the price of a house based on several significant parameters like number or rooms and area.

Unsupervised Learning

In unsupervised learning, the training data consists of a set of input vectors x without any corresponding target values. The goal in such problems may be to discover groups of similar examples within the data — this is known as clustering. Another objective could be to determine the distribution of data within the input space, known as density estimation, or to project the data from a high-dimensional space down to two or three dimensions for visualization.

Raspberry Pi PWM Fan Control

Resource for this project is available here.

Goal

In this tutorial, we're gonna build a PCB to control a fan to cool the CPU of a Raspberry Pi board. The reason we're gonna need an extra bit of hardware is the Pi's GPIO can't handle sufficient currents.

Find out more about the Raspberry Pi's GPIO in the documentation.

In electronics, we ususally separate high power and low power circuits. A low power logic circuit like a microcontroller typically controls a higher power circuit, like a motor driver. The Pi's GPIO acts as a low power circuit but, the Pi can also handle slightly higher currents on other pins known as supply pins: 5 V or 3.3 V. Unlike the GPIO, those can't be turned on or off: they're always on. They can handle currents sufficient for our 5 V 0.16 A fan, but we can't control them. We would therefore need some kind of switch that the GPIO can control. When closed, the fan would be connected to +5 V on one side and to the ground (GND) on the other side and would thus rotate. Turns out we're lucky! This is exactly what a transistor does.

It's important to understand the Pi, with its USB power supply, could never drive a big electric motor or some serious actuator, but, the fans we're talking about are very small and have a very limited torque: they won't require currents as high as, say, a small robot's motor.

Parts choice

To get started, we're gonna need to pick a transistor. This might seem a bit overwhelming at first as they are literally thousands of suitable options out there, but we're gonna limit out scope to something common and cheap we can solder ourselves.

I went for a 2N2222A transistor in a TO-92 package. What's that beast may you ask? Well, that's pretty simple. This is an NPN transistor we're gonna use as a switch. The first thing we need to make sure of is it can drive sufficient currents, we definitely wouldn't want to blow it when using our fan. According to the transistor's datasheet, it's suited for 500 mA applications which is more than enough in our case. We could pick a transistor suited for smaller currents but that won't make a huge difference in price and it's always nice to have some margin allowing to use the PCB in another context.

At this point, we're almost done. We're still gonna need two extra components: a diode and a resistor. Don't worry I'll explain!

The main objective of our setup is to control a fan, by opening and closing a switch. Opening the switch does not immediately stop current in the motor windings. The motor acts as an inductor and this causes current to continue to flow when the switch is opened suddenly. Charge builds up on what was the negative terminal of the motor which may cause damage! A reverse current surge may occur or, in extreme cases, an arc may form across the switch resulting in a discharge to ground. We definitely want to avoid this and need a way to safely dissipate electrical energy when the switch is opened. The addition of a diode in parallel with the motor (usually refered to as flyback or freewheeling diode) creates a way for stored energy to dissipate.

The last bit we'll need is far simpler: adding a series resistor between the control input and the base of the transistor limits current into the base. The type of transistor we're using typically accepts 0.6 V to turn on — more voltage means more current.

Schematics

We're all set to produce some schematics at this point. Here's the outcome:

Raspberry Pi Fan Control Layout

I've been using EAGLE with some nice libraries to produce those. The SCH file is available in this archive.

Layout

This is a very simple monolayer layout. When routing your board, it's important to respect the manufaturer's design rules. I'm personally using DirtyPCBs which is cheap and overall reliable, especially for boards that simple. If you're using EAGLE, grab their DRU file here and run a DRU check.

Here's the outcome:

Raspberry Pi Fan Control Layout

  • I used 0.8128 mm traces as we're not really restricted here.
  • To make it nice and user-friendly, I added some informative labels on the tPlace layer.
  • The board will be about 1.5 cm².

The layout BRD file is available in this archive as well.

Production

This is what the PCBs I received look like. They're pretty clean considering I paid about $10 excluding shipping.

Raw PCB

Here's what the outcome looks like. All components fit nicely, just like expected. Make sure to bend the transistor's pins so that it's touching the board.

Populated PCB

Scripts

It's now time to test the thing. Let's wire it and run some simple Python script to turn the fan on and off. We'll deal with PWM later.

#!/usr/bin/env python3

import os
from time import sleep
import signal
import sys
import RPi.GPIO as GPIO

pin = 18 # the pin ID, edit here to change it
maximum_temperature = 40 # the maximum temperature in Celsius after which we trigger the fan

def setup():
    GPIO.setmode(GPIO.BCM)
    GPIO.setup(pin, GPIO.OUT)
    GPIO.setwarnings(False)
    return()

def get_cpu_temperature():
    res = os.popen(‘vcgencmd measure_temp’).readline()
    temp =(res.replace(“temp=”,””).replace(“’C\n”,””))
    return temp

def get_temperature():
    CPU_temp = float(get_cpu_temperature())
    if CPU_temp > maximum_temperature:
        fan_on()
    else:
        fan_off()
    return()

def fan_on():
    set_pin(True)
    return()

def fan_off():
    set_pin(False)
    return()

def set_pin(mode): # useful if you want to add logging
    GPIO.output(pin, mode)
    return()

try:
    setup() 
    while True:
        get_temperature()
    sleep(5) # read the temperature every 5 sec, increase or decrease this limit if you want
except KeyboardInterrupt: # Ctrl+C keyboard interrupt 
    GPIO.cleanup() # resets all GPIO ports

Everything works just as planned! The last thing we want to be able to do is change the speed of the fan depending on the CPU's temperature using PWM. That will be our final script.

#!/usr/bin/env python3

import os
from time import sleep
import signal
import sys
import RPi.GPIO as GPIO


fan_pin = 17 # fan pin ID
desired_temperature = 45 # maximum temperature in Celsius after which fan is triggered

fan_speed = 100
sum = 0
p_temp = 15
i_temp = 0.4

def get_cpu_temperature():
    res = os.popen('vcgencmd measure_temp').readline()
    temp =(res.replace("temp=","").replace("'C\n",""))
    return temp

def fan_off():
    PWMControl.ChangeDutyCycle(0) # switch fan off
    return()

def handle_fan():
    global fan_speed,sum
    actual_temperature = float(get_cpu_temperature())
    diff = actual_temperature - desired_temperature
    sum = sum + diff
    pDiff = diff * p_temp
    iDiff = sum * i_temp
    fan_speed = pDiff + iDiff
    if fan_speed > 100:
        fan_speed = 100
    if fan_speed < 15:
        fan_speed = 0
    if sum > 100:
        sum = 100
    if sum < -100:
        sum = -100
    PWMControl.ChangeDutyCycle(fan_speed)
    return()

def set_pin(mode): # useful to add logging
    GPIO.output(fan_pin, mode)
    return()
try:
    GPIO.setwarnings(False)
    GPIO.setmode(GPIO.BCM)
    GPIO.setup(fan_pin, GPIO.OUT)
    PWMControl = GPIO.PWM(fan_pin,50)
    PWMControl.start(50)
    GPIO.setwarnings(False)
    fan_off()
    while True:
        handle_fan()
        sleep(1) # read the temperature every 5 seconds
except KeyboardInterrupt: # Ctrl+C keyboard interrupt 
    fan_off()
    GPIO.cleanup() # resets all GPIO ports

This is it! We're done with this project. Feel free to customize the scripts in any way you want.


Want to see something else added? Feel free to contact GalacticBox at mygalacticbox@gmail.com or leave a comment.

Getting Started with CSV Data Analysis using Python

Resource for this tutorial is available here.

Goal

In this tutorial, we're going to learn the basics of data analysis using Python. We'll start by importing a simple CSV file and will then plot the raw data.

While reading this tutorial, you may wonder: what's the point of doing this with Python when I can do it with Excel in a few seconds only? Well, the data we'll use to practice is ridiculously simple, but now, imagine yourself working on several hundred megabytes of data with Excel. That would be (trust me) a true nightmare. That's where Python comes in handy: it's reliable enough to handle such files with no restriction and the scripts are easy to update.

To achieve our goal, we'll use several libraries dedicated to data analysis and presentation.

Data Import

The first step is to import the CSV file. A CSV file is a text file containing data structured in a very simple way. If you open a CSV, you'll see that values are seperated by commas, and if you look right above the first values, you'll find the header: this line simply names the columns.

We should make sure our script understands where the header is. As you may imagine, the header will often be the first line but some CSVs contain other informations above the header. If the data you're analyzing was generated, say, using an oscilloscope, you can expect this additional information to be present — in this case, the first lines will contain the device name and maybe additional acquisition parameters. A simple approach could be to simply remove those lines, and we'll stick with that for now, but at the end of this article, I'll present a straightforward way to do this easily and safely.

Now, this is what data1.csv looks like:

Time,Amplitude
1,4
2,5
3,6
4,6.5
5,7
6,7.5
7,7.75
8,8
9,8.25
10,8.25

And this is what data2.csv looks like:

Time,Amplitude
1,9
2,8
3,7
4,6.75
5,6.5
6,6.25
7,6
8,5.75
9,5.5
10,5.5

Quite straightforward if you ask me. Let's import data1.csv:

import csv

read_data = csv.reader(open('data1.csv', 'r'))
data = []

for row in read_data:
    data.append(row)

At this point, you created an empty list and used the csv module to open your file. You then populated your list with all lines in data1.csv. If you try data[0], you'll notice it returns a list containing the two column names: the header. Let's keep track of this by doing: header = data[0] for later use. We can now safely remove the header from the data list, and keep only pure data in it: data.pop(0)

If we print data[0] one more time, we get the value on the first line: the header is gone, as expected.

You might agree it's not very practical to have data stored in terms of lines. If we want to plot it, we need it organized by columns so we can plot y against x. There is a nice module known as pandas that will allow us to do this conversion and much more!

Let's fire this:

import pandas as pd
dataframe = pd.DataFrame(data, columns=header)

That's as simple as it gets. Make sure to specify the column names. Here, we simply used our previously created header list. At this point you can go ahead and try: print(dataframe). This will return a summary of your data in the form you'd expect. Notice that the column names are rightly placed, on top.

The next step is to plot the data using Matplotlib.

Data Plot

import matplotlib.pyplot as plt

figure = plt.figure()
subplot = fig_angle.add_subplot(111)
subplot.plot(dataframe[header[0]].tolist(), dataframe[header[1]].tolist(), color = 'r', label = 'Output voltage')

At this point, a figure containing a subplot consisting was created. On this subplot, a red curve labeled as 'Output voltage' was drawn. Let's adjust the presentation a little more:

subplot.legend(loc = 'upper left')
subplot.set_ylabel('Amplitude (V)', fontsize = 15)
subplot.set_xlabel('Time (s)', fontsize = 15)
subplot.grid(True)

We should be good to go. To see your masterpiece, simply add: plt.show() to show all figures. In our case, there's only one.

CSV Data Plot


Want to see something else added? Feel free to contact GalacticBox at mygalacticbox@gmail.com or leave a comment.