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
styles

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

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.
express-keys

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:
macros

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.
screen

Attiny85 Breakout Keychain Game

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

IMG_20140923_152553

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

IMG_20140921_141441

Clean off the toner with acetone:

IMG_20140921_141543

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.

IMG_20140923_150658

3D print the case from Thingiverse

IMG_20140923_133214

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--;} 
   return;
}
void playerInc(){ // PB2 pin button interrupt
  if (player <128-platformWidth){player++;}
}

void setup() {
  resetGame();
  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
  attachInterrupt(0,playerInc,CHANGE);
  lastFrame = millis();
}
void loop() { 
  delay(40);
      noInterrupts();
      ssd1306_init();
      ssd1306_fillscreen(0x00);
      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);
      delay(2000);
	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_fillscreen(0x00);                
                  ssd1306_char_f6x8(32, 3, "Game Over");
                  ssd1306_char_f6x8(32, 5, "score:");
                  char temp[4] = {0,0,0,0};
                  itoa(score,temp,10);
                  ssd1306_char_f6x8(70, 5, temp);
                  ssd1306_char_f6x8(32, 6, "top score:");
                  itoa(topScore,temp,10);
                  ssd1306_char_f6x8(90, 6, temp);
                  for (int i = 0; i<1000; i++){
                    beep(1,random(0,i*2));
                  }
                  delay(1000);
                  system_sleep();
                  resetGame();
                }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
                  noInterrupts();

                  // blocks
                  ssd1306_setpos(0,0);
                  ssd1306_send_data_start();
                  for (int bl = 0; bl <16; bl++){
                    if(row1[bl]==1){
                      sendBlock(1);
                    }else {
                      sendBlock(0);
                    }
                   }   
                  ssd1306_send_data_stop();
                  ssd1306_setpos(0,1);
                  ssd1306_send_data_start();
                  for (int bl = 0; bl <16; bl++){
                    if(row2[bl]==1){
                      sendBlock(1);
                    }else {
                      sendBlock(0);
                    }
                   }   
                  ssd1306_send_data_stop();
                  ssd1306_setpos(0,2);
                  ssd1306_send_data_start();
                  for (int bl = 0; bl <16; bl++){
                    if(row3[bl]==1){
                      sendBlock(1);
                    }else {
                      sendBlock(0);
                    }
                   }   
                  ssd1306_send_data_stop();

                  // clear area below the blocks
                  ssd1306_setpos(0,3);
                  ssd1306_send_data_start();
                  for (byte i =0; i<128; i++){
                     ssd1306_send_byte(B00000000);
                  }
                  ssd1306_send_data_stop();
                  ssd1306_setpos(0,4);
                  ssd1306_send_data_start();
                  for (byte i =0; i<128; i++){
                     ssd1306_send_byte(B00000000);
                  }
                  ssd1306_send_data_stop();
                  ssd1306_setpos(0,5);
                  ssd1306_send_data_start();
                  for (byte i =0; i<128; i++){
                     ssd1306_send_byte(B00000000);
                  }
                  ssd1306_send_data_stop();
                  ssd1306_setpos(0,6);
                  ssd1306_send_data_start();
                  for (byte i =0; i<128; i++){
                     ssd1306_send_byte(B00000000);
                  }
                  ssd1306_send_data_stop();
                  ssd1306_setpos(0,7);
                  ssd1306_send_data_start();
                  for (byte i =0; i<128; i++){
                     ssd1306_send_byte(B00000000);
                  }
                  ssd1306_send_data_stop();

                  // draw ball
                  ssd1306_setpos(ballx,bally/8);
                  uint8_t temp = B00000001;
                  ssd1306_send_data_start();
                  temp = temp << bally%8+1;
                  ssd1306_send_byte(temp);  
                  ssd1306_send_data_stop();

                  drawPlatform();
          	  interrupts();
             //     

	}
}
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);
  delay(2000);
  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;
  }

  beep(30,300);

}
void drawPlatform(){
 noInterrupts();
 ssd1306_setpos(player,7);
 ssd1306_send_data_start();
 for (byte pw = 1; pw <platformWidth; pw++){ssd1306_send_byte(B00000011);}                
 ssd1306_send_data_stop();  
 interrupts(); 
}
void sendBlock(boolean fill){
  if (fill==1){
   ssd1306_send_byte(B00000000);
   ssd1306_send_byte(B01111110);
   ssd1306_send_byte(B01111110);
   ssd1306_send_byte(B01111110);
   ssd1306_send_byte(B01111110);
   ssd1306_send_byte(B01111110);
   ssd1306_send_byte(B01111110);
   ssd1306_send_byte(B00000000);
  }else {
   ssd1306_send_byte(B00000000);
   ssd1306_send_byte(B00000000);
   ssd1306_send_byte(B00000000);
   ssd1306_send_byte(B00000000);
   ssd1306_send_byte(B00000000);
   ssd1306_send_byte(B00000000);
   ssd1306_send_byte(B00000000);
   ssd1306_send_byte(B00000000);
  } 
}
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");}}
}
#define DIGITAL_WRITE_HIGH(PORT) PORTB |= (1 << PORT)
#define DIGITAL_WRITE_LOW(PORT) PORTB &= ~(1 << PORT)

// 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
#endif
#ifndef SSD1306_SDA
#define SSD1306_SDA		PB4	// SDA,	Pin 4 on SSD1306 Board
#endif
#ifndef SSD1306_SA
#define SSD1306_SA		0x78	// Slave address
#endif
// ----------------------------------------------------------------------------
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[]);
// ----------------------------------------------------------------------------
#endif
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(0xA8);
	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(0x02);
	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){
	DIGITAL_WRITE_HIGH(SSD1306_SCL);	// Set to HIGH
	DIGITAL_WRITE_HIGH(SSD1306_SDA);	// Set to HIGH
	DIGITAL_WRITE_LOW(SSD1306_SDA);		// Set to LOW
	DIGITAL_WRITE_LOW(SSD1306_SCL);		// Set to LOW
}

void ssd1306_xfer_stop(void){
	DIGITAL_WRITE_LOW(SSD1306_SCL);		// Set to LOW
	DIGITAL_WRITE_LOW(SSD1306_SDA);		// Set to LOW
	DIGITAL_WRITE_HIGH(SSD1306_SCL);	// Set to HIGH
	DIGITAL_WRITE_HIGH(SSD1306_SDA);	// Set to HIGH
}

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

	ssd1306_xfer_stop();
}

void ssd1306_fillscreen(uint8_t fill_Data){
	uint8_t m,n;
	for(m=0;m<8;m++)
	{
		ssd1306_send_command(0xb0+m);	//page0-page1
		ssd1306_send_command(0x00);		//low column start address
		ssd1306_send_command(0x10);		//high column start address
		ssd1306_send_data_start();
		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=0;
			y++;
		}
		ssd1306_setpos(x,y);
		ssd1306_send_data_start();
		for(i=0;i<6;i++)
		{
			ssd1306_send_byte(pgm_read_byte(&ssd1306xled_font6x8[c*6+i]));
		}
		ssd1306_send_data_stop();
		x += 6;
		j++;
	}
}

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

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

References:

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
M107
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++){
PORTB |= _BV(PORTB0);
delayMicroseconds(del);
PORTB &= ~_BV(PORTB0);
delayMicroseconds(del);
}
delayMicroseconds(del2);
for (int i =0; i<16; i++){
PORTB |= _BV(PORTB0);
delayMicroseconds(del);
PORTB &= ~_BV(PORTB0);
delayMicroseconds(del);
}
delay(100);
}

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

model

 

References

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.

pcb

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

IMG_20140212_083904

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.

IMG_20140212_083004

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:

http://ilyatitov.com/pov.php?string=MESSAGE

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

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

IMG_20140212_083344

Parts:

  • 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'])));
//print_r($type);
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();
      delay(5);\n";
}

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.

_MG_5564_web

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