tag:blogger.com,1999:blog-23146772386383294712024-02-06T21:38:26.039-08:00Andrew NormanAndrew Normanhttp://www.blogger.com/profile/01150024579948499668noreply@blogger.comBlogger55125tag:blogger.com,1999:blog-2314677238638329471.post-87375307756328682072021-11-05T06:17:00.004-07:002021-11-05T06:31:34.437-07:00Eduroam on Raspberry PiI bought a <a href="https://www.raspberrypi.com/products/raspberry-pi-zero-2-w/" target="_blank">Raspberry Pi Zero 2 W</a> last week and worked out how to do something I've been wanting to do for a while - get a Raspberry Pi to connect to Eduroam. These instructions will be specific to the University of Leicester but hopefully helpful to others.<h3 style="text-align: left;">1. Eduroam</h3><div>On the Pi, either go to <a href="https://cat.eduroam.org">cat.eduroam.org</a> and then go through the process to find the appropriate Eduroam installer, or download the Linux script from <a href="http://wireless.le.ac.uk/setup/linux">wireless.le.ac.uk/setup/linux</a>. The link says it's to download the certificate, but that isn't what's downloaded - what you get is a Python script.</div><h3 style="text-align: left;">2. Generating the certificate and wpa-supplicant file</h3><div>Run the script (<span style="font-family: courier;">chmod +x</span> first) and it asks for your username and password. You end up with a directory <span style="font-family: courier;">.cat_installer</span> in your home directory, and this contains <span style="font-family: courier;">ca.pem</span> and <span style="font-family: courier;">cat_installer.conf</span>. The latter file contains your password in plain text, which is not a good thing.</div><h3 style="text-align: left;">3. Hash your password</h3><div><span style="font-family: courier;">echo -n </span><i><span style="color: red; font-family: inherit;">your-actual-password</span></i><span style="font-family: courier;"> | iconv -t utf16le | openssl md4 > pw.txt</span></div><h3 style="text-align: left;">4. Edit wpa_supplicant.conf</h3><div>Edit <span style="font-family: courier;">/etc/wpa_supplicant/wpa_supplicant.conf</span> and add the contents of <span style="font-family: courier;">~/.cat_installer/cat_installer.conf</span>. Replace the line </div><blockquote style="border: none; margin: 0px 0px 0px 40px; padding: 0px;"><div style="text-align: left;"><span style="font-family: courier;">password="</span><span style="color: red; font-family: inherit;"><i>your-actual-password</i></span><span style="font-family: courier;">" </span></div></blockquote><div>with the line</div><blockquote style="border: none; margin: 0px 0px 0px 40px; padding: 0px;"><div style="text-align: left;"><span style="font-family: courier;">password=hash:1234567</span></div></blockquote><div>Where <span style="font-family: courier;">1234567</span> is the contents of the <span style="font-family: courier;">pw.txt </span>file you created in step 3 (which will be a considerably longer hex number). No quotes. Now delete that file in <span style="font-family: courier;">~/.cat_installer</span> which has your plaintext password in it!</div><div>I moved the <span style="font-family: courier;">ca.pem</span> file from step 2 to <span style="font-family: courier;">/etc/ssl/certs/leicester.pem</span><span style="font-family: inherit;">, and edited the </span><span style="font-family: courier;">ca_cert</span><span style="font-family: inherit;"> line to reflect the new location and name of the certificate. I also made some other changes based on <a href="https://git.ecdf.ed.ac.uk/design-informatics/di-wiki/blob/34a4d1df8ff54a4619c42eae42e2cc05129579fd/pi-headless-setup.md" target="_blank">this site</a>, and a bit of experimentation to see what worked. See below for the final version of the file.</span></div><h3 style="text-align: left;">5. Reboot</h3><div>I now found that I was automatically connected to Eduroam, but DNS lookup wasn't working so I couldn't see websites. I checked the Eduroam settings on my phone, found the IP addresses of the DHCP servers that was using, and entered them on the Pi through the network settings (click on the wifi symbol, change the settings for the Eduroam SSID). The result was an /etc/resolv.conf file that looked like this:</div><blockquote style="border: none; margin: 0px 0px 0px 40px; padding: 0px; text-align: left;"><div style="text-align: left;"><span style="font-family: courier;"># Generated by resolvconf</span></div><div style="text-align: left;"><span style="font-family: courier;">domain le.ac.uk</span></div><div style="text-align: left;"><span style="font-family: courier;">nameserver 143.210.12.158</span></div><div style="text-align: left;"><span style="font-family: courier;">nameserver 143.210.12.159</span></div></blockquote><p>I also had to set priorities for the two networks in <span style="font-family: courier;">wpa_supplicant.conf</span>, because the Pi was sometimes connecting to the Cloud (free wifi) rather than Eduroam. I use the Cloud sometimes, but it's no good in headless mode because you have to finish the log in process using a browser (and lynx has stopped working for that). The final file looks like this, and the Pi is now reliably connecting to Eduroam without any further intervention (I often use a Pi Zero in <a href="https://learn.adafruit.com/turning-your-raspberry-pi-zero-into-a-usb-gadget/serial-gadget" target="_blank">serial gadget mode</a> so a web browser is out of the question).</p>
<pre>ctrl_interface=DIR=/var/run/wpa_supplicant GROUP=netdev
update_config=1
country=GB
network={
ssid="_The Cloud"
key_mgmt=NONE
priority=10
}
network={
ssid="eduroam"
key_mgmt=WPA-EAP
pairwise=CCMP
eap=PEAP
ca_cert="/etc/ssl/certs/leicester.pem"
identity="nja@leicester.ac.uk"
anonymous_identity="anonymous@le.ac.uk"
password=hash:9911066b9816dc8dd0e82209ecc138a4
altsubject_match="DNS:radius.le.ac.uk;DNS:radius.le.ac.uk"
phase2="auth=MSCHAPV2"
priority=20
}
</pre>
Andrew Normanhttp://www.blogger.com/profile/16475081894748925557noreply@blogger.com0tag:blogger.com,1999:blog-2314677238638329471.post-81834960262847152622019-05-01T06:26:00.003-07:002019-05-01T06:27:16.582-07:00A second image clientThis one is quite different from the previous client. It sends the width and height of the image to the server, and reconstitutes the pickled <tt>Image</tt> object from the byte stream the server sends back. It then uses <tt>pygame</tt> to display the image in a window, and does that as fast as it can. The width and height default to 300x300, but can be altered from the command line.<br />
<br />
<br />
<a name='more'></a><br />
<pre>
#!/usr/bin/env python3
import socket
import sys
import argparse
import pickle
import pygame
from PIL import Image
parser = argparse.ArgumentParser(description='Explorer (fake) client.')
parser.add_argument('--hostip', '-i', action='store', dest='address',
default='127.0.0.1', help='IP address of server host.')
parser.add_argument('--port', '-p', action='store', type=int, dest='port',
default=12345, help='Port number.')
parser.add_argument('--height', '-y', action='store', type=int, dest='height',
default=300, help='Image height.')
parser.add_argument('--width', '-x', action='store', type=int, dest='width',
default=300, help='Image width.')
parser.add_argument('--debug', '-d', action='store_true', default=False,
dest='debug', help='Print debugging information.')
args = parser.parse_args()
def get_image(width=300, height=300):
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock:
data = b''
sock.connect((args.address, args.port))
sock.sendall(bytes('{} {}'.format(width, height), 'utf-8'))
while True:
received = sock.recv(4069)
if not received: break
data = data + received
sock.close()
if args.debug: print('Received {} bytes'.format(len(data)))
return pickle.loads(data)
pygame.init()
display_surface = pygame.display.set_mode((args.width, args.height))
pygame.display.set_caption('Picam')
try:
while True:
picture = get_image(width=args.width, height=args.height)
data = picture.tobytes()
img = pygame.image.fromstring(data, picture.size, 'RGB')
display_surface.blit(img, (0, 0))
pygame.display.flip()
for event in pygame.event.get():
if event.type == pygame.QUIT:
sys.exit()
except KeyboardInterrupt:
pass
</pre>Andrew Normanhttp://www.blogger.com/profile/16475081894748925557noreply@blogger.com0tag:blogger.com,1999:blog-2314677238638329471.post-76976538859047337352019-05-01T06:12:00.000-07:002019-05-01T06:26:43.082-07:00A second image serverVariation on the first. This server expects the client to send the dimensions of the image to be returned - if the aspect ratio is different to the camera's aspect ratio, the camera image is cropped (centrally) to the correct aspect ratio, resized using <tt>thumbnail()</tt>, and sent. Also, rather than sending a byte stream of the image data, I'm using <tt>pickle.dumps()</tt> to create a byte stream from an <tt>Image</tt> object, and sending that (since I'm already creating an <tt>Image</tt> object for the cropping and resizing).<br />
<br />
<br />
<a name='more'></a><br />
<pre>#!/usr/bin/env python3
import argparse
import pickle
import socketserver
import sys
from io import BytesIO
from PIL import Image
from picamera import PiCamera
class CamHandler(socketserver.BaseRequestHandler):
def handle(self):
global cam
global args
data = self.request.recv(1024)
width, height = [int(x) for x in data.decode('utf-8').split()]
if args.debug:
print("Width {} height {}".format(width, height))
stream = BytesIO()
cam.capture(stream, format='jpeg')
stream.seek(0)
picture = Image.open(stream)
ratio1 = width / height
ratio2 = picture.width / picture.height
if ratio1 < ratio2:
# Camera image is broader than desired image ratio
new_width = ratio1 * picture.height
delta = (picture.width - new_width) / 2
picture = picture.crop((delta, 0, (new_width + delta),
picture.height))
if ratio1 > ratio2:
# Camera image is taller than desired image ratio
new_height = picture.width / ratio1
delta = (picture.height - new_height) / 2
picture = picture.crop((0, delta, picture.width,
(new_height + delta)))
picture.thumbnail((width, height), Image.ANTIALIAS)
if args.debug:
print("Sending image {}".format(picture.size))
self.request.sendall(pickle.dumps(picture))
class ImageServer(socketserver.ThreadingMixIn, socketserver.TCPServer):
def __init__ (self, server_address, handler_class=CamHandler):
socketserver.TCPServer.__init__(self, server_address, handler_class)
return
if __name__ == '__main__':
parser = argparse.ArgumentParser(description='Image server.')
parser.add_argument('--hostip', '-i', action='store', dest='address',
default='127.0.0.1', help='IP address of server host.')
parser.add_argument('--port', '-p', action='store', type=int, dest='port',
default=12345, help='Port number.')
parser.add_argument('--debug', '-d', action='store_true', default=False,
dest='debug', help='Print debugging information.')
args = parser.parse_args()
try:
cam = PiCamera()
cam.rotation = 180
time.sleep(2)
if args.debug:
print("Camera started.")
print("Server starting on {}:{}".format(args.address, args.port))
server = ImageServer((args.address, args.port),
handler_class=CamHandler)
server.serve_forever()
except Exception as e:
if args.debug:
print(e)
except KeyboardInterrupt:
pass
finally:
if args.debug:
print("Server shutting down.")
</pre>
Andrew Normanhttp://www.blogger.com/profile/16475081894748925557noreply@blogger.com0tag:blogger.com,1999:blog-2314677238638329471.post-66123042469612863532019-04-17T04:59:00.001-07:002019-04-17T05:00:33.714-07:00A terrible security camera 3 - the client softwareNotes:<br />
<ul>
<li>Again, I'm using <span style="font-family: "courier new" , "courier" , monospace;">argparse</span>. The IP address now has to be the <u>server's</u> address, not the client's!</li>
<li>The client receives the image data, puts it into a PIL Image object, and then crops it to a square and resizes it to the dimensions of the Unicorn Hat (16 x 16).</li>
<li>If the <span style="font-family: "courier new" , "courier" , monospace;">--debug</span> flag was used, the original and cropped/resized image are saved to files.</li>
<li>The client keeps sending requests to the server as fast as it can (which is not very fast given the other work it's doing).</li>
</ul>
If I run the client with the command below, the current webcam image is displayed on the Unicorn Hat (updating about twice a second) until I press CTRL-C. <br />
<br />
<pre><b><span style="color: #38761d;">pi@pibow:~/Python/camclient $</span></b> <span style="color: #666666;"><b>./image-client.py -i 192.168.1.106</b></span>
</pre>
<h3>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi6L0auUDKVINVLEParMu1aMORwzdezOzQJqVwJhco_9PZV40AQ2zEoBN1Apm2gfBoDwCqb0XVGcqjhUbhvisVnunCjb5qBVZvxsUDNnYDL9vBUvw6yvhWrZp3m1wRT_MkR2exz1EmmEHyy/s1600/webcam.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="450" data-original-width="450" height="320" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi6L0auUDKVINVLEParMu1aMORwzdezOzQJqVwJhco_9PZV40AQ2zEoBN1Apm2gfBoDwCqb0XVGcqjhUbhvisVnunCjb5qBVZvxsUDNnYDL9vBUvw6yvhWrZp3m1wRT_MkR2exz1EmmEHyy/s320/webcam.jpg" width="320" /></a></div>
</h3>
<h3>
</h3>
<h3>
The code</h3>
<br />
<a name='more'></a><br />
<pre>#!/usr/bin/env python3
import sys
import socket
import argparse
import unicornhathd as uhat
from PIL import Image
from io import BytesIO
parser = argparse.ArgumentParser(description='Image client.')
parser.add_argument('--hostip', '-i', action='store', dest='address',
default='127.0.0.1', help='IP address of server host.')
parser.add_argument('--port', '-p', action='store', type=int, dest='port',
default=12345, help='Port number.')
parser.add_argument('--debug', '-d', action='store_true', default=False,
dest='debug', help='Print debugging information.')
args = parser.parse_args()
width, height = uhat.get_shape()
uhat.brightness(0.5)
while True:
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock:
try:
sock.connect((args.address, args.port))
data = b''
while True:
received = sock.recv(4096)
if not received: break
data = data + received
sock.close()
im = Image.open(BytesIO(data))
imx, imy = im.size
mindim = min(imx, imy)
xdiff = (imx - mindim)/2
ydiff = (imy - mindim)/2
im.crop((xdiff, ydiff, xdiff+mindim, ydiff+mindim))
if args.debug: im.save('original.jpg')
im = im.resize((width, height), Image.LANCZOS)
if args.debug: im.save('resized.jpg')
for x in range(width):
for y in range(height):
pixel = im.getpixel((x, y))
(r, g, b) = [pixel[i] for i in range(3)]
uhat.set_pixel(x, y, r, g, b)
uhat.show()
except Exception as e:
print(e)
uhat.off()
sys.exit()
except KeyboardInterrupt:
uhat.off()
sys.exit()
</pre>
Andrew Normanhttp://www.blogger.com/profile/16475081894748925557noreply@blogger.com0tag:blogger.com,1999:blog-2314677238638329471.post-49490984681714905042019-04-17T04:59:00.000-07:002019-04-17T05:01:35.391-07:00A terrible security camera 2 - the server softwareA few notes on the server software:<br />
<ul>
<li>I'm using the <span style="font-family: "courier new" , "courier" , monospace;">argparse</span> module to allow me to change key information (IP address, port, debug information) from the command line. The default is to use port 12345 on localhost.</li>
<li>The server simply takes a photo and sends it to the client.</li>
<li>The server runs forever until the process is stopped (ideally with CTRL-C).</li>
</ul>
An example of running the server: picam's IP address is 192.168.1.106 (I have set it up to have a static address on my home wifi). To run the server:<br />
<br />
<tt>
<span style="font-size: small;"><b><span style="color: #38761d;">pi@picam:~/Python/camserver $</span><span style="background-color: white;"><span style="color: #666666;"> ./image-server.py -i 192.168.1.106 --debug</span></span>
<br />Server started on 192.168.1.106:12345</b></span>
</tt>
<br />
<br />
Because I've used the --debug flag, the server tells me it has started and the address and port number it's using. <br />
<h3>
</h3>
<h3>
The code</h3>
<pre><a name='more'></a>#!/usr/bin/env python3
import socketserver
import argparse
import sys
import time
from io import BytesIO
from picamera import PiCamera
class ImageHandler(socketserver.BaseRequestHandler):
def handle(self):
global camera
global args
try:
if args.debug:
print("Connection from {}".format(self.client_address[0]))
stream = BytesIO()
camera.capture(stream, format='jpeg')
stream.seek(0)
self.request.sendall(stream.getvalue())
if args.debug:
print("Image sent.")
except Exception as e:
print("Server exception")
print(e)
return
class ImageServer(socketserver.ThreadingMixIn, socketserver.TCPServer):
def __init__ (self, server_address, handler_class=ImageHandler):
socketserver.TCPServer.__init__(self, server_address, handler_class)
return
if __name__ == '__main__':
parser = argparse.ArgumentParser(description='Image server.')
parser.add_argument('--hostip', '-i', action='store', dest='address',
default='127.0.0.1', help='IP address of server host.')
parser.add_argument('--port', '-p', action='store', type=int, dest='port',
default=12345, help='Port number.')
parser.add_argument('--debug', '-d', action='store_true', default=False,
dest='debug', help='Print debugging information.')
args = parser.parse_args()
try:
camera = PiCamera()
time.sleep(2)
server = ImageServer((args.address, args.port), ImageHandler)
if args.debug:
print("Server started on {}:{}".format(args.address, args.port))
server.serve_forever()
except Exception as e:
print(e)
except KeyboardInterrupt:
if args.debug:
print("Server stopped.\n\n")
</pre>
Andrew Normanhttp://www.blogger.com/profile/16475081894748925557noreply@blogger.com0tag:blogger.com,1999:blog-2314677238638329471.post-18292591553721543642019-04-17T04:50:00.000-07:002019-04-17T05:01:02.750-07:00A terrible security camera 1 - the hardware<a href="https://www.raspberrypi.org/products/raspberry-pi-3-model-b/">Raspberry Pi 3B</a> in a <a href="https://shop.pimoroni.com/products/pibow-for-raspberry-pi-3-b-plus?variant=2601121284106">Pibow case</a>, with a <a href="https://shop.pimoroni.com/products/unicorn-hat-hd">Unicorn Hat HD</a> attached and a <a href="https://shop.pimoroni.com/products/pibow-modification-layers?variant=1047618757">diffuser</a> on top of the case (machine name "pibow"). The Unicorn Hat HD comes with a dark diffuser, I prefer the look of the lighter diffuser which covers the whole top of the case.<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjBny5xE7L_YpLmmEKWYT33mSfeamDq6dCI6h3o4vZFPLE8ofhEk7If0ia1X-asMFog9QL3i2B6gAvKkfXZ5uVyDCgtVL2kzu2WCr7OqzrPNmYwpun3YvLPSuV1TArFKVNaBpzOy1mf4yYh/s1600/pibow.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="300" data-original-width="300" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjBny5xE7L_YpLmmEKWYT33mSfeamDq6dCI6h3o4vZFPLE8ofhEk7If0ia1X-asMFog9QL3i2B6gAvKkfXZ5uVyDCgtVL2kzu2WCr7OqzrPNmYwpun3YvLPSuV1TArFKVNaBpzOy1mf4yYh/s1600/pibow.jpg" /></a></div>
Raspberry Pi <a href="https://www.raspberrypi.org/products/raspberry-pi-zero-w/">Zero W</a> in the <a href="https://www.raspberrypi.org/products/raspberry-pi-zero-case/">official Pi case</a> with <a href="https://www.raspberrypi.org/products/camera-module-v2/">a camera</a> attached (machine name "picam"). The official case comes with a cable to attach the camera to the smaller connection on the Zero.<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj5lnIcWWeYltxXecfq0Iv4khiiOEdeuREZmloYpwp7b9BDHkYxRxcOzVT3eafQEPRFt8yhGFz-6Z9maZqjZDf8gMURN4Y8S-IgGBXZfbp_IQoMliOgqkg4Lkzap_uh-G100joNpI7qLB43/s1600/picam.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="300" data-original-width="300" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj5lnIcWWeYltxXecfq0Iv4khiiOEdeuREZmloYpwp7b9BDHkYxRxcOzVT3eafQEPRFt8yhGFz-6Z9maZqjZDf8gMURN4Y8S-IgGBXZfbp_IQoMliOgqkg4Lkzap_uh-G100joNpI7qLB43/s1600/picam.jpg" /></a>
</div>
Andrew Normanhttp://www.blogger.com/profile/16475081894748925557noreply@blogger.com0tag:blogger.com,1999:blog-2314677238638329471.post-72369114131596072422019-04-17T03:48:00.000-07:002019-04-17T03:48:26.448-07:00Raspberry Pi setupA few notes on how I set up Raspberry Pi devices.<br />
<h3>
Static IP</h3>
My home router won't allocate addresses above 100 (I have set it up not to do so), so it is safe to use static IP addresses above that. I keep a master list of allocated static addresses in <span style="font-family: "courier new" , "courier" , monospace;">/etc/hosts</span> on my desktop (Linux) PC to avoid conflict with other addresses (doesn't do anything to avoid conflicts apart from having a master list in one place!). For example, a new Pi Zero W which I have called "pish" (it originally had a Pimoroni LED shim attached).<br />
<br />
<pre>192.168.1.108 pish</pre>
<pre> </pre>
Log in to pish and edit <span style="font-family: "courier new" , "courier" , monospace;">/etc/dhcpcd.conf</span><b><span style="font-family: "courier new" , "courier" , monospace;"></span></b> to add the lines below, and reboot.<br />
<span style="font-size: small;"><br /></span>
<br />
<pre><span style="font-size: small;">interface wlan0
static ip_address=192.168.1.108/24
static routers=192.168.1.1
static domain_name_servers=192.168.1.1</span></pre>
<pre> </pre>
Assuming <span style="font-family: "courier new" , "courier" , monospace;">ssh</span> is enabled on pish, on the desktop machine to allow login without password:<br />
<br />
<pre>ssh-copy-id pi@pish
</pre>
<h3>
Shell</h3>
<ul>
</ul>
Add this line to <span style="font-family: "courier new" , "courier" , monospace;">.bashrc</span><br />
<pre> </pre>
<pre>export PATH=~/bin:$PATH
</pre>
<ul>
</ul>
Create a <span style="font-family: "courier new" , "courier" , monospace;"><span style="font-family: "courier new" , "courier" , monospace;">~/bin</span></span> folder, and add this script to it in a file called <span style="font-family: "courier new" , "courier" , monospace;">update</span> - allows quick system updates just by typing <span style="font-family: "courier new" , "courier" , monospace;">update</span> at the command prompt.<br />
<ul>
</ul>
<pre>#!/usr/bin/env bash
sudo apt update
sudo apt dist-upgrade
sudo apt autoremove</pre>
<ul>
</ul>
Create <span style="font-family: "Courier New", Courier, monospace;">.inputrc</span> and add this line to it (allows tab completion of file names ignoring upper and lower case variants).<br />
<ul>
</ul>
<pre>set completion-ignore-case on</pre>
<h3>
Editing</h3>
<span style="font-family: "Courier New", Courier, monospace;">gvim</span> (my preferred editor) gives a warning unless these packages are installed:<br />
<br />
<pre>sudo apt install libcanberra-gtk*-module
</pre>
Andrew Normanhttp://www.blogger.com/profile/16475081894748925557noreply@blogger.com0tag:blogger.com,1999:blog-2314677238638329471.post-4578870802549281772017-11-08T08:59:00.001-08:002017-11-25T10:00:20.509-08:00Ubuntu 17.10After a brief hiatus...<br />
<br />
I've been using <a href="https://ubuntu-mate.org/">Ubuntu Mate</a> for a while because of its simplicity, but recently bought a reasonably high-end PC for home use (i7 processor, huge hard disk, smaller SSD which makes booting and running a Windows virtual machine blisteringly fast) and tried out <a href="http://releases.ubuntu.com/17.10/">standard Ubuntu 17.10</a> at the weekend. Ubuntu is now using Gnome 3 as the standard desktop, which is what made me look again. It was so successful (with a bit of tweaking) that I'm now installing it on my work Linux PC (which these days is my spare machine, used for odd occasions when I need to try out something the university's locked-down Windows installation won't let me do).<br />
<br />
<br />
<ol>
<li>Install Ubuntu 17.10. Takes about half an hour.</li>
<li>Replace the hideous purple and orange wallpaper.</li>
<li><span style="font-family: "courier new" , "courier" , monospace;">sudo apt-get update; sudo apt-get dist-upgrade</span></li>
<li><span style="font-family: "courier new" , "courier" , monospace;">sudo apt-get install gnome-tweak-tool</span></li>
<li><span style="font-family: inherit;">Using the tweak tool, remove desktop icons (Icons / Show Icons)</span></li>
<li><span style="font-family: "courier new" , "courier" , monospace;">sudo apt-get install chromium-browser chromium-codecs-ffmpeg-extra chrome-gnome-shell</span></li>
<li><span style="font-family: inherit;">Start Chromium, go to <a href="http://extensions.gnome.org/">extensions.gnome.org</a>. Install the browser extension so you can install shell extensions.</span></li>
<li><span style="font-family: inherit;">Install <a href="https://extensions.gnome.org/extension/307/dash-to-dock/">Dash to Dock</a>, and <a href="https://extensions.gnome.org/extension/1011/dynamic-panel-transparency/">Dynamic Panel Transparency</a>.</span></li>
<li><span style="font-family: inherit;">Back to the tweak tool, and in Extensions change the DtD settings to display on the bottom, panel mode, and change the autohide settings so "push to show" is disabled. In the Launchers tab move the applications button to the beginning of the dock. Change the DPT settings (in the Background tab) to have custom opacity with an unmaximized opacity of 100%. The latter is because I really don't like the effect where the top bar is semi-transparent if no windows are touching it, or a solid colour if a window is touching it. Doesn't take effect until you log out and then log on again.</span></li>
<li><span style="font-family: inherit;">Add the Numix theme and icons:</span><br /><span style="font-family: "courier new" , "courier" , monospace;">sudo add-apt-repository ppa:numix/ppa<br />sudo apt-get update<br />sudo apt-get install numix-gtk-theme numix-icon-theme-circle</span></li>
<li><span style="font-family: inherit;">Back to the tweak tool again, select the Numix theme and numix circle icons. </span></li>
</ol>
<span style="font-family: inherit;">Addendum:</span><br />
<span style="font-family: inherit;">Icons from the dash/dock were appearing in the lockscreen and activities view - the solution was </span><span style="font-family: inherit;"><br /><span style="font-family: "Courier New", Courier, monospace;">apt-get purge gnome-shell-</span></span><wbr></wbr><span style="font-family: "Courier New", Courier, monospace;">extension-</span><wbr></wbr><span style="font-family: "Courier New", Courier, monospace;">ubuntu-</span><wbr></wbr><span style="font-family: "Courier New", Courier, monospace;">dock</span><br />
which removes an alternative dock.<br />
<span style="font-family: inherit;"> </span><br />
<ol>
</ol>
Andrew Normanhttp://www.blogger.com/profile/16475081894748925557noreply@blogger.com0tag:blogger.com,1999:blog-2314677238638329471.post-21666490825063700102013-07-30T04:23:00.000-07:002013-07-30T04:23:07.833-07:00Twitter, again<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjYxZhxu4YUUJR9BWqqaeM_q8EVlqOGBAUU7tCauhpYeoEskxVfEkv3ICGEI1be5mZ0dpdWfA_-xhEPz5FVfteq4RNoAMEewLG-1JpgShaGx3vV-MP0kGJtJD6MNCml6FnrOBVmfkT3dm29/s1600/2013-07-30+11.27.52.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="240" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjYxZhxu4YUUJR9BWqqaeM_q8EVlqOGBAUU7tCauhpYeoEskxVfEkv3ICGEI1be5mZ0dpdWfA_-xhEPz5FVfteq4RNoAMEewLG-1JpgShaGx3vV-MP0kGJtJD6MNCml6FnrOBVmfkT3dm29/s320/2013-07-30+11.27.52.jpg" width="320" /></a></div>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjIuEm_MzR2wDIawPfVGQ80NCz38Woa1a-sBY2R9jeMsX0pEm2LzjcgW0Wawz5H2o73Kuy7-YPDCOlfGYIgsHSRBpXyzjQyz9Uht4X_pn7c04eytrztaM2J1NobdArEM4ITkEuHO9nYWQre/s1600/2013-07-30+11.27.52.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><br /></a></div>
Using the Arduino ethernet shield on the <a href="http://arduino.cc/en/Main/arduinoBoardMega2560">Mega 2560</a> Arduino, this displays tweets. But not directly - Twitter's new API requires SSL, and the Arduino isn't up to doing that. So I set up a CGI program on my desktop computer which checks a twitter feed, and returns a plain text version of the username and message. Code for the CGI program:<br />
<br />
<pre>#!/usr/bin/python
import twitter, sys, time, pickle
class Timeline:
def __init__ (self):
self.seen = set()
self.cache = []
def add_to_cache (self, statuses):
for d in [s.AsDict() for s in statuses]:
if d["id"] not in self.seen:
self.seen.add(d["id"])
self.cache.append(d)
def __iter__ (self):
return self
def next (self):
if len(self.cache) == 0:
raise StopIteration
else:
return self.cache.pop()
oauth_file = open("access_token.txt", "r")
akey = oauth_file.readline().rstrip()
asec = oauth_file.readline().rstrip()
consumer_file = open("consumer_keys.txt", "r")
ckey = consumer_file.readline().rstrip()
csec = consumer_file.readline().rstrip()
try:
api = twitter.Api(consumer_key = ckey, consumer_secret = csec,
access_token_key = akey, access_token_secret = asec)
except Exception as e:
print e
else:
try:
timeline = pickle.load(open("timeline.dat"))
except:
timeline = Timeline()
timeline.add_to_cache(api.GetUserTimeline("LE17RH"))
print "Content-type: text/plain\n"
try:
x = timeline.next()
print x["user"]["screen_name"]
print x["text"]
except StopIteration:
pass
pickle.dump(timeline, open("timeline.dat", "w"))
</pre>
The Timeline class is an iterator because in an earlier incarnation I was loading a lot of tweets and wanting to iterate over them, it isn't necessary for this program which just returns a plain text rendition of one username and message.<br />
<br />
Code for the Arduino:<br />
<br />
<pre>#include <SPI.h>
#include <Ethernet.h>
#include <LiquidCrystal.h>
byte mac[] = { 0x90, 0xA2, 0xDA, 0x00, 0x7F, 0xB6 };
const IPAddress ip(143,210,109,74);
const IPAddress dnsserver(143,210,12,154);
const IPAddress server(143,210,108,92);
const char get_header[] = "GET /cgi-bin/twittino.cgi";
const char server_header[] = "Host: 143.210.108.92 80";
EthernetClient client;
LiquidCrystal lcd(8, 9, 17, 16, 15, 14);
const int SDSELECT = 4;
const int PIN_G = 21; // Pins on Mega board!
const int PIN_R = 20;
const int PIN_B = 19;
void setup() {
pinMode(PIN_R, OUTPUT);
pinMode(PIN_G, OUTPUT);
pinMode(PIN_B, OUTPUT);
pinMode(SDSELECT, OUTPUT);
digitalWrite(PIN_R, HIGH);
digitalWrite(PIN_G, LOW);
digitalWrite(PIN_B, HIGH);
digitalWrite(SDSELECT, LOW);
Serial.begin(9600);
lcd.begin(16, 2);
Ethernet.begin(mac, ip, dnsserver);
delay(1000);
digitalWrite(SDSELECT, HIGH);
Serial.println(Ethernet.localIP());
digitalWrite(PIN_G, HIGH);
}
void loop()
{
static int counter = 1;
char username[30];
char tweet[141];
Serial.println(counter++);
if (client.connect(server, 80))
{
// Serial.println("Connected");
// Make a HTTP request:
client.println(get_header);
client.println(server_header);
client.println("Connection: close");
client.println();
int ulen = 0;
int tlen = 0;
boolean username_found = false;
while (client.connected())
{
if (client.available())
{
char c = client.read();
if (!username_found)
{
if (c == '\n')
{
username_found = true;
username[ulen] = '\0';
}
else
{
if (c != '\n') username[ulen++] = c;
}
}
else
{
if (c == '\n')
{
tweet[tlen++] = ' ';
}
else
{
tweet[tlen++] = c;
}
switch(c)
{
case 'R' : digitalWrite(PIN_R, LOW); break;
case 'G' : digitalWrite(PIN_G, LOW); break;
case 'B' : digitalWrite(PIN_B, LOW); break;
case 'r' : digitalWrite(PIN_R, HIGH); break;
case 'g' : digitalWrite(PIN_G, HIGH); break;
case 'b' : digitalWrite(PIN_B, HIGH); break;
}
}
}
}
client.stop();
// Serial.println("Disconnected");
tweet[tlen] = '\0';
if (ulen != 0 && tlen != 0)
{
Serial.print("Username : ");
Serial.println(username);
Serial.print("Tweet :\n");
Serial.println(tweet);
lcd.setCursor(0, 0);
for (int i=0; i < ulen; i++)
{
lcd.print(username[i]);
}
for (int i=0; i <= tlen; i++)
{
lcd.setCursor(0, 1);
for (int j=0; j < 16; j++)
{
if (j + i < tlen) lcd.print(tweet[j+i]);
}
lcd.print(' ');
delay(500);
}
}
else
{
Serial.println("Nothing.");
}
}
else
{
Serial.println("Connection failed.");
}
delay(10000); // Check every 10 seconds
lcd.clear();
}
</pre>
<br />
Things to note: the SD card, if present, needs to be deactivated (by setting SD_SELECT, pin 4, to LOW) before setting up the ethernet connection. In addition to the LCD panel, I have one of my RGB LED units connected, and if there is a capital R, G or B in the tweet, the appropriate colour is displayed (the lowercase equivalents turn the colour off). The username is displayed on the top row of the LCD panel, and the tweet scrolls along the bottom row.<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgqGw8aSX8H4EfzG9IxwNEZis8IoF0TYE0c7niCkDisDmA7YKXf3JL4vBq2ci19qD6O8-Yg7xMOUHss_WwnNOWdq4iUalL2KNPqrqpJtjcVD0RkzaeoEpeYQ2BBuIambueKf28D3zGg52LR/s1600/2013-07-30+11.33.16.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="240" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgqGw8aSX8H4EfzG9IxwNEZis8IoF0TYE0c7niCkDisDmA7YKXf3JL4vBq2ci19qD6O8-Yg7xMOUHss_WwnNOWdq4iUalL2KNPqrqpJtjcVD0RkzaeoEpeYQ2BBuIambueKf28D3zGg52LR/s320/2013-07-30+11.33.16.jpg" width="320" /></a></div>
Andrew Normanhttp://www.blogger.com/profile/16475081894748925557noreply@blogger.com0tag:blogger.com,1999:blog-2314677238638329471.post-16657353905031363022013-07-26T03:38:00.000-07:002013-07-26T06:17:35.941-07:00Shift Registers<div class="separator" style="clear: both; text-align: center;">
<iframe allowfullscreen='allowfullscreen' webkitallowfullscreen='webkitallowfullscreen' mozallowfullscreen='mozallowfullscreen' width='320' height='266' src='https://www.blogger.com/video.g?token=AD6v5dxDUcALkOqyX5qvy_cQhmoU9B52Gl-sfY6Zg4MLlJmDj6pj1KPj2J6LcHWJGDeTveQExLLHgy4bjI70BNsULg' class='b-hbp-video b-uploaded' frameborder='0'></iframe></div>
I bought a "Nano 3.0 for Arduino" for eight quid - a neat little thing that reminds me of the <a href="http://www.hexbug.com/nano">Hexbug Nanos </a>I bought my niece and nephew for Christmas a couple of years ago, but is rather more interesting for grownups. It fits directly into a breadboard, and is powered and programmed through a USB lead (standard "mini A" connector).<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgh8dGu5cX01uyxdClXL7-Bkgf0XKtS06OjP97xd-GlZG57_cZEQzz2BmYFKnb7emuhhk2pR55clF_Pe5MluCTvD5BVt-fymVuZUq4tXKMiVdSxoO0WbyaJHdvqIr3YtrVZnTnytKxNxLhF/s1600/nano.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="125" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgh8dGu5cX01uyxdClXL7-Bkgf0XKtS06OjP97xd-GlZG57_cZEQzz2BmYFKnb7emuhhk2pR55clF_Pe5MluCTvD5BVt-fymVuZUq4tXKMiVdSxoO0WbyaJHdvqIr3YtrVZnTnytKxNxLhF/s320/nano.jpg" width="320" /></a></div>
<br />
The pins are:<br />
<br />
Top row: digital pins D12 to D2 (D2 is just above the TX LED), ground, reset, RX and TX (RX and TX are D0 and D1 on an Arduino Uno).<br />
<br />
Bottom row: digital 13, 3.3 V, REF (analogue reference, labelled AREF on bigger boards), analogue inputs A0 to A7 (two more than on the Uno, A7 is just below the LED), 5 V, reset, ground, and input voltage (VIN).<br />
<br />
This is almost, but not quite, the same layout as the <a href="http://arduino.cc/en/Main/ArduinoBoardNano">official Arduino Nano</a>, and considerably cheaper. I stuck it on a breadboard with a couple of <a href="http://arduino.cc/en/Tutorial/ShiftOut">shift register chips</a> (74HC595), and used it to control some RGB LED units.<br />
<br />
The RGB units have three LEDs with a common anode, so controlling four of them requires twelve bits. I've used two shift registers, so that gives sixteen bits to play with.<br />
<br />
A diagram is below.<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiwFW9qia6pmx-7beBQfXgfwIzrUTB4q7q8g-gXGEDiM8bSanNG-meyqR8nw3aVxQiLokne4uaLGT_HaO2oaBTfBSXZuYtMzr7nycOKXQyQExll4PQHhcf2HHCRvUnKM9mSJrsBkIo8kKqR/s1600/Shift+Registers_bb.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="215" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiwFW9qia6pmx-7beBQfXgfwIzrUTB4q7q8g-gXGEDiM8bSanNG-meyqR8nw3aVxQiLokne4uaLGT_HaO2oaBTfBSXZuYtMzr7nycOKXQyQExll4PQHhcf2HHCRvUnKM9mSJrsBkIo8kKqR/s320/Shift+Registers_bb.png" width="320" /></a></div>
<div class="separator" style="clear: both; text-align: center;">
</div>
<br />
<div class="separator" style="clear: both; text-align: center;">
</div>
Things to note: the board shown in the diagram doesn't have the same pin order as mine, and my breadboard (see below) has a very different layout but the circuit is the same. The connections from the board are: 5V pin to the top power rail (which is then linked to the lower power rail in the middle, and the bottom power rail), ground pin to the top ground rail (similarly linked to the middle and bottom), and the SPI connection to the first shift register: blue wire links D2 to the data pin on the 74HC595, green wire links D4 to the latch pin, and yellow wire links D3 to the clock pin.<br />
<br />
The two 74HC595 chips have the clock and latch pins linked (horizontal green and yellow wires), and the serial output from the first goes to the data pin on the second (horizontal blue wire below the yellow one). This means that when two bytes are sent into the first chip, the first byte is displaced into the second chip (you can chain these things even further - so if you had four linked chips and sent four bytes into the first one, the fourth chip would contain the first byte sent, the third chip the second byte, the second chip the third byte and the first chip the fourth byte).<br />
<br />
I have only shown the connections for one of the LED units - the horizontal red, blue and green wires connect from pins Q3, Q2 and Q1 of the first 74HC595 to the cathodes of one of the LED units. Pins Q7-Q5 of the first 74HC595 go to the RGB cathodes of the second unit, and the third and fourth units are connected to the same pins on the second 74HC595. On my breadboard (see below) there are no wires making these connections - I just bridged the pins of the 74HC595 chips to the LED units with resistors (270 Ω).<br />
<br />
I'm not using pins Q0 or Q4 on either 74HC595 - they aren't needed (and Q0 is in a slightly inconvenient position, on the other side of the chip to the other seven output pins, next to the data pin). <br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjUieem54grfY1y-nL4lTjI5Vx34sBG-tEdTZFFsxcjT3aiBmHyNCb6y2LC0e4esZYPrEN0m2MYspg5vfy8or6Ap6fJ2SbVaV5WY6kPUas3ofUT_yx5h87ui91lem6i3wXPzyPCuPTeg7VY/s1600/breadboard.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="240" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjUieem54grfY1y-nL4lTjI5Vx34sBG-tEdTZFFsxcjT3aiBmHyNCb6y2LC0e4esZYPrEN0m2MYspg5vfy8or6Ap6fJ2SbVaV5WY6kPUas3ofUT_yx5h87ui91lem6i3wXPzyPCuPTeg7VY/s320/breadboard.jpg" width="320" /></a></div>
<br />
Code:<br />
<br />
<pre>const int latchPin = 4;
const int clockPin = 3;
const int dataPin = 2;
const byte red = 0x08;
const byte green = 0x02;
const byte blue = 0x04;
const byte yellow = red | green;
const byte magenta = red | blue;
const byte cyan = green | blue;
const byte white = red | green | blue;
const byte black = 0x00;
const byte sequence[8] = {black, red, yellow, green,
cyan, blue, magenta, white};
void put_four_leds(byte leds[4])
{
byte a = leds[0] | (leds[1] << 4);
byte b = leds[2] | (leds[3] << 4);
digitalWrite(latchPin, LOW);
// Flip bits because low is on, high is off
// Second pair goes first because that byte is shifted
// through to the second shift register.
shiftOut(dataPin, clockPin, MSBFIRST, ~b);
shiftOut(dataPin, clockPin, MSBFIRST, ~a);
digitalWrite(latchPin, HIGH);
}
void setup()
{
//set pins to output so you can control the shift register
pinMode(latchPin, OUTPUT);
pinMode(clockPin, OUTPUT);
pinMode(dataPin, OUTPUT);
}
void loop()
{
byte led_array[4];
int i, j;
for (i=0; i<8; i++)
{
for (j=0; j<4; j++)
{
led_array[j] = sequence[(i + j) % 8];
}
put_four_leds(led_array);
delay(500);
}
}</pre>
<pre> </pre>
<pre> </pre>
<br />Andrew Normanhttp://www.blogger.com/profile/16475081894748925557noreply@blogger.com0tag:blogger.com,1999:blog-2314677238638329471.post-22235368453386992842013-07-01T09:44:00.002-07:002013-07-01T10:21:37.125-07:00Open Day, Part IIThe main things I learned from the open day:<br />
<div class="separator" style="clear: both; text-align: center;">
</div>
<ol>
<li>It is possible to mull over these things for a few weeks when you are too busy to do anything serious, and then put in a few hours' work late on a Friday afternoon and come up with something working.</li>
<li>iPads are fun, and there are some great apps to do serious engineering, but you can't hack those apps, and you can't do much to hack the iPad. You get to do what Apple, and Apple-approved developers, allow you to do. Which is a great deal, but it's not the sort of activity that engineers find really satisfying (and despite the fact that I'm an ex-geochemist with a master's degree in philosophy, I still consider myself a kind of engineer).</li>
<li>On Friday afternoon, I needed the <a href="http://code.google.com/p/python-twitter/">python-twitter</a> library to check the twitter feed from Python. Downloading and installing it via <tt>apt-get</tt> from the Ubuntu repository took a few seconds. When I realised I needed a newer version to cope with changes in Twitter's API, it took a couple of minutes to find the newer version, download it and install it. If I had been using the locked-down campus version of Windows, it would not have been that trivial. Perhaps not even possible. Linux makes it much, much easier to do this sort of thing.</li>
<li>I enjoyed myself more late on Friday afternoon than I had done for weeks doing more "important" things. It really fired my enthusiasm for using these things in next year's programmable electronics module (I have sixty kits sitting in a cupboard), and encouraging students to play around with them and see what they can do. </li>
<li>I thought I had fried the GPIO port on my <a href="http://www.raspberrypi.org/">Raspberry Pi</a>. A morning sitting in a room with disappointingly few visitors meant I had time to do some troubleshooting with a multimeter, and it turned out to be a broken connection on the ground rail of one of my breadboards, which was a relief. I love <a href="http://www.mastech.com.cn/html/en/products-mas830.htm">my multimeter</a>.</li>
<li>To some visitors, sending a tweet to turn on an LED was like magic. To some, it was deeply unimpressive. Only colleagues were able to grasp how much was really going on between pressing the send button on the iPad, and the red and green lights coming on.</li>
</ol>
<div style="text-align: center;">
<iframe allowfullscreen='allowfullscreen' webkitallowfullscreen='webkitallowfullscreen' mozallowfullscreen='mozallowfullscreen' width='320' height='266' src='https://www.blogger.com/video.g?token=AD6v5dxJ4qJQvL210_lX7Ukyuii2EmhlLMI_pGG8dQavVcLqZgCpdt79lTRzaRKqAH1X3RHCj1l3V1qe8-sgG9s--w' class='b-hbp-video b-uploaded' frameborder='0'></iframe>
</div>
Andrew Normanhttp://www.blogger.com/profile/16475081894748925557noreply@blogger.com0tag:blogger.com,1999:blog-2314677238638329471.post-2318585245912244272013-07-01T09:27:00.003-07:002013-07-01T09:55:05.246-07:00Open Day, Part II was asked to demonstrate the use of an iPhone in Engineering at Saturday's alumni open day. I demonstrated <a href="http://www.autodesk.com/mobile-apps">AutoDesk ForceEffect Motion</a> (which is pretty good fun) on the iPad rather than iPhone, but also demonstrated using the iPad to send tweets to the department's Twitter account (<a href="https://twitter.com/le_engineering">@le_engineering</a>), which was more fun for me to do. I had a Python program running on my Linux netbook, which checked Twitter for mentions of @le_engineering, and then searched the rest of the text for the characters R, G, r, g, b and 1 or 0. These characters were sent via USB to an Arduino, which interpreted them as commands to light (uppercase) or turn off (lowercase) a red and green LED, sound a buzzer, or move a servo to 90 or 0 degrees.<br />
<br />
<a name='more'></a><br />
<h4>
Code for Python on Linux. </h4>
I used the OpenAuth keys generated <a href="http://andrew-j-norman.blogspot.co.uk/2010/12/aside-twitter-and-rss.html">here</a>. I hadn't appreciated how great <a href="http://docs.python.org/2/library/pickle.html">pickle</a> is until now. Because of Twitter's limits on API calls, and the fact that I wasn't expecting vast numbers of tweets (in the event, I was the only person who used this), I got the program to check the feed once every minute, and to stop after ten minutes. Needs to be run under sudo, or you need to add your own account to the dialout group (<tt>sudo adduser $USER dialout</tt>) to be able to write to the serial port. Obviously, you need to change the port device (currently <tt>/dev/ttyACM0</tt>) if that isn't the serial port your Arduino is attached to.<br />
<br />
<pre>#!/usr/bin/python
import twitter, serial, sys, time, pickle
try:
ser = serial.Serial(port="/dev/ttyACM0", baudrate=9600, timeout=0)
# Allow the port to wake up, if using this in non-interactive mode
time.sleep(0.5)
except:
print "Failed to open port."
# Load the times of tweets that have already been dealt with (into a set).
times_seen = pickle.load(open("times-seen.dat"))
oauth_file = open("access_token.txt", "r")
akey = oauth_file.readline().rstrip()
asec = oauth_file.readline().rstrip()
consumer_file = open("consumer_keys.txt", "r")
ckey = consumer_file.readline().rstrip()
csec = consumer_file.readline().rstrip()
try:
api = twitter.Api(consumer_key=ckey, consumer_secret=csec,
access_token_key=akey, access_token_secret=asec)
except Exception as e:
print e
else:
for i in range(10):
# Check the twitter feed every minute for ten minutes
# (API limit is 100 calls per hour)
print "Minute {0}/10".format(i)
try:
mentions = api.GetMentions()
for m in mentions:
t = m.GetCreatedAt()
if t in times_seen: continue
times_seen.add(t)
print "{0}: {1}".format(m.GetUser().name, m.text)
text = m.text.replace("@le_engineering", "")
for c in text:
try:
if c in "rgRG10b":
print c,
ser.write(c)
time.sleep(1)
except:
pass
print
except twitter.TwitterError as te:
print te.message
time.sleep(60)
# Save the time of tweets already dealt with.
pickle.dump(times_seen, open("times-seen.dat", "w"))
print "Done."
</pre>
<br />
<h4>
Arduino code.</h4>
Not much to say about this. The playTone function is ripped off from <a href="http://oomlout.com/a/products/ardx/circ-06/">Oomlout</a>, but all this program really does is check for incoming characters on the serial port, and perform an action if a valid character is found. <br />
<br />
<pre>#include <servo.h>
Servo myservo;
int green_led = 7;
int red_led = 6;
int speaker = 5;
int servo_pin = 3;
void playTone(int tone, int duration) {
for (long i = 0; i < duration * 1000L; i += tone * 2) {
digitalWrite(speaker, HIGH);
delayMicroseconds(tone);
digitalWrite(speaker, LOW);
delayMicroseconds(tone);
}
}
void setup() {
pinMode(green_led, OUTPUT);
pinMode(red_led, OUTPUT);
pinMode(speaker, OUTPUT);
myservo.attach(servo_pin);
myservo.write(0);
playTone(1000, 500);
Serial.begin(9600);
}
void loop() {
byte command;
int i;
if (Serial.available())
{
command = Serial.read();
Serial.write(command);
switch (command)
{
case 'G': digitalWrite(green_led, HIGH); break;
case 'R': digitalWrite(red_led, HIGH); break;
case 'g': digitalWrite(green_led, LOW); break;
case 'r': digitalWrite(red_led, LOW); break;
case 'b': playTone(2000, 500); break;
case '0': myservo.write(0); break;
case '1': myservo.write(90); break;
default: break;
}
}
}</pre>
<h4>
Hardware </h4>
<div class="separator" style="clear: both; text-align: center;">
</div>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjF2ofLryFSIfmFKYL7FJ07z6LNIYAd27vUzTcjFBRQO7wRVZ2ySBh_Nrr536o-9e_0XnXy9ZHG9Xwpk2sxW6aZFxIAcsZc4T80GhyR1nV7_5h-C9iwjftJOtGOwj9LSlORIy6T-hr-nGbH/s1600/Demo+Arduino_bb.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="229" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjF2ofLryFSIfmFKYL7FJ07z6LNIYAd27vUzTcjFBRQO7wRVZ2ySBh_Nrr536o-9e_0XnXy9ZHG9Xwpk2sxW6aZFxIAcsZc4T80GhyR1nV7_5h-C9iwjftJOtGOwj9LSlORIy6T-hr-nGbH/s320/Demo+Arduino_bb.png" width="320" /></a></div>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjRTclyZmJ8yu63xJEGE_ByrJTXSIsRDoh4LqI3WWZUUQenZDHXD15e7wzQYBRgSs8J7HoEz31ZtJkmi9r6Od-gPrRDqs_otqqffJsBPzQ2HigMfduoBL2p3V1HBC7j0KPewD0QN9zdL1bK/s1600/Demo+Arduino_bb.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><br /></a></div>
Thanks to <a href="http://fritzing.org/download/">Fritzing</a>,which I found thanks to <a href="http://tronixstuff.wordpress.com/">John Boxall</a>'s book <a href="http://nostarch.com/arduino"><i>Arduino Workshop</i></a>. The capacitor linked to the reset pin is in there because in an earlier iteration of this, I was attaching it to a web server rather than sending it instructions from a Python program. Without the capacitor, the system reset every time a new serial connection was made, which wasn't the behaviour I wanted. <a href="http://playground.arduino.cc/Main/DisablingAutoResetOnSerialConnection">This explains why, and how to fix it</a>.Andrew Normanhttp://www.blogger.com/profile/16475081894748925557noreply@blogger.com1tag:blogger.com,1999:blog-2314677238638329471.post-43128041973446937362013-06-12T06:34:00.001-07:002013-06-12T06:34:42.206-07:00Raspberry PiI haven't done much with the Raspberry Pi since I bought it, but having semi-volunteered to give some talks at a forthcoming open day, I thought I would use it as a web server to interface with an Arduino and some hardware. So I set it up again.<br />
<h3>
SD Card</h3>
I bought a new 16 gigabyte SD card for a tenner from the students' union shop. I think I paid about £130 for a 2 gigabyte hard disk back in the mid-nineties (I have a pile of them propping up a bookshelf in my office). Downloaded the <a href="http://www.raspberrypi.org/downloads">raspbian image</a> on to my Linux netbook, popped the SD card into the slot, and created the image (the card was automounted as <span style="font-family: "Courier New",Courier,monospace;">/dev/sdb1</span>, hence the first command):<br />
<span style="font-family: "Courier New",Courier,monospace;"> umount /dev/sdb1</span><br />
<span style="font-family: "Courier New",Courier,monospace;"> dd if=2013-05-25-wheezy-raspbian.img of=/dev/sdb bs=4M</span><br />
<h3>
Network</h3>
The RPi was already registered for the campus network, so I haven't bothered with any of the static network address recipes - it picks up its address through DHCP.<br />
<h3>
Additional software</h3>
I installed (using <span style="font-family: "Courier New",Courier,monospace;">apt-get</span>) the following packages:<br />
<ul>
<li><span style="font-family: "Courier New",Courier,monospace;">apache2</span></li>
<li><span style="font-family: "Courier New",Courier,monospace;">php5</span></li>
<li><span style="font-family: "Courier New",Courier,monospace;">libapache2-mod-php5</span></li>
<li><span style="font-family: "Courier New",Courier,monospace;">xfce4</span></li>
<li><span style="font-family: "Courier New",Courier,monospace;">tightvnc</span></li>
<li><span style="font-family: "Courier New",Courier,monospace;">arduino </span></li>
</ul>
<h3>
VNC</h3>
I want to run the RPi headless most of the time, and don't want to have to muck about with monitor cables and spare keyboards when I do need to use the GUI. Initially, I took the RPi up to one of the labs where I could pinch a monitor with DVI input, and work in peace. <a href="http://www.penguintutor.com/linux/tightvnc">These instructions</a> worked to get VNC up and running. I used <a href="http://projects.gnome.org/vinagre/">vinagre</a> and <a href="http://remmina.sourceforge.net/">remmina</a> to test the setup - VNC is now starting reliably at boot time.<br />
<h3>
Web server</h3>
I want the web server to use one of my subdirectories as root (documents in <span style="font-family: "Courier New",Courier,monospace;">/home/pi/www/htdocs</span>, CGI scripts in <span style="font-family: "Courier New",Courier,monospace;">/home/pi/www/cgi-bin</span>), so I edited <span style="font-family: "Courier New",Courier,monospace;">/etc/apache2/sites-enabled/000-default</span> to change the values of <span style="font-family: "Courier New",Courier,monospace;">DocumentRoot</span>, <span style="font-family: "Courier New",Courier,monospace;">ScriptAlias</span>, and the permissions on <span style="font-family: "Courier New",Courier,monospace;">/home/pi/www/htdocs/</span>.<br />
<h3>
Desktop</h3>
I use xfce as my standard desktop (I like Gnome 4 on netbooks, have never got on with Unity). Selecting that as the default is simple (like most of the commands above, this has to be done using sudo to give yourself root privileges):<br />
<span style="font-family: "Courier New",Courier,monospace;"> update-alternatives --config x-session-manager</span><br />
A menu appears, and I was able to choose <span style="font-family: "Courier New",Courier,monospace;">/usr/bin/startxfce4</span> as my session manager.<br />
<br />Andrew Normanhttp://www.blogger.com/profile/16475081894748925557noreply@blogger.com0tag:blogger.com,1999:blog-2314677238638329471.post-36221483534007369732013-04-17T06:21:00.002-07:002013-04-17T09:14:07.116-07:00GmailI've been using Python to email .MOBI files to the Kindle "Personal Documents" service, which adds them to your documents list on Amazon and allows them to be downloaded, synchronised between reading devices, etc. I've been sending via Gmail's SMTP server rather than the University server (because my Gmail account is authorised to send to Kindle, and my University account is not). That stopped working today, with the mail server reporting an incorrect password.<br />
<br />
The answer: <a href="https://accounts.google.com/DisplayUnlockCaptcha">https://accounts.google.com/DisplayUnlockCaptcha</a><br />
<br />
This doesn't actually display a Captcha, but it opens a ten minute window during which, if you use the application which is getting password rejections, the password will be accepted and the application added to a "whitelist".Andrew Normanhttp://www.blogger.com/profile/16475081894748925557noreply@blogger.com0tag:blogger.com,1999:blog-2314677238638329471.post-44757243511027085012012-09-27T08:19:00.001-07:002012-09-27T08:33:43.957-07:00CTRL-ALT-DELA couple of things arose today in connection with the university's new Windows 7 service. Our lab machines, for reasons I won't go into because I can only vaguely remember them, are set up as staff PCs rather than student PCs. This means you can do <tt>CTRL-ALT-DEL</tt> and lock the screen, which isn't what you want in a student lab (I remember this being a major cause of disputes in Charles Wilson labs during the Windows 3.1 days, when there was fierce competition for machines and students would lock the screen and then go off for an hour to a lecture). There's a simple fix: run <tt>gpedit.msc</tt> as an administrator, and enable <tt>User Configuration / Administrative Templates / System / Ctrl+Alt+Del Options / Remove Lock Computer</tt>. This also has the effect of disabling the "Lock" option in the Windows start button (it is still there, it just doesn't do anything).
<br />
<br />
I was accidentally switched to the new service a couple of days ago, and found that access to the Z drive from both my Linux PC, and the Windows 7 virtual machine I run on it, no longer work (the X drive still works, but that seems to still be on older file servers). Pending the resolution of that problem, a new PC fell off the back of a lorry and I am back to having two computers on my desk, one running Linux and one Windows 7 (which I think we are officially supposed to call "UOL" because there are no other things associated with the University of Leicester which might use the initials "UOL" and it will <u>not be at all confusing</u>). Linking them using <a href="http://synergy-foss.org/">synergy</a>:
<br />
<br />
<h3>
Linux</h3>
From <a href="http://cfabbri.dyndns.org/index.php/2012/05/10/starting-synergy-automatically-on-ubuntu">this page</a>. I had to install a newer version of synergy (1.4, the current Ubuntu version is 1.3).
<br />
<br />
In the instructions below, EG-PC845 is the Linux box which will be running the server, and EG-PC1184 is the Windows machine.<br />
<br />
As root, create /etc/synergy, and put this lot into <tt>/etc/synergy/synergy.conf</tt>:<br />
<br />
<pre>section: screens
EG-PC845:
EG-PC1184:
end
section: links
EG-PC845:
left = EG-PC1184
EG-PC1184:
right = EG-PC845
end
</pre>
<br />
Then put this lot into <tt>/etc/synergy/startsynergys</tt>, followed by <tt>chmod +x /etc/synergy/startsynergys</tt> to make the file executable:<br />
<br />
<pre>#!/bin/bash
/usr/bin/synergys -c /etc/synergy/synergy.conf -n EG-PC845
</pre>
<br />
And finally, append this to <tt>/etc/lightdm/lightdm.conf</tt>:<br />
<br />
<pre>greeter-setup-script=/etc/synergy/startsynergys
</pre>
<br />
The synergy server will now start when the machine boots.<br />
<br />
<h3>
Windows</h3>
Install synergy as normal, setting it up to start as a client when the machine does, and connect to the IP address of EG-PC845. The only remaining problem is to do CTRL-ALT-DEL to log on to Windows 7, because you are no longer using the keyboard attached to the PC. The solution is to use <tt>gpedit.msc</tt> again, as described <a href="http://synergy-foss.org/osqa/questions/1561/control-alt-delete-win-7-login-prompt">here</a>: enable <tt>Local Computer Policy / Computer Configuration / Administrative Templates / Windows Components / Windows Logon Options</tt>, with the option "Services and Ease of Access applications". <tt>CTRL-ALT-PAUSE</tt> then works in place of <tt>CTRL-ALT-DEL</tt>.
Andrew Normanhttp://www.blogger.com/profile/16475081894748925557noreply@blogger.com0tag:blogger.com,1999:blog-2314677238638329471.post-24734191433939118422012-07-26T09:02:00.001-07:002012-07-26T09:04:13.467-07:00Looking aheadAs the previous post suggests, I've been thinking about Twitter bots today, and in particular how to post lengthy text in 140-character chunks. A diversion was to think about how to "read ahead" - I wanted to be able to say "if the next chunk of text will take me over the 140-character limit, don't read it in". But I can't do that without reading it in (I could of course read the whole file into an array, then use <tt>pop()</tt> and <tt>append()</tt> to treat it as a stack, but I want to avoid having to read in the whole file if possible). A solution is below:
<pre>
#!/usr/bin/python
from tempfile import mkstemp
import shutil
import os
ifpath = "a.txt"
fh, ofpath = mkstemp()
ofile = open(ofpath, "w")
ifile = open(ifpath)
lines = []
length = 0
while True:
previous = ifile.tell()
line = ifile.readline()
if length + len(line) < 140:
lines.append(line)
length += len(line)
else:
ifile.seek(previous)
print lines
break
for line in ifile:
ofile.write(line)
ifile.close()
ofile.close()
os.remove(ifpath)
shutil.move(ofpath, ifpath)
</pre>
This opens <tt>a.txt</tt> for reading (as <tt>ifile</tt>), and creates a temporary file (<tt>ofile</tt>). There's then an infinite loop - each time round, I store the current file location as <tt>previous</tt>, read in a new line, and check to see if that would take it over the limit. If not, I add the new line to the stored array. If it will go over the limit, I use <tt>ifile.seek()</tt> to go back to the file location before I read in the new line, and then print out the lines stored so far. All that remains is to write the unread lines (including the one I read in and decided not to use) to a temporary file, remove the original <tt>a.txt</tt>, and replace it with the temporary one.Andrew Normanhttp://www.blogger.com/profile/16475081894748925557noreply@blogger.com0tag:blogger.com,1999:blog-2314677238638329471.post-65675071128710413152012-07-25T07:51:00.003-07:002012-07-25T07:54:45.950-07:00Twitter UpdatingRevised version of the code I originally wrote about <a href="http://andrew-j-norman.blogspot.co.uk/2010/12/aside-twitter-and-rss.html">here</a>. The main difference is that I've included a counter so it doesn't post more than two tweets every time it is run (and since it only runs once an hour, this shouldn't make it too antisocial even when it hasn't run for a while and there are a lot of items in the RSS feed).
<pre>
#!/usr/bin/python
import tweepy
import feedparser
import urllib
import urllib2
url = "http://www2.le.ac.uk/departments/engineering/news-and-events/blog/RSS"
oauth_file = open("access_token.txt", "r")
oauth_token = oauth_file.readline().rstrip()
oauth_token_secret = oauth_file.readline().rstrip()
consumer_file = open("consumer_keys.txt", "r")
consumer_key = consumer_file.readline().rstrip()
consumer_secret = consumer_file.readline().rstrip()
bitly_file = open("bitly.txt", "r")
bitly_username = bitly_file.readline().rstrip()
bitly_apikey = bitly_file.readline().rstrip()
bitly_base = "http://api.bit.ly/v3/shorten?"
bitly_data = {
"login" : bitly_username,
"apiKey" : bitly_apikey,
"format" : "txt",
"longUrl" : ""
}
already_done = []
done_file = open("done.txt", "r")
for line in done_file:
already_done.append(line.rstrip())
done_file.close()
auth = tweepy.OAuthHandler(consumer_key, consumer_secret)
auth.set_access_token(oauth_token, oauth_token_secret)
api = tweepy.API(auth)
feed = feedparser.parse(url)
count = 0
for item in feed["items"]:
url = item["link"]
title = item["title"]
if url not in already_done and count < 2:
bitly_data["longUrl"] = url
to_shorten = bitly_base + urllib.urlencode(bitly_data)
result = urllib2.urlopen(to_shorten).read()
api.update_status(title + " : " + result)
already_done.append(url)
count = count + 1
done_file = open("done.txt", "w")
for url in already_done:
done_file.write(url + "\n")
done_file.close()
</pre>Andrew Normanhttp://www.blogger.com/profile/16475081894748925557noreply@blogger.com0tag:blogger.com,1999:blog-2314677238638329471.post-77554878204425358292012-06-04T14:00:00.000-07:002012-06-04T14:02:33.806-07:00Raspberry Pi<p>A <a href="http://www.raspberrypi.org/">Raspberry Pi</a> arrived late last week, just before I came away for the bank holiday. So I bought a cheap USB keyboard, checked that an old iPod power supply coupled with a Kindle cable would provide enough oomph (just about), and took the lot to my mother's. Unfortunately the keyboard (PC World Essentials) turned out to be one which won't work. Also, I can't keep coming to my mother's house every time I want access to a TV with an HDMI socket. Here's how I got access to the board via my laptop, which runs both Windows and Ubuntu Linux 12.04 - instructions are for the latter, and while I think the instructions below could be adapted for Windows they would probably be more complex. In any case, if you're going to be doing things with an ARM board running Linux, why wouldn't you also be running Linux on your other machines?
</p><ol>
<li>Enable SSH. I paid for an SD card pre-loaded with Debian. Stick the card in the laptop, rename <tt>boot_enable_ssh.rc</tt> to <tt>boot.rc</tt>, and put the card back into the Pi before powering up.</li>
<li>Plug the Pi into one of the ports on the back of the wireless router. Using a web browser on the laptop, go to the router's management page and look up the list of attached devices. I found that the Pi was on 192.168.0.3.</li>
<li>Open a command prompt on the laptop, and do "<tt>ssh pi@192.168.0.3</tt>". Password is "raspberry", and you should be logged on to the Pi.</li>
<li>Update packages: <tt>sudo apt-get update</tt> followed by <tt>sudo apt-get upgrade</tt>.</li>
<li>Install a VNC server: <tt>sudo apt-get install tightvncserver</tt>.</li>
<li>Run the server: <tt>tightvncserver</tt> - you'll be asked to set a password.</li>
<li>Start a VNC server with a resolution that will fit in your monitor - I used <tt>vncserver :1 -geometry 800x600 -depth 24</tt></li>
<li>On the laptop, run <tt>vinagre</tt>, a VNC client, and choose to connect to 192.168.0.3:1 via VNC - the password you will be asked for is the one you set in step 6, not the password from step 3.</li>
<li>An LXDE desktop should appear:</li>
</ol>
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh77A0caoc53ny7Hk2qCZQ_boYeXU-YiiJZ2a_WZZoXyTkSh3KukNiYfrin8RJP-jPjYHcMf9tG06QsHv1mJe6JO-TWzw8miteCKvyuWlljt7hX29-nFyFtgwwGFr5rQfBkkZBPcM2N-JTe/s1600/pi_desktop.png" imageanchor="1" style="margin-left:1em; margin-right:1em"><img border="0" height="259" width="320" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh77A0caoc53ny7Hk2qCZQ_boYeXU-YiiJZ2a_WZZoXyTkSh3KukNiYfrin8RJP-jPjYHcMf9tG06QsHv1mJe6JO-TWzw8miteCKvyuWlljt7hX29-nFyFtgwwGFr5rQfBkkZBPcM2N-JTe/s320/pi_desktop.png" /></a>Andrew Normanhttp://www.blogger.com/profile/16475081894748925557noreply@blogger.com0tag:blogger.com,1999:blog-2314677238638329471.post-27903503961663437952012-01-06T07:49:00.000-08:002012-01-06T07:54:06.164-08:00Lectures"Students expect lectures", I am told. I think lectures are a rotten way to try to teach programming - it is a practical skill, and you learn (at least at the elementary level) by doing it, not by reading about it.
Via the philosophy/politics blog <a href="http://crookedtimber.org/2012/01/06/dont-lecture-me/">Crooked Timber</a>:<br />
<br />
<ul>
<li> <a href="http://americanradioworks.publicradio.org/features/tomorrows-college/lectures/">Don't Lecture Me</a> </li>
</ul>
<br />
I already subscribe to too many podcasts, but this looks good.<br />
<br />
As an aside, <a href="http://americanradioworks.publicradio.org/features/tomorrows-college/lectures/problem-with-lecturing.html">this section on teaching physics</a> echoes some of the material on "folk psychology" I studied a couple of years ago as part of an OU philosophy course - "eliminative materialists" think our traditional analysis of mental events ("folk psychology") can be replaced with a more scientific version, just as "folk physics" has been replaced by mathematical physics (and I think Hestenes and Halloun were cited in the paper I read - I'll have to dig it out). If you ask people who haven't studied physics how weights behave in gravity, what happens if you cut a pendulum string, and other similar questions, they'll give the wrong answer. But chuck a ball at them, and they'll catch it (that's "folk physics" - being able to predict how things behave, despite lacking an underlying theory). What reminded me of the philosophy paper was the observation that even after a course of lectures, most of the students who had in theory been taught mathematical physics were still giving the wrong answers.Andrew Normanhttp://www.blogger.com/profile/16475081894748925557noreply@blogger.com0tag:blogger.com,1999:blog-2314677238638329471.post-72375512851824923432011-12-16T08:33:00.000-08:002012-01-09T09:40:32.717-08:00A stack on the Arduino<iframe allowfullscreen="" frameborder="0" height="315" src="http://www.youtube.com/embed/eCJJbTW1FBM?rel=0" width="560"></iframe>
<br />
<br />
I have put together a collection of Python scripts which allow me to build proper C++ programs for the Arduino Uno and Mega 2560 from the command line, and upload them all in one go. At the moment, these are only working in Linux but I think I know enough now that in principle I could get this going on Windows, possibly within Dev-C++ - a project for after Christmas, I think.
<br />
<br />
<a name='more'></a><br />
<br />
<iframe src="http://pastebin.com/embed_iframe.php?i=5az1RPNn" style="border: none; width: 100%; height: 500px"></iframe>Andrew Normanhttp://www.blogger.com/profile/16475081894748925557noreply@blogger.com0tag:blogger.com,1999:blog-2314677238638329471.post-28309367481082687842011-12-14T09:32:00.001-08:002011-12-14T09:32:26.905-08:00Home Made<iframe width="560" height="315" src="http://www.youtube.com/embed/heT3nA67HtI?rel=0" frameborder="0" allowfullscreen></iframe>
I used to be really, really terrible at soldering things. Thank goodness for instructional videos on YouTube. The board was programmed by the method described in the previous post.Andrew Normanhttp://www.blogger.com/profile/16475081894748925557noreply@blogger.com0tag:blogger.com,1999:blog-2314677238638329471.post-27458582088637815412011-12-14T08:46:00.000-08:002011-12-14T14:04:24.342-08:00Bypassing the IDEA colleague has objected, justifiably, that the Arduino can't be used for teaching C/C++ as it stands because of the way the environment works (hiding the main function, and confusing issues of scope, global variables, etc). Judging by a conversation I had this afternoon there is probably no money in the equipment budget to buy fifty of the things for next year's programming course anyway, but it would be nice to try. There are various tutorials on the web dealing with using the avr-gcc compiler directly, mainly reliant on using modified makefiles. The Arduino 1.0 release seems to have broken these (some of the files are now in different directories, some have disappeared), so I tried looking at the output if you ask the Arduino environment to give detailed output on compilation and upload (in File / Preferences).<br />
<br />
<a name='more'></a><br />
My "sketch" is called led_array_test. Here's the output.
<br />
<br />
<pre>avr-g++ -c -g -Os -Wall -fno-exceptions -ffunction-sections -fdata-sections -mmcu=atmega328p -DF_CPU=16000000L -DARDUINO=100 -I/home/nja/arduino-1.0/hardware/arduino/cores/arduino -I/home/nja/arduino-1.0/hardware/arduino/variants/standard /tmp/build7868979522428053699.tmp/led_array_test.cpp -o/tmp/build7868979522428053699.tmp/led_array_test.cpp.o
avr-gcc -c -g -Os -Wall -ffunction-sections -fdata-sections -mmcu=atmega328p -DF_CPU=16000000L -DARDUINO=100 -I/home/nja/arduino-1.0/hardware/arduino/cores/arduino -I/home/nja/arduino-1.0/hardware/arduino/variants/standard /home/nja/arduino-1.0/hardware/arduino/cores/arduino/wiring_pulse.c -o/tmp/build7868979522428053699.tmp/wiring_pulse.c.o
avr-gcc -c -g -Os -Wall -ffunction-sections -fdata-sections -mmcu=atmega328p -DF_CPU=16000000L -DARDUINO=100 -I/home/nja/arduino-1.0/hardware/arduino/cores/arduino -I/home/nja/arduino-1.0/hardware/arduino/variants/standard /home/nja/arduino-1.0/hardware/arduino/cores/arduino/wiring_analog.c -o/tmp/build7868979522428053699.tmp/wiring_analog.c.o
avr-gcc -c -g -Os -Wall -ffunction-sections -fdata-sections -mmcu=atmega328p -DF_CPU=16000000L -DARDUINO=100 -I/home/nja/arduino-1.0/hardware/arduino/cores/arduino -I/home/nja/arduino-1.0/hardware/arduino/variants/standard /home/nja/arduino-1.0/hardware/arduino/cores/arduino/wiring.c -o/tmp/build7868979522428053699.tmp/wiring.c.o
avr-gcc -c -g -Os -Wall -ffunction-sections -fdata-sections -mmcu=atmega328p -DF_CPU=16000000L -DARDUINO=100 -I/home/nja/arduino-1.0/hardware/arduino/cores/arduino -I/home/nja/arduino-1.0/hardware/arduino/variants/standard /home/nja/arduino-1.0/hardware/arduino/cores/arduino/wiring_shift.c -o/tmp/build7868979522428053699.tmp/wiring_shift.c.o
avr-gcc -c -g -Os -Wall -ffunction-sections -fdata-sections -mmcu=atmega328p -DF_CPU=16000000L -DARDUINO=100 -I/home/nja/arduino-1.0/hardware/arduino/cores/arduino -I/home/nja/arduino-1.0/hardware/arduino/variants/standard /home/nja/arduino-1.0/hardware/arduino/cores/arduino/wiring_digital.c -o/tmp/build7868979522428053699.tmp/wiring_digital.c.o
avr-gcc -c -g -Os -Wall -ffunction-sections -fdata-sections -mmcu=atmega328p -DF_CPU=16000000L -DARDUINO=100 -I/home/nja/arduino-1.0/hardware/arduino/cores/arduino -I/home/nja/arduino-1.0/hardware/arduino/variants/standard /home/nja/arduino-1.0/hardware/arduino/cores/arduino/WInterrupts.c -o/tmp/build7868979522428053699.tmp/WInterrupts.c.o
avr-g++ -c -g -Os -Wall -fno-exceptions -ffunction-sections -fdata-sections -mmcu=atmega328p -DF_CPU=16000000L -DARDUINO=100 -I/home/nja/arduino-1.0/hardware/arduino/cores/arduino -I/home/nja/arduino-1.0/hardware/arduino/variants/standard /home/nja/arduino-1.0/hardware/arduino/cores/arduino/USBCore.cpp -o/tmp/build7868979522428053699.tmp/USBCore.cpp.o
avr-g++ -c -g -Os -Wall -fno-exceptions -ffunction-sections -fdata-sections -mmcu=atmega328p -DF_CPU=16000000L -DARDUINO=100 -I/home/nja/arduino-1.0/hardware/arduino/cores/arduino -I/home/nja/arduino-1.0/hardware/arduino/variants/standard /home/nja/arduino-1.0/hardware/arduino/cores/arduino/main.cpp -o/tmp/build7868979522428053699.tmp/main.cpp.o
avr-g++ -c -g -Os -Wall -fno-exceptions -ffunction-sections -fdata-sections -mmcu=atmega328p -DF_CPU=16000000L -DARDUINO=100 -I/home/nja/arduino-1.0/hardware/arduino/cores/arduino -I/home/nja/arduino-1.0/hardware/arduino/variants/standard /home/nja/arduino-1.0/hardware/arduino/cores/arduino/Tone.cpp -o/tmp/build7868979522428053699.tmp/Tone.cpp.o
/home/nja/arduino-1.0/hardware/arduino/cores/arduino/Tone.cpp:108: warning: only initialised variables can be placed into program memory area
avr-g++ -c -g -Os -Wall -fno-exceptions -ffunction-sections -fdata-sections -mmcu=atmega328p -DF_CPU=16000000L -DARDUINO=100 -I/home/nja/arduino-1.0/hardware/arduino/cores/arduino -I/home/nja/arduino-1.0/hardware/arduino/variants/standard /home/nja/arduino-1.0/hardware/arduino/cores/arduino/HardwareSerial.cpp -o/tmp/build7868979522428053699.tmp/HardwareSerial.cpp.o
avr-g++ -c -g -Os -Wall -fno-exceptions -ffunction-sections -fdata-sections -mmcu=atmega328p -DF_CPU=16000000L -DARDUINO=100 -I/home/nja/arduino-1.0/hardware/arduino/cores/arduino -I/home/nja/arduino-1.0/hardware/arduino/variants/standard /home/nja/arduino-1.0/hardware/arduino/cores/arduino/new.cpp -o/tmp/build7868979522428053699.tmp/new.cpp.o
avr-g++ -c -g -Os -Wall -fno-exceptions -ffunction-sections -fdata-sections -mmcu=atmega328p -DF_CPU=16000000L -DARDUINO=100 -I/home/nja/arduino-1.0/hardware/arduino/cores/arduino -I/home/nja/arduino-1.0/hardware/arduino/variants/standard /home/nja/arduino-1.0/hardware/arduino/cores/arduino/Stream.cpp -o/tmp/build7868979522428053699.tmp/Stream.cpp.o
avr-g++ -c -g -Os -Wall -fno-exceptions -ffunction-sections -fdata-sections -mmcu=atmega328p -DF_CPU=16000000L -DARDUINO=100 -I/home/nja/arduino-1.0/hardware/arduino/cores/arduino -I/home/nja/arduino-1.0/hardware/arduino/variants/standard /home/nja/arduino-1.0/hardware/arduino/cores/arduino/CDC.cpp -o/tmp/build7868979522428053699.tmp/CDC.cpp.o
avr-g++ -c -g -Os -Wall -fno-exceptions -ffunction-sections -fdata-sections -mmcu=atmega328p -DF_CPU=16000000L -DARDUINO=100 -I/home/nja/arduino-1.0/hardware/arduino/cores/arduino -I/home/nja/arduino-1.0/hardware/arduino/variants/standard /home/nja/arduino-1.0/hardware/arduino/cores/arduino/IPAddress.cpp -o/tmp/build7868979522428053699.tmp/IPAddress.cpp.o
avr-g++ -c -g -Os -Wall -fno-exceptions -ffunction-sections -fdata-sections -mmcu=atmega328p -DF_CPU=16000000L -DARDUINO=100 -I/home/nja/arduino-1.0/hardware/arduino/cores/arduino -I/home/nja/arduino-1.0/hardware/arduino/variants/standard /home/nja/arduino-1.0/hardware/arduino/cores/arduino/HID.cpp -o/tmp/build7868979522428053699.tmp/HID.cpp.o
avr-g++ -c -g -Os -Wall -fno-exceptions -ffunction-sections -fdata-sections -mmcu=atmega328p -DF_CPU=16000000L -DARDUINO=100 -I/home/nja/arduino-1.0/hardware/arduino/cores/arduino -I/home/nja/arduino-1.0/hardware/arduino/variants/standard /home/nja/arduino-1.0/hardware/arduino/cores/arduino/WString.cpp -o/tmp/build7868979522428053699.tmp/WString.cpp.o
avr-g++ -c -g -Os -Wall -fno-exceptions -ffunction-sections -fdata-sections -mmcu=atmega328p -DF_CPU=16000000L -DARDUINO=100 -I/home/nja/arduino-1.0/hardware/arduino/cores/arduino -I/home/nja/arduino-1.0/hardware/arduino/variants/standard /home/nja/arduino-1.0/hardware/arduino/cores/arduino/Print.cpp -o/tmp/build7868979522428053699.tmp/Print.cpp.o
avr-g++ -c -g -Os -Wall -fno-exceptions -ffunction-sections -fdata-sections -mmcu=atmega328p -DF_CPU=16000000L -DARDUINO=100 -I/home/nja/arduino-1.0/hardware/arduino/cores/arduino -I/home/nja/arduino-1.0/hardware/arduino/variants/standard /home/nja/arduino-1.0/hardware/arduino/cores/arduino/WMath.cpp -o/tmp/build7868979522428053699.tmp/WMath.cpp.o
avr-ar rcs /tmp/build7868979522428053699.tmp/core.a /tmp/build7868979522428053699.tmp/wiring_pulse.c.o
avr-ar rcs /tmp/build7868979522428053699.tmp/core.a /tmp/build7868979522428053699.tmp/wiring_analog.c.o
avr-ar rcs /tmp/build7868979522428053699.tmp/core.a /tmp/build7868979522428053699.tmp/wiring.c.o
avr-ar rcs /tmp/build7868979522428053699.tmp/core.a /tmp/build7868979522428053699.tmp/wiring_shift.c.o
avr-ar rcs /tmp/build7868979522428053699.tmp/core.a /tmp/build7868979522428053699.tmp/wiring_digital.c.o
avr-ar rcs /tmp/build7868979522428053699.tmp/core.a /tmp/build7868979522428053699.tmp/WInterrupts.c.o
avr-ar rcs /tmp/build7868979522428053699.tmp/core.a /tmp/build7868979522428053699.tmp/USBCore.cpp.o
avr-ar rcs /tmp/build7868979522428053699.tmp/core.a /tmp/build7868979522428053699.tmp/main.cpp.o
avr-ar rcs /tmp/build7868979522428053699.tmp/core.a /tmp/build7868979522428053699.tmp/Tone.cpp.o
avr-ar rcs /tmp/build7868979522428053699.tmp/core.a /tmp/build7868979522428053699.tmp/HardwareSerial.cpp.o
avr-ar rcs /tmp/build7868979522428053699.tmp/core.a /tmp/build7868979522428053699.tmp/new.cpp.o
avr-ar rcs /tmp/build7868979522428053699.tmp/core.a /tmp/build7868979522428053699.tmp/Stream.cpp.o
avr-ar rcs /tmp/build7868979522428053699.tmp/core.a /tmp/build7868979522428053699.tmp/CDC.cpp.o
avr-ar rcs /tmp/build7868979522428053699.tmp/core.a /tmp/build7868979522428053699.tmp/IPAddress.cpp.o
avr-ar rcs /tmp/build7868979522428053699.tmp/core.a /tmp/build7868979522428053699.tmp/HID.cpp.o
avr-ar rcs /tmp/build7868979522428053699.tmp/core.a /tmp/build7868979522428053699.tmp/WString.cpp.o
avr-ar rcs /tmp/build7868979522428053699.tmp/core.a /tmp/build7868979522428053699.tmp/Print.cpp.o
avr-ar rcs /tmp/build7868979522428053699.tmp/core.a /tmp/build7868979522428053699.tmp/WMath.cpp.o
avr-gcc -Os -Wl,--gc-sections -mmcu=atmega328p -o /tmp/build7868979522428053699.tmp/led_array_test.cpp.elf /tmp/build7868979522428053699.tmp/led_array_test.cpp.o /tmp/build7868979522428053699.tmp/core.a -L/tmp/build7868979522428053699.tmp -lm
avr-objcopy -O ihex -j .eeprom --set-section-flags=.eeprom=alloc,load --no-change-warnings --change-section-lma .eeprom=0 /tmp/build7868979522428053699.tmp/led_array_test.cpp.elf /tmp/build7868979522428053699.tmp/led_array_test.cpp.eep
avr-objcopy -O ihex -R .eeprom /tmp/build7868979522428053699.tmp/led_array_test.cpp.elf /tmp/build7868979522428053699.tmp/led_array_test.cpp.hex
Binary sketch size: 2840 bytes (of a 32256 byte maximum)
/home/nja/arduino-1.0/hardware/tools/avrdude -C/home/nja/arduino-1.0/hardware/tools/avrdude.conf -v -v -v -v -patmega328p -carduino -P/dev/ttyACM2 -b115200 -D -Uflash:w:/tmp/build7868979522428053699.tmp/led_array_test.cpp.hex:i
</pre>
<br />
What it is doing:<br />
<ol>
<li>Compiling <tt>led_array_test.cpp</tt> (which has previously been generated from the sketch's .ino file, by including <tt>Arduino.h</tt>) to <tt>led_array_test.cpp.o</tt>.</li>
<li>Compiling all the files in <tt>/home/nja/arduino-1.0/hardware/arduino/cores/arduino</tt> to object files (all the other lines starting <tt>avr-g++</tt>)</li>
<li>Archiving all those files (but not <tt>led_array_test.cpp.o</tt>) into a library, <tt>core.a</tt></li>
<li>Linking <tt>led_array_test.cpp.o</tt> with that library.</li>
<li>Generating ELF, EEP and HEX files (but it seems to do nothing with the EEP file?)</li>
<li>Calling <tt>avrdude</tt> to upload the HEX file to the Arduino.</li>
</ol>
I have put together a Python script to do the same things. It doesn't use a temporary directory, so you end up with a load of object files and other stuff scattered about, but that would be relatively simple to fix. It also assumes that nothing fails during the building and upload. <br />
<br />
<pre>#!/usr/bin/python
import os
import re
arduino_root = "/home/nja/arduino-1.0/hardware/arduino"
tools_root = "/home/nja/arduino-1.0/hardware/tools"
clock_speed = "16000000L"
device = "atmega328p"
usb_port = "/dev/ttyACM2"
compile_command = "avr-g++ -c -g -Os -Wall -fno-exceptions -ffunction-sections -fdata-sections -mmcu={1} -DF_CPU={2} -DARDUINO=100 -I{0}/cores/arduino -I{0}/variants/standard {3} -o {4}"
library_command = "avr-ar rcs core.a {0}"
library_sources = (
"wiring_pulse.c",
"wiring_analog.c",
"wiring.c",
"wiring_shift.c",
"wiring_digital.c",
"WInterrupts.c",
"USBCore.cpp",
"Tone.cpp",
"HardwareSerial.cpp",
"new.cpp",
"Stream.cpp",
"CDC.cpp",
"IPAddress.cpp",
"HID.cpp",
"WString.cpp",
"Print.cpp",
"WMath.cpp")
object_files = []
for src in library_sources:
obj = re.sub("\.cp?p?", ".o", src)
object_files.append(obj)
src = "{0}/cores/arduino/{1}".format(arduino_root, src)
os.system(compile_command.format(arduino_root, device, clock_speed, src, obj))
os.system(compile_command.format(arduino_root, device, clock_speed, "leds.cpp", "leds.o"))
for obj in object_files:
os.system(library_command.format(obj))
os.system("avr-gcc -Os -Wl,--gc-sections -mmcu={0} -o leds.elf leds.o core.a -L. -lm".format(device))
os.system("avr-objcopy -O ihex -j .eeprom --set-section-flags=.eeprom=alloc,load --no-change-warnings --change-section-lma .eeprom=0 leds.elf leds.eep")
os.system("avr-objcopy -O ihex -R .eeprom leds.elf leds.hex")
os.system("avrdude -p{1} -carduino -P{2} -b115200 -D -Uflash:w:leds.hex:i".format(tools_root, device, usb_port))
</pre>
I'm making no claims for that as an efficient, beautiful or well-written piece of code, but it works to compile a file called <tt>leds.cpp</tt>, link it with the library, and upload it to the Arduino. I sometimes need to reset the Arduino to get the program to run (there is Python code out there to do this, which I will have to find again and include). Note that I am <u>not</u> including <tt>main.cpp</tt> in the Arduino library - my <tt>leds.cpp</tt> includes the main function and looks more C++-like than the Arduino sketch. This is because it <i>is</i> C++. It is a combination of the Arduino <tt>main.cpp</tt> (which is normally included in the static library and linked with the sketch by the Arduino environment), and the <tt>setup()</tt> and <tt>loop()</tt> functions from my original sketch (the two <tt>pinMode</tt> function calls were in <tt>setup()</tt>, and everything inside the while loop apart from the final line was in <tt>loop()</tt>).<br />
<br />
<pre>#include <Arduino.h>
#define LED1 7
#define LED2 6
#define DELAY_TIME 500
int main (void)
{
init();
#if defined(USBCON)
USB.attach();
#endif
pinMode(LED1, OUTPUT);
pinMode(LED2, OUTPUT);
while(1)
{
digitalWrite(LED1, HIGH);
delay(DELAY_TIME);
digitalWrite(LED2, HIGH);
delay(DELAY_TIME);
digitalWrite(LED1, LOW);
delay(DELAY_TIME);
digitalWrite(LED2, LOW);
delay(DELAY_TIME);
if (serialEventRun) serialEventRun();
}
return 0;
}
</pre>
<br />
I think this is a promising first step towards getting a system together which allows the Arduino to be programmed in pure C/C++.Andrew Normanhttp://www.blogger.com/profile/16475081894748925557noreply@blogger.com0tag:blogger.com,1999:blog-2314677238638329471.post-17743719593290343852011-12-09T08:39:00.001-08:002011-12-09T09:04:19.835-08:00Arduino classesUnderneath the Arduino programming environment is C++, but I didn't think I'd be trying to find my copy of Leendert Ammeraal's <i>C++ for Programmers</i> this Friday afternoon (I have Stroustrup, of course, but it's not a book to find a quick and easy solution in). I hooked up four of the <a href="http://oomlout.co.uk/5mm-rgb-leds-super-flux-x3-p-203.html">RGB LED units</a> to the Arduino Mega's 12 PWM pins, and wanted to implement a little algorithm I had run with one unit earlier in the week - set a random "target" value for the RGB values, gradually increase (or decrease) the intensity to reach the target, and then set a new random target. The effect is to gradually change the colour and intensity of each unit, but in a pleasant drifting manner rather than jumping around all over the place. Once I had written the code, I looked for ways to make it simpler - first using a struct, but there are difficulties writing functions for the Arduino which take a struct as a parameter (as I understand it, the pre-processor shuffles the code around so that your function ends up before the struct declaration, which means the compiler doesn't recognise the struct when it comes to compile the function). The answer was to go straight to writing a C++ class, which turned out to be fairly simple (though I did try writing a proper constructor function, and was reminded what a complete bloody nightmare that can be especially on a Friday afternoon in a very warm office when you haven't written any O-O C++ code for about a decade - once you start dabbling in constructors, you need to think hard about copy constructors, destructors, deep and shallow copies, all that stuff).<br />
<iframe allowfullscreen="" frameborder="0" height="315" src="http://www.youtube.com/embed/up_JZ6Xx-5k?rel=0" width="560"></iframe>
<br />
<br />
<a name='more'></a><br /><br />
Here's the code:
<br />
<pre>#include "rgb.h"
rgb_led led_array[4];
void setup()
{
randomSeed(analogRead(0));
led_array[0].initialise(3, 2, 4);
led_array[1].initialise(6, 5, 7);
led_array[2].initialise(9, 8, 10);
led_array[3].initialise(12, 11, 13);
}
void loop()
{
for (int i = 0; i < 4; i++)
{
led_array[i].update();
led_array[i].output();
}
delay(10);
}
</pre>
This lot goes into a new tab in the Arduino project called <tt>rgb.h</tt>:
<br />
<pre>#ifndef RGB_LED
#define RGB_LED
#include <Arduino.h>
class rgb_led
{
byte pin[3];
byte value[3];
byte target[3];
public:
void update(void);
void output(void);
void initialise(byte, byte, byte);
};
#endif
</pre>
And this lot goes into another tab called <tt>rgb.cpp</tt>:
<br />
<pre>#include "rgb.h"
void rgb_led::initialise(byte r, byte g, byte b)
{
pin[0] = r;
pin[1] = g;
pin[2] = b;
for (int i=0; i<3; i++)
{
pinMode(pin[i], OUTPUT);
value[i] = 0;
target[i] = random(256);
}
}
void rgb_led::update(void)
{
for (int i=0; i<3; i++)
{
if (value[i] < target[i])
{
value[i]++;
}
else if (value[i] > target[i])
{
value[i]--;
}
else
{
target[i] = random(256);
}
}
}
void rgb_led::output(void)
{
for (int i=0; i<3; i++)
{
analogWrite(pin[i], value[i]);
}
}
</pre>Andrew Normanhttp://www.blogger.com/profile/16475081894748925557noreply@blogger.com0tag:blogger.com,1999:blog-2314677238638329471.post-30839522654065363582011-12-09T02:51:00.001-08:002011-12-09T09:01:07.709-08:00Arduino Mega and ethernet shieldI bought an Arduino Mega 2560 to reward myself for doing well in <a href="https://msds.open.ac.uk/students/study/postgraduate/course/a851.htm">a philosophy module</a>. To connect it to the ethernet shield, the <a href="http://www.arduino.cc/cgi-bin/yabb2/YaBB.pl?num=1287919561">solution in the Arduino forum</a> works - connect pins 11, 12 and 13 on the ethernet shield to 51, 50 and 52 on the Mega (in that order, of course).<br />
<br />
Edit: the Mega sorted out the problems I'd been having with getting a web server to read off the SD card - with the Arduino Uno it kept falling over and resetting the board, with the same code on the Mega I was able to run a slow, but quite usable, server.<br />
<br />
<iframe width="560" height="315" src="http://www.youtube.com/embed/0ZdoGKDt4MY?rel=0" frameborder="0" allowfullscreen></iframe>Andrew Normanhttp://www.blogger.com/profile/16475081894748925557noreply@blogger.com7tag:blogger.com,1999:blog-2314677238638329471.post-8693680753895961702011-12-08T07:58:00.001-08:002011-12-09T09:14:59.442-08:00Scrolling text with shift registers<iframe allowfullscreen="" frameborder="0" height="315" src="http://www.youtube.com/embed/QAbuNzA2-VM?rel=0" width="560"></iframe>
Using a couple of 74HC595 shift registers, I have done away with most of the wires leading from the Arduino to the breadboard. This also simplifies the code - simply send a couple of bytes to the first shift register, and the data sorts itself out (the first byte is displaced by the second one and flows into the second shift register - I will probably add a third to the chain, to control the green LEDs). The text is now proportionally-spaced, rather than using 8 pixels per character.<br />
<br />
Edit: adding a third shift register to the chain worked, but at the price of making the display rather dim (because of the time spent transferring all the data, I suspect). The green LEDs aren't as bright as the red ones, which made the effect worse - the red ones were just about acceptable.<br />
<br />
<a name='more'></a><br />
<br />
<pre>#include <string.h>
#include "ascii_hex.h"
#define SPEED 5
#define DELAY 2
#define ROW_OFF LOW
#define ROW_ON HIGH
#define COL_OFF HIGH
#define COL_ON LOW
//Pin Definitions
const byte latchPin = 8;
const byte clockPin = 12;
const byte dataPin = 11;
const byte mask[] = {1,2,4,8,16,32,64,128};
byte led_state[8] = {0, 0, 0, 0, 0, 0, 0, 0};
void setup()
{
pinMode(latchPin, OUTPUT);
pinMode(clockPin, OUTPUT);
pinMode(dataPin, OUTPUT);
}
void loop()
{
char to_print[] = "andrew.norman@leicester.ac.uk ()[]{} ";
int length = strlen(to_print);
const byte *next;
for (int pos = 0; pos < length; pos++)
{
next = alphabet[to_print[pos]];
// next[8] is the width of the character
for (int i = 0; i < next[8]; i++)
{
// Update each row of the current dispay
// by shifting right, and inserting the
// appropriate bits of the next frame
for (int j = 0; j < 8; j++)
{
led_state[j] = (led_state[j] << 1) |
((next[j] >> (7 - i)));
}
showArray();
}
// Insert a space between letters
for (int j = 0; j < 8; j++)
{
led_state[j] <<= 1;
}
showArray();
}
}
void showArray(void)
{
byte columnSelect;
// Show the array multiple times to slow down
// the "refresh rate"
for (int s = 0; s < SPEED; s++)
{
for(int column = 0; column < 8; column++)
{
columnSelect = ~mask[column];
sendControl(0x00, columnSelect);
sendControl(led_state[column], columnSelect);
delay(DELAY);
}
}
}
void sendControl(byte rc, byte cc)
{
digitalWrite(latchPin, LOW);
shiftOut(dataPin, clockPin, MSBFIRST, cc);
shiftOut(dataPin, clockPin, MSBFIRST, rc);
digitalWrite(latchPin, HIGH);
}
</pre>
<pre></pre>
Here is the header file with all the character definitions - I put together an Excel spreadsheet to help me generate these (by ticking cells until I had the right shape, and then reading off the hex values).
<br />
<br />
<pre>#include <Arduino.h>
byte alphabet[128][9] =
{
// First 32 characters (0 to 31) are control characters, don't print
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0}, /* ASCII 000 */
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0}, /* ASCII 001 */
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0}, /* ASCII 002 */
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0}, /* ASCII 003 */
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0}, /* ASCII 004 */
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0}, /* ASCII 005 */
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0}, /* ASCII 006 */
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0}, /* ASCII 007 */
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0}, /* ASCII 008 */
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0}, /* ASCII 009 */
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0}, /* ASCII 010 */
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0}, /* ASCII 011 */
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0}, /* ASCII 012 */
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0}, /* ASCII 013 */
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0}, /* ASCII 014 */
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0}, /* ASCII 015 */
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0}, /* ASCII 016 */
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0}, /* ASCII 017 */
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0}, /* ASCII 018 */
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0}, /* ASCII 019 */
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0}, /* ASCII 020 */
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0}, /* ASCII 021 */
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0}, /* ASCII 022 */
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0}, /* ASCII 023 */
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0}, /* ASCII 024 */
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0}, /* ASCII 025 */
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0}, /* ASCII 026 */
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0}, /* ASCII 027 */
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0}, /* ASCII 028 */
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0}, /* ASCII 029 */
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0}, /* ASCII 030 */
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0}, /* ASCII 031 */
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 4}, /* ASCII 032 = Space */
{0x80, 0x80, 0x80, 0x80, 0x80, 0x00, 0x80, 0x00, 1}, /* ASCII 033 = ! */
{0xA0, 0xA0, 0xA0, 0x00, 0x00, 0x00, 0x00, 0x00, 3}, /* ASCII 034 = " */
{0x50, 0x50, 0xF8, 0x50, 0xF8, 0x50, 0x50, 0x00, 5}, /* ASCII 035 = # */
{0x20, 0x78, 0xA0, 0x70, 0x28, 0xF0, 0x20, 0x00, 5}, /* ASCII 036 = $ */
{0x42, 0xA4, 0x48, 0x10, 0x24, 0x4A, 0x84, 0x00, 7}, /* ASCII 037 = % */
{0x60, 0x90, 0x80, 0x68, 0x90, 0x90, 0x68, 0x00, 6}, /* ASCII 038 = & */
{0x80, 0x80, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 1}, /* ASCII 039 = ' */
{0x40, 0x80, 0x80, 0x80, 0x80, 0x80, 0x40, 0x00, 2}, /* ASCII 040 = ( */
{0x80, 0x40, 0x40, 0x40, 0x40, 0x40, 0x80, 0x00, 2}, /* ASCII 041 = ) */
{0x10, 0x92, 0x54, 0x38, 0x54, 0x92, 0x10, 0x00, 7}, /* ASCII 042 = * */
{0x00, 0x20, 0x20, 0xF8, 0x20, 0x20, 0x00, 0x00, 5}, /* ASCII 043 = + */
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x80, 2}, /* ASCII 044 = , */
{0x00, 0x00, 0x00, 0xF8, 0x00, 0x00, 0x00, 0x00, 5}, /* ASCII 045 = - */
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 1}, /* ASCII 046 = . */
{0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x00, 7}, /* ASCII 047 = / */
{0x60, 0x90, 0x90, 0x90, 0x90, 0x90, 0x60, 0x00, 4}, /* ASCII 048 = 0 */
{0x20, 0x60, 0x20, 0x20, 0x20, 0x20, 0x70, 0x00, 4}, /* ASCII 049 = 1 */
{0x60, 0x90, 0x10, 0x20, 0x40, 0x80, 0xF0, 0x00, 4}, /* ASCII 050 = 2 */
{0x60, 0x90, 0x10, 0x60, 0x10, 0x90, 0x60, 0x00, 4}, /* ASCII 051 = 3 */
{0x20, 0x60, 0xA0, 0xA0, 0xF0, 0x20, 0x20, 0x00, 4}, /* ASCII 052 = 4 */
{0xF0, 0x80, 0x80, 0xE0, 0x10, 0x90, 0x60, 0x00, 4}, /* ASCII 053 = 5 */
{0x60, 0x90, 0x80, 0xE0, 0x90, 0x90, 0x60, 0x00, 4}, /* ASCII 054 = 6 */
{0xF0, 0x10, 0x10, 0x20, 0x20, 0x40, 0x40, 0x00, 4}, /* ASCII 055 = 7 */
{0x60, 0x90, 0x90, 0x60, 0x90, 0x90, 0x60, 0x00, 4}, /* ASCII 056 = 8 */
{0x60, 0x90, 0x90, 0x70, 0x10, 0x90, 0x60, 0x00, 4}, /* ASCII 057 = 9 */
{0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x80, 0x00, 1}, /* ASCII 058 = : */
{0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x40, 0x80, 2}, /* ASCII 059 = ; */
{0x10, 0x20, 0x40, 0x80, 0x40, 0x20, 0x10, 0x00, 4}, /* ASCII 060 = < */
{0x00, 0x00, 0xF0, 0x00, 0xF0, 0x00, 0x00, 0x00, 4}, /* ASCII 061 = = */
{0x80, 0x40, 0x20, 0x10, 0x20, 0x40, 0x80, 0x00, 4}, /* ASCII 062 = > */
{0x70, 0x88, 0x88, 0x10, 0x20, 0x00, 0x20, 0x00, 5}, /* ASCII 063 = ? */
{0x30, 0x48, 0x98, 0xA8, 0x98, 0x40, 0x38, 0x00, 5}, /* ASCII 064 = @ */
{0x20, 0x50, 0x88, 0x88, 0xF8, 0x88, 0x88, 0x00, 5}, /* ASCII 065 = A */
{0xE0, 0x90, 0x90, 0xE0, 0x90, 0x90, 0xE0, 0x00, 4}, /* ASCII 066 = B */
{0x60, 0x90, 0x80, 0x80, 0x80, 0x90, 0x60, 0x00, 4}, /* ASCII 067 = C */
{0xE0, 0x90, 0x90, 0x90, 0x90, 0x90, 0xE0, 0x00, 4}, /* ASCII 068 = D */
{0xF0, 0x80, 0x80, 0xE0, 0x80, 0x80, 0xF0, 0x00, 4}, /* ASCII 069 = E */
{0xF0, 0x80, 0x80, 0xE0, 0x80, 0x80, 0x80, 0x00, 4}, /* ASCII 070 = F */
{0x60, 0x90, 0x80, 0x80, 0xB0, 0x90, 0x60, 0x00, 4}, /* ASCII 071 = G */
{0x90, 0x90, 0x90, 0xF0, 0x90, 0x90, 0x90, 0x00, 4}, /* ASCII 072 = H */
{0xE0, 0x40, 0x40, 0x40, 0x40, 0x40, 0xE0, 0x00, 3}, /* ASCII 073 = I */
{0x10, 0x10, 0x10, 0x10, 0x10, 0x90, 0x60, 0x00, 4}, /* ASCII 074 = J */
{0x88, 0x90, 0xA0, 0xC0, 0xA0, 0x90, 0x88, 0x00, 5}, /* ASCII 075 = K */
{0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0xF0, 0x00, 4}, /* ASCII 076 = L */
{0x88, 0xD8, 0xA8, 0xA8, 0x88, 0x88, 0x88, 0x00, 5}, /* ASCII 077 = M */
{0x88, 0xC8, 0xA8, 0x98, 0x88, 0x88, 0x88, 0x00, 5}, /* ASCII 078 = N */
{0x20, 0x50, 0x88, 0x88, 0x88, 0x50, 0x20, 0x00, 5}, /* ASCII 079 = O */
{0xE0, 0x90, 0x90, 0xE0, 0x80, 0x80, 0x80, 0x00, 4}, /* ASCII 080 = P */
{0x20, 0x50, 0x88, 0x88, 0xA8, 0x50, 0x28, 0x00, 5}, /* ASCII 081 = Q */
{0xE0, 0x90, 0x90, 0xE0, 0xC0, 0xA0, 0x90, 0x00, 4}, /* ASCII 082 = R */
{0x60, 0x90, 0x80, 0x60, 0x10, 0x90, 0x60, 0x00, 4}, /* ASCII 083 = S */
{0xF8, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x00, 5}, /* ASCII 084 = T */
{0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x60, 0x00, 4}, /* ASCII 085 = U */
{0x88, 0x88, 0x88, 0x88, 0x50, 0x50, 0x20, 0x00, 5}, /* ASCII 086 = V */
{0x88, 0x88, 0x88, 0xA8, 0xA8, 0xA8, 0x50, 0x00, 5}, /* ASCII 087 = W */
{0x88, 0x88, 0x50, 0x20, 0x50, 0x88, 0x88, 0x00, 5}, /* ASCII 088 = X */
{0x88, 0x88, 0x50, 0x20, 0x20, 0x20, 0x20, 0x00, 5}, /* ASCII 089 = Y */
{0xF8, 0x08, 0x10, 0x20, 0x40, 0x80, 0xF8, 0x00, 5}, /* ASCII 090 = Z */
{0xE0, 0x80, 0x80, 0x80, 0x80, 0x80, 0xE0, 0x00, 3}, /* ASCII 091 = [ */
{0x80, 0x40, 0x20, 0x10, 0x08, 0x04, 0x02, 0x00, 7}, /* ASCII 092 = \ */
{0xE0, 0x20, 0x20, 0x20, 0x20, 0x20, 0xE0, 0x00, 3}, /* ASCII 093 = ] */
{0x20, 0x50, 0x88, 0x00, 0x00, 0x00, 0x00, 0x00, 5}, /* ASCII 094 = ^ */
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFE, 4}, /* ASCII 095 = _ */
{0x80, 0x40, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 3}, /* ASCII 096 = ` */
{0x00, 0x00, 0x60, 0x10, 0x70, 0x90, 0x70, 0x00, 4}, /* ASCII 097 = a */
{0x80, 0x80, 0xE0, 0x90, 0x90, 0x90, 0xE0, 0x00, 4}, /* ASCII 098 = b */
{0x00, 0x00, 0x60, 0x80, 0x80, 0x80, 0x60, 0x00, 3}, /* ASCII 099 = c */
{0x10, 0x10, 0x70, 0x90, 0x90, 0x90, 0x70, 0x00, 4}, /* ASCII 100 = d */
{0x00, 0x00, 0x60, 0x90, 0xF0, 0x80, 0x70, 0x00, 4}, /* ASCII 101 = e */
{0x20, 0x50, 0x40, 0xE0, 0x40, 0x40, 0x40, 0x00, 4}, /* ASCII 102 = f */
{0x00, 0x00, 0x60, 0x90, 0x90, 0x60, 0x10, 0x60, 4}, /* ASCII 103 = g */
{0x80, 0x80, 0xE0, 0x90, 0x90, 0x90, 0x90, 0x00, 4}, /* ASCII 104 = h */
{0x00, 0x80, 0x00, 0x80, 0x80, 0x80, 0x80, 0x00, 1}, /* ASCII 105 = i */
{0x00, 0x20, 0x00, 0x20, 0x20, 0x20, 0xA0, 0x40, 3}, /* ASCII 106 = j */
{0x80, 0x80, 0x90, 0xA0, 0xC0, 0xA0, 0x90, 0x00, 4}, /* ASCII 107 = k */
{0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x00, 1}, /* ASCII 108 = l */
{0x00, 0x00, 0x50, 0xA8, 0xA8, 0xA8, 0xA8, 0x00, 5}, /* ASCII 109 = m */
{0x00, 0x00, 0x60, 0x90, 0x90, 0x90, 0x90, 0x00, 4}, /* ASCII 110 = n */
{0x00, 0x00, 0x60, 0x90, 0x90, 0x90, 0x60, 0x00, 4}, /* ASCII 111 = o */
{0x00, 0x00, 0x60, 0x90, 0x90, 0xE0, 0x80, 0x80, 4}, /* ASCII 112 = p */
{0x00, 0x00, 0x60, 0x90, 0x90, 0x70, 0x10, 0x10, 4}, /* ASCII 113 = q */
{0x00, 0x00, 0x60, 0x90, 0x80, 0x80, 0x80, 0x00, 4}, /* ASCII 114 = r */
{0x00, 0x00, 0x70, 0x80, 0x60, 0x10, 0xE0, 0x00, 4}, /* ASCII 115 = s */
{0x40, 0x40, 0xE0, 0x40, 0x40, 0x50, 0x20, 0x00, 4}, /* ASCII 116 = t */
{0x00, 0x00, 0x90, 0x90, 0x90, 0x90, 0x60, 0x00, 4}, /* ASCII 117 = u */
{0x00, 0x00, 0x88, 0x88, 0x88, 0x50, 0x20, 0x00, 5}, /* ASCII 118 = v */
{0x00, 0x00, 0x88, 0x88, 0xA8, 0xA8, 0x50, 0x00, 5}, /* ASCII 119 = w */
{0x00, 0x00, 0x88, 0x50, 0x20, 0x50, 0x88, 0x00, 5}, /* ASCII 120 = x */
{0x00, 0x00, 0x90, 0x90, 0x70, 0x10, 0x90, 0x60, 4}, /* ASCII 121 = y */
{0x00, 0x00, 0xF0, 0x20, 0x40, 0x80, 0xF0, 0x00, 4}, /* ASCII 122 = z */
{0x20, 0x40, 0x40, 0x80, 0x40, 0x40, 0x20, 0x00, 3}, /* ASCII 123 = { */
{0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x00, 1}, /* ASCII 124 = | */
{0x80, 0x40, 0x40, 0x20, 0x40, 0x40, 0x80, 0x00, 3}, /* ASCII 125 = } */
{0x00, 0x50, 0xA0, 0x00, 0x00, 0x00, 0x00, 0x00, 4}, /* ASCII 126 = ~ */
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0} /* ASCII 127 = DEL (don't print)*/
};
const byte invader1[9] = {0x18, 0x3c, 0x7e, 0xdb, 0xff, 0x24, 0x5a, 0xa5, 8};
const byte invader2[9] = {0x3c, 0x7e, 0xff, 0x99, 0xff, 0x66, 0xdb, 0x81, 8};
</pre>Andrew Normanhttp://www.blogger.com/profile/16475081894748925557noreply@blogger.com1