Introduction: PyPrintPi on a Raspberry Pi

This project aims first to teach you how to calculate π to great precision in a short amount of time using various algorithms. And then using Python, a Raspberry Pi and a a thermal printer I will explain how to create a project that continuously prints pi to a selected number of decimal places. The final project uses a number pad to take input and can run without a screen.

There are two main parts to this instructable:

  • Part 1 (steps 1-12): Calculating π, the only materials necessary are some basic math skills and a computer with Python 3 installed. If you only chose to follow this part you'll learn about π's relation to trigonometry.
  • Part 2 (steps 13 onwards): Printing π, this is the more instructables orientated part, using a Raspberry Pi, a thermal printer and a small keypad to run a program to print π on the thermal printer. This part teaches you about getting Python 3 to talk to Python 2, using the Pi's serial port, Adafruit's thermal printer library and the GPIO library.

Parts list for part two:

Raspberry Pi

Adafruit thermal printer or Proto-Pic thermal printer

Adafruit thermal printer paper or Proto-Pic thermal printer paper

Breadboard

keypad

Step 1: Understanding Π

EDIT: in the rush of the deadline I forgot to credit Wikipedia Creative Commons for the image: https://en.wikipedia.org/wiki/File:Pi-unrolled-720.gif

The first step to calculating π is understanding what π really is.


Given a circle of diameter length d, and circumference c : π=c/d

Seems simple, right? All you have to do is divide one number by another, and you have π. So why all the fuss about this number? Well there are many interesting things about π:

  • It's an irrational number meaning it cannot be expressed as a fraction of two integers (whole numbers).
  • As far as we know π is normal, meaning that if you calculate π to a reasonable number of digits then each digit will appear roughly the same number of times.
  • It seems to appear everywhere in maths from trigonometry and other forms of of geometry, to probability and the Mandelbrot set, and bizarre equations such as e^(π*i) + 1 = 0.

Step 2: Approximate Π Using Some String, a Ruler and a Round Object

Time for our first approximation of π, based on the definition of π itself. It's quite easy to accomplish and doesn't involve any programming:

  • First measure across the middle of the round object from side to side. Record this length as d
  • Now wrap string around the edge of the round object so it completes one loop, straighten out the string and measure it's distance. Record this length as c.
  • π=c/d so divide c by d.

Using a bowl, I got d=14.3cm and c=49cm, my estimate of π is 49cm/14.3cm=3.4

Whilst this is a quick way to get a rough estimate of π, there isn't any easy way to turn this method into an algorithm a program can compute.

Step 3: Approximating Π Using Polygons

This method for approximating π is very old, it was first discovered by Archimedes, and relies heavily on trigonometry. It works by creating more and more accurate polygons inside a circle as in the picture above. Once an accurate polygon has been created, the length of the sides can be easily calculated, then it becomes easy to divide the perimeter of the polygon, the approximation of the circumference, by the diameter of the circle in order to give an approximation of π.

Step 4: Splitting the Polygon Into Segments

In order to make this method computable we focus on one segment of a polygon (OAB in the diagram). We divide this segment in two using a line OC starting at the centre of the circle and ending at the circumference. Then we use the point C where the line meets the circumference to draw two new lines CA and CB to the two corners of the old segment, as shown in the diagram.

This step will double the sides in our polygon. If we can work out how the length of a side is changed by this step then we can work out the length of a polygon with twice the number of sides in a single iteration.

Step 5: How the Perimeter Length of a Polygon Changes When the Number of Sides Doubles

We will consider a polygon drawn inside a circle of radius 1, so the length of lines OA, OB and OC = 1.

Line OC cuts line AB in half at point X.

Let L be the length of side AB of our original polygon and N be the length of the new side AC.

Therefore the length of line AX = L/2.

To calculate N, we can use Pythagoras theorem on triangle AXC:

AC²=AX²+ XC²

so

N²= (L/2)² + XC²

To find XC, remember OC = 1 and OC = OX + XC, so

XC = 1 - OX

which means

N²= (L/2)² + (1 - OX)² . . . . . . . . . Equation 1

Notice that OX is part a right angled triangle OXA. We know OA=1 and XA=L/2, so using Pythagoras on this triangle we find:

OA²=OX²+XA²

which gives

1² = OX² + (L/2)²

so OX² = 1 - (L/2)²

We can substitute this back into Equation 1:

N²= (L/2)² + (1 - OX)² = (L/2)² + (1 - √(1 - (L/2)²))²

which simplifies to

N = √(2 - 2 √(1 - (L/2)²))) . . . . . . . . . Equation 2

We now have an equation that relates the length of the sides L of any given polygon inside a circle of radius 1 with the length of sides N in a polygon with double the number of sides sitting inside that same circle.

Step 6: A Starting Polygon

In order to be able to do any calculations we first need to know the length of a side for a certain polygon. A square is a good polygon to start with as it is split up into four right angled triangles. The right angled triangles all have side lengths one as they are formed from radii of the circle. We thus get that:

hyp² = 1² + 1² where hyp is the hypotenuse of the triangle (and it is also the length of one side of the square),

or simplified:

Length of one side of the square = √2

Therefore the total circumference c of the square = 4 * √2 = 5.66

The circle has radius of 1, so a diameter d = 2.

This gives us a very rough approximation of π = c/d = 5.66 / 2 = 2.83

Improving the estimate by doubling the number of sides:

We can now use Equation 2 from the previous step, with L = √2 to calculate the length of the side of an 8 sided shape (an octagon):

N = √(2 - 2 √(1 - (L/2)²)))

= √(2 - 2 √(1 - (√2/2)²)))

= 0.765

Therefore the total circumference c of the octagon = 8 * 0.765 = 6.12

This gives us an approximation of π = c/d = 6.12 / 2 = 3.06

Notice how doubling the number of sides has improved the estimate of π from 2.83 to 3.06 !

Step 7: Doing It in Python

OK lets code this in a Python 3 script called polyPyPi.py.

Note that all code used in this Instructables is available at https://github.com/MeaningOf42/PyPrinterPi.

A few things to note about the code:

  • Rounding errors can easily become a problem as the value for the side length of a polygon gets smaller and smaller, meaning if you don't pay attention it can get rounded down to zero. So if you ask for π to 2 decimal places, after three or so iterations you get the correct 3.14 whereas at 100 or so iterations you get 0.0. In order to avoid this type of error my code sets the number of decimal places used in the calculations based on whichever is higher: the number of iterations or the number of decimal places being asked for. The code also calculates to twice the number of decimal places needed, this is to ensure the any rounding errors caused by rounding up square roots don't make it in to the final answer.
  • The code uses the Decimal library in order to be able to calculate numbers to more decimal places than the seventeen or so default float type allows for.

  • While it runs fast, it doesn't run nearly fast enough to be able to print out π continuously on a thermal printer, we need a faster algorithm.

Step 8: Approximating Π Using Arctan

The next method involves using the trigonometric function arctan to create an infinite series that converge to π. It was first discovered by Madhava of Sangamagrama, who lived from 1340 to 1425.

This step goes into mathematical details. You can skip it if you want to jump straight to the algorithm.

Using calculus it has been proven that:

arctan(x) = x - (x³/3) + (x⁵/5) - (x⁷/7) + (x⁹/9) - (x¹¹/11) ...

How does this help us? Well the output of the arctan in this formula is in radians, and radians are defined as the angle you get when you split a full revolution into 2π segments.

Next we need to find a way to relate the arctan formula with π. Tan(x) is the ratio of the opposite side to the adjacent side in a right angled triangle with angle x, so if we want this ratio to be 1, a nice whole round number, then both the opposite and adjacent sides of the triangle must be of equal length. This is a right-angled isosceles triangle, so one angle is a right angle, and the other two must be equal. We know that all angles in a triangles must add up to π radians (this is the equivalent rule as all angles in a triangle add up to 180°), and that one angle is π/2 radians (the right angle). From this we can work out that the other two angles must add up to π/2 radians, so each must be π/4 radians. So there we have it, for the ratio of the opposite and adjacent side of a triangle to be one, the angle of the right angle triangle must be π/4 radians, in other words:

tan(π/4) = 1

As arctan is the inverse function of tan it follows that:

arctan(1) = π/4

Now things start to become a little more interesting. We know from the rule:

arctan(x) = x - (x³/3) + (x⁵/5) - (x⁷/7) + (x⁹/9) - (x¹¹/11) ...

that:

arctan(1) = 1 - (1³/3) + (1⁵/5) - (1⁷/7) + (1⁹/9) - (1¹¹/11) ...

If we substitute that into the equation arctan(1) = π/4, we get

π/4 = 1 - (1³/3) + (1⁵/5) - (1⁷/7) + (1⁹/9) - (1¹¹/11)

which simplifies to:

π = 4 - (4/3) + (4/5) - (4/7) + (4/9) - (4/11) ...

Finally we have a formula perfect for calculating π! All we have to do now is implement it in python.

Step 9: Implementing the Arctan Infinite Series in Python

I have written a Python 3 program to implement the infinite series method described in the previous step. I called this program ArctanPyPi.py.

If you run the code you'll find there are several problems with the algorithm:

  • The fraction representing π grows extremely rapidly, at one iteration the fraction is 8/3 but at ten iterations it is 47028692/14549535 so this algorithm can't be used to find a nice fractional approximation of π.
  • The algorithm seems to take a long time to approximate π. At one iteration the approximation is 2.667, which isn't even correct to 1 digit! It actually takes 6 iterations just to get an approximation that begins with 3. But what if we want our approximation accurate to 1 decimal place? Well it turns out that at ten iterations our approximation is 3.2 a number correct to one significant figure, with 100 approximations it was 3.15 (correct to two significant figures), at 1000 iterations it was 3.142 (correct to three significant figures). This means that to calculate a π to an extra significant figure we have to do ten times more iterations. This means that even with an extremely fast processor calculating π to a reasonable amount of decimal places would soon become unreasonably long. I had to stop calculating π at three decimal places as that took two and a half minuets on my computer.

As you can see if we want to calculate π to a large number of decimal places then we're going to need another algorithm that converges much faster than the two algorithms we have seen so far.

Step 10: Trigonometric Combinations to Approximate Π (part 1)

In our last step we learnt how to use arctan(1) = π/4, but we found that the algorithm was too slow.

It turns out that using some trigonometric tricks we can adapt the previous algorithm to make it much faster.

This step and the next step go into mathematical details. You can skip these two steps if you want to jump straight to the new algorithm.

In trigonometry it is known that:

tan(x + y) = (tan(x) + tan(y) ) / (1 - (tan(x)*tan(y))

If we substitute x and y with arctan(x) and arctan(y) we get:

tan(arctan(x) + arctan(y)) = (x + y) / (1 - x*y)

(Note that arctan(tan(x)) is equal to x). Finally if we apply arctan to both sides we get:

arctan(x) + arctan(y) = arctan( (x + y) / (1 - x*y) )

This gives us a method to add two arctans. We can find values for x and y such that

arctan(x) + arctan(y) = arctan(1) = π/4.

To do this we must have:

(x + y) / (1 - x*y) = 1

which simplifies to:

y = (1 - x)/(1+x)

So, arctan(x) + arctan(y) = arctan(x) + arctan((1 - x)/(1+x))

Which gives us:

π/4 = arctan(x) + arctan((1 - x)/(1+x))

Step 11: Trigonometric Combinations to Approximate Π (part 2)

In the previous step we found that

π/4 = arctan(x) + arctan((1 - x)/(1+x))

Therefore, if you plot the graph of y=(1-x)/(1+x) (see the first image) then the arctan of the x coordinate and the arctan of the y coordinate at any point on the curve adds up to π/4.

On the second image I've shown how other equations found by other mathematicians lie on the same curve. It should be noted that in most cases the formulae is split into three terms, however in order to keep things simple I combined two of the terms.

Out of these equations we will be using Gauss's formula which others have suggested provides the fastest algorithm:

π/4=12*arctan(1/18) + 8*arctan(1/57) - 5*arctan(1/239)

In the next step we'll code this equation and get our best approximation of π yet.

Step 12: Coding Gauss's Formula

In our previous step we found the formula:

π/4=12*arctan(1/18) + 8*arctan(1/57) - 5*arctan(1/239)

we can easily turn this into a function in python if we assume we've defined an arctan function:

def gauss_pi_method(precision): 

       return 4*(12*arctan(18) + 8*arctan(57) - 5*arctan(239))

Now we need to create an arctan function. The quickest method to implement would be to use the formula we saw in step 9:

arctan(x) = x - (x³/3) + (x⁵/5) - (x⁷/7) + (x⁹/9) - (x¹¹/11) ...

however seeing as we are only calculating arctans of numbers in the form of 1/x it makes more sense to redefine the formula as:

arctan(1/x) = (1/x) - (1/3x³) + (1/5x⁵) - (1/7x⁷) + (1/9x⁹) - (1/11x¹¹) ...

This gives us the following function in Python:

def arctan(x): #defins a function for defining pi using the formula we derived    
    acuracy = Decimal(Decimal(1)/Decimal(10**(getcontext().prec-3)))
    result = Decimal(Decimal(1)/Decimal(x)) #result is a variable used to store our current value of pi as a decimal, 1 and x are turned into  decimals to avoid rounding errors
    sign = -1 #sign keeps track of whether to add or subtract the fraction
    denominatorval = 3 #
    fract = 0 #fraction keeps the value of the fraction we want to add or subtract to the result, it is later used to see if function can stop.
    
    while True:
        fract = Decimal(Decimal(1)/(denominatorval*(Decimal(x)**denominatorval)))
        result += sign*(fract)
        if fract < acuracy:
            break
        sign *= -1
        denominatorval += 2

    return result

This function is a bit more advanced than our previous functions as it does not require the user to input how many iterations to run. Instead it looks at the value of getcontext().prec and finds the smallest value that the Python program can distinguish from zero (for instance if getcontext().prec = 2 then the smallest value Python can distinguish from 0 is 0.1).

It then compares the value of the last term in the sequence: if it is smaller than the smallest value python can distinguish from 0 then there is no point in continuing so the program stops.

If we put the two functions together we get the following program (gauss_pi_method.py) :

from decimal import *

def gauss_pi_method():
    return 4*(12*arctan(18) + 8*arctan(57) - 5*arctan(239))

def arctan(x): #defins a function for defining pi using the formula we derived
    
    acuracy = Decimal(Decimal(1)/Decimal(10**(getcontext().prec-3)))
    result = Decimal(Decimal(1)/Decimal(x)) #result is a variable used to store our current value of pi as a decimal, 1 and x are turned into  decimals to avoid rounding errors
    sign = -1 #sign keeps track of whether to add or subtract the fraction
    denominatorval = 3 #
    fract = 0 #fraction keeps the value of the fraction we want to add or subtract to the result, it is later used to see if function can stop.
    
    while True:
        fract = Decimal(Decimal(1)/(denominatorval*(Decimal(x)**denominatorval)))
        result += sign*(fract)
        if fract<acuracy:

            break

	sign *= -1

        denominatorval += 2

    return result

while 1:
    #Asks the user how many decimal places they want there answer to be given to
    decimals = int(input("Enter how many decimal places do you want the answer to be given to: "))+1

    getcontext().prec = decimals+4 #sets precision to 4 more decimal places than asked for.
    
    PiEst = gauss_pi_method() #computes pi

    getcontext().prec = decimals #this block of code rounds pi to the number of decimal places asked for
    PiEst = +PiEst
    
    #next line prints the results
    print("Pi to " + str(decimals-1) + " decimal places is: " + str(PiEst))

If you run this to find 10,000 digits of π it should take under a minute. On my computer it took 17 seconds. That's much better than the previous best program we had, the one based on the polygon method, which took 43 seconds on my computer to calculate 100 digits of π.

However we can do a lot better using the same function to calculate π if we could calculate arctan(1/x) more quickly.

Fortunately, Euler came up with a way of doing just that:

arctan(1/x) = ( x / (1+x²) ) + ( ( 2*x) / (3*(1+x²)²) ) + ( ( 2*4*x) / (3*5*(1+x²)³) ) + ( ( 2*4*6*x) / (3*5*7*(1+x²)⁴) ) + ...

The nth term in this series is given by the function f(n):

f(n) = f(n-1) * (2*n) / ( (2*n+1)*(1+x²) )

where the first term is ( x / (1+x²) )

In order to make the code run faster we can calculate 1+x² before the loop so we only have to calculate it once. The updated code (gauss_pi_method_accelerated_arctan.py) is:

from decimal import *

def gauss_pi_method():
    return 4*(12*arctan(18) + 8*arctan(57) - 5*arctan(239))

def arctan(x): #defins a function for defining pi using the formula we derived
    
    acuracy = Decimal(Decimal(1)/Decimal(10**(getcontext().prec-3)))
    xSquaredPlusOne= Decimal(x**2 + 1)
    result = Decimal(Decimal(x)/Decimal(xSquaredPlusOne)) #result is a variable used to store our current value of pi as a decimal, 1 and x are turned into  decimals to avoid rounding errors
    fract = result
    numurator_num = 2 # even num is used to multiplie the denominator
    denominator_num = 3
    
    while True:
        fract = (fract*numurator_num)/(Decimal(denominator_num)*xSquaredPlusOne)
        result += fract
        if fract<acuracy:

            break
        numurator_num += 2
        denominator_num += 2

    return result

while 1:
    #Asks the user how many decimal places they want there answer to be given to
    decimals = int(input("Enter how many decimal places do you want the answer to be given to: "))+1

    getcontext().prec = decimals+4 #sets precision to 4 more decimal places than asked for.
    
    PiEst = gauss_pi_method() #computes pi

    getcontext().prec = decimals #this block of code rounds pi to the number of decimal places asked for
    PiEst = +PiEst
    
    #next line prints the results
    print("Pi to " + str(decimals-1) + " decimal places is: " + str(PiEst))

This code took just over half a second to calculate π to 10,000 digits, that's roughly 30 times quicker than before!

There is a neat trick to make the code run even faster. Up until now we've been using the decimal library in Python. However, if we do the calculations using whole integers it will be much quicker. To do this we first multiply the starting value by a large power of 10 and later, when we want to use the result, we divide it by that same power of 10. Here is how the code will look (gauss_pi_method_fixed_point.py):

from decimal import *
import time

def gauss_pi_method():
    return 4*(12*arctan(18) + 8*arctan(57) - 5*arctan(239))

def arctan(x):
    one=10**getcontext().prec
    
    xSquaredPlusOne = (x*x) + 1
    fract = (x * one) // xSquaredPlusOne
    total = fract
    numurator_num = 2
    while 1:
        denominator = (numurator_num+1) * xSquaredPlusOne
        fract *= numurator_num
        fract = fract // denominator
        if fract == 0:
            break
        total += fract
        numurator_num += 2
    return Decimal(Decimal(total)/Decimal(one))

while 1:
    #Asks the user how many decimal places they want there answer to be given to
    decimals = int(input("Enter how many decimal places do you want the answer to be given to: "))+1
    start_time = time.time()
    getcontext().prec = decimals+5 #sets precision to 4 more decimal places than asked for.
    
    PiEst = gauss_pi_method() #computes pi

    getcontext().prec = decimals #this block of code rounds pi to the number of decimal places asked for
    PiEst = +PiEst

    end_time = time.time()
    #next line prints the results
    print("Pi to " + str(decimals-1) + " decimal places is: " + str(PiEst))
    
    print("The algorithm ran in %s seconds" % (time.time() - start_time))

This version of the code calculated 10,000 decimal places of π in just 0.26 seconds, that's almost twice as quick as the previous method!

Step 13: Creating Code to Use With the Thermal Printer

We could still use better methods to calculate π such as the Chudnovsky algorithm or the Gauss–Legendre algorithm, however the maths starts to become a lot more complicated and I prefer to use methods I fully understand. Since we are going to be printing out the results and the printer's speed is limited , our program only needs to print as fast as the printer.

We have two main options for a program that prints out π: either the program calculates π to a large number of decimal places and then prints it out, or the program could print out the first ten digits of π whilst calculating the next ten digits of π. I decided to calculate π first then print it out, as this requires much less work.

In order to be printed our program needs to create a text document with the value of π in it (gauss_pi_method_save_to_text.py):

from decimal import *

import time

def gauss_pi_method():
    return 4*(12*arctan(18) + 8*arctan(57) - 5*arctan(239))

def arctan(x):
    
    one=10**getcontext().prec
    
    xSquaredPlusOne = (x*x) + 1
    fract = (x * one) // xSquaredPlusOne
    total = fract
    numurator_num = 2
    while 1:
        denominator = (numurator_num+1) * xSquaredPlusOne
        fract *= numurator_num
        fract = fract // denominator
        if fract == 0:
            break
        total += fract
        numurator_num += 2
    return Decimal(Decimal(total)/Decimal(one))

while 1:
    #Asks the user how many decimal places they want there answer to be given to
    decimals = int(input("Enter how many decimal places do you want the answer to be given to: "))+1
    start_time = time.time()
    getcontext().prec = decimals+5 #sets precision to 4 more decimal places than asked for.
    
    PiEst = gauss_pi_method() #computes pi

    getcontext().prec = decimals #this block of code rounds pi to the number of decimal places asked for
    PiEst = +PiEst

    end_time = time.time()

    #this block saves our value of pi to pi.txt

    f = open('pi.txt', 'w')
    f.write(str(PiEst))
    f.close()

         #next line prints the results

    print("Pi to " + str(decimals-1) + " decimal places is: " + str(PiEst))
    
    print("The algorithm ran in %s seconds" % (time.time() - start_time))

It's also going to be a lot easier to code a final script if we can create a small library with the Gauss method function and the arctan function. This will allow us to call them from any program in the same directory. When we create this library we need to change the Gauss method function so it saves it's output to a text file. Creating a library in Python is quite easy, we just need to create a Python file with all the functions we want included, that's it!

Here's the library (gauss_pi_lib.py):

from decimal import *

def gauss_pi_method(decimals):
    getcontext().prec = decimals+10 #sets precision to 10 more decimal places than asked for.
    pi = 4*(12*arctan(18) + 8*arctan(57) - 5*arctan(239))
    
    getcontext().prec = decimals #this block of code rounds pi to the number of decimal places asked for
    pi = +pi

    f = open('pi.txt', 'w') #this block saves our value of pi to pi.txt
    f.write(str(pi))
    f.close()
    return

def arctan(x):
    
    one=10**getcontext().prec
    
    xSquaredPlusOne = (x*x) + 1
    fract = (x * one) // xSquaredPlusOne
    total = fract
    numurator_num = 2
    while 1:
        denominator = (numurator_num+1) * xSquaredPlusOne
        fract *= numurator_num
        fract = fract // denominator
        if fract == 0:
            break
        total += fract
        numurator_num += 2
    return Decimal(Decimal(total)/Decimal(one))

Note that when creating a library, you put all the dependences at the top, outside of any function. We now have a library and we can use its functions by calling it from another program (gauss_pi_lib_test.py):

import gauss_pi_lib

import time

while 1:
    #Asks the user how many decimal places they want there answer to be given to
    decimals = int(input("Enter how many decimal places do you want pi to be given to: "))+1
    start_time = time.time()

    gauss_pi_lib.gauss_pi_method(decimals)
    
    
    end_time = time.time()
    #next line copies results from pi.txt
    f = open('pi.txt', 'r')
    pi = f.read()
    f.close()
    
    print("Pi to " + str(decimals-1) + " decimal places is: " + pi)
    
    print("The algorithm ran in %s seconds" % (time.time() - start_time))

In this program "import gauss_pi_lib", imports the library we just made. If you run the program you'll find it runs the same as before with the exception that it runs about twice as slow. Unfortunately we have to accept this loss in speed as we will be using Python 2 to talk to the printer and the easiest way of communicating between Python 2 and 3 is by saving a program's output to a .txt file in one program then reading that .txt file in another program running a different language.

Step 14: Loading Paper Into the Thermal Printer

The first part of creating the final project is getting the printer working. To load the printer with paper simply lift the lever on top of the printer, open the paper compartment, drop in a roll of paper, pull some paper out to the side and then close the compartment lid. As you close the lid the lever should be pushed to it's original position. It's worth noting that you don't need to feed the paper through anything -- you just close the box.

Step 15: Powering and Testing the Printer

In order to get the printer to work you must have a power supply that provides 5-9 volts and at least two amps. A USB cable is perfect for this, you can either strip a USB cable and use the power lines or you can solder two wires to the end of a USB plug as shown in the above images. Alternatively a bench top power supply also works fine.

If you're using USB to power the printer you can't plug it in directly to the Raspberry Pi because the Pi won't be able to provide enough power.

The printer should come with two cables: one with two wires, one red and one black and one with three wires a black a yellow and a green wire. The cable with a red and black wires is used for power, and the other cable for serial connections.

First plug the red and black cable into where it says DC IN and underneath GND and VH. Make sure the red wire lines up with the VH mark and the black wires lines up with GND.

Now plug the serial cable into the printer, again make sure the black wire lines up with GND.

Next connect ground on your power supply to ground on the printer (the black wire of the DC IN cable) and positive to positive on the printer (the red wire). The wires from my power supply didn't fit in the socket, so instead I used some wires that did then connected those wires to the power supply with alligator clips.

When you power on the printer for the first time the little light should give two long flashes then continuous short flashes.

If you press the button on the printer it should dispense some paper.

In order to print the test page, unplug the printer from the power source then plug it back in again whilst holding the button as shown in the animated Gif image. The printer should print out a character code table, and the voltage the printer was connected to, amongst other things.

If instead you only see CP437 being printed continuously (until you stop pressing the button), this is not an error message but it does indicate that there isn't enough power. If you encounter this problem you need to find a power source that can provide more current.

Step 16: Setting Up Your Raspberry Pi

First download and install the latest version of NOOBs onto your SD card.

Put the SD card into the Raspberry Pi, connect a keyboard a mouse and a screen and switch the Pi on. On the latest version of Raspbian it logs you in automatically for the first time, although if your using an older version or you've already logged in it may ask you for a username and password. The default username is pi and the default password is raspberry. If your Pi does not automatically boot to desktop type:

startx

to get the Pi to create a desktop environment.

Open up a terminal and type:

sudo raspi-config

This should bring up a box of different configuration options, there are several we need to change:

  • First select "Change User Password", and change the password from the default.
  • Next select "Boot Options", press enter then select "Console" without auto login. This prevents Raspbian from automatically booting into a desktop environment.
  • The other settings you need to change are under "Advanced Options", so select it and you should be presented with a new menu.
  • Select "Memory Split", the Pi's memory is shared between the CPU and the GPU. Since we'll be using the Pi without a screen we don't want the GPU taking too much memory, so when asked how much memory the GPU should have enter 8. This is the smallest value it will let the GPU have. The remaining memory will automatically be allocated to the CPU.
  • Enter advanced options again. Select "Serial". Normally the serial port on the Pi can be used to log into the Pi remotely, however we want to be able to use the serial port to talk to the printer so we need to disable this feature. To do this select "No".
  • Use the right arrow key to select "Finish". You should be asked whether or not you want to restart, select yes.

Once the Pi has restarted, log in using the username pi and the password you just set.

Step 17: Installing the Necessary Software Onto the Raspberry Pi

For this step your Raspberry Pi will need to be connected to the Internet.

Once logged into the console you need to install the necessary software. First type:

sudo apt-get install python-serial python-unidecode git

to install some Python libraries and git. When prompted if you want to proceed type "y" then enter. The next step is to download the Python code from my github repository:

git clone https://github.com/MeaningOf42/PyPrinterPi.git

This should create a directory on your computer named PyPrinterPi. Enter this directory and see the files:

cd PyPrinterPi
ls 

You should see all the Python programs we've discussed so far plus a few others.

Step 18: Wiring the Printer to the Pi

To wire the printer to the Pi, you first need to identify the rx wire on your printer. On mine it was the yellow wire. Next you need to identify the tx pin on your Pi. To do this google: rpi gpio pinout and find a good image that corresponds to your model. Look for a pin labelled "tx" on the image and find the corresponding pin on your Pi.

On a breadboard connect a ground pin from the Pi and a ground pin from the printer to the ground line. Finally on the breadboard connect the rx wire from the printer to tx pin on the Pi.

To test the printer, open a new terminal on your Pi and type:

cd PyPrinterPi
sudo python printertest.py

You should get a printout similar to the image above. If you do, go to the next step.

If you get an error message then the most likely problem is you didn't set up your Pi to let Python use the serial port. Go back to the step 16 and check your settings. If you've checked this and you're still getting an error message then you may not have installed all the correct packages. Fix this by typing:

sudo apt-get install python-serial python-unidecode

Try printing again. If you don't receive an error message but the printer doesn't print, then check the wiring from your Pi to your printer.

Step 19: Printing Π

I wanted to create a program that prints π, unfortunately Adafruit's library uses Python 2 and all our other programs run Python 3.

Not to worry! Seeing as we save our value of π to a text document in Python 3 we can simply open that text document in our Python 2 program that prints π.

printpi.py is the program I created to print π in the shape of π:

# This a program developed by Billy Timimi that prints out the value of pi in
# the shape of pi. Thanks to Adafruit for providing the Thermal_Printer library,
# which made this program possible. The original library is availible at:
# <a href="https://github.com/adafruit/Python-Thermal-Printer/blob/master/Adafruit_Thermal.py" rel="nofollow"> <a href="https://github.com/adafruit/Python-Thermal-Printe...</a"> https://github.com/adafruit/Python-Thermal-Printe...</a>>
#

#!/usr/bin/python
from Adafruit_Thermal import * #import's Adafruit's library

pi_leg_length = 9 #defines how many digits tall pi's legs should be.

f = open('pi.txt', 'r') #This block opens pi.txt and saves its contents as a string
pi = f.read()
f.close()

pi_formatted = "" #pi_formatted is used to contain a formatted version of pi, it will eventually contain the contents of pi in the shape of
                  #multiple pi symbols

count = 1 #count variable is used to place characters from pi into pi_formatted with the right spacings
for i in pi: #this loops through all the digits of pi (the variable not the number).
             #count variable increases to 50 then drops to 1, each time it reaches 49 a new pi symbol is completed
    if count<32:            #the first 31 characters of the pi symbol are placed next to each other creating the line at the top
        pi_formatted +=i
        
    elif count == ((2*pi_leg_length) + 31):         #skip this comment until you've undstood how the legs of pi are created,
        pi_formatted += "                   " + i   #adds two extra spaces before the last character of the pi symbol in order to create the outwards
                                                    #curve on the right leg of the pi symbol.
        
    elif count%2:                                   #if count is an odd number, adds spacing then the character, this creates the right leg of the pi 
        pi_formatted += "                 " + i     #symbol

    elif count == ((2*pi_leg_length) + 30):         #skip this comment until you've undstood how the legs of pi are created,
        pi_formatted += "\n     " + i               #creats a new line and spacing before the second to last character of the pi symbol, there is one less
                                                    #space in the spacing, this creates the leftwards curve on the left leg 
        
    elif count == ((2*pi_leg_length) + 32):         #On the 50th character create a new pi symbol by adding three line breaks, adding the character 
        pi_formatted += "\n \n \n" + i              # then returning count to one
        count = 1
        
    else:                                           #If none of the above elif statement's applied then 48>count>31 and count is even. If this is the case
        pi_formatted += "\n      " + i              #the character should be used to to make the left leg of the pi symbol. It does this by creating a new line
                                                    #then adding spacing
        
    count += 1 #increments the count variable
#if the loop above doesn't make sense to you try thinking about how it would run when the program is executed.
pi_line_split = pi_formatted.split("\n") #creates a list of all the lines in pi_formatted
printer = Adafruit_Thermal("/dev/ttyAMA0", 19200, timeout=5) #this line sets up the printer
printer.println("pi =") #This print the line pi=
printer.println("3.14159265358979323846264338327") #for some reason the first line of pi_line_split doesn't print on it's own so I'm printing it manually
for i in pi_line_split:
    printer.println(i) #prints every line in pi_line_split
printer.println("") #prints a couple of blank lines in order to show all of the print
printer.println("")

Take a moment to understand how this program works, if you run it, it should print out digits of π in the shape of π.

Step 20: Creating a Final Library

In the next a few steps we'll be creating a user interface where the Pi prints out instructions and the user responds using buttons.

In order to do so we'll need to expand the gauss_pi_lib library we created earlier. The new library is called PiPrintLib. Here it is:

from decimal import *
import os

def gauss_pi_method(decimals):
    getcontext().prec = decimals+10 #sets precision to 10 more decimal places than asked for.
    pi = 4*(12*arctan(18) + 8*arctan(57) - 5*arctan(239))
    
    getcontext().prec = decimals #this block of code rounds pi to the number of decimal places asked for
    pi = +pi

    f = open('pi.txt', 'w') #this block saves our value of pi to pi.txt
    f.write(str(pi))
    f.close()
    return

def arctan(x):
    
    one=10**getcontext().prec
    
    xSquaredPlusOne = (x*x) + 1
    fract = (x * one) // xSquaredPlusOne
    total = fract
    numurator_num = 2
    while 1:
        denominator = (numurator_num+1) * xSquaredPlusOne
        fract *= numurator_num
        fract = fract // denominator
        if fract == 0:
            break
        total += fract
        numurator_num += 2
    return Decimal(Decimal(total)/Decimal(one))

def printer_print(string): #this function saves a string to a text file then uses a python 2 program to print it
    f = open('passtopython2.txt', 'w') #this block saves our value of pi to pi.txt
    f.write(string)
    f.close()
    os.system("python printfrom3.py")
    return

def pi_cheat(decimals): #this function is used to change pi.txt to a value of pi correct to the number of places asked for without having to calculate pi
    f = open('pi_cheat.txt', 'r') #This block opens pi.txt and saves its contents as a string
    pi_cheat = f.read()
    f.close()
    if decimals>len(pi_cheat):
        p = open('pi.txt', 'w') #this block saves our value of pi to pi.txt
        p.write(str(pi_cheat))
        p.close()
    else:
        p = open('pi.txt', 'w') #this block saves our value of pi to pi.txt
        p.write(str(pi_cheat[:decimals]))
        p.close()
    return
def printpi():
    os.system("sudo python printpi.py")

You can view the program from your Raspberry Pi by typing:

nano PiPrintLib.py

to close nano press control x.

This library is the same as the previous library except it contains three additional functions: printer_print(), pi_cheat() and printpi().

printer_print() is used to print a string on the printer from Python 3. To do this first it saves the string to the text document passtopython2.txt, it then calls a Python 2 program called printfrom3.py which opens the text document and prints it and then deletes it.

The printfrom3.py program looks like:

#!/usr/bin/python

from Adafruit_Thermal import * #imports Adafruits_library
import os

f = open('passtopython2.txt', 'r') #This block opens passtopython2.txt and saves its contents as a string
string = f.read()
f.close()

printer = Adafruit_Thermal("/dev/ttyAMA0", 19200, timeout=5) #creates printer object
printer.println(str(string))
os.system("rm passtopython2.txt")

you can view it from your Raspberry Pi by typing:

nano printfrom3.py

The next function in the library is pi_cheat(), this function takes the argument decimals, and copies that many characters from pi_cheat.txt to pi.txt . This means you can print π to a certain number of decimal places without having to calculate it (pi_cheat.txt was made with gauss_pi_lib_test.py so it's not a complete cheat).

The last function, printpi(), simply runs the Python 2 program printpi.py which we created in the previous step.

Step 21: Connecting Buttons or a Keypad to the Raspberry Pi

In the final iteration the Raspberry Pi will boot without a screen, allow the user to select how many digits of π they want to print, then print them.

It would be a bit cumbersome to do this with full sized keyboard so I decided to use a mini keypad. You could also use 3 individual push buttons if you don't have a keypad.

Follow the wiring diagram above to connect the keypad or buttons to your Pi. If you are using a keypad there should be one common pin which you should connect to V++ on the Pi; connect 3 other keypad pins to the resistors shown in the diagram.

You maybe wondering how the circuit functions, and why we don't just connect the inputs on the Pi directly to the buttons. The answer is that unlike an LED which lights up based on current, the Pi detects voltage on its inputs which means that a build up of static electricity can trigger the input. In order to avoid this we connect each pin to ground through a 10k resistor. This keeps the input voltage to the Pi "pulled" to 0v, but when the button is pressed the input pin gets connected to the 5v source. The resistor must be big enough to prevent a large current being drawn from the Pi when the button is pressed, which would damage the Pi.

Once you have completed connecting the buttons or keypad to your Pi you can test whether or not the Pi can detect the button presses by typing:

sudo python3 button_check.py

If you press any button, the program should print (to the screen not the printer):

Button Pressed

If this doesn't happen check your wiring and try again. If a button still won't make the program print, try connecting the pin directly to 5v. If the program responds you have a faulty button, if not you may have a faulty Pi.

To see how button_check.py works open it with nano by typing:

nano button_check.py

The program should look like this:

import RPi.GPIO as GPIO
import time

#set's up gpio correctly
GPIO.setmode(GPIO.BCM)
GPIO.setup(23,GPIO.IN)
GPIO.setup(24,GPIO.IN)
GPIO.setup(25,GPIO.IN)

while True:
  if (GPIO.input(23)):
    print("Button1 Pressed")
    time.sleep(0.2) #waits 0.2 seconds to avoid button bounce

  if (GPIO.input(24)):
    print("Button2 Pressed")
    time.sleep(0.2) #waits 0.2 seconds to avoid button bounce

  if (GPIO.input(25)):
    print("Button3 Pressed")
    time.sleep(0.2) #waits 0.2 seconds to avoid button bounce

In case you are not familiar with any type of GPIO library I'll explain the basics.

First a pin on the Pi must be set up to either detect a voltage, or to output a voltage.

If we want to detect button presses on pin 23 we use:

GPIO.setup(23,GPIO.IN) 

If we instead wanted to output a voltage to pin 23 we use:

GPIO.setup(23,GPIO.OUT)

To read the voltage on pin 23 of the GPIO we use:

GPIO.input(23) 

which returns a 1 if a voltage is present on the pin or a 0 if there is no voltage on the pin.

The first line is crucial for the correct functioning of the program because it tells the Pi what chip it is using:

GPIO.setmode(GPIO.BCM)

When the program detects a button press it waits 0.2 seconds to prevent the program printing out too many messages:

time.sleep(0.2)

Step 22: The Final Program!

The final program needed to consist of four main steps:

  • Asking the user whether they want to calculate π or just use a predefined value.
  • Asking the user how many digits of π they want to calculate.
  • Calculating π, or simply copying its value if the user decided they want to do that.
  • Printing out π on thermal paper.

There are also several other requirements the program must fulfil:

  • All input and output must happen using only the buttons and the printer.
  • The program should only run once, as if we run this program on startup we want the program to exit nicely so we can continue to Linux.
  • The program should only print short lines as they work better on the thermal printer
  • The program should make it easy to customise the names of the buttons, so if people don't have a keypad or have used different buttons than the ones I used they can rename their buttons accordingly. This should only affect what the program prints, not how it runs.

To see how I fulfilled these requirements on your Pi, type:

nano main.py

or just look at main.py:

#import dependancies
import RPi.GPIO as GPIO import time import PiPrintLib #set up gpio correctly GPIO.setmode(GPIO.BCM) GPIO.setup(23,GPIO.IN) GPIO.setup(24,GPIO.IN) GPIO.setup(25,GPIO.IN) #set up names for buttons, used for talking to user
button1_name = "*"
button2_name = "7"
button3_name = "#"
#This block of code ask user if they want to cheat
PiPrintLib.printer_print("Do you want to cheat")
PiPrintLib.printer_print("and use values")
PiPrintLib.printer_print("from pi_cheat.txt?\n")
PiPrintLib.printer_print("")
PiPrintLib.printer_print("")
PiPrintLib.printer_print("If you cheat you won't")
PiPrintLib.printer_print("calculate pi,")
PiPrintLib.printer_print("just print it.")
PiPrintLib.printer_print("")
PiPrintLib.printer_print("If you want to cheat,")
PiPrintLib.printer_print("press " + button1_name)
PiPrintLib.printer_print("")
PiPrintLib.printer_print("If you want to calculate,")
PiPrintLib.printer_print("press " + button2_name)
PiPrintLib.printer_print("")
PiPrintLib.printer_print("")

cheat = 0 #value for whether or not user wants to cheat

while True: #This loop waits for the user to select whether or not the want to cheat
    if (GPIO.input(23)):
        PiPrintLib.printer_print("You chose to cheat")
        cheat = 1
        break
    if (GPIO.input(24)):
        PiPrintLib.printer_print("You chose to calculate")
        break
PiPrintLib.printer_print("")

decimal_power_of_ten = 4 #a value for how many places of pi the user wants printing

PiPrintLib.printer_print("You will now select")
PiPrintLib.printer_print("how many places of pi")
PiPrintLib.printer_print("you want printed.")
PiPrintLib.printer_print("You can only chose")
PiPrintLib.printer_print("a power of ten.")

updated = 1 #variable used for the next loop. Is used to check whether or not a button has been pressed since the last iteration
while True: #This loop waits for the user to the number of digits of pi they want to print out.
    if updated == 1:
        #this block of code asks the user what they want to do
        PiPrintLib.printer_print("This program")
        PiPrintLib.printer_print("is currently set")
        PiPrintLib.printer_print("to print 10^" + str(decimal_power_of_ten) + "digits of pi")                     
        PiPrintLib.printer_print("To increase")
        PiPrintLib.printer_print("the power by 1:")
        PiPrintLib.printer_print("Press " + button3_name)
        PiPrintLib.printer_print("")
        PiPrintLib.printer_print("To decrease")
        PiPrintLib.printer_print("the power by 1:")
        PiPrintLib.printer_print("Press " + button1_name)
        PiPrintLib.printer_print("")
        PiPrintLib.printer_print("To run the program:")
        PiPrintLib.printer_print("Press " + button2_name)
        PiPrintLib.printer_print("")
        PiPrintLib.printer_print("")
        updated = 0
    
    if (GPIO.input(23)):
        decimal_power_of_ten -= 1
        updated = 1
        if decimal_power_of_ten<1:
            PiPrintLib.printer_print("You can't go that low.")
            decimal_power_of_ten = 1
        
    if (GPIO.input(25)):
        decimal_power_of_ten += 1
        updated = 1
        if decimal_power_of_ten>6:
            PiPrintLib.printer_print("You can't go that high.")
            decimal_power_of_ten = 6

    if (GPIO.input(24)):
        break

start_time = time.time()

if cheat:
    PiPrintLib.pi_cheat(10**decimal_power_of_ten)
else:
    PiPrintLib.gauss_pi_method(10**decimal_power_of_ten)

end_time = time.time()

PiPrintLib.printer_print("Pi was just calculated in:")
PiPrintLib.printer_print("%s seconds" % (time.time() - start_time))
PiPrintLib.printer_print("")
PiPrintLib.printer_print("Pi to " + str(10**decimal_power_of_ten) + "decimal places is")
PiPrintLib.printpi()

While this program maybe a bit longer than the previous programs it easy to understand if you go through it step by step.

To change the names of the buttons, just change the following piece of code:

button1_name = "*"
button2_name = "7"
button3_name = "#"

to fit your needs.

Try running the program by typing:

sudo python3 main.py

You should be able to select whether or not you want to calculate π, how many places you want it to print π to then get it to print π!

Step 23: Final Product

To create the final product first we need to configure the Raspberry Pi so it runs main.py on startup. To do this first we need to know exactly where main.py is being stored, so type

echo $(cd $(dirname "$1") && pwd -P)/$(basename "$1")

into the terminal.

Copy the path on a piece paper, from now on when I write in a command complete it with the full path you copied down. In my case I got: /home/pi/PyPrinterPi/ .

Next in order to check your path is correct type:

sudo python3 <path>main.py

It should run main.py as before. Press Command+Z to exit the program once you have confirmed it is working properly.

The next step is to make the Pi run the command:

sudo python3 <path>main.py

on start up. To do this we need to edit a file called /etc/rc.local that contains a list of commands the Pi runs as it starts up:

cd /etc
sudo nano rc.local

Before the "exit 0" line, you need to add the lines:

cd <path>
sudo python3 main.py
cd /

Exit nano by pressing Command+x and then typing y and entering to save. Test to see if the program runs on startup type:

sudo reboot

If it runs the script on reboot, you're done!

For aesthetics I like to create enclosures for my projects. If you want to create an enclosure I suggest using a cardboard box: you can cut a slit in it to pass through wires from the keyboard, and you can cut a hole in it to let the printer sit flush with the cardboard. Then hide the Pi, breadboard and wires inside the box to get something looking like what you see in the video above.

Congratulations! You made it through to the end of the project!.