#ifndef __usbledfader_h_included__ #define __usbledfader_h_included__ /** * \file usbledfader.h * \brief Global definitions and datatypes, used by the firmware and the commandline-client. Also contains the main doxygen-documentation. * \author Ronald Schaten & Thomas Stegemann * \version $Id: usbledfader.h,v 1.2 2006/10/01 16:28:01 rschaten Exp $ * * License: See documentation. */ /** * \mainpage USB-LED-Fader * * \section sec_intro Introduction * * The USB-LED-Fader is a device to control a number of LEDs via USB. I built * it to display the online-status of my internet-connection, the * recording-status of my videorecorder, and warnings if the available * disc-space is low. You can imagine an endless number of applications for * this. * * The LEDs are controlled with pulse width modulation (PWM). That way, they * are not only on or off, it is possible to control the brightness. Included * in the device is a number of 'waveforms' that can be displayed on the LEDs. * That way, one LED can display some kind of a sinus- or triangular wave * without any interaction with the controlling host. * * Every LED can be controlled individually, each one can display it's own * waveforms. * * You can assign three different waves to every LED: two 'eternal' waves (0 & * 1). They are displayed alternating until anything different is required. The * third wave (2) is only displayed once, afterwards the device will switch * back to alternating between the first two waves. * * One wave is described by three parameters: the waveform, the duration for * one repetition of the wave and the number of repetitions before switching to * the next wave. * * This version supports four LEDs, it should be quite easy to change that * number between one and eight. I have not tested any number greater than * four, but I can imagine that the load on the controller can be too high to * reliably communicate via USB. * * There are three parts included in the distribution: The firmware for an * ATmega8 microcontroller, a commandline-client that can be run under Linux, * and the circuits needed to build the device. * * This project is based on the PowerSwitch example application by Objective * Development. Like that, it uses Objective Development's firmware-only USB * driver for Atmel's AVR microcontrollers. * * Objective Development's USB driver is a firmware-only implementation of the * USB 1.1 standard (low speed device) on cheap single chip microcomputers of * Atmel's AVR series, such as the ATtiny2313 or even some of the small 8 pin * devices. It implements the standard to the point where useful applications * can be implemented. See the file "firmware/usbdrv/usbdrv.h" for features and * limitations. * * \section sec_install Building and installing * * Both, the firmware and Unix command line tool are built with "make". You may * need to customize both makefiles. * * \subsection sec_fw Firmware * * The firmware for this project requires avr-gcc and avr-libc (a C-library for * the AVR controller). Please read the instructions at * http://www.nongnu.org/avr-libc/user-manual/install_tools.html for how to * install the GNU toolchain (avr-gcc, assembler, linker etc.) and avr-libc. * * Once you have the GNU toolchain for AVR microcontrollers installed, you can * run "make" in the subdirectory "firmware". You may have to edit the Makefile * to use your preferred downloader with "make program". The current version is * built for avrdude with a parallel connection to an stk200-compatible * programmer. * * If working with a brand-new controller, you may have to set the fuse-bits to * use the external crystal: * * \code * avrdude -p atmega8 -P /dev/parport0 -c stk200 -U hfuse:w:0xC9:m -U lfuse:w:0x9F:m * \endcode * * Afterwards, you can compile and flash to the device: * * \code * make program * \endcode * * \subsection sec_client Commandline client * * The command line tool requires libusb. Please take the packages from your * system's distribution or download libusb from http://libusb.sourceforge.net/ * and install it before you compile. Change to directory "commandline", check * the Makefile and edit the settings if required and type * * \code * make * \endcode * * This will build the unix executable "usb-led-fader" which can be used to * control the device. * * \section sec_usage Usage * * Connect the device to the USB-port. All LED should flash up to indicate that * the device is initialized. * * Then use the commandline-client as follows: * * \code * usb-led-fader status * usb-led-fader set * usb-led-fader clear * usb-led-fader reset * usb-led-fader show * usb-led-fader test * \endcode * * When using the set-function, it is possible to define several waves at once. * You simply have to give the parameters for all waves. See examples below. * * \subsection sec_params Parameters * * - \e ledId: ID of the LED (0-n, depending on the number of LEDs in your * circuit). * - \e waveId: ID of the wave (0-1: constant waves, 2: override). * - \e waveformId: ID of the waveform (0-31: brightness, 32-37: patterns). For * a reference to the patterns, consult the function fade_calculateWaveform() * in the file "firmware/main.c". * - \e periodDuration: Time in sec/10 for one repetition of the waveform. A * value of 0 can be used to reset the wave. * - \e repetitionCount: Number of repetitions before switching to the next * wave. A value of 0 can be used to repeat this forever. * * \subsection sec_examples Examples * * Get the status of all LEDs: * \code * usb-led-fader status * \endcode * This will result in an output similar to this: * \code * LED 0 curid curvalue curpos currep nextupd * 0 2 26 0 23 * wave waveform length repeat duration updtime * 0 38 32 1 20 45 * 1 0 1 1 0 1 * 2 0 1 1 0 1 * LED 1 curid curvalue curpos currep nextupd * 0 14 19 0 19 * wave waveform length repeat duration updtime * 0 38 32 1 20 45 * 1 0 1 1 0 1 * 2 0 1 1 0 1 * LED 2 curid curvalue curpos currep nextupd * 0 31 16 0 43 * wave waveform length repeat duration updtime * 0 38 32 1 20 45 * 1 0 1 1 0 1 * 2 0 1 1 0 1 * LED 3 curid curvalue curpos currep nextupd * 0 6 9 0 39 * wave waveform length repeat duration updtime * 0 38 32 1 20 45 * 1 0 1 1 0 1 * 2 0 1 1 0 1 * \endcode * In this output, the values curvalue, curpos, nextupd and updtime are for * debugging purposes only. They shouldn't be of interest to the common user. * The meaning of the other values should be clear. * * Set the first LED to keep a middle brightness: * \code * usb-led-fader set 0 0 15 10 1 * \endcode * So, on LED 0 the wave 0 is set to waveform 15. It will stay there for one * second and will be repeated once before switching to the next wave. There is * no next wave because we didn't define one, so this waveform will stay * forever. * * Now set a second wave on the first LED, a little brighter than the one * before: * \code * usb-led-fader set 0 1 25 10 1 * \endcode * This is wave 1 on LED 0, waveform 25 indicates a constant level of * brightness. After setting the second wave, it will alternate with the first * one after every second, because both waves have the same duration and the * same number of repetitions. * * Set a third wave on the first LED: * \code * usb-led-fader set 0 2 36 20 5 * \endcode * This sets the third wave (wave 2) on the first LED. Waveform 36 is a nice * sinus-like wave, so the LED starts to fade. One period of the fading takes 2 * seconds, it is repeated for 5 times. Since this is the third wave, after the * repetitions the LED returns to alternating between wave 0 and wave 1, this * wave is discarded. * * Set multiple waves at once: * \code * usb-led-fader set 0 0 15 10 1 0 1 25 10 1 0 2 36 20 5 * \endcode * This will set all of the above waves at once. Thus, the first LED will first * fade the sinus-wave five times, then start alternating between the two * brightnesses in one-second-rhythm. * * Clear the first LED: * \code * usb-led-fader clear 0 * \endcode * This will clear all three waves on the first LED. * * Reset the device: * \code * usb-led-fader reset * \endcode * All LEDs will flash once, to indicate that the device is reset and the LEDs * are working. * * Show a waveform on the screen: * \code * usb-led-fader show 36 * \endcode * This will lead to an output like the following: * \code * wave 36 - length 64 * 31: ***** * 30: ********* * 29: *********** * 28: *************** * 27: ***************** * 26: ******************* * 25: ******************* * 24: ********************* * 23: *********************** * 22: ************************* * 21: ************************* * 20: *************************** * 19: ***************************** * 18: ***************************** * 17: ******************************* * 16: ********************************* * 15: *********************************** * 14: *********************************** * 13: ************************************* * 12: *************************************** * 11: *************************************** * 10: ***************************************** * 9: ******************************************* * 8: ********************************************* * 7: ********************************************* * 6: *********************************************** * 5: ************************************************* * 4: ***************************************************** * 3: ******************************************************* * 2: *********************************************************** * 1: **************************************************************** * ================================================================ * \endcode * Keep in mind that the width of the displayed wave corresponds to the length * of the waveform. If you display a very simple one like the constant * brightness levels (0-31), the length is 1. Therefore only one column is * displayed. * * Test the device: * \code * usb-led-fader test * \endcode * This function sends many random numbers to the device. The device returns * the packages, and the client looks for differences in the sent and the * received numbers. * * \section sec_drawbacks Drawbacks * * As mentioned above, controlling the PWM for several LEDs is a lot of work * for one small microcontroller. Speaking the USB protocol is so, either. Both * combined result in a lot of load on the device, so the communication with * the device is not 100% reliable. More than 99% though, at least in our * tests. * * SO BE WARNED: You should not use this device to control the state of * your nuclear reactor. If you intend to use it in that way despite of this * warning, please let me know... ;-) * * * \section sec_files Files in the distribution * * - \e Readme.txt: The file you are currently reading. * - \e firmware: Source code of the controller firmware. * - \e firmware/usbdrv: USB driver -- See Readme.txt in this directory for * info * - \e commandline: Source code of the host software (needs libusb). * - \e common: Files needed by the firmware and the commandline-client. * - \e circuit: Circuit diagrams in PDF and EAGLE 4 format. A free version of * EAGLE is available for Linux, Mac OS X and Windows from * http://www.cadsoft.de/. * - \e License.txt: Public license for all contents of this project, except * for the USB driver. Look in firmware/usbdrv/License.txt for further info. * - \e Changelog.txt: Logfile documenting changes in soft-, firm- and * hardware. * * \section sec_thanks Thanks! * * I'd like to thank Objective Development for the possibility to use * their driver for my project. In fact, this project wouldn't exist without * the driver. * * And I'd like to give special credits to Thomas Stegemann. He wrote * the PWM-stuff, and I guess it would have been nearly to impossible to me to * write the rest of the project without his help since C isn't my natural * language. * * \section sec_license About the license * * Our work - all contents except for the USB driver - are licensed under the * GNU General Public License (GPL). A copy of the GPL is included in * License.txt. The driver itself is licensed under a special license by * Objective Development. See firmware/usbdrv/License.txt for further info. * * (c) 2006 by Ronald Schaten - http://www.schatenseite.de */ #include /* return codes for USB-communication */ #define msgOK 0 /**< Return code for OK. */ #define msgErr 1 /**< Return code for Error. */ /* These are the vendor specific SETUP commands implemented by our USB device */ #define CMD_ECHO 0 /**< Command to echo the sent data */ #define CMD_GET 1 /**< Command to fetch values */ #define CMD_SET 2 /**< Command to send values */ #define CMD_CLEAR 3 /**< Command to switch off a certain LED */ #define CMD_RESET 4 /**< Command to reset the whole device */ /** Description of one waveform. */ typedef struct S_fade_Waveform { uint8_t waveformId; /**< ID of this waveform. */ uint8_t waveformLength; /**< Length of this waveform. */ uint8_t waveformRepetition; /**< How often is this waveform to be repeated? */ uint8_t waveformDuration; /**< Duration for one cycle of this waveform, stored for status-output. */ uint32_t waveformUpdateTime; /**< Time between two waveform-samples in calls of timerInterrupt(), calculated from waveformDuration. */ } fade_Waveform; /** The state of one LED. */ typedef struct S_fade_LedState { fade_Waveform wave[3]; /**< Three waveforms: base-function1, base-function2 and override-function. */ uint8_t waveCurrentId; /**< Which of the three waveforms is currently displayed? */ uint8_t waveCurrentValue; /**< The current brightness. */ uint8_t waveCurrentPosition; /**< Our position in the current waveform. */ uint8_t waveCurrentRepetition; /**< We are in the n-th repetition. */ int32_t waveNextUpdate; /**< Number of cycles till next update. */ } fade_LedState; /** Contains the state of all four LEDs. */ typedef struct S_fade_GlobalData { fade_LedState led[4]; /**< Data for four LEDs. */ } fade_GlobalData; uint8_t fade_calculateWaveform(uint8_t waveformId, uint8_t waveformPosition); /** * Calculate a waveform. Returns either the length of a given waveform or the * output-level at a certain position in the wave. * \param waveformId ID of the waveform in question. * \param waveformPosition 0 or position in the given waveform. * \return If the waveformPosition is 0, the number of steps in this waveform is returned. Otherwise the resulting output-level, an integer between 0 and 31. */ uint8_t fade_calculateWaveform(uint8_t waveformId, uint8_t waveformPosition) { /* * values for sinus-wave, amplitude 31, 64 steps: * awk 'BEGIN{ pi=3.1415927; for(i=1; i<=64; i++) { printf("%.0f, ", sin(i*pi/32)*31) } printf("\n"); }' * 3, 6, 9, 12, 15, 17, 20, 22, 24, 26, 27, 29, 30, 30, 31, 31, 31, 30, * 30, 29, 27, 26, 24, 22, 20, 17, 15, 12, 9, 6, 3, -0, -3, -6, -9, * -12, -15, -17, -20, -22, -24, -26, -27, -29, -30, -30, -31, -31, -31, * -30, -30, -29, -27, -26, -24, -22, -20, -17, -15, -12, -9, -6, -3, 0 */ /* sinus-wave: * awk 'BEGIN{ pi=3.1415927; for(i=1; i<=64; i++) { printf("%.0f, ", sin((i+48)*pi/32)*15+16) } printf("\n"); }' */ uint8_t sinus[] = { 1, 1, 2, 2, 3, 4, 4, 5, 6, 8, 9, 10, 12, 13, 15, 16, 17, 19, 20, 22, 23, 24, 26, 27, 28, 28, 29, 30, 30, 31, 31, 31, 31, 31, 30, 30, 29, 28, 28, 27, 26, 24, 23, 22, 20, 19, 17, 16, 15, 13, 12, 10, 9, 8, 6, 5, 4, 4, 3, 2, 2, 1, 1, 1 }; /* * another nice wave, wider than the original sinus: * awk 'BEGIN{ pi=3.1415927; for(i=1; i<=32; i++) { printf("%.0f, ", sqrt(sin(i*pi/32)*31+.00001)*sqrt(32)) } printf("\n"); }' */ uint8_t widecurve[] = { 10, 14, 17, 19, 22, 23, 25, 26, 28, 29, 30, 30, 31, 31, 31, 31, 31, 31, 31, 30, 30, 29, 28, 26, 25, 23, 22, 19, 17, 14, 10, 0 }; if (waveformId <= 31) { /* No fading, just a constant level */ if (waveformPosition == 0) { return 1; } else { return waveformId; } } else { switch (waveformId) { case 32: /* blink */ if (waveformPosition == 0) { return 2; } else { if (waveformPosition == 1) { return 31; } else { return 0; } } case 33: /* triangular */ if (waveformPosition == 0) { return 62; } else { if (waveformPosition <= 32) { return waveformPosition - 1; } else { return 63 - waveformPosition; } } case 34: /* sawtooth rising */ if (waveformPosition == 0) { return 32; } else { return waveformPosition - 1; } case 35: /* sawtooth falling */ if (waveformPosition == 0) { return 32; } else { return 31 - (waveformPosition - 1); } case 36: /* sinus */ if (waveformPosition == 0) { return 64; } else { return sinus[waveformPosition - 1]; } case 37: /* wide curve */ if (waveformPosition == 0) { return 32; } else { return widecurve[waveformPosition - 1]; } case 38: /* wide curve - inverted */ if (waveformPosition == 0) { return 32; } else { return 31 - widecurve[(waveformPosition + 15) % 32]; } } } return 0; } #endif