I wanted to quickly demonstrate using a stepper motor from a floppy drive with the Arduino/Wiring platform as a follow up to the weekend’s workshop. By the time we got to the stepper parts I was a bit scattered. So here is an example that I set up yesterday.
I have a talking George Bush doll from several years ago that was ripped in half (by people watching my punk band (w.busholini.org)) so I went ahead and finished dismantling it. For Halloween I thought his head should turn around Linda Blair style.
I wired up a ULN2803 Darlington array and a floppy drive stepper motor from the workshop as shown in the diagram above .
I figured out which wire was the common wire by taking an ohmmeter to the wires on the stepper. Most of the combinations were about 150 except from one of the wires on the end that read 75 ohms. Checking the 75 against all of the other wires I was able to determine that one of the wires was the common one and marked it with a sharpie.
Then I ran up the arduino (v 17) example program for the stepper library. I modified it so that it just made steps in one direction. When I ran it the motion was really jittery so I checked to make sure that my wiring was good and then rather than rewiring the stepper physically I changed the line in the code from
Stepper stepper(STEPS, 9, 10, 11, 12);
to
Stepper stepper(STEPS, 9, 11, 10,12);
And Whah La! his head started spinning just like he was possessed by Dick Cheney! I wired the talk button to pin 8 and then added some random delays which gave me the following.
#include <Stepper.h>
// change this to the number of steps on your motor
#define STEPS 100
int relayPin=8;
int ledPin=13;
// create an instance of the stepper class, specifying
// the number of steps of the motor and the pins it's
// attached to
Stepper stepper(STEPS, 9, 11, 10,12);
// the previous reading from the analog input
int previous = 0;
void setup()
{ pinMode(relayPin,OUTPUT);
pinMode(ledPin,OUTPUT);
// set the speed of the motor to 30 RPMs
stepper.setSpeed(100);
}
void loop()
{
// get the sensor value
int val = analogRead(0);
// move a number of steps equal to the change in the
// sensor reading
//stepper.step(val - previous);
digitalWrite(ledPin, HIGH);
stepper.step(random(5,90));
delay(random(60,2000));
digitalWrite(relayPin, HIGH);
delay(20);
digitalWrite(relayPin, LOW);
digitalWrite(ledPin, LOW);
stepper.step(-random(15,200));
delay(random(90,3000));
// remember the previous value of the sensor
previous = val;
}
A while back I started thinking about a way to display stationary bike race results that didn’t require either a projector or a really complicated mechanical assembly. The thing that came to mind was a race tree like at the drag races. I asked Amanda who has been running bike events in portland if she could get me 12 lights. I wound up with pile of Bike Planet lights which required about a weeks worth of surgery.
From one of our led driving discussions a few years back I wound up with a tube of 74hc595s which I wired up like so.
While there are examples using bit banging to drive shift registers with the arduino they ignore the built in hardware capabilities of the AVR family.
Using the built in spi greatly simplifies your code and is remarkably fast. In the code sample below there are 5 bytes which represent the 40 pins from 5 shift registers. The main loop just toggles the bits and sends them out the door.
byte outbytes[5]={0x55,0x55,0x55,0x55,0x55};
//uncomment this for a standard arduino
//#define PIN_SCK 13 // SPI clock (also Arduino LED!)
//#define PIN_MISO 12 // SPI data input
//#define PIN_MOSI 11 // SPI data output#define PIN_HEARTBEAT 7 // added LED
#define PIN_SCK 9 // SPI clock (also Arduino LED!)
#define PIN_MISO 11 // SPI data input
#define PIN_MOSI 10 // SPI data output
#define PIN_SS 8 // SPI slave select
void EnableSPI(void) {
SPCR |= 1 << SPE;
}
void DisableSPI(void) {
SPCR &= ~(1 << SPE);
}
byte SendRecSPI(byte Dbyte) { // send one byte, get another in exchange
SPDR = Dbyte;
while (! (SPSR & (1 << SPIF))) {
continue;
}
return SPDR; // SPIF will be cleared
}
void RunShiftRegister(void) {
byte bitBucket;
int i;
digitalWrite(PIN_SS,HIGH);
EnableSPI(); // turn on the SPI hardware
for (i=0; i<5; i++) {
bitBucket = SendRecSPI(outbytes[i]);
}
DisableSPI(); // return to manual control
digitalWrite(PIN_SS,LOW);
}
void setup() {
pinMode(PIN_SCK,OUTPUT);
digitalWrite(PIN_SCK,LOW);
pinMode(PIN_SS,OUTPUT);
digitalWrite(PIN_SS,HIGH);
pinMode(PIN_MOSI,OUTPUT);
digitalWrite(PIN_MOSI,LOW);
pinMode(PIN_MISO,INPUT);
digitalWrite(PIN_MISO,HIGH);
SPCR = B01110001; // Auto SPI: no int, enable, LSB first, master, + edge, leading, f/16
SPSR = B00000000; // not double data rate
}
void loop(){
int i;
RunShiftRegister();
for (i=0; i<5; i++){
outbytes[i]= ~outbytes[i];
}
delay(1000);
};
Last year I cast a series of led arrays for thing-a-day but hadnt wired them up. With this in mind I ordered some hc595s in the last group order but the snow canceled the meeting.
I have been thinking about using the avr to drive the arrays directly and control them using i2c. I hacked 4 dorkboards by cutting the traces for pins 5-12 on the top of the board and soldering 220 ohm resistors on the other side. This gives a one chip solution which does not require a constant refresh or other attention.
/*
Simple 4 node array of leds (8x8).
*/
#include <Wire.h>
int node=0; /* CHANGE THIS BASED ON POSITION OF ARRAY */
volatile int twi_group_offset=128;
volatile int colpins[]={//12,11,10,9,8,7,6,5};
5,6,7,8,9,10,11,12};
volatile int rowpins[]={
17,16,15,14,13,4,3,2};
//2,3,4,13,14,15,16,17};
volatile unsigned long int myTics;
unsigned char pixels[]={0x3E, 0x51, 0x49, 0x45, 0x3E,0,0,node,
0x00, 0x42, 0x7F, 0x40, 0x00,0,1,1,
0x42, 0x61, 0x51, 0x49, 0x46,0,2,2,
0x21, 0x41, 0x45, 0x4B, 0x31,0,3,3
};
void setup() // run once, when the sketch starts
{
int i;
myTics=millis();
for(i=0;i<8;i++){
pinMode(rowpins[i],OUTPUT);
digitalWrite(rowpins[i],LOW);
pinMode(colpins[i],OUTPUT);
digitalWrite(colpins[i],HIGH);
} // sets the digital pin as output
if (node==0) {
Wire.begin();
Serial.begin(19200);
updateSlaveNodes();
} else {
Wire.begin(node+twi_group_offset);
Wire.onReceive(updatePixels); // register event
}
}
void loop() //move most of this into a timer loop.
{
int r,c;
int lastcolpin=colpins[0];
for (c=0;c<8;c++){
lastcolpin=colpins[c];
for(r=0;r<8;r++){
if ((pixels[c]>>r)&0x01){
//if ((!r)||((7-c)==r)||(c==r)){
digitalWrite(rowpins[r], HIGH);
} else {
digitalWrite(rowpins[r], LOW);
}
}
digitalWrite(colpins[c], LOW);
if (node==0) {
updateSlaveNodes();
} else {
//delayMicroseconds(1500); //
delay(3);
}
digitalWrite(lastcolpin, HIGH);
}
}
void updateSlaveNodes() {
int slavenode, row, pixel8;
pixel8=8;
for (slavenode=1;slavenode<4;slavenode++){
Wire.beginTransmission(slavenode+twi_group_offset);
for (row=0;row<8;row++) {
Wire.send(pixels[pixel8++]);
}
Wire.endTransmission();
};
}
// function that executes whenever data is received from master
// this function is registered as an event, see setup()
void updatePixels(int howMany)
{ int r=0;
while(Wire.available()) //
{
pixels[r++] = Wire.receive(); // receive byte as a character
}
}
The next step is to move the actual refresh into a timer routine so that the body of code for the main node can focus on content.
/*------------------------------------------------------------ xmas timer array
*
* This is the program for a 4x8x8 led display using a processor for each segment
* To program the different nodes set the node variable below. Node 0 is the
* master node which keeps the pixel memory for the remaining nodes.
*
* The master node takes its memory and sends updates to each of the other segments
* via the i2c buss using the Wire library.
*
* Each node takes its 8byte array of data and strobes it onto the display using
* the 8 bit timer2 overflow interrupt.
*
* The first 7 characters are loaded into the display memory using the
* loadCharacters function. The array is then shifted to the left in the main loop
* with the next character being loaded a collumn at a time.
*
* The character data and the bitmap for the 5x7 array are stored in program
* memory.
*
* Since interrupts are used all of the globals are marked as volatile.
*
* CopyLeft 2008 Donald Delmar Davis, Tempus Dictum, Inc.
* This is free software (GPL)
*/
#include <Wire.h>
#include <avr/interrupt.h>
#include <avr/io.h>
#include <avr/pgmspace.h>
#define FREEK 180
int node=0; /* CHANGE THIS BASED ON POSITION OF ARRAY */
unsigned char PROGMEM Font5x7[] = {
0x00, 0x00, 0x00, 0x00, 0x00,// (space)
0x00, 0x00, 0x5F, 0x00, 0x00,// !
0x00, 0x07, 0x00, 0x07, 0x00,// "
0x14, 0x7F, 0x14, 0x7F, 0x14,// #
0x24, 0x2A, 0x7F, 0x2A, 0x12,// $
0x23, 0x13, 0x08, 0x64, 0x62,// %
0x36, 0x49, 0x55, 0x22, 0x50,// &
0x00, 0x05, 0x03, 0x00, 0x00,// '
0x00, 0x1C, 0x22, 0x41, 0x00,// (
0x00, 0x41, 0x22, 0x1C, 0x00,// )
0x08, 0x2A, 0x1C, 0x2A, 0x08,// *
0x08, 0x08, 0x3E, 0x08, 0x08,// +
0x00, 0x50, 0x30, 0x00, 0x00,// ,
0x08, 0x08, 0x08, 0x08, 0x08,// -
0x00, 0x60, 0x60, 0x00, 0x00,// .
0x20, 0x10, 0x08, 0x04, 0x02,// /
0x3E, 0x51, 0x49, 0x45, 0x3E,// 0
0x00, 0x42, 0x7F, 0x40, 0x00,// 1
0x42, 0x61, 0x51, 0x49, 0x46,// 2
0x21, 0x41, 0x45, 0x4B, 0x31,// 3
0x18, 0x14, 0x12, 0x7F, 0x10,// 4
0x27, 0x45, 0x45, 0x45, 0x39,// 5
0x3C, 0x4A, 0x49, 0x49, 0x30,// 6
0x01, 0x71, 0x09, 0x05, 0x03,// 7
0x36, 0x49, 0x49, 0x49, 0x36,// 8
0x06, 0x49, 0x49, 0x29, 0x1E,// 9
0x00, 0x36, 0x36, 0x00, 0x00,// :
0x00, 0x56, 0x36, 0x00, 0x00,// ;
0x00, 0x08, 0x14, 0x22, 0x41,// <
0x14, 0x14, 0x14, 0x14, 0x14,// =
0x41, 0x22, 0x14, 0x08, 0x00,// >
0x02, 0x01, 0x51, 0x09, 0x06,//
0x32, 0x49, 0x79, 0x41, 0x3E,// @
0x7E, 0x11, 0x11, 0x11, 0x7E,// A
0x7F, 0x49, 0x49, 0x49, 0x36,// B
0x3E, 0x41, 0x41, 0x41, 0x22,// C
0x7F, 0x41, 0x41, 0x22, 0x1C,// D
0x7F, 0x49, 0x49, 0x49, 0x41,// E
0x7F, 0x09, 0x09, 0x01, 0x01,// F
0x3E, 0x41, 0x41, 0x51, 0x32,// G
0x7F, 0x08, 0x08, 0x08, 0x7F,// H
0x00, 0x41, 0x7F, 0x41, 0x00,// I
0x20, 0x40, 0x41, 0x3F, 0x01,// J
0x7F, 0x08, 0x14, 0x22, 0x41,// K
0x7F, 0x40, 0x40, 0x40, 0x40,// L
0x7F, 0x02, 0x04, 0x02, 0x7F,// M
0x7F, 0x04, 0x08, 0x10, 0x7F,// N
0x3E, 0x41, 0x41, 0x41, 0x3E,// O
0x7F, 0x09, 0x09, 0x09, 0x06,// P
0x3E, 0x41, 0x51, 0x21, 0x5E,// Q
0x7F, 0x09, 0x19, 0x29, 0x46,// R
0x46, 0x49, 0x49, 0x49, 0x31,// S
0x01, 0x01, 0x7F, 0x01, 0x01,// T
0x3F, 0x40, 0x40, 0x40, 0x3F,// U
0x1F, 0x20, 0x40, 0x20, 0x1F,// V
0x7F, 0x20, 0x18, 0x20, 0x7F,// W
0x63, 0x14, 0x08, 0x14, 0x63,// X
0x03, 0x04, 0x78, 0x04, 0x03,// Y
0x61, 0x51, 0x49, 0x45, 0x43,// Z
0x00, 0x00, 0x7F, 0x41, 0x41,// [
0x02, 0x04, 0x08, 0x10, 0x20,// "\"
0x41, 0x41, 0x7F, 0x00, 0x00,// ]
0x04, 0x02, 0x01, 0x02, 0x04,// ^
0x40, 0x40, 0x40, 0x40, 0x40,// _
0x00, 0x01, 0x02, 0x04, 0x00,// `
0x20, 0x54, 0x54, 0x54, 0x78,// a
0x7F, 0x48, 0x44, 0x44, 0x38,// b
0x38, 0x44, 0x44, 0x44, 0x20,// c
0x38, 0x44, 0x44, 0x48, 0x7F,// d
0x38, 0x54, 0x54, 0x54, 0x18,// e
0x08, 0x7E, 0x09, 0x01, 0x02,// f
0x08, 0x14, 0x54, 0x54, 0x3C,// g
0x7F, 0x08, 0x04, 0x04, 0x78,// h
0x00, 0x44, 0x7D, 0x40, 0x00,// i
0x20, 0x40, 0x44, 0x3D, 0x00,// j
0x00, 0x7F, 0x10, 0x28, 0x44,// k
0x00, 0x41, 0x7F, 0x40, 0x00,// l
0x7C, 0x04, 0x18, 0x04, 0x78,// m
0x7C, 0x08, 0x04, 0x04, 0x78,// n
0x38, 0x44, 0x44, 0x44, 0x38,// o
0x7C, 0x14, 0x14, 0x14, 0x08,// p
0x08, 0x14, 0x14, 0x18, 0x7C,// q
0x7C, 0x08, 0x04, 0x04, 0x08,// r
0x48, 0x54, 0x54, 0x54, 0x20,// s
0x04, 0x3F, 0x44, 0x40, 0x20,// t
0x3C, 0x40, 0x40, 0x20, 0x7C,// u
0x1C, 0x20, 0x40, 0x20, 0x1C,// v
0x3C, 0x40, 0x30, 0x40, 0x3C,// w
0x44, 0x28, 0x10, 0x28, 0x44,// x
0x0C, 0x50, 0x50, 0x50, 0x3C,// y
0x44, 0x64, 0x54, 0x4C, 0x44,// z
0x00, 0x08, 0x36, 0x41, 0x00,// {
0x00, 0x00, 0x7F, 0x00, 0x00,// |
0x00, 0x41, 0x36, 0x08, 0x00,// }
0x08, 0x08, 0x2A, 0x1C, 0x08,// ->
0x08, 0x1C, 0x2A, 0x08, 0x08 // <-
};
unsigned char PROGMEM banner[] = "Happy Holidays!!! Merry Christmas!!! May every man with \"Merry Christmas\" on his lips be boiled in his own blood pudding with a steak of holly driven through his heart! Christmas! BAH! HUMBUG!!!";
volatile int twi_group_offset=128;
volatile int colpins[]={5,6,7,8,9,10,11,12};
volatile int rowpins[]={17,16,15,14,13,4,3,2};
volatile unsigned long int myTics; //counter for timer interrupt.
volatile unsigned char pixels[]={
0x3E, 0x51, 0x49, 0x45, 0x3E,0,0,node,
0x00, 0x42, 0x7F, 0x40, 0x00,0,0,1,
0x42, 0x61, 0x51, 0x49, 0x46,0,0,2,
0x21, 0x41, 0x45, 0x4B, 0x31,0,0,3,
0x00, 0x00, 0x00, 0x00, 0,0,0,0}; //pixel buffer rounded out to another 8 bytes
volatile unsigned int cursorpos=0;
volatile unsigned int charcursor=0;
volatile unsigned int currentcol;
volatile unsigned int charmod;
volatile unsigned char currentchar;
volatile unsigned int currentcharoffset;
/*---------------------------------------------------------------------setup()
* initialize timer2
* setup the i/o pins (initialized with everything off);
* Initialize the i2c buss;
* if master node then load initial characters into display.
*/
void setup() // run once, when the sketch starts
{
int i;
currentcol=0;
/*------------ setting up timer two. ----------------*/
TCCR2A = 0; // normal mode
TCCR2B = 1<<CS22 | 1<<CS21 | 0<<CS20; // clock selection
TIMSK2 |= 1<<TOIE2; // enable overflow interupt
TCNT2=FREEK; // adjustment of period
ASSR=0; // paranoid
myTics=0; // counter for delays
sei(); // enable interrupts
for(i=0;i<8;i++){
pinMode(rowpins[i],OUTPUT);
digitalWrite(rowpins[i],LOW);
pinMode(colpins[i],OUTPUT);
digitalWrite(colpins[i],HIGH);
}
if (node==0) { // master node
Wire.begin();
Serial.begin(19200);
loadCharacters();
updateSlaveNodes();
} else {
Wire.begin(node+twi_group_offset);
Wire.onReceive(updatePixels); // register event
}
}
/*------------------------------------------------------------ISR(TIMER2_OVF_vect)
* loads the next collumn into the led array
*
*/
ISR(TIMER2_OVF_vect) {
int r;
digitalWrite(colpins[currentcol], HIGH); //turn off the last column.
if (++currentcol > 7) {
currentcol=0;
}
for(r=0;r<8;r++){
if ((pixels[currentcol]>>r)&0x01){
digitalWrite(rowpins[r], HIGH);
} else {
digitalWrite(rowpins[r], LOW);
}
}
digitalWrite(colpins[currentcol], LOW);
myTics++;
TCNT2 = FREEK;
};
/*--------------------------------------------------------------loadCharacters()
* load the initial characters into the pixel buffer
*/
void loadCharacters(){
int ch,i,j;
cursorpos=0;
for (charcursor=0;charcursor<7;charcursor++) {
ch=pgm_read_byte_near(banner+charcursor);
j= (ch-32) & 0x000000ff;
j = j*5;
for (charmod=0;charmod<5;charmod++){
pixels[cursorpos++]=pgm_read_byte_near(Font5x7+j+charmod);
}
}
}
/*------------------------------------------------------------updateSlaveNodes()
* send the pixel data to the slave nodes.
*/
void updateSlaveNodes() {
int slavenode, row, pixel8;
if (node==0) {
pixel8=8;
for (slavenode=1;slavenode<4;slavenode++){
Wire.beginTransmission(slavenode+twi_group_offset);
for (row=0;row<8;row++) {
Wire.send(pixels[pixel8++]);
}
Wire.endTransmission();
};
}
}
/*------------------------------------------------------------------udatePixels()
* recieve the pixel data from the master node.
* executes whenever data is received from master
* this function is registered as an event, see setup()
*/
void updatePixels(int ignored)
{ int r=0;
while(Wire.available()) //
{
pixels[r++] = Wire.receive(); // receive byte as a character
}
}
/*-------------------------------------------------------------------------loop()
*
*/
void loop()
{int c;
//int head=pixels[0];
updateSlaveNodes();
for (c=1;c<35;c++) {
pixels[c-1]=pixels[c];
}
if (charmod==5) {//get next char
charcursor++;
charmod=0;
currentchar=pgm_read_byte_near(banner+charcursor);
if (currentchar=='\0'){
currentchar=pgm_read_byte_near(banner);
charcursor=0;
}
currentcharoffset = (currentchar-32) & 0x000000ff;
currentcharoffset = currentcharoffset*5;
}
pixels[34]=pgm_read_byte_near(Font5x7+currentcharoffset+charmod);
charmod++;
while (myTics<90) ; myTics=0L; //wait a few hundred millisecconds
}