290 lines
11 KiB
C
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;
|
|
}
|