Loading CSV markers data into Google Maps API

Here is a nifty solution designed to populate a google map with geo location markers and infowindow popup boxes.

First you’ll need to get a Google Maps API developer key.

Then we need to prep the data — we need a csv table with a title, content, latitude and longtitude values:

If you open the file with a text editor it will look like this:

Stonehenge,Stonehenge is a prehistoric monument located in Wiltshire,51.1788823,-1.8262155
Bodiam Castle,Bodiam Castle is a 14th-century moated castle near Robertsbridge in East Sussex,51.002266,0.543549
Eden Project,The Eden Project is an artificial biodome visitor attraction in Cornwall,50.3601344,-4.7447178

Next we need the code that will read the csv file line by line and generate the javascript to feed the data to Google Maps API: https://drive.google.com/folderview?id=0B3bO_ZNaxxnlc2FiLWxfbTZCeFU&usp=sharing

$data = file_get_contents('google_map_data.csv');
$api_key = 'your key here';
$lines = explode("\n",$data);

foreach ($lines as $key => $value){
	if ($key>0&&strlen($value)>20){ // skip csv header row and blank rows
		$line = explode(",",$value);
		$markers[$key] = trim($line[0]).','.trim($line[1]).','.trim($line[2]).','.trim($line[3]); // title,content,lat,long
<!DOCTYPE html>
    <style type="text/css">
      html, body, #map-canvas { height: 100%; margin: 0; padding: 0;}
    <script type="text/javascript"
    <script type="text/javascript">
		var map;
		var marker = {};
		function initialize() {
			var mapOptions = {
			center: { lat: 51.1788823, lng: -1.8262155},
			zoom: 5
		map = new google.maps.Map(document.getElementById('map-canvas'),mapOptions);
		var markers = [];
			$counter = 0;
			foreach ($markers as $index => $list){
				   $marker_details = explode(',',$list);
				   echo 'markers["m'.($index-1).'"] = {};'."\n";
				   echo "markers['m".($index-1)."'].lat = '".$marker_details[2]."';\n";
				   echo "markers['m".($index-1)."'].lon = '".$marker_details[3]."';\n";
				   echo "markers['m".($index-1)."'].name = '".$marker_details[0]."';\n";
				   echo "markers['m".($index-1)."'].content = '".$marker_details[1]."';\n";
		var totalMarkers = <?=$counter?>;
		var i = 0;
		var infowindow;
		var contentString;
		for (var i = 0; i<totalMarkers; i++){
			contentString = '<div class="content">'+
				  '<h1 class="firstHeading">'+markers['m'+i].name+'</h1>'+
				  '<div class="bodyContent">'+
			infowindow = new google.maps.InfoWindow({
				  content: contentString

			marker['c'+i] = new google.maps.Marker({
					position: new google.maps.LatLng(markers['m'+i].lat,markers['m'+i].lon),
					map: map,
					title: markers['m'+i].name,
					infowindow: infowindow
			google.maps.event.addListener(marker['c'+i], 'click', function() {
					for (var key in marker){
					this.infowindow.open(map, this);

	  function panMap(la,lo){
			map.panTo(new google.maps.LatLng(la,lo));
	  function openMarker(mName){
		  for (var key in marker){
		  for (var key in marker){
			if (marker[key].title.search(mName) != -1){
      google.maps.event.addDomListener(window, 'load', initialize);
<div id="map-canvas"></div>

Thats it, when you run the php file you will be presented with a google map that can be embedded using an iframe tag.

You can extend the script with custom icons by adding another column to the csv file with the icon file path and adding an “icon:markers[‘m’+i].icon” option to the google.maps.Marker function.

Blue Screen of Death Caused by Wacom Driver

So if you get a blue screen of death when using a Wacom Intuos Pro tablet on your desktop, the Wacom driver may be to blame. The error message reads DRIVER_POWER_STATE_FAILURE 0x0000009f

In my case the issue was preceeded by the tablet switching off when I plugged the usb cable in.

To fix the issue, unistall the Wacom tablet in Control Panel > Applications and Features, then restart and reinstall. The issue should go away.

Virtual Tour Online Embedder

There are plenty of ways to use your equirectangular panoramas online. The main downside of the programs offering this functionality is the fiddly time consuming process you have to go through to publish the panoramas online, whether they have been saved as an .swf or as an html package. If you want compatibility you’d go with html and get a folder with half a dozen files that you have to upload to the website. If you want to have a blog then you would have to use ftp to upload the files and then work out a way of making it all work. I wanted a simple to use tool that would take care of the embedding of the panoramas and minimise the work involved. Here is how it goes:

  1. Create your 360 panorama (using photoshop, hugin, pano tools)
  2. Upload the jpeg of the panorama to http://orb.photo/embed_panoramas/
  3. Tweak the size and the embed options
  4. Copy the code and paste it into the html/text of your website or blog.

Thats it, it does the fiddly work for you. You can embed photospheres right from the google nexus phone if you wanted and, the plugin has a “Page background” option where the panorama fills the complete screen and is floating behind any content you’ve got. (Make sure that you don’t have non-transparent elements above z-index:-1 covering the panorama if nothing shows up.) The panorama embed code looks like this:

<iframe src="http://orb.photo/embedded_player.php?view=2015021387f3d2139222ad5ddcd26a6c69c4cd38" width="900" height="600" frameborder="0" scrolling="no">Please enable iframes to view content.</iframe>

The tool is available on the orb.photo website   The script uses webgl and javascript to place the equirectangular texture over a sphere which is then animated for the immersive experience. Javascript implementation ensures the panoramas will work on the mobile devices.

Hover compatibility of dropdown menus on touch devices.

I’ve seen different attempts to make the dropdown menus work intuitively on touch devices and while some are trying to utilise the double tap (not the best as this triggers zoom) and various jquery contraptions, all you need is for the top level link to ignore the first tap and let the user see the dropdown.

This can be done by placing a bit of javascript into the onclick property of the anchor tag that prevents location change and self destructs so the link becomes clickable on the second interaction:

<a onclick="if ('ontouchstart' in document.documentElement){this.onclick=''; return false;}" href="/news/">News</a>

Use Wacom tablet’s buttons to streamline your workflow

Design software is becoming more functional and sophisticated but as far as productivity goes — us humans still have to press the buttons to make stuff happen.IMG_20150103_221634

Don’t get me wrong, if you are just starting out in design college it is ok to mouse through the menus to get to a function for those 20 objects you are working on. But when you are trying to get the job done as quick as possible, nothing beats the speed and satisfaction of using keyboard shortcuts and pre-defined macros.

Adobe software is generally great with keyboard shortcuts and in this example scenario I will go through using the Wacom Intuos Pro’s ExpressKeys to improve on built in shortcuts by applying custom text heading styles to an Indesign project using the physical buttons of a Wacom tablet.

You can of course apply the custom styles via the character style menu

Or you could press Ctrl+Enter and use the quick apply to look up the style by typing your custom style name

But if you have a Wacom tablet you can program the tablet’s physical buttons that will apply up to 8 commonly used styles and boost your performance. To do so, you will need to open the Wacom Tablet Properties, click on the Functions icon and pick the application you want to define the functions for.

Pick the Keystroke option for the button you are programming and type Ctrl+Enter followed by the custom style name and then enter again exactly as if you were doing a quick-apply:

That’s it, you’ve just created a physical button to save yourself over a dozen keypresses to get to the page title style. Repeat this for all the actions you will be using and enjoy the quicker and easier workflow.

Attiny85 Breakout Keychain Game

attiny So, what can you do with Attiny’s 5 i/o pins?


I saw this great Attiny OLED project on http://tinusaur.wordpress.com/ and decided to try out the screen (It will be very handy as a mode display for a remote for my http://orb.photo/ project)

So I set out to make myself familiar with the screen and make something fun in the process. I made a remake of the classic breakout game, here is a video of the gameplay:

Parts required:

  • Attiny85 + dip8 socket
  • SSD1306 OLED screen
  • 2x push buttons
  • 2x resistors (10kOhm optimal)
  • 3x4cm copper clad board
  • Piezo speaker
  • 3V 2032 coin cell battery
  • Paper clip

So first of all you want to create the PCB, to do this just design the layout (printable pcb pdf)

Print the design on a page from a glossy magazine with a laser printer and use an iron or a laminator to transfer the design to the copper. Put the PCB into ferric chloride until the copper is etched away


Clean off the toner with acetone:


Drill the holes and solder the parts. The speaker is soldered to PB1 pin on the attiny and ground. Connect the large ground pads with a bit of wire. Bend and solder the paper clip to keep the battery in place.


3D print the case from Thingiverse


Program the attiny85 with the code and enjoy:

/* 2014
 * Breakout game by Ilya Titov. Find building instructions on http://webboggles.com/
 * The code that does not fall under the licenses of sources listed below can be used non commercially with attribution.
 * If you have problems uploading this sketch, this is probably due to sketch size - you need to update ld.exe in arduino\hardware\tools\avr\avr\bin
 * https://github.com/TCWORLD/ATTinyCore/tree/master/PCREL%20Patch%20for%20GCC
 * This sketch is using the screen control and font functions written by Neven Boyanov for the http://tinusaur.wordpress.com/ project
 * Source code and font files available at: https://bitbucket.org/tinusaur/ssd1306xled
 * Sleep code is based on this blog post by Matthew Little:
 * http://www.re-innovation.co.uk/web12/index.php/en/blog-75/306-sleep-modes-on-attiny85
#include <EEPROM.h>
#include "font6x8.h"
#include <avr/sleep.h>
#include <avr/interrupt.h> // needed for the additional interrupt

volatile byte player = 0; //0 to 128-platformWidth  - this is the position of the bounce platform
byte platformWidth = 16; 
byte ballx = 62; // coordinate of the ball
byte bally = 50; // coordinate of the ball
int vdir = -1; // vertical direction and step  distance
int hdir = -1; // horizontal direction and step distance
long lastFrame = 0; // time since the screen was updated last
boolean row1[16]; // on-off array of blocks
boolean row2[16];
boolean row3[16];
int score = 0; // score - counts the number of blocks hit and resets the array above when devisible by 48(total blocks)
ISR(PCINT0_vect){ // PB0 pin button interrupt			     
   if (player >0){player--;} 
void playerInc(){ // PB2 pin button interrupt
  if (player <128-platformWidth){player++;}

void setup() {
  DDRB = 0b00000010;  	// set PB1 as output (for the speaker)
  PCMSK = 0b00000001;	// pin change mask: listen to portb bit 1
  GIMSK |= 0b00100000;	// enable PCINT interrupt 
  sei();			// enable all interrupts
  lastFrame = millis();
void loop() { 
      ssd1306_char_f6x8(16, 4, "B R E A K O U T");
      ssd1306_char_f6x8(20, 6, "webboggles.com");
      beep(200,600);          beep(300,200);          beep(400,300);
	while (1==1) {
              // continue moving after the interrupt
              if (digitalRead(2)==1){if (player <128-platformWidth){player++;} if (player <128-platformWidth){player++;} if (player <128-platformWidth){player++;}}               if (digitalRead(0)==1){if (player >0){player--;} if (player >0){player--;} if (player >0){player--;}}

              // bounce off the sides of the screen
              if ((bally+vdir<54&&vdir==1)||(bally-vdir>1&&vdir==-1)){bally+=vdir;}else {vdir = vdir*-1;}
              if ((ballx+hdir<127&&hdir==1)||(ballx-hdir>1&&hdir==-1)){ballx+=hdir;}else {hdir = hdir*-1;}

              // frame actions
              if (lastFrame+10<millis()){                 if(bally>10&&bally+vdir>=54&&(ballx<player||ballx>player+platformWidth)){ // game over if the ball misses the platform
                  int topScore = EEPROM.read(0);
                  topScore = topScore << 8;                   topScore = topScore |  EEPROM.read(1);                                      if (score>topScore){topScore = score; EEPROM.write(1,topScore & 0xFF); EEPROM.write(0,(topScore>>8) & 0xFF); }
                  ssd1306_char_f6x8(32, 3, "Game Over");
                  ssd1306_char_f6x8(32, 5, "score:");
                  char temp[4] = {0,0,0,0};
                  ssd1306_char_f6x8(70, 5, temp);
                  ssd1306_char_f6x8(32, 6, "top score:");
                  ssd1306_char_f6x8(90, 6, temp);
                  for (int i = 0; i<1000; i++){
                }else if (ballx<player+platformWidth/2&&bally>10&&bally+vdir>=54){ // if the ball hits left of the platform bounce left
                  hdir=-1; beep(20,600);
                }else if (ballx>player+platformWidth/2&&bally>10&&bally+vdir>=54){  // if the ball hits right of the platform bounce right
                  hdir=1; beep(20,600);
                }else if (bally+vdir>=54){
                  hdir=1; beep(20,600);

                collisionCheck: // go back to here if a collision was detected to prevent flying through a rigid
                if (floor((bally+vdir)/8)==2){
                  if (row3[ballx/8]==1){row3[ballx/8]=0; score++;  
                      collision(); goto collisionCheck; // check collision for the new direction to prevent flying through a rigid
                }else if (floor((bally+vdir)/8)==1){
                  if (row2[ballx/8]==1){row2[ballx/8]=0; score++; 
                      collision(); goto collisionCheck;
                }else if (floor((bally+vdir)/8)==0){
                  if (row1[ballx/8]==1){row1[ballx/8]=0; score++;
                      collision(); goto collisionCheck;

                // reset blocks if all have been hit
                if (score%48==0){ 
                  for (byte i =0; i<16;i++){
                   row1[i]=1; row2[i]=1; row3[i]=1;

              // update whats on the screen

                  // blocks
                  for (int bl = 0; bl <16; bl++){
                    }else {
                  for (int bl = 0; bl <16; bl++){
                    }else {
                  for (int bl = 0; bl <16; bl++){
                    }else {

                  // clear area below the blocks
                  for (byte i =0; i<128; i++){
                  for (byte i =0; i<128; i++){
                  for (byte i =0; i<128; i++){
                  for (byte i =0; i<128; i++){
                  for (byte i =0; i<128; i++){

                  // draw ball
                  uint8_t temp = B00000001;
                  temp = temp << bally%8+1;


void resetGame(){
  ssd1306_char_f6x8(16, 4, "B R E A K O U T");
  ssd1306_char_f6x8(20, 6, "webboggles.com");
  beep(200,600);          beep(300,200);          beep(400,300);
  for (byte i =0; i<16;i++){ // reset blocks
   row1[i]=1; row2[i]=1; row3[i]=1;
  platformWidth = 16;
  ballx = 64;
  bally = 50;
  hdir = -1;
  vdir = -1;
  score = 0;
  player = random(0,128-platformWidth);
  ballx = player+platformWidth/2;

void collision(){ // the collsision check is actually done befor this is called, this code works out where the ball will bounce
  if ((bally+vdir)%8==7&&(ballx+hdir)%8==7){ // bottom right corner
      if (vdir==1){hdir=1;}else if(vdir==-1&&hdir==1){vdir=1;}else {hdir=1;vdir=1;}
    }else if ((bally+vdir)%8==7&&(ballx+hdir)%8==0){ // bottom left corner
      if (vdir==1){hdir=-1;}else if(vdir==-1&&hdir==-1){vdir=1;}else {hdir=-1;vdir=1;}
    }else if ((bally+vdir)%8==0&&(ballx+hdir)%8==0){ // top left corner
      if (vdir==-1){hdir=-1;}else if(vdir==1&&hdir==-1){vdir=-1;}else {hdir=-1;vdir=-1;}
    }else if ((bally+vdir)%8==0&&(ballx+hdir)%8==7){ // top right corner
      if (vdir==-1){hdir=1;}else if(vdir==1&&hdir==1){vdir=-1;}else {hdir=1;vdir=-1;}
    }else if ((bally+vdir)%8==7){ // bottom side
      vdir = 1;
    }else if ((bally+vdir)%8==0){ // top side
      vdir = -1;
    }else if ((ballx+hdir)%8==7){ // right side
      hdir = 1;
    }else if ((ballx+hdir)%8==0){ // left side
      hdir = -1;
    }else {
      hdir = hdir*-1; vdir = vdir*-1;


void drawPlatform(){
 for (byte pw = 1; pw <platformWidth; pw++){ssd1306_send_byte(B00000011);}                
void sendBlock(boolean fill){
  if (fill==1){
  }else {
void beep(int bCount,int bDelay){
  for (int i = 0; i<=bCount; i++){digitalWrite(1,HIGH);for(int i2=0; i2<bDelay; i2++){__asm__("nop\n\t");}digitalWrite(1,LOW);for(int i2=0; i2<bDelay; i2++){__asm__("nop\n\t");}}

// Some code based on "IIC_wtihout_ACK" by http://www.14blog.com/archives/1358
#ifndef SSD1306XLED_H
#define SSD1306XLED_H
// ---------------------	// Vcc,	Pin 1 on SSD1306 Board
// ---------------------	// GND,	Pin 2 on SSD1306 Board
#ifndef SSD1306_SCL
#define SSD1306_SCL		PB3	// SCL,	Pin 3 on SSD1306 Board
#ifndef SSD1306_SDA
#define SSD1306_SDA		PB4	// SDA,	Pin 4 on SSD1306 Board
#ifndef SSD1306_SA
#define SSD1306_SA		0x78	// Slave address
// ----------------------------------------------------------------------------
void ssd1306_init(void);
void ssd1306_xfer_start(void);
void ssd1306_xfer_stop(void);
void ssd1306_send_byte(uint8_t byte);
void ssd1306_send_command(uint8_t command);
void ssd1306_send_data_start(void);
void ssd1306_send_data_stop(void);
void ssd1306_setpos(uint8_t x, uint8_t y);
void ssd1306_fillscreen(uint8_t fill_Data);
void ssd1306_char_f6x8(uint8_t x, uint8_t y, const char ch[]);
//void ssd1306_char_f8x16(uint8_t x, uint8_t y,const char ch[]);
//void ssd1306_char_f16x16(uint8_t x, uint8_t y, uint8_t N);
void ssd1306_draw_bmp(uint8_t x0, uint8_t y0, uint8_t x1, uint8_t y1, uint8_t bitmap[]);
// ----------------------------------------------------------------------------
void ssd1306_init(void){
	DDRB |= (1 << SSD1306_SDA);	// Set port as output
	DDRB |= (1 << SSD1306_SCL);	// Set port as output

	ssd1306_send_command(0xAE); // display off
	ssd1306_send_command(0x00); // Set Memory Addressing Mode
	ssd1306_send_command(0x10); // 00,Horizontal Addressing Mode;01,Vertical Addressing Mode;10,Page Addressing Mode (RESET);11,Invalid
	ssd1306_send_command(0x40); // Set Page Start Address for Page Addressing Mode,0-7
	ssd1306_send_command(0x81); // Set COM Output Scan Direction
	ssd1306_send_command(0xCF); // ---set low column address
	ssd1306_send_command(0xA1); // ---set high column address
	ssd1306_send_command(0xC8); // --set start line address
	ssd1306_send_command(0xA6); // --set contrast control register
	ssd1306_send_command(0x3F); // --set segment re-map 0 to 127
	ssd1306_send_command(0xD3); // --set normal display
	ssd1306_send_command(0x00); // --set multiplex ratio(1 to 64)
	ssd1306_send_command(0xD5); // 
	ssd1306_send_command(0x80); // 0xa4,Output follows RAM content;0xa5,Output ignores RAM content
	ssd1306_send_command(0xD9); // -set display offset
	ssd1306_send_command(0xF1); // -not offset
	ssd1306_send_command(0xDA); // --set display clock divide ratio/oscillator frequency
	ssd1306_send_command(0x12); // --set divide ratio
	ssd1306_send_command(0xDB); // --set pre-charge period
	ssd1306_send_command(0x40); // 
	ssd1306_send_command(0x20); // --set com pins hardware configuration
	ssd1306_send_command(0x8D); // --set vcomh
	ssd1306_send_command(0x14); // 0x20,0.77xVcc
	ssd1306_send_command(0xA4); // --set DC-DC enable
	ssd1306_send_command(0xA6); // 
	ssd1306_send_command(0xAF); // --turn on oled panel 

void ssd1306_xfer_start(void){

void ssd1306_xfer_stop(void){

void ssd1306_send_byte(uint8_t byte){
	uint8_t i;
	for(i=0; i<8; i++)
		if((byte << i) & 0x80) 			DIGITAL_WRITE_HIGH(SSD1306_SDA); 		else 			DIGITAL_WRITE_LOW(SSD1306_SDA); 		 		DIGITAL_WRITE_HIGH(SSD1306_SCL); 		DIGITAL_WRITE_LOW(SSD1306_SCL); 	} 	DIGITAL_WRITE_HIGH(SSD1306_SDA); 	DIGITAL_WRITE_HIGH(SSD1306_SCL); 	DIGITAL_WRITE_LOW(SSD1306_SCL); } void ssd1306_send_command(uint8_t command){ 	ssd1306_xfer_start(); 	ssd1306_send_byte(SSD1306_SA);  // Slave address, SA0=0 	ssd1306_send_byte(0x00);	// write command 	ssd1306_send_byte(command); 	ssd1306_xfer_stop(); } void ssd1306_send_data_start(void){ 	ssd1306_xfer_start(); 	ssd1306_send_byte(SSD1306_SA); 	ssd1306_send_byte(0x40);	//write data } void ssd1306_send_data_stop(void){ 	ssd1306_xfer_stop(); } void ssd1306_setpos(uint8_t x, uint8_t y) { 	ssd1306_xfer_start(); 	ssd1306_send_byte(SSD1306_SA);  //Slave address,SA0=0 	ssd1306_send_byte(0x00);	//write command 	ssd1306_send_byte(0xb0+y); 	ssd1306_send_byte(((x&0xf0)>>4)|0x10); // |0x10
	ssd1306_send_byte((x&0x0f)|0x01); // |0x01


void ssd1306_fillscreen(uint8_t fill_Data){
	uint8_t m,n;
		ssd1306_send_command(0xb0+m);	//page0-page1
		ssd1306_send_command(0x00);		//low column start address
		ssd1306_send_command(0x10);		//high column start address
		for(n=0;n<128;n++) 		{ 			ssd1306_send_byte(fill_Data); 		} 		ssd1306_send_data_stop(); 	} } void ssd1306_char_f6x8(uint8_t x, uint8_t y, const char ch[]){ 	uint8_t c,i,j=0; 	while(ch[j] != '\0') 	{ 		c = ch[j] - 32; 		if(x>126)
		x += 6;

// Routines to set and clear bits (used in the sleep code)
#ifndef cbi
#define cbi(sfr, bit) (_SFR_BYTE(sfr) &= ~_BV(bit))
#ifndef sbi
#define sbi(sfr, bit) (_SFR_BYTE(sfr) |= _BV(bit))

void system_sleep() {
  cbi(ADCSRA,ADEN);                    // switch Analog to Digitalconverter OFF
  set_sleep_mode(SLEEP_MODE_PWR_DOWN); // sleep mode is set here
  sleep_mode();                        // System actually sleeps here
  sleep_disable();                     // System continues execution here when watchdog timed out 
  sbi(ADCSRA,ADEN);                    // switch Analog to Digitalconverter ON  


Attiny85 Canon DSLR IR Remote

A nice use for the Attiny85 — a canon DSLR infrared remote.

Canon keychain IR remote

Parts required

  • Attiny85 microcontroller
  • NPN transistor
  • 3.3 or 5 V voltage regulator
  • Pushbutton
  • Infrared led
  • 12v car key/alarm battery
  • Some copper clad board

Design the PCB

pcb layout


Tweak the design for better looks in Illustrator (Download printable PDF)tweaked pcb design

Print and transfer the pcb design to the board (laser printer/laminator method)

PCB layout transferred to copper clad board

Trim the board with a dremel and etch with ferric chloride and some 3D printer assistance

Gcode for rocking the print bed

G21 ; set units to millimeters
M92 X62.6054 ; calibrate X
M92 Y61.0152 ; calibrate Y
M92 Z2387.0719 ; calibrate Z
G90 ; use absolute coordinates
G1 Y100
G1 Y110
G1 Y100
G1 Y110
G1 Y100
G1 Y110
G1 Y100
G1 Y110
.... just copy-pase the last 2 lines to extend print time

Etched pcb

Wipe off the toner with acetone, tin and drill the holes using .8mm carbide drill

PCB with drilled holes

Solder the parts and add battery contacts made from a paperclip

Assembled remote

Program the attiny85 with the following code (16 15us pulses, pause for 7330us and do 16 15us pulses again)
I am ussing the assembler port calls to trigger the pins because digitalWrite() function is too slow

void setup() {
pinMode(0, OUTPUT);

void loop() {
int del = 15;
int del2 = 7330;
for (int i =0; i<16; i++){
for (int i =0; i<16; i++){

Model and 3D print the keychain case (Download printable file from Thingiverse)




Programming the Attiny85 http://highlowtech.org/?p=1706

Timing for the trigger signal http://www.doc-diy.net/photo/rc-1_hacked/index.php

Attiny85 10 LED POV

pov attiny85

I’ve been looking to get to grips with EAGLE PCB layout software and this was a great project to start with.

Attiny POV

The idea behind persistence of vision is that an array of led pixels blinks out an image line by line, so when you move it the lines get stacked up one after another. If you can do this fast enough out retinas will see an after-image of multiple lines at once and we’ll be able to make out the image.

pov hearts

I decided to make a 10 pixel array and as there are less than 10 pins on the Attiny I decided to use Charlieplex layout to drive all of them.

Attiny charlieplex circuit

I’ve later had to cut the track to pin one and re-wire it to pin 6 as if I used the reset pin I would no longer be able to re-program the chip. Luckily we can drive up to 20 charlieplexed LEDs with 5 pins so we’ll just take care of this in the program.


I exported the board from EAGLE as a nonochrome image and laser-printed on a page from a magazine.


The printout is then pressed against a sheet of copper clad board and ironed over to make the tone stick to the copper. The board is then washed free of paper, cut and submersed in ferric chloride to etch away the areas not covered by the toner. PCB is drilled, covered with solder paste and the components are soldered on.


We then want a program that will very quickly pulse out our image bearing in mind that only one LED can be lit at any given moment. We’ll be using assembler port commands as digitalWrite would be too slow for what we are doing. To flash the program to Attiny I’ll be using arduino as isp, see high-low tech group page for more details

Here are the commands for each individual LED to light up.

  PORTB = B00001000; DDRB = B00011000; //1
  PORTB = B00001000; DDRB = B00001100; //2
  PORTB = B00000100; DDRB = B00001100; //3
  PORTB = B00000010; DDRB = B00000110; //4
  PORTB = B00000100; DDRB = B00000110; //5
  PORTB = B00000001; DDRB = B00000011; //6
  PORTB = B00000010; DDRB = B00000011; //7
  PORTB = B00010000; DDRB = B00011000; //8
  PORTB = B00010000; DDRB = B00010010; //9
  PORTB = B00000010; DDRB = B00010010; //10

The PORTB sets pins high or low and the DDRB sets the active pins as outputs and inactive pins as inputs to prevent any random leds lighting up. See how we can change state of multiple pins with a single call.

I made two functions to make the control easier. 10 bits of data are passed in 2 variables and if the bit in a variable is high the corresponding led will be lit.

Here is the complete program:

void setup() {                
  pinMode(0, OUTPUT);     
  pinMode(1, OUTPUT);
  pinMode(2, OUTPUT);
  pinMode(3, OUTPUT);
  pinMode(4, OUTPUT);

void loop() { 
    for (int i = 0; i<= 16000; i++){showRow(B00000001,B00000000);} // light up led 1 16000 times
    for (int i = 0; i<= 16000; i++){showRow(B00000010,B00000000);} // light up led 2 16000 times
    for (int i = 0; i<= 16000; i++){showRow(B00000100,B00000000);} // light up led 3 16000 times
    for (int i = 0; i<= 16000; i++){showRow(B00001000,B00000000);} // light up led 4 16000 times
    for (int i = 0; i<= 16000; i++){showRow(B00010000,B00000000);} // light up led 5 16000 times
    for (int i = 0; i<= 16000; i++){showRow(B00100000,B00000000);} // light up led 6 16000 times
    for (int i = 0; i<= 16000; i++){showRow(B01000000,B00000000);} // light up led 7 16000 times
    for (int i = 0; i<= 16000; i++){showRow(B10000000,B00000000);} // light up led 8 16000 times
    for (int i = 0; i<= 16000; i++){showRow(B00000000,B00000001);} // light up led 9 16000 times
    for (int i = 0; i<= 16000; i++){showRow(B00000000,B00000010);} // light up led 10 16000 times

void showRow(int bi, int bi2){
  if (bi2&B00000001){PORTB = B00010000; DDRB = B00010010;} //9
  if (bi2&B00000010){PORTB = B00000010; DDRB = B00010010;} //10
  if (bi&B10000000){PORTB = B00010000; DDRB = B00011000;} //8
  if (bi&B01000000){PORTB = B00000010; DDRB = B00000011;} //7
  if (bi&B00100000){PORTB = B00000001; DDRB = B00000011;} //6
  if (bi&B00010000){PORTB = B00000100; DDRB = B00000110;} //5
  if (bi&B00001000){PORTB = B00000010; DDRB = B00000110;} //4
  if (bi&B00000100){PORTB = B00000100; DDRB = B00001100;} //3
  if (bi&B00000010){PORTB = B00001000; DDRB = B00001100;} //2
  if (bi&B00000001){PORTB = B00001000; DDRB = B00011000;} //1

void dimAll(){
   PORTB = B00000000; DDRB = B00000000; // dim all 

What the above does is the switch-on iteration which lights each led in sequence you see in the video as I put the battery in.

So our POV is working, lets write some text. It would be a pain to manually create text string from vertical lines of pixels so I wrote a php program to take care of that. Feel free to use it, it converts text into a 10 bit matrix:


Make sure to go to page source to grab the code.

Download the case from thingiverse: http://www.thingiverse.com/thing:247795



  • Single sided pcb
  • 10x cpld2 leds
  • Attiny85
  • DIP8 socket
  • CR2032 battery
  • Paperclip
  • Some wire

php program to convert text to bit matrix

$letters['A'] = array('1111111111','0000010001','0000010001','0000010001','1111111111');
$letters['B'] = array('1111111111','1000010001','0100010010','0100010010','0111101110');
$letters['C'] = array('0111111110','1000000001','1000000001','1000000001','0100000010');
$letters['D'] = array('1111111111','1000000001','1000000001','0100000010','0111111110');
$letters['E'] = array('1111111111','1000010001','1000010001','1000000001');
$letters['F'] = array('1111111111','1000010001','0000010001','0000000001');
$letters['G'] = array('1111111111','1000000001','1000010001','1000010001','0100010001');
$letters['H'] = array('1111111111','0000010000','0000010000','1111111111');
$letters['I'] = array('0000000000','1111111111','0000000000');
$letters['J'] = array('0110000000','1001000001','1000000001','1111111111');
$letters['K'] = array('1111111111','0000110000','0000101000','0111000110','1000000001');
$letters['L'] = array('1111111111','1000000000','1000000000','1000000000');
$letters['M'] = array('1111111111','0000000110','0000001000','0000001100','1111111111');
$letters['N'] = array('1111111111','0000001110','0001110000','1110000000','1111111111');
$letters['O'] = array('0111111110','1000000001','1000000001','0111111110');
$letters['P'] = array('1111111111','0000010001','0000001110');
$letters['Q'] = array('0111111110','1010000001','1100000001','0111111110','1000000000');
$letters['R'] = array('1111111111','0000010001','0000101001','0111001001','1000000110');
$letters['S'] = array('0100011110','1000010001','1000010001','0111100010');
$letters['T'] = array('0000000001','0000000001','1111111111','0000000001','0000000001');
$letters['U'] = array('0111111111','1000000000','1000000000','0111111111');
$letters['V'] = array('0000001111','0001110000','1110000000','0001110000','0000001111');
$letters['W'] = array('0111111111','1000000000','0111000000','1000000000','1111111111');
$letters['X'] = array('1110000111','0011001100','0000110000','0011001100','1110000111');
$letters['Y'] = array('0000001111','0000010000','1111100000','0000010000','0000001111');
$letters['Z'] = array('1000000001','1000000111','1000011001','1001100001','1110000001');
$letters['.'] = array('1000000000');
$letters[')'] = array('0001111000','0111111110','1110111110','1101110110','1101111111','1101111111','1101110110','1110111110','0111111110','0011111000');
$letters[' '] = array('0000000000');
$letters['!'] = array('1001111111');
$type = str_split(preg_replace('/[^A-Z!\. )]/','',strtoupper($_GET['string'])));
foreach ($type as $key => $value){
	foreach ($letters[$value] as $key2 => $value2){
		echo '      for (int i = 0; i<= times; i++){showRow(B'.$value2{7}.$value2{6}.$value2{5}.$value2{4}.$value2{3}.$value2{2}.$value2{1}.$value2{0}.',B000000'.$value2{9}.$value2{8}.');}'."\n";

	echo "      dimAll();

PWM Servo Control with attiny85

Pursuing an idea to create a fully automated lightweight 360°  bracket that would work with pocket cameras I came across a challenge.


The starting point for the project was a 15kg torque mg995 servo powerful enough to move the camera when powered from a small 3v battery. I was planning to use an arduino programmed Attiny85 controller to drive the servo which is where I got stuck at first.

Continue reading PWM Servo Control with attiny85