I2C_LED_Matrix/main.c
2008-07-16 05:44:45 +00:00

290 lines
11 KiB
C

/**
* \file main.c
* \brief firmware for the i2c-ledmatrix
*
* this is a really simple piece of code, since the main work is done by the
* I2C-library.
* \author Ronald Schaten <ronald@schatenseite.de>
* \version $Id: main.c,v 1.1 2008/07/16 05:44:45 rschaten Exp $
*
* Permission to use, copy, modify, and distribute this software and its
* documentation under the terms of the GNU General Public License is hereby
* granted. No representations are made about the suitability of this software
* for any purpose. It is provided "as is" without express or implied warranty.
* See the GNU General Public License for more details.
*/
/**
* \mainpage I2C LED Matrix
*
* \section sec_intro Introduction
*
* This project turns an AVR ATmega8 microcontroller into a LED controller for
* a matrix of 8x8 LEDs. The controller is acting as I2C-slave, so you can
* control the patterns to display via this bus (also known as TWI, Two Wire
* Interface).
*
* \section sec_purpose Purpose
*
* For my next project, I need to display number values on
* seven-segment-displays. I bought a bunch of 4-digit-displays a while ago,
* now I'm going to put them to a use. They are built with four digits in one
* case, and 12 pins on the underside. Eight of them are the cathodes of the
* segments (seven segments plus dot), four are the anodes. One for each digit.
*
* You can imagine these modules as a matrix of four times eight LEDs, as can
* be seen in the included circuit. I use two of these, so I have a matrix of
* eight times eight LEDs.
*
* The rows and columns of this matrix are connected to the microcontroller, so
* it can power them row by row. This has two advantages: at first a maximum of
* eight LEDs is powered at a time, so power consumption is lowered. And at
* second you need only 16 pins of the controller to address a total of 64
* LEDs.
*
* Driving the LEDs in this way makes them flicker a bit, but the controller is
* fast enough to keep the flickering way above the level you would be able to
* recognize.
*
* I could have connected my display modules directly to the main controller of
* my next project, but I don't have enough free pins on that. As a further
* benefit, multiplexing the LEDs on a second controller makes the main program
* easier to write, since I don't have to mind the timing. So the solution is
* to use a cheap ATmega8 as LED driver and use the I2C-bus to tell it what to
* display.
*
* \section sec_i2c I2C communication
*
* The ATmega8 has a built-in hardware I2C-interface, so it doesn't take very
* much code to use it. Nevertheless, I used a little library that <strong>Uwe
* Grosse-Wortmann (uwegw)</strong> published on <a
* href="http://www.roboternetz.de/wissen/index.php/TWI_Slave_mit_avr-gcc">roboternetz.de</a>.
* I only reformatted it a bit to make the code resemble my style. It is well
* commented, but the comments are in german. Since only one global array, one
* init-function and an interrupt service routine are used, it shouldn't be too
* hard for english-speaking people to figure out how it is used.
*
* \subsection sec_usage Usage
*
* On the other end of the communication, I used the excellent <strong>Procyon
* AVRlib written by Pascal Stang</strong>. You can find it <a
* href="http://hubbard.engr.scu.edu/embedded/avr/avrlib">here</a>.
*
* A basic code example would look like this:
*
* \code
* #define I2C_LEDMATRIX 0x10 // address of the device
* timerInit(); // initialize timers
* timerPause(100); // give everything a little time to settle
* i2cInit(); // initialize i2c function library
* timerPause(100); // wait a bit more
* while (1) { // endless loop
* uint8_t buffer[9]; // prepare buffer
* // loop until 255
* for (uint8_t i = 0; i <= 255; i++) {
* // set all bytes of the buffer to value i
* memset(buffer, i, sizeof(buffer));
* // send the buffer via I2C-bus
* i2cMasterSend(I2C_LEDMATRIX, sizeof(buffer), buffer);
* timerPause(500); // wait, so you have the time to watch
* }
* }
* \endcode
*
* Note: the buffer doesn't contain any numbers that should be displayed on
* 7segment-displays. At least not in this example. It only holds bit-patterns.
*
* \subsection sec_numbers Displaying numbers
*
* If you solder 7segment displays to the unit and intend to display numbers or
* characters on it, you need to define them on the master-side of the bus. I
* didn't include the definitions in this library because I want the master to
* have the full flexibility of displaying whatever it wants to, even if it are
* no numbers.
*
* However, if you are going to use 7segment displays, definition of the
* numbers still depends on how you soldered them to the controller. I don't
* know if the pin-outs are commonly standardized.
*
* To give an example of how you would implement this, here is a fragment of
* code that defines hexadecimal numbers for usage on my displays:
*
* \code
* // Names of the segments:
* // aaaaa
* // f b
* // f b
* // ggggg
* // e c
* // e c
* // ddddd h
* uint8_t characters[16];
* // c e g a h f b d
* characters[ 0] = (1 << 0) | (1 << 1) | (0 << 2) | (1 << 3) | (0 << 4) | (1 << 5) | (1 << 6) | (1 << 7); // 0
* characters[ 1] = (1 << 0) | (0 << 1) | (0 << 2) | (0 << 3) | (0 << 4) | (0 << 5) | (1 << 6) | (0 << 7); // 1
* characters[ 2] = (0 << 0) | (1 << 1) | (1 << 2) | (1 << 3) | (0 << 4) | (0 << 5) | (1 << 6) | (1 << 7); // 2
* characters[ 3] = (1 << 0) | (0 << 1) | (1 << 2) | (1 << 3) | (0 << 4) | (0 << 5) | (1 << 6) | (1 << 7); // 3
* characters[ 4] = (1 << 0) | (0 << 1) | (1 << 2) | (0 << 3) | (0 << 4) | (1 << 5) | (1 << 6) | (0 << 7); // 4
* characters[ 5] = (1 << 0) | (0 << 1) | (1 << 2) | (1 << 3) | (0 << 4) | (1 << 5) | (0 << 6) | (1 << 7); // 5
* characters[ 6] = (1 << 0) | (1 << 1) | (1 << 2) | (1 << 3) | (0 << 4) | (1 << 5) | (0 << 6) | (1 << 7); // 6
* characters[ 7] = (1 << 0) | (0 << 1) | (0 << 2) | (1 << 3) | (0 << 4) | (0 << 5) | (1 << 6) | (0 << 7); // 7
* characters[ 8] = (1 << 0) | (1 << 1) | (1 << 2) | (1 << 3) | (0 << 4) | (1 << 5) | (1 << 6) | (1 << 7); // 8
* characters[ 9] = (1 << 0) | (0 << 1) | (1 << 2) | (1 << 3) | (0 << 4) | (1 << 5) | (1 << 6) | (1 << 7); // 9
* characters[10] = (1 << 0) | (1 << 1) | (1 << 2) | (1 << 3) | (0 << 4) | (1 << 5) | (1 << 6) | (0 << 7); // a
* characters[11] = (1 << 0) | (1 << 1) | (1 << 2) | (0 << 3) | (0 << 4) | (1 << 5) | (0 << 6) | (1 << 7); // b
* characters[12] = (0 << 0) | (1 << 1) | (0 << 2) | (1 << 3) | (0 << 4) | (1 << 5) | (0 << 6) | (1 << 7); // c
* characters[13] = (1 << 0) | (1 << 1) | (1 << 2) | (0 << 3) | (0 << 4) | (0 << 5) | (1 << 6) | (1 << 7); // d
* characters[14] = (0 << 0) | (1 << 1) | (1 << 2) | (1 << 3) | (0 << 4) | (1 << 5) | (0 << 6) | (1 << 7); // e
* characters[15] = (0 << 0) | (1 << 1) | (1 << 2) | (1 << 3) | (0 << 4) | (1 << 5) | (0 << 6) | (0 << 7); // f
* \endcode
*
* \section sec_install Building and installing
*
* The firmware is built and installed on the controller with the included
* makefile. You might need to need to customize it to match your individual
* environment.
*
* If you take a brand-new controller you shouldn't have to hassle with the
* fuses of the controller. The internal oscillator at 1MHz is enough to keep
* the display flicker-free. The settings I used are included in the makefile,
* so you can use it to reset controllers you already changed in other
* projects.
*
* Oh, and if you want the slave to use an I2C-address different from 0x10: no
* problem. Just change it in the code.
*
* \section sec_drawbacks Drawbacks
*
* Till now, the device worked in all situations I tested it in. So far
* everything is fine.
*
* \section sec_files Files in the distribution
*
* - \e Readme.txt: Documentation, created from the htmldoc-directory.
* - \e htmldoc/: Documentation, created from main.c.
* - \e refman.pdf: Documentation, created from main.c.
* - \e main.c: Source code of the firmware.
* - \e main_*.hex: Compiled version of the firmware.
* - \e twislave.c: I2C-library.
* - \e twislave.h: I2C-library.
* - \e project.doxygen: Support for creating the documentation.
* - \e License.txt: Public license for all contents of this project.
* - \e Changelog.txt: Logfile documenting changes in soft-, firm- and
* hardware.
*
* \section sec_thanks Thanks!
*
* I'd like to thank the authors of the libraries I used: <strong>Uwe
* Grosse-Wortmann (uwegw)</strong> for the I2C-slave and <strong>Pascal
* Stang</strong> for the Procyon AVRlib.
*
* \section sec_license About the license
*
* My work is licensed under the GNU General Public License (GPL). A copy of
* the GPL is included in License.txt.
*
* <b>(c) 2008 by Ronald Schaten - http://www.schatenseite.de</b>
*/
#include <avr/io.h>
#include <avr/interrupt.h>
#include <avr/wdt.h>
#include <util/delay.h>
#include <avr/pgmspace.h> // keeping constants in program memory
#include "twislave.h"
/**
* initialize hardware
*/
void init_ports(void){
// set DDR for all digit-pins
DDRB = 0xff;
DDRC |= (1 << PINC0) | (1 << PINC1);
// unset PORT for all digit-pins
PORTB = 0x00;
PORTC &= ~((1 << PINC0) | (1 << PINC1));
DDRD = 0x00; // segment selector
PORTD = 0x00; // segments, has to be 0x00
}
/**
* select which digit should be displayed
* \param digit number of the digit
*/
void selectDigit(uint8_t digit) {
switch (digit) {
case 0:
case 1:
case 2:
case 3:
case 4:
case 5:
PORTB = (1 << digit);
PORTC &= ~((1 << PINC0) | (1 << PINC1));
break;
case 6:
PORTB = 0x00;
PORTC &= ~((1 << PINC1));
PORTC |= (1 << PINC0);
break;
case 7:
PORTB = 0x00;
PORTC &= ~((1 << PINC0));
PORTC |= (1 << PINC1);
break;
default:
PORTB = 0x00;
PORTC = 0x00;
}
}
/**
* set output of the currently selected digit
* \param byte bit-pattern to show
*/
void showByte(uint8_t byte) {
DDRD = byte;
}
/**
* show a pattern on a certain digit (or row, if you don't use 7segment
* displays). the output is cleared before selecting the new digit, so there
* won't be 'shadows' on the display.
* \param digit number of the digit
* \param byte bit-pattern to show
*/
void showDigitByte(uint8_t digit, uint8_t byte) {
showByte(0x00);
selectDigit(digit);
showByte(byte);
}
/**
* main-function. initializes everything and contains the main loop which
* controls the actual output. the rxbuffer[] is filled from the I2C-library,
* so we just have go through the array and display its values on the
* corresponding digit.
* \return An integer. Whatever... :-)
*/
int main(void) {
// initialize output ports
init_ports();
// init watchdog
wdt_enable(WDTO_15MS); // 15ms watchdog
// init I2C communication
init_twi_slave(0x10);
while (1) {
wdt_reset(); // feed the watchdog
for (uint8_t digit = 0; digit <= 7; digit++) {
// display all eight digits
showDigitByte(digit, rxbuffer[digit]);
}
}
return 0;
}