/*
Able to use PROGMEM by using #include avr/pgmspace.h
See https://www.nongnu.org/avr-libc/user-manual/group__avr__pgmspace.html#ga73084a8bbde259ffae72980354b3f027
for details.
CM 10/15/24: This sketch is working well.
- Create a huminoid looking robot with the 8x8 display as its head. The body could
encorperate the ATtiny85, maybe a pulsing LED for a heart?
- Cleaned up sketch, removed Debug.print lines.
- Nearly ready for production.
CM 1/15/25: Project moved to breadboard, running on ATtiny85.
- Add code to have cpu go to sleep when motion not detected and wake with PIR interrupt.
*/
#include <avr/pgmspace.h> // used with 'PROGMEM'
#include "data_bytes.h" // MAX7219 animation arrays
#include <avr/io.h>
#include <avr/interrupt.h> // required for ISR(INT0_vect) macro
#include <TinyDebug.h>
// These variables are for 8x8 display
static uint8_t MAX7219_state[8] = {0,0,0,0,0,0,0,0}; // used with funtion MAX7219_set_pixel
unsigned int randNum_row; // used with function: random_pixels
unsigned int randNum_col; // used with function: random_pixels
uint8_t pixel = 0; // used with function: random_pixels
// These variable are for PIR
uint8_t timeSeconds = 10;
#define ledEyes PB4
#define motionSensor PB3 // PIR pin
// Timer: Auxiliary variables
unsigned long lastTrigger = 0;
boolean startTimer = false;
boolean motion = false;
uint8_t randomNum;
// Interrupt routine
// Runs when motion is detected, sets ledEyes HIGH and starts a timer
ISR (PCINT0_vect) { // Interrupt service routine
digitalWrite(ledEyes, HIGH);
randomNum = random(1, 8); // random number 1 to 7
startTimer = true;
lastTrigger = millis();
}
void setup() {
Debug.begin();
pinMode(ledEyes, OUTPUT);
pinMode(motionSensor, INPUT_PULLUP); // PIR Motion Sensor mode INPUT_PULLUP
pin_change_interrupt();
MAX7219_init();
/*
if analog input pin (pin number) is unconnected, random analog noise will cause the call to randomSeed() to generate
different seed numbers each time the sketch runs. randomSeed() will then shuffle the random function.
*/
randomSeed(analogRead(A0)); // Pin PB5 = ADC0
digitalWrite(ledEyes, LOW); // Set initial state of led to LOW
}
void loop() {
if ((digitalRead(ledEyes) == HIGH) && (motion == false)) {
Debug.println("MOTION DETECTED!!!");
Debug.print("motionSensor = "); Debug.println(digitalRead(motionSensor));
if (randomNum == 1) { // - - - WORKS - - -
while ((millis() - lastTrigger) < timeSeconds * 1000) {
display_byte_array(smiley);
delay(100);
}
}
if (randomNum == 2) { // - - - WORKS - - - set 'delayTime' to .1
while ((millis() - lastTrigger) < timeSeconds * 1000) {
display_byte_2D_array(dogface, 0);
delay(200);
display_byte_2D_array(dogface, 1);
delay(200);
}
}
if (randomNum == 3) { // - - - WORKS - - - set 'delayTime' to .1
while ((millis() - lastTrigger) < timeSeconds * 1000) {
scrolling_2D_array_with_delay(invader1);
scrolling_2D_array_with_delay(invader2);
}
}
if (randomNum == 4) { // - - - WORKS - - - set 'delayTime' to .1
while ((millis() - lastTrigger) < timeSeconds * 1000) {
scrolling_2D_array(running_man_LR, 21);
}
}
if (randomNum == 5) { // - - - WORKS - - - set 'delayTime' to .1
while ((millis() - lastTrigger) < timeSeconds * 1000) {
scrolling_2D_array(LSU, 28);
}
}
if (randomNum == 6) { // - - - WORKS - - -
while ((millis() - lastTrigger) < timeSeconds * 1000) {
scrolling_2D_array(dancing_man_RL, 45);
}
}
if (randomNum == 7) { // - - - WORKS - - -
while ((millis() - lastTrigger) < timeSeconds * 1000) {
random_pixels(500);
}
}
motion = true; // setting 'motion' to true ensures if statement runs only once
}
// Turn off MAX7219 and led after time limit assigned in timeSeconds variable
if (startTimer) {
Debug.println("Motion stopped...");
Debug.print("motionSensor = "); Debug.println(digitalRead(motionSensor));
digitalWrite(ledEyes, LOW);
startTimer = false;
motion = false;
MAX7219_clear_display();
}
}
// = = = = = = = = = = = FUNCTIONS = = = = = = = = = = = = =
void pin_change_interrupt() {
// GIMSK – General Interrupt Mask Register
// Setting PCIE bit enables the pin change interrupt.
// Also need to configure the pins that will be triggered by this interrupt.
GIMSK |= (1 << PCIE); // enable PCIE (pin change interrupt)
PCMSK |= (1 << PCINT3); // Set which pin will trigger the interrupt
// same as SREG – Status Register: • Bit 7 – I: Global Interrupt Enable
sei(); // 'Set Enable Interrupt': (global) interrupt enable bit, turning on all interrupts that have been configured.
}
void MAX7219_init() {
// PB0 -> DIN, PB1 -> CS, PB2 -> CLK
DDRB |= (1 << PB0) | (1 << PB1) | (1 << PB2); // Set pins as outputs
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
MAX7219_write(0x09, 0); // Decode-Mode Register: No decode for digits 7–0
MAX7219_write(0x0A, 8); // Intensity Register: Format 0 to 16
MAX7219_write(0x0B, 7); // Scan-Limit Register: Display digits 0 1 2 3 4 5 6 7
MAX7219_write(0x0C, 1); // Shutdown Register: Normal Operation, turn on MAX7219
}
// = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
void SPI_send(uint8_t data) { // variable 'data' is used in 'command_byte' and 'data_byte'
for (uint8_t i = 0; i < 8; i++) { // try uint8_t i = 8; i >= 1; i-- (no difference) // Loop 8 times to shift out MSB first, of byte to MAX7219
PORTB &= ~(1 << PB2); // Set CLK to LOW
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
if (data & 0b10000000) { // Mask the MSB 0x80 of the data
PORTB |= (1 << PB0); // Set DIN to HIGH if MSB = 1
}
else (PORTB &= ~(1 << PB0)); // Set DIN to LOW if MSB = 0
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
PORTB |= (1 << PB2); // Set CLK to HIGH
data = data << 1; // Shift data to left 1 bit
}
}
// = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
// Write to a row, 1 through 8
void MAX7219_write(uint8_t command_byte, uint8_t data_byte) {
PORTB &= ~(1 << PB1); // CS set LOW to enable MAX7219
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
SPI_send(command_byte); // Send command byte
SPI_send(data_byte); // Send data byte
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
PORTB |= (1 << PB1); // CS set HIGH to disable MAX7219
}
// = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
void MAX7219_clear_display() {
uint8_t i;
for (i = 0; i < 8; i++) {
MAX7219_write(i + 1, 0);
}
}
// = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
// Not used in this sketch: MAX7219_set_pixel
// Display one pixel, (row 0 to 7, column 0 to 7, ON = 1)
void MAX7219_set_pixel(uint8_t row, uint8_t col, bool value) {
uint8_t data;
if (row > 7 || col > 7)
return;
data = 1 << col;
if (value) {
MAX7219_state[row] |= data;
}
else {
MAX7219_state[row] &= ~data;
}
MAX7219_write(row + 1, MAX7219_state[row]);
}
// = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
// Display a static 8x8 image from a 1D array
void display_byte_array(uint8_t image[8]) {
uint8_t i;
for (i = 0; i < 8; i++) {
MAX7219_write(i + 1, image[i]); // Write to rows 1 through 8
}
}
// = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
// Displays a static 8x8 image from one row of a 2D array
void display_byte_2D_array(uint8_t image[][8], uint8_t num) {
uint8_t i;
for (i = 0; i < 8; i++) {
// MAX7219_write(i + 1, image[num][i]); // Use without PROGMEM
MAX7219_write(i + 1, pgm_read_byte(&image[num][i])); // Use with PROGMEM
}
}
// = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
// Displays scrolling 'invader' array, with delay (array name)
void scrolling_2D_array_with_delay(uint8_t image[][8]) {
for (uint8_t j = 0; j < 21; j++) {
if (j == 9 || j == 10 || j == 11 || j == 12) {
delay(250);
}
else {
delay(100);
}
for (uint8_t i = 0; i < 8; i++) {
// MAX7219_write(i + 1, image[j][i]); // Use without PROGMEM
MAX7219_write(i + 1, pgm_read_byte(&image[j][i])); // Use with PROGMEM
}
}
}
// = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
// Displays scrolling array, (array name, number of rows)
void scrolling_2D_array(uint8_t image[][8], uint8_t num) {
for (uint8_t j = 0; j < num; j++) {
delay(180);
for (uint8_t i = 0; i < 8; i++) {
//MAX7219_write(i + 1, image[j][i]); // Use without PROGMEM
MAX7219_write(i + 1, pgm_read_byte(&image[j][i])); // Use with PROGMEM
}
}
}
// = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
// displays random pixels. Time of while loop is set in function
// with '100' equaling 1 second. delay(10) x 100 = 1000ms
void random_pixels(uint16_t count) {
uint16_t num = 0;
while (num < count) {
randNum_row = random(8);
randNum_col = random(8);
MAX7219_set_pixel(randNum_row, randNum_col, ++pixel & 0x01);
delay(10);
num = num + 1;
}
}
// = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =