What I needed was a simple way to program (and burn the bootloader) onto my ATtiny chips. I have made a breadboard breakout board (soon to be a different instructable) for my ATTiny84/44s as well as one for my ATTiny85/45s.
These sub-boards are for breadboard prototyping so I don't have to setup the crystal, the power, and the reset every time I want to move to a breadboard.
Step 1:
This is the ATTiny*4 Breadboard breakout. It's the board's traces are a mess I know. I made it with a sharpie marker as I didn't have access to a proper laser printer late at night when I made it. But it works like a charm.
The ICSP header breaks out the chip's MOSI, MISO, SCK, VCC, GND, and RESET pins. That is wired to a normal 6 wire cable. The other end is where the trick comes in.
Step 2:
Typically the ICSP on the arduino boards is used to program the chip on that board. So more like an "IN" as opposed to an "OUT". The reason for that is pin 5 is wired to the reset pin on that chip, and not the reset needed inside the "Arduino as ISP" sketch. I've see "how-to's" that tell you to cut those traces and rewire it. I had no interest in modifying my board permanently so I found it way easier to modify the cable.
Using an 8 pin cable I scavenged I remove the previous cable, then cut 6 wires from an old IDE ribbon cable. After that I pulled the 5th wire from the ribbon cable and made it about 3 inches longer, and did NOT put it into the punchdown for the header.
I punched the other wires in and cut them flush. I put a dab of super glue on both sides of the ribbon cable to try and prevent it from splitting down the cable any more. at the end of the reset pin (5th wire) I put a male connector on it so it would fit into the female pin 10 for programming.
Now all I need to do to program (or burn the bootloader) onto really any AVR that I have a bootloader for is attach this to the ICSP on the slave board and then the other end to my main arduino board and wire up pin 10 for reset. I am able to use the stock "Arduino as ISP" sketch with no mods to the boards or the sketch.
This page will stay since it explains the original scripts but I've made a number of updates and have packaged them up on the PyPI so they are easy to maintain for me and to distribute and install for everyone else. Let me know if there are any issues and I'll attempt to take care of them as time allows.
Another module of my alarm interface is done. I picked up a 3x4 matrix keypad from Adafruit (http://www.adafruit.com/products/419) But couldn't find any good python code examples for it's use. I was able to find a few examples that lead me down the right path in terms of scanning rows first as inputs and then swapping pins to scan the columns. I wrote two libraries, the first just uses 7 of the Raspberry Pi's GPIO pins then I also wrote one that works for the MCP23008 chip since I'm really in a mode of saving pins when I can :) Then also just wrote a demo script to show calling the libraries and their use.
Here is the code as it stands now, I have some ideas on how to change it to be better but this is fully working for now.
UPDATE:
I received a 4x4 keypad to test changes to the script and it worked like a charm once I changed the keypad and the Row and Column values like so:
KEYPAD = [ [1,2,3,"A"], [4,5,6,"B"], [7,8,9,"C"], ["*",0,"#","D"] ] ROW = [7,6,5,4] COLUMN = [3,2,1,0]
matrixKeypad_RPi_GPIO.py:
# #####################################################
# Python Library for 3x4 matrix keypad using
# 7 of the avialable GPIO pins on the Raspberry Pi.
#
# This could easily be expanded to handle a 4x4 but I
# don't have one for testing. The KEYPAD constant
# would need to be updated. Also the setting/checking
# of the colVal part would need to be expanded to
# handle the extra column.
#
# Written by Chris Crumpacker
# May 2013
#
# main structure is adapted from Bandono's
# matrixQPI which is wiringPi based.
# https://github.com/bandono/matrixQPi?source=cc
# #####################################################
import RPi.GPIO as GPIO
class keypad():
# CONSTANTS
KEYPAD = [
[1,2,3],
[4,5,6],
[7,8,9],
["*",0,"#"]
]
ROW = [18,23,24,25]
COLUMN = [4,17,22]
def __init__(self):
GPIO.setmode(GPIO.BCM)
def getKey(self):
# Set all columns as output low
for j in range(len(self.COLUMN)):
GPIO.setup(self.COLUMN[j], GPIO.OUT)
GPIO.output(self.COLUMN[j], GPIO.LOW)
# Set all rows as input
for i in range(len(self.ROW)):
GPIO.setup(self.ROW[i], GPIO.IN, pull_up_down=GPIO.PUD_UP)
# Scan rows for pushed key/button
# A valid key press should set "rowVal" between 0 and 3.
rowVal = -1
for i in range(len(self.ROW)):
tmpRead = GPIO.input(self.ROW[i])
if tmpRead == 0:
rowVal = i
# if rowVal is not 0 thru 3 then no button was pressed and we can exit
if rowVal <0 data-blogger-escaped-or="" data-blogger-escaped-rowval="">3:
self.exit()
return
# Convert columns to input
for j in range(len(self.COLUMN)):
GPIO.setup(self.COLUMN[j], GPIO.IN, pull_up_down=GPIO.PUD_DOWN)
# Switch the i-th row found from scan to output
GPIO.setup(self.ROW[rowVal], GPIO.OUT)
GPIO.output(self.ROW[rowVal], GPIO.HIGH)
# Scan columns for still-pushed key/button
# A valid key press should set "colVal" between 0 and 2.
colVal = -1
for j in range(len(self.COLUMN)):
tmpRead = GPIO.input(self.COLUMN[j])
if tmpRead == 1:
colVal=j
# if colVal is not 0 thru 2 then no button was pressed and we can exit
if colVal <0 data-blogger-escaped-colval="" data-blogger-escaped-or="">2:
self.exit()
return
# Return the value of the key pressed
self.exit()
return self.KEYPAD[rowVal][colVal]
def exit(self):
# Reinitialize all rows and columns as input at exit
for i in range(len(self.ROW)):
GPIO.setup(self.ROW[i], GPIO.IN, pull_up_down=GPIO.PUD_UP)
for j in range(len(self.COLUMN)):
GPIO.setup(self.COLUMN[j], GPIO.IN, pull_up_down=GPIO.PUD_UP)
if __name__ == '__main__':
# Initialize the keypad class
kp = keypad()
# Loop while waiting for a keypress
digit = None
while digit == None:
digit = kp.getKey()
# Print the result
print digit
matrixKeypad_MCP230xx.py
# #####################################################
# Python Library for 3x4 matrix keypad using
# the MCP23008 chip via I2C from the Raspberry Pi.
#
# This could easily be expanded to handle a 4x4 but I
# don't have one for testing. The KEYPAD constant
# would need to be updated. Also the setting/checking
# of the colVal part would need to be expanded to
# handle the extra column.
#
# Written by Chris Crumpacker
# May 2013
#
# main structure is adapted from Bandono's
# matrixQPI which is wiringPi based.
# https://github.com/bandono/matrixQPi?source=cc
# #####################################################
from Adafruit_MCP230xx import Adafruit_MCP230XX
class keypad(Adafruit_MCP230XX):
# Constants
INPUT = 0
OUTPUT = 1
HIGH = 1
LOW = 0
KEYPAD = [
[1,2,3],
[4,5,6],
[7,8,9],
["*",0,"#"]
]
ROW = [6,5,4,3]
COLUMN = [2,1,0]
def __init__(self, address=0x21, num_gpios=8):
self.mcp2 = Adafruit_MCP230XX(address, num_gpios)
def getKey(self):
# Set all columns as output low
for j in range(len(self.COLUMN)):
self.mcp2.config(self.COLUMN[j], self.mcp2.OUTPUT)
self.mcp2.output(self.COLUMN[j], self.LOW)
# Set all rows as input
for i in range(len(self.ROW)):
self.mcp2.config(self.ROW[i], self.mcp2.INPUT)
self.mcp2.pullup(self.ROW[i], True)
# Scan rows for pushed key/button
# valid rowVal" should be between 0 and 3 when a key is pressed. Pre-setting it to -1
rowVal = -1
for i in range(len(self.ROW)):
tmpRead = self.mcp2.input(self.ROW[i])
if tmpRead == 0:
rowVal = i
# if rowVal is still "return" then no button was pressed and we can exit
if rowVal == -1:
self.exit()
return
# Convert columns to input
for j in range(len(self.COLUMN)):
self.mcp2.config(self.COLUMN[j], self.mcp2.INPUT)
# Switch the i-th row found from scan to output
self.mcp2.config(self.ROW[rowVal], self.mcp2.OUTPUT)
self.mcp2.output(self.ROW[rowVal], self.HIGH)
# Scan columns for still-pushed key/button
colVal = -1
for j in range(len(self.COLUMN)):
tmpRead = self.mcp2.input(self.COLUMN[j])
if tmpRead == 1:
colVal=j
if colVal == -1:
self.exit()
return
# Return the value of the key pressed
self.exit()
return self.KEYPAD[rowVal][colVal]
def exit(self):
# Reinitialize all rows and columns as input before exiting
for i in range(len(self.ROW)):
self.mcp2.config(self.ROW[i], self.INPUT)
for j in range(len(self.COLUMN)):
self.mcp2.config(self.COLUMN[j], self.INPUT)
if __name__ == '__main__':
# Initialize the keypad class
kp = keypad()
# Loop while waiting for a keypress
r = None
while r == None:
r = kp.getKey()
# Print the result
print r
Demo code, matrixKeypad_test.py
# #####################################################
# Demo script showing the use of the Python
# matrix Keypad library for both the Raspberry Pi
# GPIO and the MSP230xx I2C Chip set.
#
# Librarys needed:
# matrixKeypad_MCP230xx.py or matrixKeypad_RPi_GPIO.py
#
# Also needed is the Adafruit python libraries for the
# MCP230xx chips (Adafruit_MCP230xx.py) and I2C (Adafruit_I2C.py)
#
# Written by Chris Crumpacker
# May 2013
#
# #####################################################
from matrixKeypad_MCP230xx import keypad
#from matrixKeypad_RPi_GPIO import keypad
from time import sleep
# Initialize the keypad class
kp = keypad()
def digit():
# Loop while waiting for a keypress
r = None
while r == None:
r = kp.getKey()
return r
print "Please enter a 4 digit code: "
# Getting digit 1, printing it, then sleep to allow the next digit press.
d1 = digit()
print d1
sleep(1)
d2 = digit()
print d2
sleep(1)
d3 = digit()
print d3
sleep(1)
d4 = digit()
print d4
# printing out the assembled 4 digit code.
print "You Entered %s%s%s%s "%(d1,d2,d3,d4)
Just a quick demo showing off some code I made to handle displaying multiple data points on a 20x4 LCD without having the update the entire screen with each refresh. This keeps the screen clean and a bit easier to read... and a ton easier to code against.
#!/usr/bin/python
#----------------------------------------------------------------
# Author: Chris Crumpacker
# Date: May 2013
#
# Testing a data table on an 20x4 LCD,
# using a RaspberyPi and an MCP23017 I2C port expander
#
# Using Adafruit_CharLCD code with the I2C and MCP230xx code as well
#----------------------------------------------------------------
from time import sleep
from Adafruit_CharLCDPlate4line import Adafruit_CharLCDPlate
from LCD_EoL_HandlingAnd4LineSupport import LCD_EoL_Handling
class LCD_DataTable(Adafruit_CharLCDPlate):
# Limited to 4 characters,
# position is left or right,
# line can be 0 thru 3
def updateHalfLabel(self, text, position, line):
self.clearHalfDataSet(position,line)
if position == "left":
lcd.setCursor(0,line)
eol.message(text[0:4]+':')
elif position == "right":
lcd.setCursor(10,line)
eol.message('|'+text[0:4]+':')
# Limited to 4 characters,
# position is left or right,
# line can be 0 thru 3
def updateHalfValue(self, text, position, line):
if position == "left":
lcd.setCursor(5,line)
eol.message(" ")
lcd.setCursor(5,line)
eol.message(text[0:4])
elif position == "right":
lcd.setCursor(16,line)
eol.message(" ")
lcd.setCursor(16,line)
eol.message(text[0:4])
# Writes up to a 9 character label and value to a full line
def updateWholeLineLabel(self, label, line):
self.clearWholeLine(line)
lcd.setCursor(0,line)
eol.message(label[0:9] + ': ')
# Writes up to a 9 character label and value to a full line
def updateWholeLineValue(self, value, line):
lcd.setCursor(10,line)
eol.message(value[0:10])
#Clears an entire line
def clearWholeLine(self, line):
lcd.setCursor(0,line)
eol.message(" " * columns)
#Clears an entire line
def clearWholeLineValue(self, line):
lcd.setCursor(10,line)
eol.message(" " * 10)
# Clears just a half data set, label and value
def clearHalfDataSet(self, position,line):
if position == "left":
lcd.setCursor(0,line)
eol.message(" " * 10)
elif position == "right":
lcd.setCursor(10,line)
eol.message(" " * 10)
# Clears just the value portion for a half data set
def clearHalfValue(self, position,line):
if position == "left":
lcd.setCursor(5,line)
eol.message(" ")
elif position == "right":
lcd.setCursor(16,line)
eol.message(" ")
#----------------------------------------------------------------
# Main program, just trowing bogus data "against the wall"
#----------------------------------------------------------------
if __name__ == '__main__':
#lcd size reference
columns = 20
rows = 4
eol = LCD_EoL_Handling()
lcd = Adafruit_CharLCDPlate()
dt = LCD_DataTable()
lcd.begin(columns, rows)
lcd.backlight(lcd.ON)
lcd.clear()
lcd.message("20x4 Table Testing")
sleep(2)
#Filling the table with bogus info
lcd.clear()
dt.updateHalfLabel("Temp","left",0)
dt.updateHalfLabel("Mode","right",0)
dt.updateHalfLabel("Targ","left",1)
dt.updateHalfLabel("Fan","right",1)
dt.updateHalfValue("Cool","right",0)
dt.updateHalfValue("75.5","left",0)
dt.updateHalfValue("Auto","right",1)
dt.updateHalfValue("74.0","left",1)
dt.updateWholeLineLabel("Tempurature",2)
dt.updateWholeLineValue("Too Hot!!!",2)
dt.updateWholeLineLabel("Humidity",3)
dt.updateWholeLineValue("100%!!!",3)
#Start testing updating and clearing parts
# Clearing entire lines
sleep(2)
dt.clearWholeLine(0)
sleep(1)
dt.clearWholeLine(3)
sleep(1)
# Repopulating the lines just cleared
dt.updateHalfLabel("Temp","left",0)
dt.updateHalfValue("75.3","left",0)
dt.updateHalfLabel("Mode","right",0)
dt.updateHalfValue("Cool","right",0)
dt.updateWholeLineLabel("Humidity",3)
dt.updateWholeLineValue("100%!!!",3)
sleep(2)
# Clearing the entire Data set, both Label and Value
dt.clearHalfDataSet("left",0)
sleep(1)
dt.clearHalfDataSet("right",0)
sleep(1)
dt.clearHalfDataSet("left",1)
sleep(1)
dt.clearHalfDataSet("right",1)
sleep(2)
# Repopulating the half labels and values just removed
dt.updateHalfLabel("Temp","left",0)
dt.updateHalfLabel("Mode","right",0)
dt.updateHalfLabel("Targ","left",1)
dt.updateHalfLabel("Fan","right",1)
dt.updateHalfValue("75.5","left",0)
dt.updateHalfValue("Cool","right",0)
dt.updateHalfValue("74.0","left",1)
dt.updateHalfValue("On","right",1)
sleep(2)
# Clearing the values in the half data sets
dt.clearHalfValue("left",0)
sleep(1)
dt.clearHalfValue("right",0)
sleep(1)
dt.clearHalfValue("left",1)
sleep(1)
dt.clearHalfValue("right",1)
sleep(2)
# Repopulating half data set values
dt.updateHalfValue("74.7","left",0)
sleep(1)
dt.updateHalfValue("Auto","right",0)
sleep(1)
dt.updateHalfValue("74.0","left",1)
sleep(1)
dt.updateHalfValue("On","right",1)
sleep(2)
# Clearing the value on a full line entry
dt.clearWholeLineValue(2)
dt.clearWholeLineValue(3)
sleep(2)
# Repopulating the values that was just removed
dt.updateWholeLineValue("Still Hot",2)
dt.updateWholeLineValue("90%",3)
Now that's all well and good but as part of an upcoming project I am planning I have bigger needs. First LCD size. I want to run the 20x4 size so I can display outputs on the first 3 lines and then use the fourth line for menu navigation. Second I am going to be using a 4x3 keypad, a "D-Pad" style button setup,a buzzer, and a few status LEDs. All of this is going to require one 16 pin (28dip)digital I/O expansion chip, MCP23017 to drive the display, the d-pad, buzzers and LEDs. Then an 8pin (18dip) MCP23008 chip to handle the 4x3 keypad. On top of that it needs to be almost 70' away.
So I used their good work in the coding department to jump off from. Obviously since the product limits to the use of the 16x2 displays due to physical space the code needed to be optimized to handle differing display sizes. I went a little beyond that and added some support for handling longer text strings that would usually overflow into the buffer, and in the case of the 4 line displays that means any text in the buffer for line one appears on line 3... :(
Running the example code provided below to demonstrate the EoL handling.
Here is the new "message" function from the Adafruit_CharLCDPlate.py script/class. I just checked in to their Github repo. One thing to note is that this in no way changes the functionality for the normal plate. Any existing scripts will function just like before. But with the addition of handling 4 line displays seamlessly and then all of the EoL features can be added be putting a 1 or 2 in the message function call after your test to print. My github fork
def message(self, text, limitMode = 0):
""" Send string to LCD. Newline wraps to next line"""
lines = str(text).split('\n') # Split at newline(s)
for i, line in enumerate(lines): # For each substring...
if i == 1: # If newline(s),
self.write(0xC0) # set DDRAM address to 2nd line
elif i == 2:
self.write(0x94)
elif i >= 3:
self.write(0xD4)
"""Now depending on the limit mode set by the function call this will handle """
lineLength = len(line)
limit = self.numcols
if limitMode <= 0:
self.write(line, True)
elif lineLength >= limit and limitMode == 1:
'''With the limit mode set to 1 the line is truncated
at the number of columns available on the display'''
limitedLine = line[0:self.numcols]
self.write(limitedLine, True)
elif lineLength >= limit and limitMode == 2:
'''With the limit mode set to 2 the line is truncated
at the number of columns minus 3 to add in an elipse'''
limitedLine = line[0:self.numcols-3]+'...'
self.write(limitedLine, True)
elif lineLength >= limit and limitMode >= 3:
'''Future todo, add in proper, "line after line" cariage return'''
else:
self.write(line, True)
I also had to add the "self.numlines = lines" to the begin function so I could use the column count.
I also spent some time making an example script to leverage this new message function as well as some of the other standard built in functions. Here is my example script on github
#!/usr/bin/python
#----------------------------------------------------------------
# Author: Chris Crumpacker
# Date: May 2013
#
# A demo of some of the built in helper functions of
# the Adafruit_CharLCDPlate.py and Using the EoL_HandlingAnd4LineSupport.py
#
# Using Adafruit_CharLCD code with the I2C and MCP230xx code as well
#----------------------------------------------------------------
numcolumns = 20
numrows = 4
from time import sleep
from Adafruit_CharLCDPlate import Adafruit_CharLCDPlate
lcd = Adafruit_CharLCDPlate()
lcd.begin(numcolumns, numrows)
lcd.backlight(lcd.ON)
lcd.message("LCD 20x4\nDemonstration")
sleep(2)
while True:
#Text on each line alone.
lcd.clear()
lcd.setCursor(0,0)
lcd.message("Line 1")
sleep(1)
lcd.clear()
lcd.setCursor(0,1)
lcd.message("Line 2")
sleep(1)
lcd.clear()
lcd.setCursor(0,2)
lcd.message("Line 3")
sleep(1)
lcd.clear()
lcd.setCursor(0,3)
lcd.message("Line 4")
sleep(1)
# Using the "\n" new line marker
lcd.clear()
lcd.setCursor(0,0)
lcd.message("Line 1")
sleep(1)
lcd.clear()
lcd.setCursor(0,0)
lcd.message("Line 1\nLine 2")
sleep(1)
lcd.clear()
lcd.setCursor(0,0)
lcd.message("Line 1\nLine 2\nLine 3")
sleep(1)
lcd.clear()
lcd.setCursor(0,0)
lcd.message("Line 1\nLine 2\nLine 3\nLine 4")
sleep(1)
# Auto line limiting by length as to not overflow the display
# This is line by line and does not to any caraige returns
lcd.clear()
lcd.setCursor(0,0)
lcd.message("This String is 33 Characters long",1)
sleep(2)
lcd.clear()
lcd.setCursor(0,0)
lcd.message("This String has elpise",2)
sleep(2)
#Scroll text to the right
messageToPrint = "Scrolling Right"
i=0
while i<20:
lcd.clear()
lcd.setCursor(0,0)
suffix = " " * i
lcd.message(suffix + messageToPrint,1)
sleep(.25)
i += 1
# Scroll test in from the Left
messageToPrint = "Scrolling Left"
i=20
while i>=0:
lcd.clear()
lcd.setCursor(0,0)
suffix = " " * i
lcd.message(suffix + messageToPrint,1)
sleep(.25)
i -= 1
sleep(2)
# Printing text backwards, NOT right justified
lcd.clear()
lcd.setCursor(0,0)
lcd.message("Right to left:")
lcd.setCursor(10,1)
lcd.rightToLeft()
lcd.message("Testing")
sleep(2)
# Printing normally from the middle of the line
lcd.clear()
lcd.setCursor(0,0)
lcd.message("Left to Right:")
lcd.setCursor(10,1)
lcd.message("Testing")
sleep(2)
# Enabling the cursor and having it blink
lcd.clear()
lcd.setCursor(0,0)
lcd.cursor()
lcd.blink()
lcd.message("Cursor is blinking")
lcd.setCursor(0,1)
sleep(3)
lcd.noCursor()
lcd.noBlink()
# Turning the backlight off and showing a simple count down
lcd.clear()
lcd.setCursor(0,0)
lcd.message("Backlight off in")
lcd.setCursor(0,3)
lcd.message("Back on in 3sec")
lcd.setCursor(17,0) #Reseting the cursor here keeps us from having to clear the screen, this over writes the previous character
lcd.message("3")
sleep(1)
lcd.setCursor(17,0)
lcd.message("2")
sleep(1)
lcd.setCursor(17,0)
lcd.message("1")
sleep(1)
lcd.backlight(lcd.OFF)
lcd.clear()
lcd.setCursor(0,0)
sleep(3)
lcd.backlight(lcd.ON)
lcd.message("Backlight on")