Archiv verlassen und diese Seite im Standarddesign anzeigen : NIBObee: Hier ist eine alternative C Library
s.frings
03.04.2010, 12:29
Hallo Moderator: Kannst Du vielleicht für den NIBObee ein eigenes Unterforum anlegen?
Ich hatte ein wenig Schwierigkeiten, die Quelltexte der originalen C Library zu verstehen. Da passieren einige Dinge, die mit unnötig aufwendig erscheinen, manches ist wohl auch etwas konfus umgesetzt.
Zum Beispiel die PWM Steuerung: Wenn man die Fahrtrichtung ändert, wird der Motor zuerst gestoppt und erst beim nächsten Interrupt die neue Fahrtrichtung und Geschwindigkeit eingestellt. Das klingt ja erstmal sinnvoll, aber beim nächsten Interrupt steht der Motor noch lange nicht. Also muss man entweder viel länger warten oder man lässt es ganz bleiben.
Irgendwie nervt mich auch, dass die eigentlich winzigen Funktionen über mehrere include-Files und mehrere (nicht gleich lautende) Libraries (*.a files) verstreut sind.
Und ich vermisse, dass der serielle Port als standart Eingabe und Ausgabe für stdio.h eingerichtet wird, so dass Befehle wie fgets, puts, prinf, etc.funktionieren.
Kurzum, ich habe meine eigene Library geschrieben. Sie ist kleiner, einfacher, und ganz sicher nicht weniger funktional. Ihr dürft sie gerne kopieren, ändern und benutzen.
nibobee.h:
// This file contains macros, definitions and function to access the NIBObee
// hardware.
// Autor: Stefan Frings <stefan@meinemullemaus.de>
// Date: 06.04.2010
#ifndef _HARDWARE_H_
#define _HARDWARE_H_
#include <stdint.h>
#include <avr/io.h>
//================================================== =========================
// Serial port
//================================================== =========================
// To enable use of the serial port as stdin and stdout, set four definitions
// as compiler options in your Makefile:
//
// -DUSE_SERIAL=1 -DBAUD=115200 -DTERMINAL_MODE=1 -DSERIAL_ECHO=1
//
// To disable use of the serial port only one settings is required:
//
// -DUSE_SERIAL=0
//
// The terminal mode enables support for line breaks in old DOS format, which
// is the default in most terminal programs.
//================================================== =========================
// Access to single port bits
//================================================== =========================
// These macros have been primarily designed to access single I/O lines,
// but they can also be used with registers and variables. These macros are
// optimized pretty good by the compiler, so don't hesitate to use them
// wherever it makes the source code better readable. Examples:
// writeBit(PORTB,3,1);
// uint8_t portC7=readBit(PINC,7);
// Set a bit to high if the value is >0, otherwise set the bit to low.
#define writeBit(port,bit,value) { if ((value)>0) (port) |= (1<<bit); else (port) &= ~(1<<bit); }
// Read a single bit. The result is alwas either 0 or 1.
#define readBit(port,bit) (((port) >> (bit)) & 1)
//================================================== =========================
// Analog channels
//================================================== =========================
// The analog channels are sampled in background (interrupt driven), and their
// values are internally stored in an array, which can be read using the
// analog() function. Each channel is sampled twice: with PB4=high and with
// PB4=low, that switches the line sensor light on and off.
//
// Line sensors should be read with VCC as reference (which is the default).
// Their value is usually below 600 for dark surfaces and above 600 for bright
// surfaces.
//
// To get meaningful values from the battery channel, you must use the 2.56V
// reference instead.
// Analog channels, PB4=low (line sensor light on)
#define AN0 0
#define AN1 1
#define AN2 2
#define AN3 3
#define VBAT 4
#define LINE_L 5
#define LINE_C 6
#define LINE_R 7
// Analog channels, PB4=high (line sensor light off)
#define _AN0 8
#define _AN1 9
#define _AN2 10
#define _AN3 11
#define _VBAT 12
#define _LINE_L 13
#define _LINE_C 14
#define _LINE_R 15
// Read values from the ADC (see above for possible channels values).
uint16_t analog(uint8_t channel);
// Analog reference inputs (VCC or 2.56V)
#define REF_VCC (1<<REFS0)
#define REF_256 ((1<<REFS0) | (1<<REFS1))
// Change the reference input of ADC (see above for possible input values).
// After changing, you must wait a while (e.g. 100ms) to load C16.
void set_AREF(uint8_t input);
//================================================== =========================
// Sensor switches
//================================================== =========================
// These macros returns 1 if the switch is pressed
#define SENS_SW1 !readBit(PINC,4)
#define SENS_SW2 !readBit(PINC,5)
#define SENS_SW3 !readBit(PINC,7)
#define SENS_SW4 !readBit(PINC,6)
#define SENS_L1 !readBit(PINC,4)
#define SENS_L2 !readBit(PINC,5)
#define SENS_R1 !readBit(PINC,6)
#define SENS_R2 !readBit(PINC,7)
//================================================== =========================
// Status Led's
//================================================== =========================
// The status LED's are free programmable for whatever you like.
// The LED goes on if the value is >0.
#define set_LED0(value) writeBit(PORTB,0,value)
#define set_LED1(value) writeBit(PORTB,1,value)
#define set_LED2(value) writeBit(PORTB,2,value)
#define set_LED3(value) writeBit(PORTB,3,value)
// Returns 1, if the LED is on
#define LED0 readBit(PINB,0)
#define LED1 readBit(PINB,1)
#define LED2 readBit(PINB,2)
#define LED3 readBit(PINB,3)
//================================================== =========================
// Motor control
//================================================== =========================
// The motor direction is controlled by one port pin for each side. The motor
// speed is controlled by Timer1 which generates a 10bit PWM signal. With
// good batteries on a plain surface, the robotr starts moving at value 200.
// Motor direction
// 0=clockwise, 1=counter-clockwise
#define set_DIR_L(value) writeBit(PORTD,6,value)
#define set_DIR_R(value) writeBit(PORTD,7,value)
#define DIR_L readBit(PIND,6)
#define DIR_R readBit(PIND,7)
// Motor speed
// 0=stop, 1023=maximum speed.
#define PWM_L OCR1A
#define PWM_R OCR1B
//================================================== =========================
// Odometer sensors
//================================================== =========================
// The odometry sensors count interrupts that are generated by photo sensors
// in the gears. They measure the distance of movement. Keep in mind that the
// wheels may slip, specially when the robot accelerates or decellerates too
// quickly. When the wheels slip, the odometry counters contain false
// information.
// The odometer sensors generate on counter tick per 6 millimeters (with the
// original wheels that I got).
// Odometer photo sensors
#define ODO_L readBit(PIND,2)
#define ODO_R readBit(PIND,3)
// Reset odometer counters
void reset_odometer();
// Get left odometer counter
uint32_t odometer_left();
// Get right odometer counter
uint32_t odometer_right();
//================================================== =========================
// System timer
//================================================== =========================
// The system timer is interrupt driven from Timer0. It counts the number of
// milliseconds since last reset.
// Get system time in milliseconds
uint32_t system_time();
#endif // _HARDWARE_H_
nibobee.c:
#include "nibobee.h"
#include <stdio.h>
#include <avr/interrupt.h>
#include <util/atomic.h>
#include <util/setbaud.h>
// Autor: Stefan Frings <stefan@meinemullemaus.de>
// Date: 06.04.2010
// Values from ADC
// Index 0-7 are channel 0-7 captured while PB4=low
// Index 8-15 are channel 0-7 captured while PB4=high
static uint16_t analog_values[16];
// Odometry counters
static uint32_t odometry_left;
static uint32_t odometry_right;
// System time counter (in milliseconds)
static uint32_t system_timer;
// System timer prescaler. Counts Timer0 interrupts.
static uint8_t system_timer_prescaler;
// The following code binds the serial port to standard input and output of stdio.h
#if USE_SERIAL
static int serial_write(char, FILE *);
static int serial_read(FILE *);
static FILE serialPort = FDEV_SETUP_STREAM(serial_write, serial_read, _FDEV_SETUP_RW);
// Write a character to the serial port
static int serial_write(char c, FILE *f) {
#if TERMINAL_MODE
if (c=='\n') {
loop_until_bit_is_set(UCSRA, UDRE);
UDR='\r';
}
#endif /* TERMINAL_MODE */
loop_until_bit_is_set(UCSRA, UDRE);
UDR = c;
return 0;
}
// Read a character from serial port
static int serial_read(FILE *f) {
loop_until_bit_is_set(UCSRA, RXC);
char c=UDR;
#if SERIAL_ECHO
loop_until_bit_is_set(UCSRA, UDRE);
UDR = c;
#endif /* SERIAL_ECHO */
#if TERMINAL_MODE
if (c=='\r') {
c='\n';
#ifdef SERIAL_ECHO
loop_until_bit_is_set(UCSRA, UDRE);
UDR = c;
#endif /* SERIAL_ECHO */
}
#endif /* TERMINAL_MODE */
return c;
}
// Initialize the serial port
void initserial(void) {
// set baudrate
UBRRH = UBRRH_VALUE;
UBRRL = UBRRL_VALUE;
#if USE_2X
UCSRA |= (1 << U2X);
#else
UCSRA &= ~(1 << U2X);
#endif
// enable receiver and transmitter
UCSRB = (1<<RXEN) | (1<<TXEN);
// framing format 8N1
UCSRC = (1<<UCSZ1) | (1<<UCSZ0);
// Bind stdout and stdin to the serial port
stdout = &serialPort;
stdin = &serialPort;
}
#endif /* USE_SERIAL */
// Interrupt from the ADC
ISR(ADC_vect) {
uint16_t value = ADC;
uint8_t channel = ADMUX & 7;
uint8_t pb4 = readBit(PORTB,4);
analog_values[channel+(pb4<<3)]=value;
// Increment channel number as 3 bit counter
// and toggle PB4 on every overflow
if (++channel > 7) {
PORTB ^= (1<<4);
channel=0;
}
// switch to next channel
ADMUX=(ADMUX & ~7) | channel;
// start next conversion
ADCSRA |= (1<<ADSC);
}
// Read values from the ADC
uint16_t analog(uint8_t channel) {
ATOMIC_BLOCK(ATOMIC_RESTORESTATE) {
return analog_values[channel];
}
return 0; // will never be reached
}
// Change the reference input of ADC
void set_AREF(uint8_t input) {
ADMUX=(ADMUX & ~((1<<REFS0) | (1<<REFS1))) | input;
}
// Interrupt from left odometry sensor
ISR(INT0_vect) {
odometry_left++;
}
// Interrupt from right odometry sensor
ISR(INT1_vect) {
odometry_right++;
}
// Timer 0 overflow interrupt, generates a 1khz system clock (not exactly).
// Is called with a rate of 100khz
ISR(TIMER0_COMP_vect) {
system_timer_prescaler++;
if (system_timer_prescaler==100) {
system_timer_prescaler=0;
system_timer++;
}
}
// get clock counter
uint32_t system_time() {
ATOMIC_BLOCK(ATOMIC_RESTORESTATE) {
return system_timer;
}
return 0; // will never be executed
}
// reset odometer counters
void reset_odometer() {
ATOMIC_BLOCK(ATOMIC_RESTORESTATE) {
odometry_left = 0;
odometry_right = 0;
}
}
// get left odometer counter
uint32_t odometer_left() {
ATOMIC_BLOCK(ATOMIC_RESTORESTATE) {
return odometry_left;
}
return 0; // will never be executed
}
// get right odometer counter
uint32_t odometer_right() {
ATOMIC_BLOCK(ATOMIC_RESTORESTATE) {
return odometry_right;
}
return 0; // will never be executed
}
// Install the nibobee_init() procedure in the startup code section 5
void nibobee_init() __attribute__ ((naked)) __attribute__ ((section (".init5")));
// Initialize the hardware
void nibobee_init() {
// Set output pins direction
DDRB |= 1+2+4+8+16; // LED0-3 and LINE_EN are outputs
DDRD |= 16+32+64+128; // PWM_R, PWM_L, DIR_L and DIR_R are outputs
PORTC |= 16+32+64+128; // Enable Pull-ups for sensor switches
// Initial drive direction is forward
set_DIR_L(1); // Left motor counter-clockwise
set_DIR_R(0); // Right motor clockwise
// Initialize the serial port
#if USE_SERIAL
initserial();
#endif /* USE_SERIAL */
// Initialize the motor PWM
// Mode 2 = phase correct 10 bit PWM,
// channel A+B outputs are set on compare match when upcounting
// and cleared on compare match when downcounting.
TCCR1A = (1<<COM1A1) | (1<<COM1A0) | (1<<COM1B1) | (1<<COM1B0) | (1<<WGM11) | (1<<WGM10);
// Use I/O clock without prescaling
TCCR1B = (1<<CS10);
// Initialize odometry counters
// Trigger interrupts on rising edge of INT0 and INT1 pins.
// Enable interrupts for input INT0 and INT1.
#ifdef EICRA
// ATmega644
EICRA |= (1<<ISC11) | (1<<ISC10) | (1<<ISC01) | (1<<ISC00);
EIMSK |= (1<<INT1) | (1<<INT0);
#else
// ATmega16
MCUCR |= (1<<ISC11) | (1<<ISC10) | (1<<ISC01) | (1<<ISC00);
GICR |= (1<<INT1) | (1<<INT0);
#endif
odometry_left = 0;
odometry_right = 0;
// Initialize the ADC
// Enable ADC in single conversion mode with 117khz clock rate (F_CPU/128)
// Which defines a performance of approx. 1000 samples/sec each of the 16 channels
ADCSRA = (1<<ADEN) | (1<<ADPS2) | (1<<ADPS1) | (1<<ADPS0) | (1<<ADIE) | (1<<ADSC);
// Use AVCC as reference
ADMUX = REF_VCC;
// Initialize system timer
// Divide clock by 150 (=100khz)
// Mode: Clear Timer On Compare, no clk prescaler
// Enable interrupt on compare match
#ifdef TIMSK0
// ATmega644
OCR0A = 150;
TCCR0A = (1<<WGM1) | (1<<CS00);
TIMSK0 |= (1<<TOIE0);
#else
// ATmega16
OCR0 = 150;
TCCR0 = (1<<WGM01) | (1<<CS00);
TIMSK |= (1<<OCIE0);
#endif
// Enable interrupts
sei();
}
Beispiel main.c:
#include "nibobee.h"
#include <util/delay.h>
uint16_t previousSpeed;
// Display battery status on the 4 led's
// LED0 = >4.1V
// LED1 = >4.3V
// LED2 = >4.5V
// LED3 = >4.7V
void battery_check() {
set_AREF(REF_256);
for (uint8_t i=0; i<10; i++) {
_delay_ms(50);
set_LED0(analog(VBAT)>(410*2));
set_LED1(analog(VBAT)>(430*2));
set_LED2(analog(VBAT)>(450*2));
set_LED3(analog(VBAT)>(470*2));
_delay_ms(50);
set_LED0(0);
set_LED1(0);
set_LED2(0);
set_LED3(0);
}
set_AREF(REF_VCC);
_delay_ms(50);
}
// Wait for start signal (touch any sensor)
// While waiting, display debug information from sensors:
// LED0: Left odometer sensor
// LED3: Right odometer sensor
// LED1: System timer
// LED2: Center line sensor
void wait_for_start() {
while (!(SENS_SW1 || SENS_SW2 || SENS_SW3 || SENS_SW4)) {
// Display status of odometry sensors while waiting
set_LED0(ODO_L);
set_LED3(ODO_R);
// Display system timer (flashes every second)
set_LED1((system_time() % 1000)==0);
// Display line sensor
set_LED2(analog(LINE_C)>600);
}
set_LED0(0);
set_LED3(0);
_delay_ms(10);
while ((SENS_SW1 || SENS_SW2 || SENS_SW3 || SENS_SW4)) {}
_delay_ms(400);
}
#define FORWARD 1
#define BACKWARD 0
// Drive forward or backward.
// Accellerate or decellerate softly to the maxSpeed.
// The distance is measured in 1/20 wheel rotations.
// LED0 or LED3 light if a which motor runs too fast.
void drive(uint8_t direction, uint16_t distance, uint16_t maxSpeed) {
if (direction) {
set_DIR_L(1);
set_DIR_R(0);
}
else {
set_DIR_L(0);
set_DIR_R(1);
}
// Start with the previous speed
uint16_t speed=previousSpeed;
uint32_t lastTime=system_time();
reset_odometer();
while (odometer_left()<distance || odometer_right()<distance) {
int16_t diff=odometer_left()-odometer_right();
// If left motor is too fast
if (diff>0) {
set_LED0(1);
PWM_L=speed/2;
PWM_R=speed;
}
// If right motor is too fast
else if (diff<0) {
set_LED3(1);
PWM_R=speed/2;
PWM_L=speed;
}
// Speed of both motors is equal
else {
set_LED0(0);
set_LED3(0);
PWM_L=speed;
PWM_R=speed;
}
// If a millisecond has elapsed, increase or decrease the speed
// a little until the maxSpeed has been reached.
uint32_t currentTime=system_time();
if (currentTime>lastTime) {
if (speed<maxSpeed)
speed++;
else if (speed>maxSpeed)
speed--;
lastTime=currentTime;
}
}
previousSpeed=speed;
}
// Stop driving.
int stop() {
PWM_L=0;
PWM_R=0;
previousSpeed=0;
}
// Main program
int main() {
// Display battery status
battery_check();
// Wait for a start signal before start driving
wait_for_start();
// Drive forward and backward repeatedly, to demonstrate
// that the drive() function keeps straight on properly.
while(1) {
// Drive forward 2 meters, accellerating up to nearly maximum speed
drive(FORWARD,2000/6,900);
// Drive forward 30cm, decellerating down to the minimum speed
drive(FORWARD,300/6,200);
stop();
_delay_ms(500);
// Drive backward 2 meters, accellerating up to nearly maximum speed
drive(BACKWARD,2000/6,900);
// Drive backward 30cm, decellerating down to the minimum speed
drive(BACKWARD,300/6,200);
stop();
_delay_ms(500);
}
return 0;
}
Rabenauge
03.04.2010, 13:33
Mal als Zwischenfrage (ich komme heute zu nix mehr, nachher geht es raus, Roboter hin, NIBOBee her): fährt deine Biene bei einem PWM-Wert von 200 wirklich schon zuverlässig?
Meine muss ich da regelrecht betteln, sicher wirds erst so ab 270.
Ich frage, weil ich als nächstes eine Fahrregelung (irgendwann muss ich anfangen, mich da durchzubeissen) programmieren will, um so exakt wie nur möglich manövrieren zu können.
Ohne die ist schliesslich die beste Odometrie nix wert.
s.frings
03.04.2010, 15:34
Ab 200 bewegt er sich, aber nur unter idealen Bedingungen: Glatter Laminat-Boden, Batterien einigermaßen voll.
Mit fast leeren Batterien (4,2 Volt) muss der Wert schon mindestens 300 sein.
Ich habe das Getriebe mit ein wenig Vaseline eingefettet, vielleicht spielt das eine Rolle.
Hi s.frings; sieht erstmal gut aus; =D>
ich bin mit den Original Bibs und deren Enbindung ins AVR-Studio auch nicht so ganz glücklich. Deshalb habe ich mir sämtliche headers in eine nibobee.h und alle Sourcen in eine nibobee.c zusammen kopiert, in denen ich jetzt meine Anpassungen vornehme.
Aber ich war gerade auf Sourcefourge - die Nibobeelib ist mittlerweile Revision 29 vom 2.4.'10. Ein Terminalmode und eine Motorenregellung sind hinzugekommen. (Aber die Lösungen sehen wieder viiel komplizierter aus, als Deine "Links_zu_schnell_dann_Links_ Stopp" Methode. Die neuen Sourcen sind noch nicht released, sondern in einem Ordner "trunk" unter der Rubrik develop versteckelt.
Die Original Sourcen sind es wert, verfolgt zu werden - "Again what learned" denk ich mir.
Der Timer 1 macht ja nicht nur die beiden PWM Ausgänge, mit dem OVF wird auch eine Systemuhr gebildet (clock.c), deren Initialisierung in der motpid.c codiert war. Bei mir ist sie nach motpwm.c gewandert. Dadurch dass ich jetzt eine Systemzeit habe, kann ich generell auf delay() verzichten. Meine Hauptschleife wird (noch) im ms-Takt (Zykluszeit) abgearbeitet; wenn ich eine Zeitverzögerung brauche, wird einfache eine Variable auf die Zeit gestellt und dann zyklisch dekrementiert. Solange die Variable > 0 ist wird der zu verzögernde Programmteil halt nicht ausgeführt, aber alles andere dann trotzdem.
Während ich auf die volle ms warte, kann ich ja mal eben eine gemultiplexte LED Ausgabe machen, und mir mal den Sensorstatus in eine Variable einlesen nachdem ich mir den Vorherstand für eine Flankenerkennung gemerkt habe.
Tja dann wollte ich mir einen weiteren PWM-Ausgang für eine Analoganzeige einrichten. Aber der OC0 ist mit LED3 und der OC2 mit DIR_R belegt. Also jetzt fliegen erstmal die Original LEDs und Widerstände (die leider mit Masse und nicht mit den Pins verbunden sind). Der PB3 wird dann extra PWM Ausgang vom Timer 0 und mit PB0..PB2 werden dann 6 LEDs gemuxt.
Sorry, dass ich jetzt hier soviel reingeschrieben habe, aber mich stört immer Verwendung von delay(), das direkte Setzen und Abfragen der IO-Pins innerhalb der Mainloop. und ausserdem fehlt in der nibobee.h "B" bei "#define ODO_L readit(PORTD,2)"
:-b :-b
s.frings
04.04.2010, 11:49
Ich habe zur obigen Library noch einen System-Timer hinzugefügt, die Inline-Doku verbessert und ein besseres Beispielprogramm eingefügt. Leider ist die Nachricht nun zu groß, das Makefile passt nicht mehr rein. Darum reiche ich es hier nach:
# Makefile for this AVR project
# make Compiles the source code into hex files.
# make fuses Program fuses
# make program Program flash and eeprom
# make list Create generated code listing
# make clean Delete all generated files
# Parameters for avrdude
AVRDUDE_HW = -c usbasp -P usb
# Source files, separated by space.
SRC = main.c nibobee.c
# Microcontroller
# The code is designed for atmega16 or atmega644
MCU = atmega16
F_CPU = 15000000
# Serial port settings
# Set TERMINAL_MODE = 1 to support terminal programs that send and
# expect line breaks in old MS-DOS format.
# Set SERIAL_ECHO = 1 to enable sending back an echo of all incoming
# characters.
USE_SERIAL = 0
BAUD = 115200
TERMINAL_MODE = 1
SERIAL_ECHO = 1
# Fuses
HFUSE = 0xd1
LFUSE = 0xef
#EFUSE = 0xff
# Binaries to be used
# You may add the path to them if they are not in the PATH variable.
CC = avr-gcc
OBJCOPY = avr-objcopy
OBJDUMP = avr-objdump
AVRDUDE = avrdude
# Name of the program without extension
PRG = nibobee
# Do we need to write Eeprom? (yes/no)
EEPROM = no
# Libraries
#LIBS = -L ../nibobee-lib/lib -lnibobee_base -lnibobee_usart -lnibobee_utils
# Includes
#INCLUDES = -I../nibobee-lib/include
# Compiler options for all c source files
CFLAGS = -std=c99 -Os -Wall -mmcu=$(MCU) -DF_CPU=$(F_CPU) $(INCLUDES)
CFLAGS += -DUSE_SERIAL=$(USE_SERIAL) -DBAUD=$(BAUD) -DTERMINAL_MODE=$(TERMINAL_MODE) -DSERIAL_ECHO=$(SERIAL_ECHO)
# Linker options
LDFLAGS = -Wl,-Map,$(PRG).map
# Enable floating-point support in printf
#LDFLAGS += -Wl,-u,vfprintf -lprintf_flt -lm
# Collect fuse operations for avrdude
ifdef FUSE
FUSES += -U fuse:w:$(FUSE):m
endif
ifdef LFUSE
FUSES += -U lfuse:w:$(LFUSE):m
endif
ifdef HFUSE
FUSES += -U hfuse:w:$(HFUSE):m
endif
ifdef EFUSE
FUSES += -U efuse:w:$(EFUSE):m
endif
# Default sections
ifeq ($(EEPROM),yes)
all: code eeprom
else
all: code
endif
# Program code
code: $(PRG).hex
# Eeprom content
eeprom: $(PRG)_eeprom.hex
# Generated code listing
list: $(PRG).lst
# Remove all generated files
clean:
rm -f *.o $(PRG).hex $(PRG).elf $(PRG).lst $(PRG).map $(PRG)_eeprom.hex
# Program flash memory with or without eeprom
ifeq ($(EEPROM),yes)
program: code eeprom
$(AVRDUDE) -p $(MCU) $(AVRDUDE_HW) -U flash:w:$(PRG).hex:i -U eeprom:w:$(PRG)_eeprom.hex:i
else
program: code
$(AVRDUDE) -p $(MCU) $(AVRDUDE_HW) -U flash:w:$(PRG).hex:i
endif
# Program fuses
fuses:
$(AVRDUDE) -p $(MCU) $(AVRDUDE_HW) $(FUSES)
$(PRG).elf: $(SRC:.c=.o)
$(CC) $(CFLAGS) $(LDFLAGS) -o $@ $^ $(LIBS)
%.lst: %.elf
$(OBJDUMP) -h -S $< > $@
%.hex: %.elf
$(OBJCOPY) -j .text -j .data -O ihex $< $@
%_eeprom.hex: %.elf
$(OBJCOPY) -j .eeprom --change-section-lma .eeprom=0 -O ihex $< $@
Ja Supi, hier ist einer aktiv..
Ich werde dann wohl auf s.frings Bibliothek umsteigen und damit weitermachen.
Btw. Ich habe mir jetzt den PB3 frei geschaufelt. Da wo vorher die gelben LEDs waren sind jetzt DUO Leds und die beiden vorderen roten habe ich durch 2 rote 10mm Leds "ersetzt*".
*Eigentlich sind anstelle der LEDs jetzt 100 Ohm Widerstände drin, und dort wo vorher die 180 Ohm Widerstände drin waren, stecken die LEDs (in zusätzlichen Bohrungen).
Der Timer 1 muß ja sowieso für die Motren initialisiert werden, warum denn nicht den gleich den OVF Interrupt freigeben, und damit den Systemtimer realisieren? So wie es in der Original Bibliothek motpid.c und clock.c gemacht wurde. Ich hatte nämlich mit Timer 0 und OC0 was anderes vor. :-({|= Analoganzeige oder PiezoPieper..
s.frings
05.04.2010, 09:32
Ich habe Timer0 für den Systemtimer verwendet, um auf exakte Millisekunden zu kommen. Mit Timer 1 wäre das nicht möglich, jedenfalls nicht ohne komplizierte Mathematik. An exakte Millisekunden Zähler habe ich mich inzwischen sehr gewöhnt (vom PC her). Darauf möchte ich nicht verzichten.
Piepen kann man auch mit den Motoren und Timer1, wenn das Fahrzeug steht.
Du könntest Timer 2 statt Timer 0 für den Millisekunden-Zähler benutzen.
Hi BirgerT!
Hast Du irgendwelche weiteren Infos über den Terminalmode - hat ja anscheind was mit dem BGX1 zu tun - da find ich aber nix zu :-s
Bei sourceforge gibt es nur ne H-Datei:
http://nibobeelib.svn.sourceforge.net/viewvc/nibobeelib/trunk/src/nibobee/bgx1.h?revision=29&view=markup
Weitere Infos zum BGX1 habe ich nicht wirklich; hier heisst's wohl abwarten und beobachten; ist alles develop und noch kein release.
Ich vermute das hiermit etwas geplant ist;
http://shop.nicai-systems.de/shop.php?view=2&id=25
aber dafür ist womöglich extra Hardware erforderlich (I2C Porterweiterung o.ä.)
Nur die h-Datei gefunden?! schau mal hier: http://nibobeelib.svn.sourceforge.net/viewvc/nibobeelib/
Rabenauge und Pinsel120866 haben Bilder- und Videolinks gepostet. Demnach haben sie schon 2-zeilige LCD und Taster am NiboBee in Betrieb.
Mit Timer 1 wäre das nicht möglich, jedenfalls nicht ohne komplizierte Mathematik
Also Timer1 läuft bei s.frings Alternativ Bib im 10bit Mode das sind dann 15000000/(2*1023) = 7331 Hz, der OVF Interupt kommt folglich alle 1/7331 Sek. = 136uS.
In der Original Bib läuft der Timer im 9 Bit Mode; Overflow alle 68uS..
In der ISR(Timer1_OVF_vect) werden also immer 136 bzw. 68 auf den uint16_t takt_us addiert, ist dieser >1000, werden wieder 1000 abgezogen und der uint16_t takt_ms inkrementiert...
Ich habe drei Varianten ausprobiert PWM 9 bit, PWM 10bit und Timer0. nach jeder ms den PB5 geXodert und ein Scope drangehängt: Ergebnis ist (ver)gleich(bar); das 1KHz (halt, eigentlich 500Hz 1ms H, 1ms L) Signal jittert bei allen drei Varianten, ob's am Billig-Scope liegt oder am Signal ?!
Was ist für die Motorenansteuerung eigentlich besser, hohe oder niedrigere PWM Frequenz?
s.frings
06.04.2010, 19:28
Diese Zählmethode ist einfach und einleuchtend. Komisch, dass ich nicht selbst drauf gekommen bin.
Wegen dem Jitter: Der Timer jittert, weil eben keine Millisekunden gezählt werden und auch nicht ein genaues Vielfaches davon. Im Durchschnitt passt es zwar, aber wenn man wirklich Millisekunden braucht, ist das störend.
Wegen dem PWM: Das wüsste ich auch gerne. Bei meiner 10 bit Variante hört man die Motoren manchmal leise piepsen. Das empfinde ich als als gerinfügigen Nachteil. Andererseits dachte mir, warum nicht alle 10 bit Nutzen, wenn sie zur Verfügung stehen?
Bei meiner 10 bit Variante hört man die Motoren manchmal leise piepsen
7Khz hört man halt - aber als Nachteil finde ich das nicht. Ich fänd's cool, wenn er proportional zur Geschwindigkeit brummen tät. Irgendwas mit Prescaler 64 und Fast PWM oder so..
/************************************************** *******************
N I B O B E E Feste Zykluszeit für Mainloop und
zyklische (Sensor) Ein- und (LED) Ausgabe
Version 1.07 für alternative Nibobee C Bib von s.frings
20100411 Birger Töpelmann
************************************************** *******************/
#ifndef _ZYKLUS_H_
#define _ZYKLUS_H_
// Folgende Zeile auskommentieren für "normalen" NiboBee
#define _LED6_MUX_ // 6 LEDs an PB0..PB2; PB3 ist frei
/*--------------------------------------------------------------------
- PB2 -|100R|-->(x) es leuchtet pro Takt immer nur eine LED;
- PB1 -|100R|-->(y) ein PIN ist Plus, ein PIN Minus, ein Pin offen
- PB0 -|100R|-->(z) LED (Vor-)Widerstand 2 x 100R
LED_Pattern
Bit Anode - Kathode DDRB PORTB
0 DUO links grün (y) - (z) ..110 ..010
1 DUO links rot (x) - (z) ..101 ..001
2 DUO rechts grün (z) - (y) ..110 ..100
3 DUO rechts rot (x) - (y) ..011 ..001
4 ROTAUGE links (z) - (x) ..011 ..010
5 ROTAUGE rechts (y) - (x) ..101 ..100
Zuordnung LED_Pattern Bit# und LED anpassbar in led_ausgabe():
case 0:{ ddr = 0x06; pin = 0x02;} break; // DUO links grün
case 1:{ ddr = 0x05; pin = 0x01;} break; // DUO links rot
case 2:{ ddr = 0x06; pin = 0x04;} break; // DUO rechts grün
case 3:{ ddr = 0x03; pin = 0x01;} break; // DUO rechts rot
case 4:{ ddr = 0x03; pin = 0x02;} break; // ROTAUGE links
case 5:{ ddr = 0x05; pin = 0x04;} break; // ROTAUGE rechts
--------------------------------------------------------------------*/
//#include "nibobee.h" // s.frings Bibliothek
#include "zyklus.h" // also includes "nibobee.h"
#include <stdio.h>
#include <avr/interrupt.h>
#include <util/atomic.h>
#include <util/delay.h>
/******************************************* ZYKLUS & CLOCK ***********/
// zählt bis 1000 (1s) in 1ms Schritten
volatile uint16_t takt_ms;
// zählt die Sekunden seit dem Systemstart
volatile uint32_t takt_sec;
// Zyklustimer wird in ISR inkrementiert und in Zyklus()
volatile uint8_t zyklus_ms; // überwacht und genullt
// bremst die Hauptschleife auf feste Zykluszeit (Zzeit in ms)
// organisert LED Ausgabe und Sensoren Eingabe mit Flankenerkennung
uint8_t Zyklus(uint8_t Zzeit);
/****************************************** SENSOREN ******************/
volatile uint8_t Sensors; // Aktueller Eingabestatus
volatile uint8_t SensSFL; // Steigende Flanke erkannt
volatile uint8_t SensFFL; // Fallende Flanke erkannt
/*---------------------------------------------------------------------
Flankenerkennung (Trigger):
0101 Bitmuster vorher
0011 Bitmuster aktuell
0010 Bitmuster steigende Flanke
0100 Bitmuster fallende Flanke
----------------------------------------------------------------------*/
void sens_eingabe(); // SensorPins lesen/Flankenerkennung
#define SENS_SW1_1 readBit(Sensors,4)
#define SENS_SW2_1 readBit(Sensors,5)
#define SENS_SW3_1 readBit(Sensors,7)
#define SENS_SW4_1 readBit(Sensors,6)
#define SENS_SW1_S readBit(SensSFL,4)
#define SENS_SW2_S readBit(SensSFL,5)
#define SENS_SW3_S readBit(SensSFL,7)
#define SENS_SW4_S readBit(SensSFL,6)
#define SENS_SW1_F readBit(SensFFL,4)
#define SENS_SW2_F readBit(SensFFL,5)
#define SENS_SW3_F readBit(SensFFL,7)
#define SENS_SW4_F readBit(SensFFL,6)
/***************************************** LED MULTIPLEX **************/
volatile uint8_t LED_Pattern; // LED Ausgabemuster
volatile uint8_t LED_PattSFL;
volatile uint8_t LED_PattFFL;
volatile uint8_t LED_Blinker; // gesetztes Bit lässt LED blinken
#ifdef _LED6_MUX_ // umgebauter NiboBee
// Kompatibilität zu s.frings: entsprechendes Bit in Pattern setzen
#define set_LED0(value) writeBit(LED_Pattern,1,value)
#define set_LED1(value) writeBit(LED_Pattern,4,value)
#define set_LED2(value) writeBit(LED_Pattern,5,value)
#define set_LED3(value) writeBit(LED_Pattern,3,value)
// Returns 1, if the LED is on
#define LED0 readBit(LED_Pattern,1)
#define LED1 readBit(LED_Pattern,4)
#define LED2 readBit(LED_Pattern,5)
#define LED3 readBit(LED_Pattern,3)
#else // normaler NiboBee
// Kompatibilität zu s.frings: entsprechendes Bit in Pattern setzen
#define set_LED0(value) writeBit(LED_Pattern,0,value)
#define set_LED1(value) writeBit(LED_Pattern,1,value)
#define set_LED2(value) writeBit(LED_Pattern,2,value)
#define set_LED3(value) writeBit(LED_Pattern,3,value)
// Returns 1, if the LED is on
#define LED0 readBit(LED_Pattern,0)
#define LED1 readBit(LED_Pattern,1)
#define LED2 readBit(LED_Pattern,2)
#define LED3 readBit(LED_Pattern,3)
#endif //_LED6_MUX_
// meine LED Makros basieren auf LED Nummer
#define LED_EIN(NR) LED_Pattern |= (1 << NR);
#define LED_AUS(NR) LED_Pattern &= ~(1 << NR); LED_Blinker &= ~(1 << NR);
#define LED_BLINK(NR) LED_Pattern |= (1 << NR); LED_Blinker |= (1 << NR);
#define LED_TOGGLE(NR) LED_Pattern ^= (1 << NR);
void led_ausgabe(); // LED Multiplex Ausgabe
// Debughilfen: vordere LED links/rechts bei ea==0 aus-,
// sonst einschalten, !! schaltet jew. die andere LED aus
void augeL(uint8_t ea);
void augeR(uint8_t ea);
/***************************************** LED MULTIPLEX **************/
#endif // _ZYKLUS_H_
/************************************************** *******************
N I B O B E E Feste Zykluszeit für Mainloop und
zyklische (Sensor) Ein- und (LED) Ausgabe
Version 1.07 für alternative Nibobee C Bib von s.frings
20100411 Birger Töpelmann
************************************************** *******************/
#include "zyklus.h"
#include <stdio.h>
#include <avr/interrupt.h>
#include <util/atomic.h>
#include <util/delay.h>
/***************************************** CLOCK & MOTPID ***********/
volatile uint16_t takt_ms;
volatile uint32_t takt_sec;
//#define Zykluszeit 1 // Zykluszeit der Hauptschleife in ms
volatile uint8_t zyklus_ms; // Zyklustimer wird in ISR inkrementiert
// und in Zyklus() überwacht und genullt
/************************************************* SENS *************/
volatile uint8_t Sensors = 0; // Aktueller Eingabestatus
volatile uint8_t SensSFL = 0; // Steigende Flanke erkannt
volatile uint8_t SensFFL = 0; // Fallende Flanke erkannt
void sens_eingabe()
{
SensSFL = ~Sensors; // vorherigen Zustand merken für Flankenerkennung
SensFFL = Sensors;
Sensors ^= Sensors; // bits nullen für neuen Zustand
Sensors |= ~(PINC & 0xf0); // 1 == Sensor aktiv
// Hier Eingänge von Ext-Ports Pins verOdern
SensSFL &= Sensors; // Steigende Flanke erkennen
SensFFL &= ~Sensors; // Fallende Flanke erkennen
}
/************************************************* LED **************/
/*
#define LED_EIN(NR) LED_Pattern |= (1 << NR);
#define LED_AUS(NR) LED_Pattern &= ~(1 << NR); LED_Blinker &= ~(1 << NR);
#define LED_BLINK(NR) LED_Pattern |= (1 << NR); LED_Blinker |= (1 << NR);
#define LED_TOGGLE(NR) LED_Pattern ^= (1 << NR);
*/
volatile uint8_t LED_Pattern = 0;
volatile uint8_t LED_Blinker = 0;
void led_ausgabe()
{
static uint16_t tmux = 0;
if (0 == tmux--) tmux = 640; // !Wert = vielfaches von 8
uint8_t leds = LED_Pattern; // Pattern für Ausgabe
if (tmux < 400) leds &= ~LED_Blinker; // Blinkbits
#ifdef _LED6_MUX_ // umgebauter NiboBee
uint8_t ddr = 0;
uint8_t pin = 0;
if ((1 << (tmux & 0x07)) & leds) // welche LED soll leuchten?
{
switch((tmux & 0x07))
{
case 0:{ ddr = 0x06; pin = 0x02;} break; // DUO links grün
case 1:{ ddr = 0x05; pin = 0x01;} break; // DUO links rot
case 2:{ ddr = 0x06; pin = 0x04;} break; // DUO rechts grün
case 3:{ ddr = 0x03; pin = 0x01;} break; // DUO rechts rot
case 4:{ ddr = 0x03; pin = 0x02;} break; // ROTAUGE links
case 5:{ ddr = 0x05; pin = 0x04;} break; // ROTAUGE rechts
// case 6:{ ddr = 0x00; pin = 0x00;} break;
// case 7:{ ddr = 0x00; pin = 0x00;} break;
}
}
DDRB = (DDRB & ~0x07) | ddr; // zwei PINS als Ausgang
PORTB = (PORTB & ~0x07) | pin; // ein Pin = 1, Rest = 0
#else // nicht umgebauter NiboBee
// Auch hier wird immer nur eine LED eingeschaltet
// 1 * Zykluszeit Ein; 3 * Zykluszeit Aus
PORTB = (PORTB & ~0x0f) | (leds & (1 << (tmux & 3)));
#endif //_LED6_MUX_
}
/*
// Debughilfen
void augeL(uint8_t ea)
{
uint8_t ddr = 0;
uint8_t pin = 0;
if(ea){ ddr = 0x03; pin = 0x02;} // ROTAUGE links
DDRB = (DDRB & ~0x07) | ddr; // zwei PINS als Ausgang
PORTB = (PORTB & ~0x07) | pin; // ein Pin = 1, Rest = 0
}
void augeR(uint8_t ea)
{
uint8_t ddr = 0;
uint8_t pin = 0;
if(ea){ ddr = 0x05; pin = 0x04;} // ROTAUGE rechts
DDRB = (DDRB & ~0x07) | ddr; // zwei PINS als Ausgang
PORTB = (PORTB & ~0x07) | pin; // ein Pin = 1, Rest = 0
}
*/
/******************************* TIMER 1 CONFIG PWM & TAKT **********/
// s.frings setup:
// Initialize the motor PWM
// Mode 2 = phase correct 10 bit PWM,
// channel A+B outputs are set on compare match when upcounting
// and cleared on compare match when downcounting.
// TCCR1A = PWM invertiert + + 10bit PWM Betrieb
// TCCR1A = (1<<COM1A1) | (1<<COM1A0) | (1<<COM1B1) | (1<<COM1B0) | (1<<WGM11); | (1<<WGM10);
// Original nibobee (springob) setup
// TCCR1A = PWM invertiert + + 9bit PWM Betrieb
// TCCR1A = (1<<COM1A1) | (1<<COM1A0) | (1<<COM1B1) | (1<<COM1B0) | (1<<WGM11);
// Use I/O clock without prescaling
// TCCR1B = (1<<CS10);
// Overflow Interrupt enable
// TIMSK |= (1<<TOIE1);
// F_CPU = 15 Mhz
// Prescale = 1
// Frequenz bei 10 bit 15000000/(2*1023)= 7.331 kHz ; OVF alle 136 uS
// Frequenz bei 9 bit 15000000/(2* 511)=14.677 kHz ; OVF alle 68 uS
#define T1PERIODE 136 // Periodendauer zwischen Overflows in uS
/************************************* TIMER 1 OVERFLOW ISR **********/
ISR(TIMER1_OVF_vect)
{
static uint16_t takt_us=0;
takt_us += T1PERIODE;
if (takt_us >= 1000)
{
takt_us -= 1000;
takt_ms++;
zyklus_ms++;
}
if (takt_ms >= 1000)
{
takt_ms -= 1000;
takt_sec++;
}
}
/**************************************** ZYKLUS & WATCHDOG **********/
uint8_t Zyklus(uint8_t Zzeit)
{
if(Zzeit == 0) // Zyklustimer Reset / Init
{
// Timer 1 Overflow Interrupt freigeben, damit die Uhr tickt
#ifdef TIMSK1
// ATmega644
TIMSK1 |= (1<<TOIE1);
#else
// ATmega16
TIMSK |= (1<<TOIE1);
#endif
zyklus_ms = 0;
return(0);
}
sei(); // Interrupts freigeben
led_ausgabe(); // Ausgänge aktualisieren
uint8_t zms = zyklus_ms; // aktuelle Zykluszeit merken
while(zyklus_ms < Zzeit){}; // Zykluszeit abwarten
sens_eingabe(); // Eingänge lesen
// -------------------- WATCHDOG ---------------------------
// es wird kein Reset/Neustart ausgelöst -> Endlosschleife
// LED blinken im Binär Muster der erfassten Zykluszeit
if(zyklus_ms > Zzeit) // Zykluszeit überschritten ?
{
uint8_t zyt = 0; // alternativ Zyklustimer (Blinken)
if (zms > 15) zms = 15; // Zykluszeit >= 15 -> alle LED ein
while(1==1) // Fehlerfalle Zykluszeit überschritten
{
_delay_ms(10); // Klassische Verzögerung
if(zyt-- == 0) zyt = 70;// Blinktakt 700ms
if(zyt > 50) {
LED_Pattern = zms; // Ist-Zykluszeit Binär ausgeben
} else {
LED_Pattern = 0; // LED aus für Blinklicht
}
led_ausgabe(); // LED Muster am Port ausgeben
}
}//--------------------------------------------------------
else zyklus_ms = 0; // Zykluszeit Timer Reset
return(zms); // Interessant bei Zykluszeit > 1:
// Wie lang war der Zyklus tatsächlich
}
Und soweit bin ich bisher mit der Main() von s.frings gekommen: hier sind noch etliche Anpassungen nötig; _delay_ms(), while{} oder blockierende Funktionen sind eigentlich No Gos. Aber ich verspreche mir davon, dass mehrere "prozesse" quasi gleichzeitig bearbeitet werden können.
/************************************************** *******************
N I B O B E E Projekt Alternative s.frings und Birger.T
zum Selberbasteln und Ändern Ver.20100411
************************************************** *******************/
#define Zykluszeit 1 // Hauptschleife Zykluszeit in ms
#define USE_SERIAL 0
#include "nibobee.h"
#include "zyklus.h"
uint16_t previousSpeed;
// Display battery status on the 4 led's
// LED0 = >4.1V
// LED1 = >4.3V
// LED2 = >4.5V
// LED3 = >4.7V
void battery_check() {
set_AREF(REF_256);
for (uint8_t i=0; i<10; i++) {
// _delay_ms(50);
for (uint8_t t=0; t<50; t++) {
_delay_ms(1);
led_ausgabe();
}
set_LED0(analog(VBAT)>(410*2));
set_LED1(analog(VBAT)>(430*2));
set_LED2(analog(VBAT)>(450*2));
set_LED3(analog(VBAT)>(470*2));
// _delay_ms(50);
for (uint8_t t=0; t<50; t++) {
_delay_ms(1);
led_ausgabe();
}
set_LED0(0);
set_LED1(0);
set_LED2(0);
set_LED3(0);
}
set_AREF(REF_VCC);
_delay_ms(50);
}
// Wait for start signal (touch any sensor)
// While waiting, display debug information from sensors:
// LED0: Left odometer sensor
// LED3: Right odometer sensor
// LED1: System timer
// LED2: Center line sensor
void wait_for_start() {
while (!(SENS_SW1 || SENS_SW2 || SENS_SW3 || SENS_SW4)) {
// Display status of odometry sensors while waiting
set_LED0(ODO_L);
set_LED3(ODO_R);
// Display system timer (flashes every second)
set_LED1((system_time() % 1000) < 20);
// Display line sensor
set_LED2(analog(LINE_C)>600);
_delay_ms(1);
led_ausgabe();
}
set_LED0(0);
set_LED3(0);
_delay_ms(10);
while ((SENS_SW1 || SENS_SW2 || SENS_SW3 || SENS_SW4)) {}
_delay_ms(400);
}
#define FORWARD 1
#define BACKWARD 0
volatile uint8_t running = 0;
// Drive forward or backward.
// Accellerate or decellerate softly to the maxSpeed.
// The distance is measured in 1/20 wheel rotations.
// LED0 or LED3 light if a which motor runs too fast.
uint8_t drive(uint8_t direction, uint16_t distance, uint16_t maxSpeed) {
static uint16_t speed= 0;
static uint32_t lastTime=0;
if(!running){
if (direction) {
set_DIR_L(1);
set_DIR_R(0);
}
else {
set_DIR_L(0);
set_DIR_R(1);
}
// Start with the previous speed
// uint16_t speed=previousSpeed;
// uint32_t lastTime=system_time();
speed=previousSpeed;
lastTime=system_time();
reset_odometer();
running = 1;
}
// while (odometer_left()<distance || odometer_right()<distance) {
if(!(odometer_left()<distance || odometer_right()<distance)) running = 0; // Ziel erreicht
else{
int16_t diff=odometer_left()-odometer_right();
// If left motor is too fast
if (diff>0) {
set_LED0(1);
PWM_L=speed/2;
PWM_R=speed;
}
// If right motor is too fast
else if (diff<0) {
set_LED3(1);
PWM_R=speed/2;
PWM_L=speed;
}
// Speed of both motors is equal
else {
set_LED0(0);
set_LED3(0);
PWM_L=speed;
PWM_R=speed;
}
// If a millisecond has elapsed, increase or decrease the speed
// a little until the maxSpeed has been reached.
uint32_t currentTime=system_time();
if (currentTime>lastTime) {
if (speed<maxSpeed)
speed++;
else if (speed>maxSpeed)
speed--;
lastTime=currentTime;
}
} // vom while
previousSpeed=speed;
return(running);
}
// Stop driving.
void stop() {
PWM_L=0;
PWM_R=0;
previousSpeed=0;
running = 0;
}
// Main program
int main()
{
uint8_t tour = 0; // Merker für den Fahrtabschnitt
uint16_t warten = 0;
// nibobee_init();
// Display battery status
battery_check();
// Wait for a start signal before start driving
wait_for_start();
// Drive forward and backward repeatedly, to demonstrate
// that the drive() function keeps straight on properly.
Zyklus(0); //Zyklustimer Reset / Init
while(1)
{
// deactivate_output_bit(IO_LINE_EN); // Front IR Leds aus
writeBit(PORTB,PB5,0);
Zyklus(Zykluszeit); // Zykluszeit abwarten
// activate_output_bit(IO_LINE_EN); // Front IR LEDs blitzen im Zyklustakt für
// Zeitmessung per IR Transistor und Oszi
writeBit(PORTB,PB5,1);
// Kollision (oder an die Fühler getippt) = Stop
if(SENS_SW2_S || SENS_SW3_S) {
tour = 100;
stop();
}
// Neustart bei Sensor nach vorne tippen und loslassen
if((tour == 100) && (SENS_SW1_F || SENS_SW4_F)) tour = 0;
// Drive forward 2 meters, accellerating up to nearly maximum speed
if((tour == 0) && !drive(FORWARD,2000/6,900)) tour = 10;
// Drive forward 30cm, decellerating down to the minimum speed
if((tour == 10) && !drive(FORWARD,300/6,200)){
stop();
tour = 20;
warten = 500;
}
// _delay_ms(500);
if((tour == 20) && !warten--) tour = 30;
// Drive backward 2 meters, accellerating up to nearly maximum speed
if((tour == 30) && !drive(BACKWARD,2000/6,900)) tour = 40;
// Drive backward 30cm, decellerating down to the minimum speed
if((tour == 40) && !drive(BACKWARD,300/6,200)) {
stop();
tour = 50;
warten = 500;
}
// _delay_ms(500);
if((tour == 50) && !warten--) tour = 0;
}
return 0;
}
Oder ich weiß nicht - liege ich mit meiner Vorstellung einer Controllerprogrammierung total daneben? Dann geb' ich's auf und bastel mir 'ne Logo oder zwei auf Räder... :-s
Eigentlich wollte ich noch ein Foto machen: Meine Erfahrung LiPos halten so lange, bis man sie dann mal braucht.. bis zum WE.
s.frings
07.04.2010, 10:48
Ich hatte mich bemüht, ein möglichst simples Programmbeispiel abzugeben, welches zeigt, wie man die wesentlichen Funktionen der Library nutzt.
Natürlich stören blockierende Funktionen in einer anständigen Anwendung. Das sehe ich absolut genau so. Deswegen sind in der Library solche Sachen auch nicht drin.
Hallo,
na prima, so bauen wir alle unsere eigenen Libs für die Biene ;-) Ich baue ja auch daran herum - noch ein bißchen für mich selber, weil sie meines erachtens noch nicht so den Stand zur Veröffentlichung hat. Vielleicht liege ich damit auch falsch und sollte einfach ein freies Repository auf sourceforge anlegen, damit wir alle daran herumbauen können?
Jetzt aber zu Deiner Lib:
Mir ist gerade noch etwas wichtiges aufgefallen:
_Alle_ Variablen, die sowohl in den IRQs als auch in anderen Programmteilen verändert werden, mit "volatile" deklarieren! Sonst kann das unvorhersehbare Ergebnisse liefern: Der Compiler optimiert sonst unter Umständen unzulässig. Die atomaren Blöcke ersetzen _nicht_ volatile. Außerdem empfehle ich, alle vermeidbaren atomaren Blöcke wirklich wegzulassen.
Da passieren einige Dinge, die mit unnötig aufwendig erscheinen, manches ist wohl auch etwas konfus umgesetzt.
Dazu kann ich nur den Tipp geben: Genau darüber nachzudenken, was der Autor uns wohl damit sagen wollte. Denn bei einigen Dingen hatte ich auch immer zuerst das Gefühl "Was macht er es sich so schwer???". Aber nachdem ich das Datenblatt des ATmega gelesen hatte, wusste ich oft genug, warum ;-)
Zum Beispiel die PWM Steuerung: Wenn man die Fahrtrichtung ändert, wird der Motor zuerst gestoppt und erst beim nächsten Interrupt die neue Fahrtrichtung und Geschwindigkeit eingestellt. Das klingt ja erstmal sinnvoll, aber beim nächsten Interrupt steht der Motor noch lange nicht. Also muss man entweder viel länger warten oder man lässt es ganz bleiben.
Prima Beispiel für das, was ich oben meinte. Der Original-Code ist an dieser Stelle tadellos korrekt, alles andere ist mutig. Denn: Es wird nicht gewartet, bis der Motor stoppt, sondern bis der Motor nicht mehr durch die PWM bestromt wird, bevor die Fahrrichtung gewechselt wird. Und das finde ich durchaus sinnvoll, den Motor nicht mitten im bestromten Zustand andersherum zu polen. Wo ich Dir recht gebe: Der Kommentar an den ISRs ist etwas blöde - denn er wartet in der Tat nicht, bis der Motor steht.
Irgendwie nervt mich auch, dass die eigentlich winzigen Funktionen über mehrere include-Files und mehrere (nicht gleich lautende) Libraries (*.a files) verstreut sind.
Dazu habe ich zwei Anmerkungen: Zum einen gebe ich Dir recht, dass die Lib etwas konfus aufgeteilt ist - vor allem auch die Aufteilung auf mehrere .a-Dateien habe ich gar nicht verstanden. Sinn solcher Archive ist ja gerade die Zusammenfassung mehrerer Object-Dateien ist ( .o ).
Zum anderen sind verfolgen Libs einen anderen Ansatz als "normale" Programme. Deshalb ist es durchaus üblich und sinnvoll, die einzelnen Funktionen für die verschiedenen Peripherieteile in eigene C-Dateien zu schreiben, so etwas nennt man auch modulare Software. Das hat viele Vorteile: Wenn irgendwo ein Fehler bei einem Peripherieteil auftritt, weiß man sofort, in welcher Datei man suchen muss. Wenn mehrere Leute an einem Projekt arbeiten, hat jeder "seine" (bzw. "ihre") Dateien. Wenn man ein neues Projekt mit ein paar (aber nicht allen) gleichen Aufgaben / Peripherieteilen und ein paar neuen Teilen hat, kann man die Teile aus dem alten Projekt durch einfaches Kopieren übernehmen - und muss den Code nicht noch filetieren - welche Teile gehören jetzt wie wozu? Des weiteren sind in der Lib-Datei weiterhin die einzelnen .o-Dateien vorhanden. Der C-Compiler bindet in ein Projekt nur die .o-Dateien aus der .a-Datei ein, die wirklich eingesetzt werden. Bei nur einer .o-Datei also immer alles.
Wenn Du also eine _echte_ Lib bauen willst, die nicht jeder für sich anpasst und so wie sie ist weiter genutzt werden können soll, rate ich Dir unbedingt zu einem modularen Aufbau.
Übrigens ist in der Modularität und der Übernahme aus einem anderen Projekt auch die leichte Konfusität der Codes zu entnehmen: Übereinstimmende Funktionen sind von dem Nibo2 übernommen worden - schaut euch einfach mal dessen Sourcecode an ;-)
Und ich vermisse, dass der serielle Port als standart Eingabe und Ausgabe für stdio.h eingerichtet wird, so dass Befehle wie fgets, puts, prinf, etc.funktionieren.
Tja, klingt nach einer prima Idee :-) An dieser Stelle müsste ich dann mal überlegen, wie das bei meiner Lib funktionieren könnte. Denn ich nutze die USART ganz anders, als eigentlich gedacht... Wie man dazwischen hin und herschalten kann - mal drüber nachdenken.
Überhaupt stecken in Deinem Code gute Ideen, mal sehen, ob ich etwas übernehmen kann ;-)
Viel Spaß noch beim weiter entwickeln :-)
Ciao bantyy
Hallo,
Was ist für die Motorenansteuerung eigentlich besser, hohe oder niedrigere PWM Frequenz?
Das kommt darauf an ;-)
Bei hohen Frequenzen werden die Umsteuerverluste in den Transistoren auch höher, allerdings sind diese kHz-Frequenzen alle noch nicht in einem kritischen Bereich. Ich habe aus technischen Gründen schon mit 36 kHz experimentiert - und das geht prima. Die Trägheit des Motors und die elektrischen Eigenschaften der Spulen im Motor (Strom ändert sich stetig) führen dazu, dass beim Motor bei höheren PWM-Frequenzen die eingestellte Leistung umgesetzt wird.
Niedrige Frequenzen (unter 20 kHz) sind hörbar. Habt ihr schon mal einen IC losfahren gehört? Da gibt es eine Baureihe (oder war es doch eine ICE-Baureihe?), da ist eine PWM im hörbaren Bereich aktiv - und das klingt wie eine Tonleiter. Klingt schon bescheuert, wenn man neben dem losfahrenden Zug steht und der spielt einem eine Tonleiter vor... Das ist btw auch der Grund, warum ihr bei 7 kHz etwas hört: noch fieser sind 1 kHz. Hab ich alles ausprobiert ;-) Bin ja schließlich auch neugierig...
Die niedrigen Frequenzen haben den Vorteil, dass dort ein höheres Anfahrtmoment zur Verfügung steht. Schließlich werden die Spulen im Motor dort über einen längeren Zeitraum voll bestromt, in dem sie das volle Moment auf die Achse übertragen können.
In der Praxis habe ich herausgefunden, dass es kaum einen Unterschied macht, ob man mit einer 36 kHz oder einer 1 kHz PWM arbeitet. Lediglich der minimal notwendige PWM-Wert sinkt geringfügig - aber nicht der Rede Wert.
Übrigens ist meine Erfahrung, dass sich die Motoren ab etwa 1/8 Vollgas in Bewegung setzen, vorher steht alles still. Ich halte eine "Auflösung" von 10 Bit in der Praxis nicht für relevant. Selbst mit 8 Bit lassen sich die Motoren noch ganz entspannt genau genug steuern. Ich habe mich übrigens für Timer-Mode 10 entschieden: Damit kann ich eine beliebige "Auflösung" und damit Frequenz einstellen.
Und warum ich immer so mit 36 kHz rumwurschtel: Ich würde mir da gerne einen IR-Sender (RC5) mit dran hängen. Zur Zeit bin ich allerdings noch bei einem Empfänger. Der ist mir erstmal wichtiger. Allerdings ist mein Ansatz nicht ATmega16-kompatibel. Dafür nutze ich den SPI-Mode der USART - und den gibts erst ab ATmega xx4. Also 164 / 324 / 644 / 1284 (ja, in den Datenblättern existiert auch der letztere!!!).
Ich habe btw auch einen Systemtimer und ich muss sagen, meine "Vergangenheit" hat mich evtl. eingeholt: Die Idee, den Timer auf 32 Bit aufzubohren und darauf setzen, dass die Biene niemals länger als 49 Tage am Stück eingeschaltet ist, ist schon OK. Ich nutze statt dessen 16 Bit, die auch überlaufen dürfen, dafür kann meiner nur etwa eine Minute in die Zukunft arbeiten. Meine bisherige Software waren alle Langläufer - musste also fünf Jahre und länger ohne Probleme mit Timerüberläufen zurecht kommen.
Für den Systemtimer nutze ich btw timer2 und den Capture Compare, um wirklich auf die 1 ms zu kommen.
Meine Biene ist nur ein klein wenig stromoptimiert und ich habe ihr einen ATmega644 gegönnt, ansonsten unverändert. Ich erkenne auf jeden Fall, dass eine allgemeine Bibliothek nicht so einfach zu erstellen ist, da sich die Hardware der Biene eh jeder mehr oder weniger anpasst.
So, ich hoffe, dass ich weder langweile noch nerve mit den langen Texten :-)
Ciao bantyy
Servus; ich habe jetzt meinen aktuellen Stand der Zyklus-Dateien und der überarbeiteten Demo von s.frings hier im Thread (weiter oben) rein editiert.
zyklus baut auf der nibobee Lib von s.frings auf bzw. soll sie ergänzen; damit das ohne Fehler & Warnungen funktioniert, musste ich allerdings in der folgendes ergänzen:
in der nibobee.c
#if USE_SERIAL
#include <util/setbaud.h>
#endif
und in der nibobee.h den gesamten Block unter "Status Led's" auskommentieren.
Nun das Demoprogramm funktionierte bei mir nicht so doll; mein NiboBee hat 'nen Rechtsdrall, den die Regelung der Demo auch nicht ausregeln konnte (auch nicht das Original ohne zyklus). Ich muss wohl mal wieder Staubmäuse aus den Getrieben befreien.
Ach ja, und wen's interessiert wie ich mein Nibobee hergerichtet habe, anbei mal die Bilder:
s.frings
12.04.2010, 16:30
Danke bantyy, für Deine ausführlichen Infos zur PWM Freuqenz.
s.frings
06.08.2010, 19:11
Ich befasse mich gerade ein wenig mit Lejos. Dort finde ich toll, dass bei Fahrmanövern der geschätzte Bremsweg berücksichtigt wird. Dort kann man mit Höchstgeschwindigkeit eine bestimmte Strecke abfahren, ohne ungewollt über das Ziel hinaus zu schießen. Rechtzeitig vor dem Ziel wird abgebremst.
Diese Idee sollte man auch in eine NIBObee Library übernehmen, finde ich.
Hallo,
Ich befasse mich gerade ein wenig mit Lejos.
Welch Zufall, ich habe inzwischen auch einen NXT und bin deshalb - und wegen eines Verkehrsunfalls - mit meinem libbee-Tutorial noch nicht fertig. Aber auf einem guten Weg, ich schätze ich habe so etwa die Hälfte fertig. Und ab nächster Woche Urlaub, also ein wenig Zeit dafür :-)
Dort finde ich toll, dass bei Fahrmanövern der geschätzte Bremsweg berücksichtigt wird.
Der Bremsweg ist dort aber auch manipulierbar. Schließlich nutzen die keine diskrete Minimal-Motorbrücke sondern einen Chip, der die volle Funktionalität bietet.
Bei der Biene ist das sehr viel schwieriger: Die Motorbrücke ist vereinfacht und müsste dafür erstmal geändert werden, um einen Bremsbetrieb zu ermöglichen.
Der eigentliche Bremsweg ist darüber hinaus sehr abhängig von dem Untergrund. Wie machen sie denn das bei LeJOS genau? Ich hab nicht in den Quellcode reingeschaut, aber dort wissen sie ja nicht einmal, was für eine Art von Antrieb an dem Roboter ist (Räder, Gummi- oder Plaste-Raupen, Reifendurchmesser, Geometrie...)
Was natürlich möglich wäre: Ein annähern mit voller Geschwindigkeit auf die Sollstrecke - x % und den Rest dann mit geringster Geschwindigkeit fahren. So etwas in der Art ist schon realisierbar. Aber richtig bremsen und Motoren "festhalten", wie es beim NXT möglich ist, geht mit der Biene ohne Umbau nicht.
Ciao bantyy
s.frings
12.08.2010, 10:31
Bei Lejos gibt es einen default Wert für die geschätzte Bremsleistung. Damit kann der Bremsweg geschätzt werden und wiederum eine zielsichere Bremsung hingelegt werden - vorausgesetzt der default Wert stimmt. Den kannst Du programmatisch anpassen. Da die Lego Reifen richtig guten Grip haben, die Getriebemotoren sehr stark bremsen können, und der NXT Computer viel Gewicht auf die Reifen bringt, kommt es auf einen exaktem Schätzwert nicht so sehr an. Die Lego Hardware kann beeindruckend kurze Vollbremsungen hinlegen.
Zurück zum Nibo: Die Motorbrücke kann auch bremsen, nur einen Freilauf kennt sie nicht (wie es Lego kann). Die Bremsleistung ist aber um Welten schlechter, als bei Lego, weil die Motoren winzig klein sind, weil das Getriebe nur eine geringe Übersetzung hat, weil die Reifen (zumindest auf Laminat) wenig Grip haben und weil der ganze Roboter sehr wenig Gewicht auf die Achsen bringt. Einen Stein mit Gummiband auf's Batteriefach klemmen hilft übrigens prima.
Wegen der geringen Übersetzung und dem geringen Gewicht kann der Nibo viel schneller fahren, als der Lego Roboter. Die Bremsleistung ist aber schwach, deswegen muss der Nibo Roboter sehr viel früher bremsen.
Ich hatte es erstmal so versucht, dass während der Fahrt fortlaufend die aktuelle Geschwindigkeit ermittelt wird (alle 100ms) und der sich daraus ergebende Bremsweg geschätzt wird. Ist der dann kleiner oder gleich der Distanz zum Ziel, dann wird gebremst. Problem: Schätze ich zu viel, dann kommt der Roboter zu früh zum Stillstand.
Also dachte ich mir, lass ich die Bremse wieder los, wenn ich merke, dass ich mich verschätzt habe. Das führt aber zu unschönem Ruckeln auf den letzten Zentimetern vor dem Ziel. Man kann dann buchstäblich sehen, dass der Bremsweg falsch geschätzt wurde.
Letztendlich muss man aber mehr schätzen, als der tatsächliche Bremsweg ist, weil - wie Du schon bemerkt hast - der Bremsweg sehr vom Untergrund abhängt. Ein Staubkorn auf dem Boden kann sich da schon signifikant auswirken, insofern muss man schon wenigstens 20% Reserve einplanen.
Gestern habe ich etwas anderes versucht, mit Erfolg: Alle 100ms berechne ich die geschätzte maximal zulässige Geschwindigkeit, mit der eine Zielgenaue Bremsung gerade noch möglich wäre. Dann begrenze ich die soll-Geschwindigkeit auf genau diesen Wert. Wobei ich bei der Konstante BREMSLEISTUNG schon 20% Reserve vorgesehen habe.
sollTempo=200cm/sec;
motorPower=250;
BREMSLEISTUNG=85;
while (ziel_noch_nicht_erreicht) {
if (100ms_vergangen) {
maxTempo=sqrt(restDistanz*BREMSLEISTUNG);
if (sollTempo>maxTempo)
sollTempo=maxTempo;
fehler=sollTempo-istTempo;
motorPower=motorPower+fehler;
}
}
motorPower=0;
(Das ist Pseudo-code, kann man so nicht 1:1 übernehmen)
Aber ganz so einfach geht es leider nicht, weil das Wurzelziehen zu lange dauert. Deswegen die folgende Annäherungsschleife:
sollTempo=200cm/sec;
motorPower=250;
BREMSLEISTUNG=85;
while (ziel_noch_nicht_erreicht) {
if (100ms_vergangen) {
while (1) {
bremsweg=(sollTempo*sollTempo)/BREMSLEISTUNG;
if (bremsweg>restDistanz)
sollTempo=soll_tempo*0.9;
else
break;
}
fehler=sollTempo-istTempo;
motorPower+=fehler;
}
}
motorPower=0;
Letztendlich führt das Ganze dazu, dass der Roboter mit sehr geringer Geschwindigkeit am Ziel ankommt, wo ich dann getrost per Vollbremsung endgültig anhalten kann. Ich komme also stets wenige Millimeter hinter dem Ziel zum stehen.
Das ist bei Lejos auch so, nur fährt der wieder ein Stück zurück, wenn er das Ziel um mehr als eine gewisse Toleranzgrenze überschritten hat (denn Lejos verwendet einen PID Regler, mein Nibo jedoch nur einen PI Regler).
Ein PID Regler wäre beim Nibo problematisch, denn wenn er erstmal rückwärts in Bewegung ist, kann er nicht schnell genug stoppen. Die Abweichung vom Ziel würde dadurch eher noch größer, als kleiner. Außerdem macht man das mit einem Auto vor der Ampel ja auch nicht. Wenn ich die Haltelinie knapp verfehlt habe, setzte ich ja auch nicht zurück, da käme ich mir blöd vor.
Zudem würde ein Zurücksetzen richtig viel Zeit kosten. Ein Flüssiger Unterbrechungsfreier Übergang zwischen geradeaus-fahrt und anschließender Drehung wäre dann unmöglich (ist bei Lejos auch unmöglich).
Hallo,
Da die Lego Reifen richtig guten Grip haben, die Getriebemotoren sehr stark bremsen können, und der NXT Computer viel Gewicht auf die Reifen bringt, kommt es auf einen exaktem Schätzwert nicht so sehr an. Die Lego Hardware kann beeindruckend kurze Vollbremsungen hinlegen.
Das schreibe ich weder dem Getriebe noch dem Gewicht des Roboters zu. Die NXT-Motoren können regelrecht "festgehalten" werden - wenn Du versuchst, den von Hand zu drehen, wird das schwer ohne zerstörende Wirkung.
Irgendwie haben die eine Bremsen / Halten-Funktion implementiert - wie habe ich mir zugegebenermaßen nicht so genau angesehen. Ach ja, ich hab die Original-Firmware auf dem NXT, bei dem geht das jedenfalls.
Zurück zum Nibo: Die Motorbrücke kann auch bremsen, nur einen Freilauf kennt sie nicht (wie es Lego kann).
Ich hätte das jetzt spontan anders gesehen - denn die Motoren laufen eigentlich immer nach.
Die Bremsleistung ist aber um Welten schlechter, als bei Lego, weil die Motoren winzig klein sind, weil das Getriebe nur eine geringe Übersetzung hat, weil die Reifen (zumindest auf Laminat) wenig Grip haben und weil der ganze Roboter sehr wenig Gewicht auf die Achsen bringt.
Letzteres ist erscheint mir nicht logisch zu sein. Wenn der Roboter leicht ist, müsste er auch viel schneller stehen - Massenträgheit lässt grüßen.
[/quote]Einen Stein mit Gummiband auf's Batteriefach klemmen hilft übrigens prima.[/quote]
Damit erhöhst Du die Reibung im Getriebe -> Der Roboter beschleunigt auch langsamer und du verbesserst den Grip.
Wegen der geringen Übersetzung und dem geringen Gewicht kann der Nibo viel schneller fahren, als der Lego Roboter.
Ich hab den Lego-Roboter zur Zeit als Humanoid. Als Rollen-Roboter kann ich mir durchaus vorstellen, ihn auf die Nibobee-Geschwindigkeit zu bringen.
dann wird gebremst. Problem: Schätze ich zu viel, dann kommt der Roboter zu früh zum Stillstand.
Ich hatte schonmal versucht, mit bremsen zu arbeiten. Das ist mir leider so gar nicht gelungen. Wie lässt Du denn die Bee praktisch bremsen?
Das führt aber zu unschönem Ruckeln auf den letzten Zentimetern vor dem Ziel. Man kann dann buchstäblich sehen, dass der Bremsweg falsch geschätzt wurde.
Blöd :-(
maxTempo=sqrt(restDistanz*BREMSLEISTUNG);
OK, Du verlangsamst also mit einer Wurzelfunktion. Wie wäre es denn mit einer e-Funktion? Die ist doch immer am schnellsten am Ziel, oder hab ich das falsch in Erinnerung?
Interessant übrigens. Ich habe meine "Regelung" im Prinzip genau so nur für das Beschleunigen aufgebaut. Ich nähere mich quadratisch dem Soll.
Aber ganz so einfach geht es leider nicht, weil das Wurzelziehen zu lange dauert.
Wie so oft ;-) Aber deshalb gibt es ja Näherungslösungen.
Letztendlich führt das Ganze dazu, dass der Roboter mit sehr geringer Geschwindigkeit am Ziel ankommt, wo ich dann getrost per Vollbremsung endgültig anhalten kann. Ich komme also stets wenige Millimeter hinter dem Ziel zum stehen.
Wieviel vor dem Ziel ist denn der Bremsvorgang wirklich merkbar / sichtbar? Das könnte ich in meine Lib ja auch noch einbauen - nachdem ich die anderen Aufgaben mal erledigt habe... ;-)
Ein PID Regler wäre beim Nibo problematisch, denn wenn er erstmal rückwärts in Bewegung ist, kann er nicht schnell genug stoppen.
Mh. Dann gäbe das ein unendliches hin- und her. In dem Fall wären die Parameter irgendwie falsch gewählt...
Zudem würde ein Zurücksetzen richtig viel Zeit kosten. Ein Flüssiger Unterbrechungsfreier Übergang zwischen geradeaus-fahrt und anschließender Drehung wäre dann unmöglich
OK, das mag ich an meiner Biene :-)
(ist bei Lejos auch unmöglich).
Mh, wie gesagt, mit dem NXT hab ich erstmal die Füßchen des AlphaRex versucht, in den Gang zu bekommen. Wenn ich mal etwas fahrendes habe, werde ich da bestimmt auch eine Menge herumdoktorn...
Also: Es gibt einiges zu tun ;-) Viel Spaß beim weiter probieren...
Wenn ich neue Erkenntnisse habe, schreibe ich mal wieder :-)
Ciao bantyy
Rabenauge
13.08.2010, 01:37
Nur als Anmerkung: mit dem LEGO RCX (NXT habe und will ich nicht) liegt mein persönlicher Geschwindigkeitsrekord bei ungefähr 22 km/h, _das_ schafft das Bienchen dann doch nicht.
Allerdings sind wohl die Motoren des RCX auch stärker als die des NXT, sagt man.
Schon der einfache Roverbot dürfte durchaus Bienentempo erreichen, zumindest viel langsamer ist der nicht (mittelgrosse Räder direkt auf den Motoren, Power dafür haben die genug).
Es gibt sogar in der RCX-Anleitung einen Bot, der die grossen Räder (haben geschätzte 8cm Durchmesser, bin jetzt zu faul, sie zu messen) noch direkt auf den Motorachsen benutzt werden, zwar steigt dann der Stromverbrauch spürbar, aber es geht ohne weiteres. Der beschleunigt allerdings dann schon sichtbar, aber dank der Bremsfunktion ist er dennoch sehr wendig.
Das muss man den LEGO-Leuten lassen: das Zeug ist richtig Klasse.
s.frings
13.08.2010, 09:01
Meine Aussage zur Geschwindigkeit des Lego Motors bezog sich auf den direkten Anschluß kleiner Räder, mit etwa 3-4cm Durchmesser. Mit entsprechendem Getriebe sieht das natürlich ganz anders aus, denn die Lego Motoren sind viel stärker, als die NIBObee Motoren.
Wegen dem Bremsen: Bremsen tut sowohl der Lego Motor, als auch der NIBObee, indem er die beiden Zuleitungen des Motors gegeneinander kurz schließt. Wenn Du dann den Motor durch äußere Kräfte drehst, wirkt er wie ein kurzgeschlossener Generator - er bremst und verheizt die Energie.
Nun ist der Kurzschlußstrom beim NIBObee durch die Beschaltung 500mA begrenzt, bei Lego kann er jedoch mehrere Ampere betragen, darum bremst der Lego Motor viel stärker. Hinzu kommt, dass der Motor an sich höhere Kräfte übertragen kann (in beide Richtungen) und ein höheres Übersetzungsverhältnis im Getriebe hat (NIBObee=1:25, Lego geschätzt=1:50).
Die Lejos Software legt noch einen drauf. Wenn Du die Halte-Funktion aktivierst, dreht sie den Motor aktiv zurück, wenn er von außen verdreht wird. Dass heißt, während Du versuchst, den Motor rechts rum zu drehen, legt die Software umgekehrt herum Strom an. Dabei fließen sehr hohe Ströme, locker mehr als 3 Ampere.
Bei Vollgas fährt meine NIBObee etwa 2 Meter Pro Sekunde. Der Bremsweg beträgt dann etwa 1,5 Meter, um einigermaßen Punktgenau stoppen zu können (mit weniger als 1cm Abweichung).
Ja, die Bremsleistung der Biene ist auffällig schwach, ich kann verstehen, dass jemand denkt, sie könnte gar nicht bremsen. Doch zum Beweis, teste mal das:
Beschwere das Batteriefach so weit, dass die Biene vorne fast abhebt, um die Reibung des vorderen Gleiters zu minimieren. Schalte die Stromversorgung ein. Das Programm soll den Motor NICHT ansteuern, was dann einer normalen Bremsung entspricht. Zum Beispiel einfach eine leere Endlosschleife ohne irgendwelche Library oder Initialisierung. Gib der Biene einen Schubs und schaue, wie weit sie rollt. Schalte dann die Stromversorgung aus, und gib ihr nochmal einen Schubs. Du wirst sehen, dass die dann deutlich weiter rollt, nämlich etwa doppelt so weit.
Also, bremsen kann sie schon, nur nicht so, dass man damit jemand beeindrucken könnte. Ich finde das nicht schlimm, denn so kann man sich ausgiebig mit dem Thema Beschleunigung und Bremsweg auseinandersetzen. Beim Lego Motor ist ein gesteuertes Bremsen aufgrund der tollen Hardware meisten gar nicht notwendig. Das ist praktisch, aber auch weniger spannend.
Bremsweg in cm=(uint32) (Geschwindigkeit in cm/sec) * (Geschwindigkeit in cm/sec) / 600
Das hängt aber -wie andere schon erwähnt haben - sehr vom Untergrund und Gewicht der Biene ab. Ich habe z.B. ein Stück Eisen als Zusatzgewicht drauf gebaut. Der Wert 600 wäre den örtlichen Gegebenheiten anzupassen.
Man kann das auf kleinere Zahlen umstellen, für geringere CPU Auslastung. Zum Beispiel:
Bremsweg=(uint16) (Geschwindigkeit>>2) * (Geschwindigkeit>>2) / 16
Beim Fahren von Kurven zählt übrigens die Geschwindigkeit der schnelleren Seite. Daher kann der Roboter sich viel schneller auf der Stelle drehen (und zielsicher stoppen), als wenn man nur ein Rad antreiben würde, also extrem-Kurven nach Links oder rechts.
Jedenfalls hat Lego zweifellos die bessere Hardware, Spaß habe ich aber mit beiden Robotern. Die Biene animiert mich mehr zur Erforschung der physikalischen und programmatischen Grundlagen, während der Lego Roboter eher meine Kreativität fördert, weil man da male eben schnell eine Idee ausprobieren kann.
Rabenauge
13.08.2010, 12:16
Das mit dem aktiven Bremsen interessiert mich mal: _kann_ der NXT solche Ströme denn ab? Die Endstufen des RCX rücken maximal 500mA raus, wenn es drüber geht, stoppt er und schlägt notfalls Alarm.
Das war bei meinem Speeder eine echte Herausforderung, die drei Motoren so anzusteuern, dass die Endstufen innerhalb der tolerierten Grenzen überlastet werden.. ;)
Übrigens: die RCX-Motoren _sind_ Getriebemotoren, innen drin ist ein winziges Motörchen und eine schnuckelige Untersetzung, darum sind die Dinger so kräftig.
Natürlich ist die LEGO-Hardware besser, aber man zahlt ja selbst für nen gebrauchten RCX-Brick noch annähernd so viel wie für nen kompletten Nibobee.
Ansonsten schliesse ich mich deiner Sichtweise an: man ist, bedingt dadurch, dass man eh vieles einfach selber machen muss, irgendwie ungebundener beim NiboBee, bei LEGO weigere ich mich strikt, Teile zu modifizieren oder gar Fremdteile zu benutzen (ok, eine der LEGO-Lampen hab ich auf LED umgebaut, weil ich mehr Licht brauchte für die VLL-Schnittstelle zum Micto-Scout).
Bist du, beim NiboBee sicher mit der Geschwindigkeit? Ich hatte eher (leider läuft meiner noch immer nicht wieder, ich komme einfach nicht zu, mal weiter Fehlersuche zu betreiben) irgendwas mit nem halben Meter pro Sekunde im Hinterkopf..
s.frings
13.08.2010, 16:25
Ja. Ich weiß nicht, ob es bei Lego NXT eine Strombegrenzung gibt und wo das Limit ist. Sicher ist aber, dass mehrere Ampere möglich sind. Das ist einer der großen Vorteile vom NXT Gegenüber dem RCX.
Die NXT Motoren sind auch Getriebemotoren mit etwa 1:50 Übersetzung. Sie haben ein gelochtes Zahnrad mit Lichtschranke, zur Messung der Bewegung.
s.frings
14.08.2010, 15:36
Man sollte wohl auch den Reaktionsweg berücksichtigen. Wenn ich die aktuelle Geschwindigkeit alle 100ms berechne und zur Berechnung des Bremsweges heranziehe, dann ist in den letzten 100ms gefahrene Strecke mein Reaktionsweg.
Powered by vBulletin® Version 4.2.5 Copyright ©2024 Adduco Digital e.K. und vBulletin Solutions, Inc. Alle Rechte vorbehalten.