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:
Code:
// 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:
Code:
#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:
Code:
#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;
}