Archiv verlassen und diese Seite im Standarddesign anzeigen : Timing bzw. Semi-multitasking
Hallo zusammen,
bin seit einer Woche stolzer Besitzer eines RP6 mit Erweiterungsplatine.
Mit Elektronik hab ich schon etwas Erfahrung aus dem Modellbaubereich und vom PC-Basteln.
Probleme bereitet mir momentan eher die Programmierung, da ich (außer Quellcodes auf nem CPD 6128 mit Basic 1.1 von 1985 eintippen) noch keine Erfahrungen habe.
Jetzt mal endlich zu meinem Problem:
Basis ist das Move2-Programm für die Erweiterungsplatine.
Ich habe an dessen I/O´s 4 LED´s angeschlossen, die ich während des gesamten Programmablaufs innerhalb eines bestimmten Taktes blinken lassen will. Weiterhin habe ich einen Servo an Interupt 2 angelötet, den ich später einmal ansteuern will. Auf dem Servo sitzt ein Suchscheinwerfer, der über einen BC517 auf I/O 3 läuft. Dieser Scheinwerfer schaltet sich ab eine bestimmten Wert ein, der vorab über die LDR´s der Basis ermittelt werden.
Rein technisch funktioniert die Sache wunderbar. Auch der Programmablauf selbst funktioniert einwandfrei, aber zu langsam.
Der Ablauf der LED´s und die Abfrage der LDR´s nehmen soviel Zeit in Anspruch, dass für die Hauptprogrammfunktionen keine Zeit mehr bleibt. Dementsprechend erkennt der RP6 ein Hinterniss, fährt dann ein ganzes Stück weiter (gegen das Hinternis) und fängt dann mit dem Ausweichmanöver an).
Da der Atmel ja nicht wirklich Multitaskingfähig ist, muss ich das irgendwie anders lösen. Ich nehme an, dass es zuviel Zeit benötigt, die Ports für die LED´s einzeln zu schalten, mir fällt aber keine bessere Möglichkeit ein, ohne in die Library des Controll-Boards einzugreifen. Das wollte ich erst dann, wenn ich mich mit dem Spaß ein bischen besser auskenne. Hat jemand einen Rat, wie man das eleganter lösen kann?
Hier mal der Code (nur die Änderungen an der Move2, sonst wirds zu viel)
Bitte nicht lachen, wenn das völlig Banane ist, ich hab wirklich null Ahnung von C und kämpf mich grad durch ein Buch, was für die Atmel´s wohl nicht wirklich taugt...
Gruß
Markus
void eigenefunktion(void) /*schaltet die LEDs/*
{
PORTC |= IO_PC7;
mSleep(80);
PORTC &= ~IO_PC7;
mSleep(80);
PORTC |= IO_PC7;
mSleep(80);
PORTC &= ~IO_PC7;
mSleep(100);
PORTC |= IO_PC5;
mSleep(80);
PORTC &= ~IO_PC5;
mSleep(80);
PORTC |= IO_PC5;
mSleep(80);
PORTC &= ~IO_PC5;
mSleep(100);
PORTC |= IO_PC6;
mSleep(80);
PORTC &= ~IO_PC6;
mSleep(80);
PORTC |= IO_PC6;
mSleep(80);
PORTC &= ~IO_PC6;
mSleep(100);
PORTC |= IO_PC4;
mSleep(80);
PORTC &= ~IO_PC4;
mSleep(80);
PORTC |= IO_PC4;
mSleep(80);
PORTC &= ~IO_PC4;
mSleep(100);
}
void task_LDRinfo(void) /*liest die LDRs aus und schaltet Port3 mit Scheinwerfer/*
{
writeString_P("LDR_L: ");
writeIntegerLength(adcLSL, DEC, 4);
writeString_P(" ; LDR_R: ");
writeIntegerLength(adcLSR, DEC, 4);
writeString_P(" ||");
if(adcLSL <400)
PORTC |= IO_PC3;
if(adcLSL >450)
PORTC &= ~IO_PC3;
}
/************************************************** ***************************/
// Main function - The program starts here:
int main(void)
{
initRP6Control();
initLCD();
DDRC |= IO_PC7;
DDRC |= IO_PC6;
DDRC |= IO_PC5;
DDRC |= IO_PC4;
DDRC |= IO_PC3;
PORTC &= ~IO_PC3;
Hallo antanis,
willkommen im "RP6-Club"!
Wie du schon gesagt hast:
Deine "eigenefunktion" blockiert die Hauptprogrammschleife (wenn sie da eingebaut wird) und verhindert die zeitnahe Abarbeitung anderer Tasks.
Die Lösung dafür sind die Stopwatches.
Wenn du in der Funktion die mSleep Aufrufe durch Stopwatch-Abfragen ("if (getStopwatch1() > 80 ..." usw) ersetzt, dann wartet die Funktion nicht mehrfach 80ms (gesamt fast 1,4 sec).
Gruß Dirk
Hallo Dirk,
vielen Dank für den Tip, so in der Art müsste es eigentlich funktionieren. Aber irgendwie krieg ich es nicht hin. Hab mich erstmal auf 2 LED´s konzentriert (Port 7 und 5).
void task_LDRinfo(void) //LDR für Suchscheinwerfer auswerten
{
writeString_P("LDR_L: ");
writeIntegerLength(adcLSL, DEC, 4);
writeString_P(" ; LDR_R: ");
writeIntegerLength(adcLSR, DEC, 4);
writeString_P(" ||");
if(adcLSL <400)
PORTC |= IO_PC3;
if(adcLSL >450)
PORTC &= ~IO_PC3;
}
void eigenefunktion(void)
{
PORTC &= ~IO_PC7; //IO´s erstmal auf LOW schalten
PORTC &= ~IO_PC6;
PORTC &= ~IO_PC5;
PORTC &= ~IO_PC4;
startStopwatch3(); //Stopwatch Nr. 1 starten
if (getStopwatch3() > 100) //Stopwatch 1 auswerten
PORTC |= IO_PC7; /Wenn Stopwatch über 100-> Ports High
PORTC |= IO_PC5;
setStopwatch1(0); //Stopuhr zurückstellen auf 0
if (getStopwatch3() > 100) //Stopwatch 1 auswerten
PORTC &= ~IO_PC7; // Wenn Stopwatch über 100--> Ports LOW
PORTC &= ~IO_PC5;
setStopwatch3(0); //Stopuhr wieder zurück
int main(void) //Hauptprogramm
{
initRP6Control();
initLCD();
DDRC |= IO_PC7; //erstmal Ports festlegen
DDRC |= IO_PC6;
DDRC |= IO_PC5;
DDRC |= IO_PC4;
DDRC |= IO_PC3;
PORTC &= ~IO_PC3; /*Den Port für den Suchscheinwerfer auf LOW /*
while(true) //Endlos-Schleife starten
{
task_LCDHeartbeat();
task_checkINT0();
task_I2CTWI();
behaviourController();
eigenefunktion(); /*Hier sollten nun eigentlich die Ports nach je 100ms high/Low-Schalten /*
task_LDRinfo(); /*Abfrage für Suchscheinwerfer
}
return 0;
}
Was ich nicht ganz verstehe: Der Port 3 für den Suchscheinwerfer ist am Anfang immer aktiv ebenso die für die LED´s. Port 3 schalte ich im Hauptprogramm ja aktiv ab (PORTC &= ~IO_PC3; ) die 4 anderen nicht. Diese sind aber nach einmaligen aufleuchten ebenfalls auf Low???
Hier mal der ganze Code, falls sich jemand durchbeissen will:
// Includes:
#include "RP6ControlLib.h" // The RP6 Control Library.
// Always needs to be included!
#include "RP6I2CmasterTWI.h" // I2C Master Library
/************************************************** ***************************/
/************************************************** ***************************/
// Include our new "RP6 Control I2C Master library":
#include "RP6Control_I2CMasterLib.h"
/************************************************** ***************************/
/************************************************** ***************************/
// Behaviour command type:
#define IDLE 0
// The behaviour command data type:
typedef struct {
uint8_t speed_left; // left speed (is used for rotation and
// move distance commands - if these commands are
// active, speed_right is ignored!)
uint8_t speed_right; // right speed
unsigned dir:2; // direction (FWD, BWD, LEFT, RIGHT)
unsigned move:1; // move flag
unsigned rotate:1; // rotate flag
uint16_t move_value; // move value is used for distance and angle values
uint8_t state; // state of the behaviour
} behaviour_command_t;
behaviour_command_t STOP = {0, 0, FWD, false, false, 0, IDLE};
/************************************************** ***************************/
// Cruise Behaviour:
#define CRUISE_SPEED_FWD 80 // Default speed
#define MOVE_FORWARDS 1
behaviour_command_t cruise = {CRUISE_SPEED_FWD, CRUISE_SPEED_FWD, FWD,
false, false, 0, MOVE_FORWARDS};
/**
* Cruise Behaviour
*/
void behaviour_cruise(void)
{
}
/************************************************** ***************************/
// Escape Behaviour:
#define ESCAPE_SPEED_BWD 80
#define ESCAPE_SPEED_ROTATE 60
#define ESCAPE_FRONT 1
#define ESCAPE_FRONT_WAIT 2
#define ESCAPE_LEFT 3
#define ESCAPE_LEFT_WAIT 4
#define ESCAPE_RIGHT 5
#define ESCAPE_RIGHT_WAIT 6
#define ESCAPE_WAIT_END 7
behaviour_command_t escape = {0, 0, FWD, false, false, 0, IDLE};
/**
* This is the Escape behaviour for the Bumpers.
*/
void behaviour_escape(void)
{
static uint8_t bump_count = 0;
switch(escape.state)
{
case IDLE:
break;
case ESCAPE_FRONT:
escape.speed_left = ESCAPE_SPEED_BWD;
escape.dir = BWD;
escape.move = true;
if(bump_count > 3)
escape.move_value = 200;
else
escape.move_value = 140;
escape.state = ESCAPE_FRONT_WAIT;
bump_count+=2;
break;
case ESCAPE_FRONT_WAIT:
if(!escape.move)
{
escape.speed_left = ESCAPE_SPEED_ROTATE;
if(bump_count > 3)
{
escape.move_value = 110;
escape.dir = RIGHT;
bump_count = 0;
}
else
{
escape.dir = LEFT;
escape.move_value = 75;
}
escape.rotate = true;
escape.state = ESCAPE_WAIT_END;
}
break;
case ESCAPE_LEFT:
escape.speed_left = ESCAPE_SPEED_BWD;
escape.dir = BWD;
escape.move = true;
if(bump_count > 3)
escape.move_value = 160;
else
escape.move_value = 100;
escape.state = ESCAPE_LEFT_WAIT;
bump_count++;
break;
case ESCAPE_LEFT_WAIT:
if(!escape.move)
{
escape.speed_left = ESCAPE_SPEED_ROTATE;
escape.dir = RIGHT;
escape.rotate = true;
if(bump_count > 3)
{
escape.move_value = 100;
bump_count = 0;
}
else
escape.move_value = 65;
escape.state = ESCAPE_WAIT_END;
}
break;
case ESCAPE_RIGHT:
escape.speed_left = ESCAPE_SPEED_BWD ;
escape.dir = BWD;
escape.move = true;
if(bump_count > 3)
escape.move_value = 160;
else
escape.move_value = 100;
escape.state = ESCAPE_RIGHT_WAIT;
bump_count++;
break;
case ESCAPE_RIGHT_WAIT:
if(!escape.move)
{
escape.speed_left = ESCAPE_SPEED_ROTATE;
escape.dir = LEFT;
escape.rotate = true;
if(bump_count > 3)
{
escape.move_value = 100;
bump_count = 0;
}
else
escape.move_value = 65;
escape.state = ESCAPE_WAIT_END;
}
break;
case ESCAPE_WAIT_END:
if(!(escape.move || escape.rotate))
escape.state = IDLE;
break;
}
}
/**
* Bumpers Event handler
*/
void bumpersStateChanged(void)
{
if(bumper_left && bumper_right)
{
sound(200,100,0);
escape.state = ESCAPE_FRONT;
}
else if(bumper_left)
{
sound(200,25,10);
sound(150,25,0);
if(escape.state != ESCAPE_FRONT_WAIT)
escape.state = ESCAPE_LEFT;
}
else if(bumper_right)
{
sound(200,25,10);
sound(150,25,0);
if(escape.state != ESCAPE_FRONT_WAIT)
escape.state = ESCAPE_RIGHT;
}
}
/************************************************** ***************************/
// Avoid Behaviour:
#define AVOID_SPEED_L_ARC_LEFT 20
#define AVOID_SPEED_L_ARC_RIGHT 80
#define AVOID_SPEED_R_ARC_LEFT 80
#define AVOID_SPEED_R_ARC_RIGHT 20
#define AVOID_SPEED_ROTATE 60
#define AVOID_OBSTACLE_RIGHT 1
#define AVOID_OBSTACLE_LEFT 2
#define AVOID_OBSTACLE_MIDDLE 3
#define AVOID_OBSTACLE_MIDDLE_WAIT 4
#define AVOID_END 5
behaviour_command_t avoid = {0, 0, FWD, false, false, 0, IDLE};
/**
* Avoid behaviour with ACS IR Sensors.
*/
void behaviour_avoid(void)
{
static uint8_t last_obstacle = LEFT;
static uint8_t obstacle_counter = 0;
switch(avoid.state)
{
case IDLE:
if(obstacle_right && obstacle_left)
avoid.state = AVOID_OBSTACLE_MIDDLE;
else if(obstacle_left)
avoid.state = AVOID_OBSTACLE_LEFT;
else if(obstacle_right)
avoid.state = AVOID_OBSTACLE_RIGHT;
break;
case AVOID_OBSTACLE_MIDDLE:
avoid.dir = last_obstacle;
avoid.speed_left = AVOID_SPEED_ROTATE;
avoid.speed_right = AVOID_SPEED_ROTATE;
if(!(obstacle_left || obstacle_right))
{
if(obstacle_counter > 3)
{
obstacle_counter = 0;
setStopwatch4(0);
}
else
setStopwatch4(400);
startStopwatch4();
avoid.state = AVOID_END;
}
break;
case AVOID_OBSTACLE_RIGHT:
avoid.dir = FWD;
avoid.speed_left = AVOID_SPEED_L_ARC_LEFT;
avoid.speed_right = AVOID_SPEED_L_ARC_RIGHT;
if(obstacle_right && obstacle_left)
avoid.state = AVOID_OBSTACLE_MIDDLE;
if(!obstacle_right)
{
setStopwatch4(500);
startStopwatch4();
avoid.state = AVOID_END;
}
last_obstacle = RIGHT;
obstacle_counter++;
break;
case AVOID_OBSTACLE_LEFT:
avoid.dir = FWD;
avoid.speed_left = AVOID_SPEED_R_ARC_LEFT;
avoid.speed_right = AVOID_SPEED_R_ARC_RIGHT;
if(obstacle_right && obstacle_left)
avoid.state = AVOID_OBSTACLE_MIDDLE;
if(!obstacle_left)
{
setStopwatch4(500);
startStopwatch4();
avoid.state = AVOID_END;
}
last_obstacle = LEFT;
obstacle_counter++;
break;
case AVOID_END:
if(getStopwatch4() > 1000)
{
stopStopwatch4();
setStopwatch4(0);
avoid.state = IDLE;
}
break;
}
}
/**
* ACS Event Handler
*/
void acsStateChanged(void)
{
if(obstacle_left && obstacle_right)
statusLEDs.byte = 0b100100;
else
statusLEDs.byte = 0b000000;
statusLEDs.LED5 = obstacle_left;
statusLEDs.LED4 = (!obstacle_left);
statusLEDs.LED2 = obstacle_right;
statusLEDs.LED1 = (!obstacle_right);
updateStatusLEDs();
if(obstacle_left && obstacle_right)
{
sound(160,20,0);
}
else
{
if(obstacle_left)
sound(120,10,0);
if(obstacle_right)
sound(140,10,0);
}
}
/************************************************** ***************************/
// Behaviour waitForStart:
#define PREPARE 1
#define WAIT 2
behaviour_command_t waitForStart = {0, 0, FWD,
false, false, 0, PREPARE};
/**
* Wait for start Behaviour.
* You need to clap your hands (or make other noise) three times in order
* to start the Robot!
*/
void behaviour_waitForStart(void)
{
static uint8_t peak_count = 3;
if(waitForStart.state == PREPARE)
{
if(getStopwatch2() > 250)
{
setCursorPosLCD(1, 6);
writeIntegerLengthLCD( peak_count, DEC, 1);
dischargePeakDetector();
waitForStart.state = WAIT;
setStopwatch2(0);
}
}
else if(waitForStart.state == WAIT)
{
uint8_t key = checkReleasedKeyEvent();
if(key)
waitForStart.state = IDLE;
if(getStopwatch2() > 50)
{
uint16_t tmp = getMicrophonePeak();
if(tmp > 4)
{
externalPort.LEDS = 0;
uint16_t i;
uint8_t j;
for(i = 0, j = 2; i < tmp; i+= 40)
{
if(i < 40)
{
externalPort.LEDS++;
}
else
{
externalPort.LEDS <<=1;
externalPort.LEDS++;
}
}
outputExt();
if(tmp > 120)
{
waitForStart.state = PREPARE;
peak_count--;
}
if(peak_count == 0)
waitForStart.state = IDLE;
}
else
setLEDs(0b0000);
setStopwatch2(0);
}
}
}
/************************************************** ***************************/
// Behaviour check low Battery:
#define BATTERY_LOW 1
behaviour_command_t checkLowBattery = {0, 0, FWD,
false, false, 0, IDLE};
/**
* In this behaviour routine, we have nothing to do
*/
void behaviour_checkLowBattery(void)
{
}
/**
* This is a new Event Handler and it gets called when the Battery Voltage
* is getting low! The Parameter isVoltageLow is true, when the voltage
* is low and false, when the voltage is OK.
*/
void batteryVoltageLow(uint8_t isVoltageLow)
{
if(isVoltageLow)
checkLowBattery.state = BATTERY_LOW;
}
/************************************************** ***************************/
// Behaviour control:
/**
* This function processes the movement commands that the behaviours generate.
* Depending on the values in the behaviour_command_t struct, it sets motor
* speed, moves a given distance or rotates.
*/
void moveCommand(behaviour_command_t * cmd)
{
if(cmd->move_value > 0) // move or rotate?
{
if(cmd->rotate)
rotate(cmd->speed_left, cmd->dir, cmd->move_value, false);
else if(cmd->move)
move(cmd->speed_left, cmd->dir, DIST_MM(cmd->move_value), false);
cmd->move_value = 0; // clear move value - the move commands are only
// given once and then runs in background.
}
else if(!(cmd->move || cmd->rotate)) // just move at speed?
{
changeDirection(cmd->dir);
moveAtSpeed(cmd->speed_left,cmd->speed_right);
}
else if(isMovementComplete()) // movement complete? --> clear flags!
{
cmd->rotate = false;
cmd->move = false;
}
}
/**
* A small helper function to display the current behaviour on the
* LCD. It only prints out the active behaviour ONCE, otherwise the
* text would flicker on the LCD!
*/
void displayBehaviour(uint8_t behave)
{
static uint8_t compare = 0;
if(compare != behave)
{
compare = behave;
clearPosLCD(1, 0, 13);
setCursorPosLCD(1, 0);
switch(behave)
{
case 6: writeStringLCD_P("LOW BATTERY!"); setLEDs(0b0000); break;
case 5: writeStringLCD_P("WAIT"); setLEDs(0b0000); break;
case 4: writeStringLCD_P("ESCAPE"); setLEDs(0b0110); break;
case 3: writeStringLCD_P("AVOID"); setLEDs(0b1001); break;
case 2: writeStringLCD_P("CRUISE"); setLEDs(0b0000); break;
case 1: writeStringLCD_P("STOP"); setLEDs(0b0000); break;
}
}
if(behave == 2) // If Cruise behaviour is active, show a running light...
{
static uint8_t runLEDs = 1;
static uint8_t dir = 0;
if(getStopwatch2() > 100)
{
setLEDs(runLEDs);
if(dir == 0)
runLEDs <<= 1;
else
runLEDs >>= 1;
if(runLEDs > 7 )
dir = 1;
else if (runLEDs < 2 )
dir = 0;
setStopwatch2(0);
}
}
if(behave == 6) // If Battery is low - beep all 3 seconds!
{
if(getStopwatch2() > 3000) // We can use Stopwatch2 here and
{ // for several other things because only
sound(200,20,20); // one of these things can be active at
sound(225,20,60); // the same time! You could not do this if
sound(200,20,20); // there were things that could be active
sound(225,20,0); // at the same time!
setStopwatch2(0);
}
}
}
/**
* The behaviourController task controls the subsumption architechture.
* It implements the priority levels of the different behaviours.
*
* Here we also show which behaviour is active on the LC-Display!
*
*/
void behaviourController(void)
{
// Call all the behaviour tasks:
behaviour_checkLowBattery();
behaviour_waitForStart();
behaviour_cruise();
behaviour_avoid();
behaviour_escape();
// Execute the commands depending on priority levels:
if(checkLowBattery.state != IDLE) // Highest priority - 6
{
displayBehaviour(6);
moveCommand(&checkLowBattery);
}
else if(waitForStart.state != IDLE) // Priority - 5
{
displayBehaviour(5);
moveCommand(&waitForStart);
}
else if(escape.state != IDLE) // Priority - 4
{
displayBehaviour(4);
moveCommand(&escape);
}
else if(avoid.state != IDLE) // Priority - 3
{
displayBehaviour(3);
moveCommand(&avoid);
}
else if(cruise.state != IDLE) // Priority - 1
{
displayBehaviour(2);
moveCommand(&cruise);
}
else // Lowest priority - 0
{
displayBehaviour(1);
moveCommand(&STOP); // Default command - do nothing!
// In the current implementation this never
// happens.
}
}
/************************************************** ***************************/
/**
* Prints all Sensor Values on the Serial Interface.
*/
void printAllSensorValues(void)
{
getAllSensors();
writeString_P("\nRead Sensor Values:\n");
writeString_P("PL:");writeIntegerLength(mleft_power,DEC,3);
writeString_P(" | PR:");writeIntegerLength(mright_power,DEC,3);
writeString_P(" | VL:");writeIntegerLength(mleft_speed,DEC,3);
writeString_P(" | VR:");writeIntegerLength(mright_speed,DEC,3);
writeString_P(" | DL:");writeIntegerLength(mleft_des_speed,DEC,3);
writeString_P(" | DR:");writeIntegerLength(mright_des_speed,DEC,3);
writeChar('\n');
writeString_P("DSTL:");writeIntegerLength(mleft_dist,DEC,5);
writeString_P(" | DSTR:");writeIntegerLength(mright_dist,DEC,5);
writeChar('\n');
writeString_P("LSL:");writeIntegerLength(adcLSL,DEC,4);
writeString_P(" | LSR:");writeIntegerLength(adcLSR,DEC,4);
writeString_P(" | MCL:");writeIntegerLength(adcMotorCurrentLeft,DEC,4);
writeString_P(" | MCR:");writeIntegerLength(adcMotorCurrentRight,DEC,4);
writeString_P(" | BAT:");writeIntegerLength(adcBat,DEC,4);
writeString_P(" | AD0:");writeIntegerLength(adc0,DEC,4);
writeString_P(" | AD1:");writeIntegerLength(adc1,DEC,4);
writeChar('\n');
}
/**
* Heartbeat function
*/
void task_LCDHeartbeat(void)
{
if(getStopwatch1() > 500)
{
static uint8_t heartbeat = false;
if(heartbeat)
{
clearPosLCD(1, 15, 1);
heartbeat = false;
}
else
{
setCursorPosLCD(1, 15);
writeStringLCD_P("*");
heartbeat = true;
printAllSensorValues();
}
setStopwatch1(0);
}
}
/**
* Timed Watchdog display - the other heartbeat function
* does not work in this example as we use blocked moving functions here.
*/
void watchDogRequest(void)
{
static uint8_t heartbeat2 = false;
if(heartbeat2)
{
clearPosLCD(1, 14, 1);
heartbeat2 = false;
}
else
{
setCursorPosLCD(1, 14);
writeStringLCD_P("#");
heartbeat2 = true;
}
}
/************************************************** ***************************/
// I2C Requests:
/**
* The I2C_requestedDataReady Event Handler
*/
void I2C_requestedDataReady(uint8_t dataRequestID)
{
checkRP6Status(dataRequestID);
}
/************************************************** ***************************/
// I2C Error handler
/**
* This function gets called automatically if there was an I2C Error like
* the slave sent a "not acknowledge" (NACK, error codes e.g. 0x20 or 0x30).
*/
void I2C_transmissionError(uint8_t errorState)
{
writeString_P("\nI2C ERROR - TWI STATE: 0x");
writeInteger(errorState, HEX);
writeChar('\n');
}
void eigenefunktion(void)
{
PORTC &= ~IO_PC7;
PORTC &= ~IO_PC6;
PORTC &= ~IO_PC5;
PORTC &= ~IO_PC4;
startStopwatch3();
if (getStopwatch3() > 100)
PORTC |= IO_PC7;
PORTC |= IO_PC5;
setStopwatch1(0);
if (getStopwatch3() > 100)
PORTC &= ~IO_PC7;
PORTC &= ~IO_PC5;
setStopwatch3(0);
}
void task_LDRinfo(void)
{
writeString_P("LDR_L: ");
writeIntegerLength(adcLSL, DEC, 4);
writeString_P(" ; LDR_R: ");
writeIntegerLength(adcLSR, DEC, 4);
writeString_P(" ||");
if(adcLSL <400)
PORTC |= IO_PC3;
if(adcLSL >450)
PORTC &= ~IO_PC3;
}
/************************************************** ***************************/
// Main function - The program starts here:
int main(void)
{
initRP6Control();
initLCD();
DDRC |= IO_PC7;
DDRC |= IO_PC6;
DDRC |= IO_PC5;
DDRC |= IO_PC4;
DDRC |= IO_PC3;
PORTC &= ~IO_PC3;
writeString_P("\n\nRP6 CONTROL M32 I2C Master Example Program!\n");
writeString_P("\nMoving...\n");
// ---------------------------------------
WDT_setRequestHandler(watchDogRequest);
BUMPERS_setStateChangedHandler(bumpersStateChanged );
ACS_setStateChangedHandler(acsStateChanged);
BATTERY_setLowVoltageHandler(batteryVoltageLow);
// ---------------------------------------
I2CTWI_initMaster(100);
I2CTWI_setRequestedDataReadyHandler(I2C_requestedD ataReady);
I2CTWI_setTransmissionErrorHandler(I2C_transmissio nError);
sound(180,80,25);
sound(220,80,25);
setLEDs(0b1111);
showScreenLCD("################", "################");
mSleep(500);
showScreenLCD("I2C-Master", "Behaviours");
mSleep(1000);
setLEDs(0b0000);
// ---------------------------------------
// Setup ACS power:
I2CTWI_transmit3Bytes(I2C_RP6_BASE_ADR, 0, CMD_SET_ACS_POWER, ACS_PWR_MED);
// Enable Watchdog for Interrupt requests:
I2CTWI_transmit3Bytes(I2C_RP6_BASE_ADR, 0, CMD_SET_WDT, true);
// Enable timed watchdog requests:
I2CTWI_transmit3Bytes(I2C_RP6_BASE_ADR, 0, CMD_SET_WDT_RQ, true);
startStopwatch1();
startStopwatch2();
showScreenLCD("Active Behaviour", "");
while(true)
{
task_LCDHeartbeat();
task_checkINT0();
task_I2CTWI();
behaviourController();
eigenefunktion();
task_LDRinfo();
}
return 0;
}
Vielen Dank schonmal für die Hilfe! Library´s sind wie gesagt unverändert!
Gruß
Markus
radbruch
16.01.2008, 09:09
Hallo
Bei den Lauflichtern in "if(behave == 2)" ab Zeile 470:
if(runLEDs > 7 ) müßte wohl >5 heisen, weil es 6 LEDs sind.
Die "eigenefunktion()" vielleicht so:
void eigenefunktion(void)
{
//PORTC &= ~IO_PC7;
PORTC &= ~IO_PC6;
//PORTC &= ~IO_PC5;
PORTC &= ~IO_PC4;
startStopwatch3(); // sollte besser einmalig in main() gestartet werden
if (getStopwatch3() < 100)
{
PORTC |= IO_PC7;
PORTC |= IO_PC5;
}
else
{
PORTC &= ~IO_PC7;
PORTC &= ~IO_PC5;
if (getStopwatch3() > 200)
setStopwatch3(0);
}
}
Ich kann's nicht kompilieren, das liegt aber nicht nur daran, dass ich das Erweiterungsmodul nicht besitze.
Meine LEDs bringe ich so zum Blinken:
#include "RP6RobotBaseLib.h"
#define blinkLED6 statusLEDs.LED6=(getStopwatch1() & (1<<6)) && 1
uint8_t blink3;
int main(void)
{
initRobotBase();
setLEDs(0);
//Zeitgeber für das Blinken starten
startStopwatch1();
while(true)
{
//LED1 blinkt langsam
statusLEDs.LED1 ^=(getStopwatch1() & (1<<9));
//LED2 blinkt schnell
statusLEDs.LED2=(getStopwatch1() & (1<<7)) && 1;
//LED3 blinkt variabel (0 bedeutet aus)
blink3=8;
statusLEDs.LED3=(getStopwatch1() & (1<<blink3)) && 1;
//LED4 flasht
statusLEDs.LED4=!(getStopwatch1() & (0b101<<6)) && 1;
//LED5 flackert
statusLEDs.LED5=(getStopwatch1() & (0b1001<<5)) && 1;
//LED6 blinkt mit #define
blinkLED6;
//LED-Status aktuallisieren
updateStatusLEDs();
//das funktioniert natürlich auch mit der pwrLED
if (!(getStopwatch1() & (9<<8)) && 1) powerON(); else powerOFF();
}
return 0;
}
Gruß
mic
Hi mic,
die Lauflichter tun´s eigentlich. Das ist noch der Originalcode.
Ich hab an 4 Ports des Controlling-Boards 4 weitere LED´s angeschlossen, die angesteuert werden sollen.
Danke für den Code für eigenefunktion()
Was ich nicht verstehe: Du setzt am Anfang immer // wie hier z.b.
//PORTC &= ~IO_PC7;
das würde die Funktion aber doch als Kommentar ausblenden? So würde nur PC6 und PC4 high geschaltet werden.
Stopwatch in Main-Schleife geht klar! (Warum eigentlich? Wird ja immer mit der eigenefunktion() aufgerufen und gestartet?)
If stopwatch >100 dann LED´s ein //verstanden
else > LED´s aus // auch verstanden, aber laufe ich da nicht in Gefahr, den Zeitpunkt zwischen Stopwatch >100 aber <200 zu verpassen?
if stopwatch >200 dann stopwatch zurücksetzen // ist auch klar, damit beginnt es wieder von vorne.
Ich versuchs mal heute abend, mal sehen was rauskommt. Danke nochmal!
Gruß
Markus
radbruch
16.01.2008, 13:32
Hallo Markus
das würde die Funktion aber doch als Kommentar ausblenden?
Genau das ist die Absicht. Man könnte den Code natürlich auch löschen, aber manchmal sind es Testvarianten oder Hinweise auf die Änderung. Hier soll es zeigen, dass man die zwei Ports nicht ausschalten muss, weil sie ja später in Abhängigkeit von der StopWatch gesetzt werden. (btw: die Pins werden mit PORTC &= ~IO_PC7; auf low=0V gesetzt wenn sie Ausgang sind.)
Stopwatch in Main-Schleife geht klar! (Warum eigentlich?...)
StartStopWatch() sorgt über ein Flag (stopwatches.watches) dafür, dass die betreffende StopWatch() in der Interruptroutine des Timers mitgezählt wird. Einmaliges Setzen dieses Flags genügt, deshalb zentral in main().
aber laufe ich da nicht in Gefahr, den Zeitpunkt zwischen Stopwatch >100 aber <200 zu verpassen?
Du hast nicht richtig geschaut, es wird auf <100 und >200 geprüft. Wenn kleiner 100, dann Ausgang setzen, sonst Ausgang löschen und, wenn größer 200 auch StopWatch neu laden. "Größer 200" kann man sicher erkennen, dann wird die StopWatch() neu geladen und auf "kleiner 100" geprüft. Verpassen kannst du dabei eigentlich nichts, nur wenn der Abstand des regelmäsigen Aufrufs von meinefunktion() länger als 100ms dauert, werden die LEDs nie brennen. (Ganz korrekt wäre noch ein Stetzen der Ausgänge beim Löschen der StopWatch(), sonst fehlt in der Hell-Phase die Zeit bis zum nächsten Aufruf von meinefunktion())
Gruß
mic
Hallo mic,
langsam blick ichs.
Ziel des ganzen ist es, die LED´s in einem bestimmten Takt DURCHGEHEND laufen zu lassen (80ms an, 80ms aus, 80ms an, 100ms aus->Return)
Wie schaff ich es, diese Routine immer wieder aufzurufen?
meinefunktion() wird in der Main-schleife ja aufgerufen, dann aber durch die anderen Tasks unterbrochen und erst wieder später aufgenommen, oder??
kann man while(true) auch in einer funktion einsetzen? wohl kaum, da diese dann ja wieder die Hauptschleife blockiert...
radbruch
16.01.2008, 16:06
Hallo
Das Task-System des RP6 ist auch eines seiner Highlights. Damit kann er mehrere Abläufe parallel steuern ohne sich zu blockieren. Der Preis dafür ist eben die recht komplizierte Programmierung. Grundsätzlich muss man aber selbst dafür sorgen, dass die einzelnen Tasks abgearbeitet werden und keine Funktion (störend) blockierend auf den Rest des Programms wirkt. Hier ein Beispiel für ein 80an-80aus-80an-100aus-Blinken:
#include "RP6RobotBaseLib.h"
void eigenefunktion(void)
{
if (getStopwatch3() < 80)
{
setLEDs(63);
//PORTC |= IO_PC7;
//PORTC |= IO_PC5;
}
else if (getStopwatch3() < 80+80)
{
setLEDs(0);
//PORTC &= ~IO_PC7;
//PORTC &= ~IO_PC5;
}
else if (getStopwatch3() < 80+80+80)
{
setLEDs(63);
//PORTC |= IO_PC7;
//PORTC |= IO_PC5;
}
else if (getStopwatch3() < 80+80+80+100)
{
setLEDs(0);
//PORTC &= ~IO_PC7;
//PORTC &= ~IO_PC5;
}
else setStopwatch3(0);
}
int main(void)
{
initRobotBase();
setLEDs(0);
//DDRC |= IO_PC7 | IO_PC6 | IO_PC6 | IO_PC4
//PORTC &= ~IO_PC7;
//PORTC &= ~IO_PC6;
//PORTC &= ~IO_PC5;
//PORTC &= ~IO_PC4;
setStopwatch3(0);
startStopwatch3();
while(true)
{
eigenefunktion();
}
return 0;
}
Nicht sehr elegant, aber es funktioniert und ist anschaulich...
Gruß
mic
Danke mic,
aber irgendwie haut das mit den Timern noch nicht ganz so hin.
Die LED´s blinken zwar, aber nicht den Stopwatches entsprechend. Ich denke es liegt daran:
Annahme: Stopwatch steht bei 40
If Stopwatch3 < 80 ---> ist True
If Stopwatch3 < 80+80 ---> ist aber ebenfalls True
Irgendwie müsste ich es hinbringen dass:
If Stopwatch3 <80 ABER > 80+80
Warum eigentlich 80+80 und nicht gleich 160??
Hier mal ein Beispielcode. Die Ausgabe der Töne erfolgt ohne jede Pause und wiederholt sich ständig, die Werte auf der Stopwatch werden dabei völlig ignoriert. Eigentlich müssten da ein paar Sekunden Pause dazwischen sein. Die Stopwatches gehen ja bis 65 Sekunden...
void thebeeb(void)
{
if (getStopwatch8() < 1000)
{ sound(140,30,25); }
else if (getStopwatch8() < 6000)
{ sound(200,30,25); }
else if (getStopwatch8() < 12000)
{ sound(200,25,25);
sound(180,30,40); }
else if (getStopwatch8() < 18000)
{ sound(160,40,25);
sound(170,30,25); }
else if (getStopwatch8() < 26000)
{ sound(240,20,50);
sound(140,60,20);
sound(180,40,25); }
else if (getStopwatch8() < 50000)
{
setStopwatch8(0);
}
Weder Handbuch, noch I-net haben mir da weitergeholfen.
Funktionieren auf dem RP5 eigentlich alle C-funktionen, also auch printf oder ähnliches??
Gruß
Markus
radbruch
16.01.2008, 23:42
Hallo
Annahme: Stopwatch steht bei 40
If Stopwatch3 < 80 ---> ist True
If Stopwatch3 < 80+80 ---> ist aber ebenfalls True
Wo ist denn das "else" geblieben? < 160 wird nur geprüft, wenn die StopWatch >= 80 ist...
Warum eigentlich 80+80 und nicht gleich 160?So sieht man auf einen Blick ohne großen Kommentar, wie der Wert entsteht. Da es sich um Konstanten handelt, wird beim Kompilieren beides gleich übersetzt:
// testzeile ++++++++++++++++++++++++++++++++++++++++++++++++++ +++++++++++++++
if (getStopwatch3() < 80)
24c: 80 91 b8 00 lds r24, 0x00B8
250: 90 91 b9 00 lds r25, 0x00B9
254: 80 35 cpi r24, 0x50 ; 80
256: 91 05 cpc r25, r1
258: 90 f0 brcs .+36 ; 0x27e <eigenefunktion+0x32>
{
setLEDs(63);
//PORTC |= IO_PC7;
//PORTC |= IO_PC5;
}
else if (getStopwatch3() < 80+80)
25a: 80 91 b8 00 lds r24, 0x00B8
25e: 90 91 b9 00 lds r25, 0x00B9
262: 80 3a cpi r24, 0xA0 ; 160
264: 91 05 cpc r25, r1
266: 20 f4 brcc .+8 ; 0x270 <eigenefunktion+0x24>
//testzeile
{
setLEDs(0);
268: 80 e0 ldi r24, 0x00 ; 0
26a: 0e 94 a6 01 call 0x34c <setLEDs>
26e: 08 95 ret
//PORTC &= ~IO_PC7;
//PORTC &= ~IO_PC5;
}
else if (getStopwatch3() < 80+80+80)
270: 80 91 b8 00 lds r24, 0x00B8
274: 90 91 b9 00 lds r25, 0x00B9
278: 80 3f cpi r24, 0xF0 ; 240
27a: 91 05 cpc r25, r1
27c: 10 f4 brcc .+4 ; 0x282 <eigenefunktion+0x36>
{
setLEDs(63);
27e: 8f e3 ldi r24, 0x3F ; 63
280: f4 cf rjmp .-24 ; 0x26a <eigenefunktion+0x1e>
//PORTC |= IO_PC7;
//PORTC |= IO_PC5;
}
else if (getStopwatch3() < 80+80+80+100)
282: 80 91 b8 00 lds r24, 0x00B8
286: 90 91 b9 00 lds r25, 0x00B9
28a: 84 55 subi r24, 0x54 ; 84
28c: 91 40 sbci r25, 0x01 ; 1
28e: 60 f3 brcs .-40 ; 0x268 <eigenefunktion+0x1c>
{
setLEDs(0);
//PORTC &= ~IO_PC7;
//PORTC &= ~IO_PC5;
}
else setStopwatch3(0);
290: 10 92 b9 00 sts 0x00B9, r1
294: 10 92 b8 00 sts 0x00B8, r1
298: 08 95 ret
0000029a <main>:
}
// testzeile ++++++++++++++++++++++++++++++++++++++++++++++++++ +++++++++++++++
Bei meinem RP6 funktioniert das Programm oben wie erwartet. Wenn man z.b. die 100 in 1000 ändert, sieht man deutlich den geänderten Blinkrythmus. Warum dein Piepen nicht funktioniert liegt möglicherweise an der Erweiterung.
Gruß
mic
Nur so als kleiner Tipp:
Auch bei dem Programm mit den LEDs werden die "setLEDs" Funktionien ständig aufgerufen - ohne große Pause. Nur merkt man das da nicht da es nichts ändert (mach mal ne Textausgabe da rein ;) ).
Bei den Beep Funktionen merkt man das natürlich sofort.
Eine Bedingung wie "if(x > 2000)"
ist IMMER wahr wenn x größer als 2000 ist... auch bei 2100, 2455, 43432...
Genauso ist eine Bedingung wie "if(x < 2000)" IMMER wahr solange x kleiner als 2000 ist - also bei 0, 100, 143, 1423, 1999...
Da muss man schon zusätzliche Dinge einbauen damit das klappt.
z.B. so etwas:
startStopwatch1(); // Stopwatch1 starten!
uint8_t count = 0; // Sekundenzähler
while(true)
{
if(getStopwatch1() > 1000) // 1000ms = 1s
{
switch(count) // Sekundenzähler auswerten
{
case 1:
writeString_P("Bin jetzt bei Sekunde 1\n");
writeString_P("Wird auch nur einmal ausgegeben!\n");
break;
case 4:
writeString_P("Jetzt bei Sekunde 4\n");
break;
case 8:
writeString_P("Jetzt bei Sekunde 8\n");
count = 0;
writeString_P("Ich fang jetzt mal wieder von vorn an...\n\n");
break;
}
count++; // Sekundenzähler erhöhen
setStopwatch1(0);
}
}
Nicht verwechseln - die Ausgabe erfolgt hier BEI Sekunde 1, 4 und 8 - es wird also nicht 4 Sekunden dazwischen gewartet, sondern 3 bzw. 4.
Die Stopwatches sind vor allen dazu gedacht irgendetwas periodisch auszuführen, oder verzögert nach einer gewissen Zeit.
Eine einzige für sequenzen zu benutzen ist mit einem kleinen Automaten wie oben gezeigt deutlich einfacher.
(das switch-case kann man natürlich auch durch if-else-if-else ersetzen wenn man das schöner findet)
MfG,
SlyD
radbruch
17.01.2008, 00:58
Hallo
http://img.youtube.com/vi/8cTLP5knsfk/2.jpg (http://www.youtube.com/watch?v=8cTLP5knsfk)
(http://www.youtube.com/watch?v=8cTLP5knsfk)
Das ist die Ausgabe von diesem Programm:
#include "RP6RobotBaseLib.h"
void eigenefunktion(void)
{
// testzeile ++++++++++++++++++++++++++++++++++++++++++++++++++ +++++++++++++++
if (getStopwatch3() < 800)
{
setLEDs(63);
//PORTC |= IO_PC7;
//PORTC |= IO_PC5;
}
else if (getStopwatch3() < 800+800)
//testzeile
{
setLEDs(0);
//PORTC &= ~IO_PC7;
//PORTC &= ~IO_PC5;
}
else if (getStopwatch3() < 800+800+800)
{
setLEDs(63);
//PORTC |= IO_PC7;
//PORTC |= IO_PC5;
}
else if (getStopwatch3() < 800+800+800+1000)
{
setLEDs(0);
//PORTC &= ~IO_PC7;
//PORTC &= ~IO_PC5;
}
else setStopwatch3(0);
}
// testzeile ++++++++++++++++++++++++++++++++++++++++++++++++++ +++++++++++++++
int main(void)
{
initRobotBase();
setLEDs(0);
//DDRC |= IO_PC7 | IO_PC6 | IO_PC6 | IO_PC4
//PORTC &= ~IO_PC7;
//PORTC &= ~IO_PC6;
//PORTC &= ~IO_PC5;
//PORTC &= ~IO_PC4;
setStopwatch3(0);
startStopwatch3();
while(true)
{
eigenefunktion();
}
return 0;
}
und wird auf einem RP6-Base ausgeführt. Einzige Änderung gegenüber der ersten Version sind die geänderten StopWatch-Werte.
Die else-Zweige in den Abfragen werden doch erst ausgeführt, wenn die vorhergehende Prüfung auf "kleiner als" false ergab. "Case" würde mir hier auch besser gefallen...
Gruß
mic
[Edit]
Mit "case" sieht's nicht wirklich besser aus:
#include "RP6RobotBaseLib.h"
void eigenefunktion(void)
{
uint8_t step;
uint16_t temp;
temp = getStopwatch3();
step = (temp < 800) << 0 \
| (temp < 800+800) << 1 \
| (temp < 800+800+800) << 2 \
| (temp < 800+800+800+1000) << 3 \
| (temp >= 800+800+800+1000) << 4;
//writeInteger(step, 2);
//writeString_P("\n\r");
switch (step)
{
case(16-1): setLEDs(32+4); break;
case(16-2): setLEDs(0b10010); break;
case(16-4): setLEDs(9); break;
case(16-8): setLEDs(0); break;
case(16): setStopwatch3(0); break;
}
}
int main(void)
{
initRobotBase();
setLEDs(0);
setStopwatch3(0);
startStopwatch3();
while(true)
{
eigenefunktion();
}
return 0;
}
@mic:
Ich glaub da hast Du mich nicht so ganz verstanden.
Probier mal das aus und schau Dir die Ausgaben im Terminal an - dann sollte deutlich werden was ich meine:
startStopwatch3();
while(true)
{
if (getStopwatch3() < 800)
{
setLEDs(63);
writeString_P("das hier wird gaaanz oft aufgerufen. Stopwatch: ");
writeInteger(getStopwatch3(),DEC);
writeChar('\n');
}
else if (getStopwatch3() < 800+800)
{
setLEDs(0);
writeString_P("Noch mehr... Stand der Stopwatch: ");
writeInteger(getStopwatch3(),DEC);
writeChar('\n');
}
else if (getStopwatch3() < 800+800+800)
{
setLEDs(63);
writeString_P("und noch mehr ... Stopwatch: ");
writeInteger(getStopwatch3(),DEC);
writeChar('\n');
}
else if (getStopwatch3() < 800+800+800+1000)
{
// hier mal zur Abwechslung nix ausgeben
setLEDs(0);
}
else setStopwatch3(0);
mSleep(10); // ja das ist absichtlich hier drin, damit die
// Ausgaben nicht noch schneller erfolgen...
}
Bei den setLEDs Funktionen macht es natürlich keinen Unterschied ob man 1x setLEDs(0) aufruft oder 1000x.
Bei einer Sound Ausgabe aber schon! Die sollte man nur ein einziges mal Aufrufen sonst klappt es nicht richtig.
Mit "case" sieht's nicht wirklich besser aus:
Naja - geht aber auch deutlich einfacher ;)
startStopwatch1(); // Stopwatch1 starten!
uint16_t count = 0; // 100-Millisekunden Zähler
while(true)
{
if(getStopwatch1() > 100) // 100ms = 0.1s
{
switch(count)
{
case 8: // 800ms
setLEDs(0);
break;
case 8+8: // 1600ms
setLEDs(63);
break;
case 8+8+8: // 2400ms
setLEDs(0);
break;
case 8+8+8+8: // 3200ms
setLEDs(63);
count = 0;
break;
}
count++; // 100-Millisekunden Zähler erhöhen
setStopwatch1(0);
}
}
Das ist übrigens equivalent zu:
startStopwatch1(); // Stopwatch1 starten!
uint16_t count = 0; // 100-Millisekunden Zähler
while(true)
{
if(getStopwatch1() > 100) // 100ms = 0.1s
{
if(count==8) {
setLEDs(0);
}
else if (count == 8+8) {
setLEDs(63);
}
else if (count == 8+8+8) {
setLEDs(0);
}
else if (count == 8+8+8+8) {
setLEDs(63);
count = 0;
}
count++; // 100-Millisekunden Zähler erhöhen
setStopwatch1(0);
}
}
wenn man if-else lieber mag.
MfG,
SlyD
radbruch
17.01.2008, 13:31
Ach, jetzt hat's geklingelt. Und die Case-Lösung sieht auch nett aus. Danke.
mic
Hallo zusammen,
das heißt, ich muss die LED´s über einen Zähler laufen lassen, der wiederum von der Stopwatch "gefüttert wird"?
Macht für mich grad keinen Sinn? Funktion ist doch funktion, oder? Ob ich jetzt sage:
Wenn Stopwatch>100 dann mach das Licht an
müsste es doch mit:
Wenn Stopwatch>100 dann setz den Zähler hoch
gleich sein oder?? Ich meine in beiden Fällen soll er eine Aktion durchführen?!
Kann man eigentlich alle C-Befehle für den Atmel verwenden?
Gruß
Markus
radbruch
17.01.2008, 15:31
Hallo
Der Unterschied ist folgender:
Wenn du die StopWatch() mit > bzw. < abfragst, wird die Bedingung in mehreren Durchgängen erfüllt, also die LEDs mehrfach gesetzt oder die Sounds mehrfach gestartet. Also müßte man auf == testen, mit dem Risiko, dass die StopWatch() blöderweise genau unseren Prüfwert überspringt, weil wir den Aufruf zu lange verzögert haben.
Die Lösung ist deshalb z.B. ein zusätzlicher Zähler. Weil wir den Zähler nur erhöhen, wenn wir die Funktion ausführen, können wir keinen Wert überspringen. Allerdings stimmt das Timing nicht, wenn wir langsamer aufrufen als die StopWatch() auf 100 zählt.
Das AVR-C ist eine Untermenge von C, es funktioniert nur, was auch Sinn macht. Ein Tutorial bei den Mikrocontrollern: http://www.mikrocontroller.net/articles/AVR-GCC-Tutorial
Gruß
mic
Powered by vBulletin® Version 4.2.5 Copyright ©2024 Adduco Digital e.K. und vBulletin Solutions, Inc. Alle Rechte vorbehalten.