549 lines
23 KiB
C
549 lines
23 KiB
C
/**
|
|
* \file main.c
|
|
* \brief Firmware for the i2c-dimmer
|
|
* \author Ronald Schaten <ronald@schatenseite.de> & Thomas Stegemann
|
|
* \version $Id: main.c,v 1.1 2007/07/29 17:19:50 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-dimmer
|
|
*
|
|
* \section sec_intro Introduction
|
|
*
|
|
* I haven't done many microcontroller-projects till now, but more than one of
|
|
* the few projects I did involved controlling LEDs by pulse width modulation
|
|
* (PWM). Doing this for one or more LEDs is a stressful task for a little
|
|
* microcontroller, but if you want to do some other more or less complicated
|
|
* things while keeping LEDs at certain brightnesses is likely to ruin the
|
|
* timings that are used in the PWM. Not to talk about the program code, which
|
|
* gets more and more unreadable if you try to do several different things 'at
|
|
* the same time'.
|
|
*
|
|
* For my next project I need to fade some LEDs again, so I was looking for an
|
|
* easier way to do it. The plans include reading from memory cards, talking to
|
|
* real time clocks and displaying text on an LCD, so I'm almost sure that I
|
|
* won't be able to reliably control the five channels I'm going to use.
|
|
*
|
|
* The first plan was to use a ready-made chip. I looked around and the best
|
|
* thing I could find was one made by Philips (PCA something, I forgot the
|
|
* number) that can be controlled via I2C-bus. That part is able to control
|
|
* eight LEDs, but apart from 'on' and 'off' you can set the LEDs only to two
|
|
* different brightnesses. Those are variable, nevertheless, but it would be
|
|
* impossible to light one LED at 20%, one at 50% and one at 80%. Another
|
|
* drawback is that it is SMD-only, and my soldering-skills don't including
|
|
* working with stuff that small.
|
|
*
|
|
* So the Idea was to set up a separate controller for LED-fading, that can be
|
|
* externally controlled, ideally via I2C-bus since I intend to use several
|
|
* other devices in my next project that can make use of the bus. So I set up
|
|
* an ATtiny2313 on my breadboard, clocked it with an external 20MHz-crystal
|
|
* and we tried to control as many LEDs as possible...
|
|
*
|
|
* \section sec_pwm Pulse width modulation
|
|
*
|
|
* \subsection sec_pwm1 The old way
|
|
*
|
|
* Controlling the brightness of LEDs by PWM is a common technique, I used it
|
|
* myself in several projects. Till now I used to switch on all LEDs that
|
|
* should light up at a level greater than zero, waited till the first of the
|
|
* LEDs has to be switched off, switched it off, waited for the next one and so
|
|
* on. After a certain time all LEDs are switched off, and I start again.
|
|
*
|
|
* I try to visualize that with a little picture:
|
|
*
|
|
* \code
|
|
* . . . . .| . .
|
|
* 1 *************************************************|************************
|
|
* 2 *************************************** |************************
|
|
* 3 ********* |**********
|
|
* 4 |
|
|
* 5 ***************************** |************************
|
|
* \endcode
|
|
*
|
|
* In this example, a full cycle of the PWM would need 50 units of time. The
|
|
* first LED is switched on the full time (100%), the second for 40 of the 50
|
|
* units (80%), the third one for ten (20%) and the fifth one for 30 units
|
|
* (60%). The fourth LED is off (0%). We see that after 50 units of time the
|
|
* modulation starts again.
|
|
*
|
|
* The drawback of this technique is, that it's slow. And for each additional
|
|
* channel you try to control, it gets even slower. We tried, but we weren't
|
|
* able to control more than five LEDs in this way without them to start
|
|
* flickering to a visible amount.
|
|
*
|
|
* We tried to create an array with all states of the process, so the PWM only
|
|
* would have to loop through the array and set the outputs accordingly. But
|
|
* that didn't work either, because the used microcontroller doesn't have
|
|
* enough RAM to store the array.
|
|
*
|
|
* \subsection sec_pwm2 Thomas' idea
|
|
*
|
|
* After some tests that didn't work out too well, Thomas had a great idea how
|
|
* to implement the PWM. It also works with an array for all states, but the
|
|
* states of the modulation are not displayed for the same time. The first
|
|
* state is displayed for one time-unit, the second one for two time-units, the
|
|
* third one for four and so on. In this way the LEDs are turned on and off
|
|
* more than once per cycle of the PWM, but that doesn't hurt.
|
|
*
|
|
* Let's try to paint a picture again:
|
|
*
|
|
* \code
|
|
* . . . . . . | .
|
|
* .. . . . . |.. . .
|
|
* 1 * |*
|
|
* 2 ** | **
|
|
* 3 *** |***
|
|
* 4 **** | ****
|
|
* 5 * **** |* ****
|
|
* 6 ****** | ******
|
|
* 7 ******* |*******
|
|
* 8 ******** | ****
|
|
* \endcode
|
|
*
|
|
* So here we see a PWM with eight channels that are able to display up to 64
|
|
* different brightnesses. Channel one is switched on for one unit of time,
|
|
* channel two for two units and so on. The most interesting thing is on
|
|
* channel five: the LED is switched on for one unit of time, switched off, and
|
|
* switched on again for four units of time.
|
|
*
|
|
* Lets try a more complicated example -- with brighter LEDs, too:
|
|
*
|
|
* \code
|
|
* . . . . . . | .
|
|
* .. . . . . |.. . .
|
|
* 1 * *******************************|*
|
|
* 2 ** **************** | **
|
|
* 3 ******* **************** |*******
|
|
* 4 *******************************|
|
|
* 5 * **** **************** |* ****
|
|
* 6 *************************************************************| **********
|
|
* 7 **************************************************************|***********
|
|
* 8 ************************ | ****
|
|
* \endcode
|
|
*
|
|
* The channels 1 to 8 have the brightnesses 33, 18, 23, 32, 21, 63, 64 and 24.
|
|
*
|
|
* The advantage of this technique is that on the one hand you have to save a
|
|
* limited number of states (six states in the example), and the looping
|
|
* through the states is very simple: state n is sent to the output pins, then
|
|
* we wait for 2^(n-1) time units, then the next state is sent.
|
|
*
|
|
* Each state represents the bit-pattern that has to be sent during one step.
|
|
* In other words: one column out of the above picture at the start of a new
|
|
* time period. So in this example, we have six states: 01010101, 01100110,
|
|
* 01110100, 11100000, 11110110 and 01101001. The first one is displayed for
|
|
* one unit of time, the second one for two units, the third one for four units
|
|
* and so on...
|
|
*
|
|
* Using this technique has the advantage that adding more channels does almost
|
|
* nothing in terms of system load. The only time that the algorithm has to do
|
|
* actual calculations is when a new value has been delivered and has to be
|
|
* converted into the states. <strong>So using this algorithm, it is possible
|
|
* to show different brightnesses on all free pins of the controller. With an
|
|
* ATtiny2313 that means that you can fade 13 different LEDs while still
|
|
* talking I2C to communicate with other devices!</strong>
|
|
*
|
|
* \section sec_i2c I2C communication
|
|
*
|
|
* Speaking I2C is no rocket science, but since one has to do a lot of
|
|
* bit-shifting when implementing it, I took a ready-made library.
|
|
*
|
|
* The one I used is <strong>written by Donald R. Blake</strong>, he was so
|
|
* kind to put it under GPL and post it to avrfreaks.net. You can find the
|
|
* original post in a thread called '<a
|
|
* href="http://www.avrfreaks.net/index.php?name=PNphpBB2&file=viewtopic&t=48395">8
|
|
* bit communication between AVR using TWI</a>' and some additions in the
|
|
* thread '<a
|
|
* href="http://www.avrfreaks.net/index.php?name=PNphpBB2&file=viewtopic&t=51467">I2C
|
|
* Slave on an ATtiny45</a>'.
|
|
*
|
|
* Thanks for the great work, Donald! And for putting it under a free license.
|
|
*
|
|
* Since his package seems to be only available as a forum-attachment, and I'm
|
|
* not sure for how long that will be, I included it into the tarball of this
|
|
* project.
|
|
*
|
|
* \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.
|
|
*
|
|
* Don't forget to set the fuses on the controller to make use of the external
|
|
* crystal. This project is using a fine algorithm, but it still needs the full
|
|
* power of 20MHz. The settings I used are included in the makefile, too.
|
|
*
|
|
* 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_usage Usage
|
|
*
|
|
* You should be able to use this device in the same way you would use any
|
|
* other I2C-slave:
|
|
*
|
|
* \subsection sec_usage_hardware Connecting it
|
|
*
|
|
* The controller needs to have the following pins connected in the circuit:
|
|
*
|
|
* <ul>
|
|
* <li>Pin 1 - Reset - should be connected to VCC with a 10k-resistor</li>
|
|
* <li>Pin 4 and 5 - XTAL1 and XTAL2 - connected to a 20MHz-crystal, using
|
|
* 22p-capacitors against GND</li>
|
|
* <li>Pin 10 - GND - Ground</li>
|
|
* <li>Pin 17 - SDA - I2C-data</li>
|
|
* <li>Pin 19 - SCL - I2C-clock</li>
|
|
* <li>Pin 20 - VCC - 5V</li>
|
|
* </ul>
|
|
*
|
|
* Your I2C-data and -clock lines should be terminated by 4,7k-resistors to
|
|
* pull up the lines. All the other pins can be used to connect LEDs. They are
|
|
* arranged in this way:
|
|
*
|
|
* <ul>
|
|
* <li>Pin 2 - PD0 - Channel 0</li>
|
|
* <li>Pin 3 - PD1 - Channel 1</li>
|
|
* <li>Pin 6 - PD2 - Channel 2</li>
|
|
* <li>Pin 7 - PD3 - Channel 3</li>
|
|
* <li>Pin 8 - PD4 - Channel 4</li>
|
|
* <li>Pin 9 - PD5 - Channel 5</li>
|
|
* <li>Pin 11 - PD6 - Channel 6</li>
|
|
* <li>Pin 12 - PB0 - Channel 7</li>
|
|
* <li>Pin 13 - PB1 - Channel 8</li>
|
|
* <li>Pin 14 - PB2 - Channel 9</li>
|
|
* <li>Pin 15 - PB3 - Channel 10</li>
|
|
* <li>Pin 16 - PB4 - Channel 11</li>
|
|
* <li>Pin 18 - PB6 - Channel 12</li>
|
|
* </ul>
|
|
*
|
|
* \subsection sec_usage_software Talking to it
|
|
*
|
|
* For my tests I used an ATmega8 as I2C-master with the library
|
|
* <strong>written by Peter Fleury</strong>. You can find it on <a
|
|
* href="http://jump.to/fleury">http://jump.to/fleury</a>. Thanks to him for
|
|
* putting it online!
|
|
*
|
|
* The typical send function looks like this:
|
|
*
|
|
* \code
|
|
* #define I2C_DIMMER 0x10
|
|
*
|
|
* void sendi2cBytes(uint8_t address, uint8_t brightness) {
|
|
* // address: number of the LED to set (0..12)
|
|
* // brightness: value between 0 and 127
|
|
* // start the communication...
|
|
* i2c_start_wait((I2C_DIMMER << 1) + I2C_WRITE);
|
|
* // write a byte with the address. we want the highest bit of the
|
|
* // address to be 1, so the slave can be sure that this is an address.
|
|
* i2c_write(address | 0x80);
|
|
* // calculate the actual duration the LED should light up. we could do
|
|
* // this on the slave's side, but we assume that the device is more
|
|
* // flexible when it is done on the master side.
|
|
* uint16_t duration = (brightness + 1) * (brightness + 1) - 1;
|
|
* // calculate the low- and the high-byte and send it. note that we split
|
|
* // the duration into 7-bit-values, not 8 bit! in this way the highest
|
|
* // bit of the transferred bytes is always low, allowing the slave to
|
|
* // recognize the transmitted bytes as values, not as addresses.
|
|
* i2c_write(duration & 0x7f); // low byte
|
|
* i2c_write((duration >> 7) & 0x7f); // high byte
|
|
* // stop the communication...
|
|
* i2c_stop();
|
|
* }
|
|
* \endcode
|
|
*
|
|
* \section sec_drawbacks Drawbacks
|
|
*
|
|
* Till now, the device worked in all situations I tested it in. So far
|
|
* everything is fine.
|
|
*
|
|
* I guess that, compared to the ready-made off-the-hook-parts that controls
|
|
* LEDs via I2C, this module is a bit slow. I can't see any flickering in the
|
|
* LEDs since they are still switched very fast (about every 6ms, which would
|
|
* result in a 166Hz flickering -- too fast for me).
|
|
*
|
|
* \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 usiTwiSlave.c: I2C-library.
|
|
* - \e usiTwiSlave.h: I2C-library.
|
|
* - \e USI_TWI_Slave.zip: I2C-library (package).
|
|
* - \e i2c-dimmer.doxygen: Support for creating the documentation.
|
|
* - \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!
|
|
*
|
|
* Once again, special credits go to <b>Thomas Stegemann</b>. He had the great
|
|
* idea for the PWM-algorithm, and I am always astonished by the patience he
|
|
* has to show me how to do anything complicated in a sick language like C...
|
|
*
|
|
* \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) 2007 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 "usiTwiSlave.h" // i2c-routines by Donald R. Blake
|
|
|
|
#define TWI_SLA 0x10 /**< i2c slave address */
|
|
|
|
#define CHANNEL_COUNT 13 /**< number of 'fadeable' channels */
|
|
#define PORT_COUNT 2 /**< the channels are distributed over two ports */
|
|
|
|
#define OUTPORT0 PORTB /**< output port 0 */
|
|
#define OUTDDR0 DDRB /**< set port 0 to be output */
|
|
#define OUTMASK0 0x5F /**< see channel_pin, channel_port */
|
|
|
|
#define OUTPORT1 PORTD /**< output port 0 */
|
|
#define OUTDDR1 DDRD /**< set port 0 to be output */
|
|
#define OUTMASK1 0x7F /**< see channel_pin, channel_port */
|
|
|
|
/**
|
|
* We want to drive as many channels as possible. Unfortunately the usable pins
|
|
* aren't 'in a row', so we have to determine which channel ends up on which
|
|
* port and pin.
|
|
*/
|
|
/** this is used to determine the port that is used for output */
|
|
const uint8_t channel_port[CHANNEL_COUNT] PROGMEM = {
|
|
0, 0, 0, 0, 0, 0,
|
|
1, 1, 1, 1, 1, 1, 1 };
|
|
/** this is used to determine the pin that is used for output */
|
|
const uint8_t channel_pin[CHANNEL_COUNT] PROGMEM = {
|
|
0x01, 0x02, 0x04, 0x08, 0x10, 0x40,
|
|
0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40 };
|
|
|
|
/*
|
|
* This is a special treatment for the states lasting very long. If you simply
|
|
* double the times for each state, you eventually end up having long pauses in
|
|
* the modulation. We try to suppress this effect by not waiting for 8192
|
|
* cycles but better performing the shorter 4096-cycle twice.
|
|
*/
|
|
#define STATE_COUNT 14 /**< number of states for pwm */
|
|
#define STATE_START_COUNT 2 /**< number of state groups to be treated individually */
|
|
/** interval length of the states */
|
|
const uint16_t switch_timer[STATE_COUNT] PROGMEM = {
|
|
1, 2, 4, 8, 16, 32, 64, 128, 256, 512, 1024, 2048, 4096, 4096 };
|
|
/** start interval of the state groups */
|
|
const uint8_t switch_timer_index[STATE_START_COUNT]= { 13, 0 };
|
|
|
|
/** contains the port assignments for each interval */
|
|
uint8_t switch_state[STATE_COUNT][PORT_COUNT];
|
|
|
|
uint8_t switch_state_new[STATE_COUNT][PORT_COUNT];
|
|
/**
|
|
* Three bytes have to be received for a full command. This enum is used to
|
|
* indicate what part of the command we are waiting for.
|
|
*/
|
|
typedef enum {
|
|
WAIT_FOR_ADDRESS, /**< first byte is the address */
|
|
WAIT_FOR_VALUE_LOW, /**< second byte is the lower part of the value */
|
|
WAIT_FOR_VALUE_HIGH, /**< third byte is the higher part of the value */
|
|
} ReadCommandState;
|
|
|
|
/**
|
|
* Holds one command that is received via i2c. The command consists of an
|
|
* address (number of output channel) and a 16-bit value. The state is used to
|
|
* indicate what part of the next command we are waiting for.
|
|
*/
|
|
typedef struct {
|
|
uint8_t address; /**< number of output channel (between 0 and
|
|
CHANNEL_COUNT-1 */
|
|
uint16_t value; /**< value to be assigned to the channel (between
|
|
0 and 128*128-1 = 16383 */
|
|
ReadCommandState state; /**< what are we waiting for? */
|
|
} Command;
|
|
|
|
/** the next command is built in this variable */
|
|
Command command = {0, 0, WAIT_FOR_ADDRESS};
|
|
|
|
/**
|
|
* initialize timer
|
|
*/
|
|
void timer_start() {
|
|
TCCR1A = 0x00; // no hardware-pwm
|
|
/* CS12, CS11, CS10 (clock select bits)
|
|
* 0 1 0 cpu-clock / 8
|
|
*/
|
|
TCCR1B = (0 << WGM13) | (0 << WGM12) | (0 << CS12) | (1 << CS11) | (0 << CS10); // WGM1=4
|
|
sei();
|
|
}
|
|
|
|
/**
|
|
* Set brightness on one channel.
|
|
* \param channel the channel to address (0 .. CHANNEL_COUNT)
|
|
* \param brightness the value to set (0 .. 16383)
|
|
*/
|
|
void set_brightness(uint8_t channel, uint16_t brightness){
|
|
uint8_t i;
|
|
// read port mask and port for this channel from program memory
|
|
uint8_t mask= pgm_read_word(&channel_pin[channel]);
|
|
uint8_t port= pgm_read_word(&channel_port[channel]);
|
|
// set the bits in the output-states according to the brightness
|
|
for (i= 0; i < STATE_COUNT; i++){
|
|
// walk through all states...
|
|
if (brightness & 1) {
|
|
// set the bit if it needs to be set in this state
|
|
switch_state_new[i][port] |= mask;
|
|
} else {
|
|
// clear it otherwise
|
|
switch_state_new[i][port] &= ~mask;
|
|
}
|
|
// shift the value to look at the next bit
|
|
brightness >>= 1;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* initialize hardware
|
|
*/
|
|
void init_ports(void){
|
|
OUTDDR0 |= OUTMASK0;
|
|
OUTPORT0 &= ~OUTMASK0; // clear all masked bits
|
|
|
|
OUTDDR1 |= OUTMASK1;
|
|
OUTPORT1 &= ~OUTMASK1; // clear all masked bits
|
|
}
|
|
|
|
/**
|
|
* set output
|
|
* \param port port to set
|
|
* \param state value to be sent to the port
|
|
*/
|
|
void set_port(int port, uint8_t state){
|
|
switch(port){
|
|
case 0:
|
|
OUTPORT0 |= (state & OUTMASK0); // set bits
|
|
OUTPORT0 &= (state | ~OUTMASK0); // clear bits
|
|
break;
|
|
case 1:
|
|
OUTPORT1 |= (state & OUTMASK1); // set bits
|
|
OUTPORT1 &= (state | ~OUTMASK1); // clear bits
|
|
break;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Check if anything has been received via i2c and evaluate the received data.
|
|
* The received data is set into the command variable according to the state of
|
|
* the command we are waiting for.
|
|
*/
|
|
void evaluate_i2c_input(void) {
|
|
uint8_t byte_received = 0;
|
|
if (usiTwiDataInReceiveBuffer()) {
|
|
// we have input
|
|
byte_received = usiTwiReceiveByte();
|
|
switch(command.state){
|
|
case WAIT_FOR_ADDRESS:
|
|
if (byte_received & 0x80) {
|
|
// bit 7 is set -> address received
|
|
command.address = (byte_received & 0x7f);
|
|
command.state = WAIT_FOR_VALUE_LOW;
|
|
}
|
|
// do nothing if this byte didn't look like an address
|
|
break;
|
|
case WAIT_FOR_VALUE_LOW:
|
|
if (!(byte_received & 0x80)) {
|
|
// bit 7 is not set -> could be a value
|
|
command.value = byte_received;
|
|
command.state = WAIT_FOR_VALUE_HIGH;
|
|
} else {
|
|
// seems to be an address
|
|
command.address = byte_received;
|
|
command.state = WAIT_FOR_VALUE_LOW;
|
|
}
|
|
break;
|
|
case WAIT_FOR_VALUE_HIGH:
|
|
if (!(byte_received & 0x80)) {
|
|
// bit 7 is not set -> could be a value
|
|
command.value += (byte_received << 7);
|
|
command.state = WAIT_FOR_ADDRESS;
|
|
// we have a complete command
|
|
set_brightness(command.address, command.value);
|
|
} else {
|
|
// seems to be an address
|
|
command.address = byte_received;
|
|
command.state = WAIT_FOR_VALUE_LOW;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Main-function. Initializes everything and contains the main loop which
|
|
* controls the actual PWM output.
|
|
* \return An integer. Whatever... :-)
|
|
*/
|
|
int main(void) {
|
|
uint8_t state_number = 0;
|
|
uint8_t state_start = 0;
|
|
uint8_t port = 0;
|
|
uint16_t timer = 0;
|
|
|
|
// initialize output ports
|
|
init_ports();
|
|
// set all channels to 0
|
|
uint8_t i;
|
|
for(i= 0; i < CHANNEL_COUNT; i++) {
|
|
set_brightness(i, 0);
|
|
}
|
|
|
|
// own TWI slave address
|
|
usiTwiSlaveInit(TWI_SLA);
|
|
|
|
// start timer
|
|
timer_start();
|
|
|
|
// init watchdog
|
|
wdt_enable(WDTO_15MS); // 15ms watchdog
|
|
|
|
while (1) {
|
|
// loop forever
|
|
for (state_start = 0; state_start < STATE_START_COUNT; state_start++) {
|
|
// treat state groups...
|
|
for (state_number = switch_timer_index[state_start]; state_number < STATE_COUNT; state_number++) {
|
|
// cycle through all steps...
|
|
for (port = 0; port < PORT_COUNT; port++) {
|
|
// set all output ports according to the current step...
|
|
set_port(port, switch_state[state_number][port]);
|
|
}
|
|
// determine how long to wait for the next step
|
|
timer = pgm_read_word(&switch_timer[state_number]);
|
|
// restart timer
|
|
TCNT1 = 0;
|
|
while (timer > TCNT1) {
|
|
// wait for the next step... meanwhile...
|
|
wdt_reset(); // feed the watchdog
|
|
evaluate_i2c_input(); // read i2c commands
|
|
}
|
|
}
|
|
}
|
|
for(state_number= 0; state_number < STATE_COUNT; state_number++) {
|
|
for(port= 0; port < PORT_COUNT; port++) {
|
|
switch_state[state_number][port]=
|
|
switch_state_new[state_number][port];
|
|
}
|
|
}
|
|
}
|
|
return 0;
|
|
}
|