AdSense

Mittwoch, 31. Juli 2013

Funkstrecke mit Arduino und Atmega8

(English version) In meinen letzten beiden Posts habe ich die Verwendung der nRF24L01+ Funkmodule beschrieben. Nun möchte ich ein Projekt mit (mehr oder weniger) praktischem Nutzen vorstellen, das die Module verwendet:
Ich habe eine kleine Funkstrecke aufgebaut, bei der ein Atmega8-Mikrocontroller "Umweltdaten" erfasst und sie über Funk an einen Arduino Uno weiter leitet. Dieser stellt die Daten auf einem LCD-Display dar.

Hardware Atmega8

Der Aufbau auf der Atmega-Seite ist recht simpel. Der Funkmodule werden wie bereits beschrieben angeschlossen, zusätzlich habe ich an an den "Analog Input 1" (entspricht Pin 24 am Atmega8) den variablen Abgriff eines Potis angeschlossen (das zwischen 5V und GND liegt) um ein analoges Signal zu erhalten. Außerdem habe ich die digitalen Eingänge 0 und 1 (Pins 2 und 3 am Atmega8) über Schalter mit GND verbunden, um zwei digital Zustände darstellen zu können.

Hardware Arduino

Auch hier hält sich der Verkabelungsaufwand in Grenzen. Zusätzlich zum Funkmodul habe ich an den Arduino ein LCD-Display angeschlossen. Dieses habe ich jedoch nicht direkt mit dem Arduino verbunden, ich benutze der Einfachheit halber das LCD-I2C-Modul von Pollin (viel weniger Kabel ;) ). Dieses wird einfach an die Eingänge SDA und SCL des Arduinos gehängt und kann dann ganz einfach mit der passenden Library über I2C angesprochen werden. Zusätzlich habe ich den digitalen Eingang 2 mit einem Druckschalter auf GND gelegt.

Software Atmega8

Hier habe ich weitestgehend den Code übernommen, den ich in meinem letzten Beitrag zu den Interrupts schon vorgestellt habe:

//Include für Watchdog
#include <avr/wdt.h>

//Includes für Funkmodul
#include <SPI.h>
#include <Mirf.h>
#include <nRF24L01.h>
#include <MirfHardwareSpiDriver.h>

//Variablen
int data;    //übertragene Daten

void setup(){
  
  //Interrupt initialisieren
  attachInterrupt(0, receive, LOW);
   
  //Input-Pins initialisieren
  pinMode(0, INPUT_PULLUP);
  pinMode(1, INPUT_PULLUP);
     
  //Funkmodul initialisieren
  Mirf.spi = &MirfHardwareSpi;
  Mirf.init();
  Mirf.setRADDR((byte *)"atmeg");
  Mirf.payload = sizeof(unsigned int);
  Mirf.channel = 120;
  
  //auf 250kbit/s umstellen (0x26) (2Mbit => 0x0f)
  Mirf.configRegister(RF_SETUP, 0x26);
  
  //Interrupt nur wenn Daten empfangen
  Mirf.configRegister(CONFIG, 0x48);
  
  Mirf.config();
  
  //Watchdog starten
  wdt_enable (WDTO_2S);

}

void loop(){ 
  //hier könnte episches passieren
}

//wenn Daten empfangen wurden
void receive() { 
  noInterrupts();
  
  if(!Mirf.isSending() && Mirf.dataReady()){
    Mirf.getData((byte *)&data);
    
    //ADC einlesen
    if (data == 1) {
      data = analogRead(A1);
    }
    
    //IO einlesen
    else if (data == 2) {
      data = 0xff;
      
      for (int i = 0; i<= 1; i++) {
        bitWrite(data, i, digitalRead(i));
      }
    }
    
    //eingelesene Daten senden  
    Mirf.setTADDR((byte *)"ardui");
    Mirf.send((byte *)&data);
  }
  
  wdt_reset ();    //Watchdog zurücksetzen
  interrupts();
  
}

Da ich den Watchdog verwenden möchte, habe ich zusätzlich #include <avr/wdt.h> eingefügt . Die void setup() is soweit bekannt, neu sind hier nur die Initialisierung der Input-Pins sowie der Start des Watchdogs. Ich betreibe das Funkmodul auf Kanal 120 mit 250kbit/s um Störungen zu vermeiden.
Die void loop() ist (immer noch) leer, könnte aber mit "Leben" gefüllt werden. Die void receive(), die aufgerufen wird, wenn das Funkmodul Daten empfangen hat, hat sich auch nicht großartig verändert. Um Überlappungen zu vermeiden, werden zuerst die Interrupts deaktiviert. Dann wird geprüft, ob die Daten vom ADC oder von den digitalen Input-Pins angefordert wurden. Diese werden dann entsprechend übertragen. Zum Schluss wird der Watchdog zurück gesetzt und die Interrupts werden wieder aktiviert. Den Watchdog habe ich eingefügt, damit der Controller automatisch einen Reset druchführt, sollte er sich einmal (aus welchen Gründen auch immer) "verschlucken" und deswegen nichts mehr senden können.
Ein paar Worte noch zum Einlesen und Übertragen der digitalen Pins: die Pins sind intern mit Pullups verbunden, sind also active low. Um nun die Zustände aller Pins in einer "Sendung" übertragen zu können, gehe ich folgerndermaßen vor: zuerst setze ich die zu übertragende data-Variable auf 0xff (also 0b1111'1111). Dies bedeutet erstmal, dass alle Eingänge inaktiv sind. Dann prüft eine for-Schleife alle (zwei, gerne aber auch mehr) Eingänge und schreibt den Zustand des Eingangs mit bitWrite in das entsprechende Bit von data (also den Zustand von Eingang 0 in Bit 0, den Zustand von Eingang 1 in Bit 1 usw.).

Software Arduino

Hier hat sich ein bisschen mehr getan:

//Includes für Funkmodul
#include <SPI.h>
#include <Mirf.h>
#include <nRF24L01.h>
#include <MirfHardwareSpiDriver.h>

//Includes für LCD-Display
#include <Wire.h> 
#include <LiquidCrystal_I2C.h>


//Variablen
int data;          //übertragene Daten
int data_old = 0;  //beim letzten Mal übertragene Daten
unsigned long time;         //Zeitspeicher für Timeout
unsigned char button = 2;   //Pin des Schalters
unsigned char state = 2;    //Zustand AD (1) oder IO (2)
int debug = 0;

LiquidCrystal_I2C lcd(0x20,16,2);  //I2C-LCD-Display

void setup(){
  //Pins initialisieren
  pinMode(button, INPUT_PULLUP);
  
  //Interrupts initialisieren
  attachInterrupt(0, buttonPressed, LOW);
  
  //LCD-Display initialisieren
  lcd.init();                      
  lcd.backlight();
 
  //Funkmodul initialisieren
  Mirf.spi = &MirfHardwareSpi;
  Mirf.init();
  
  Mirf.setRADDR((byte *)"ardui");
  Mirf.payload = sizeof(unsigned int);
  Mirf.channel = 120;
  
  //auf 250kbit/s umstellen (0x26) (2Mbit => 0x0f)
  Mirf.configRegister(RF_SETUP, 0x26);
      
  Mirf.config();
  
}

void loop(){
  
    Mirf.setTADDR((byte *)"atmeg");
    data = state;                    //AD oder IO erwartet=?
    Mirf.send((byte *)&data);
    while(Mirf.isSending()){
    }
    delay(100);
    time = millis();
    while(!Mirf.dataReady()){
      if ((millis() - time) > 1000) {
        //Timeout beim Empfangen
        lcd.clear();
        lcd.setCursor(0,0);
        lcd.print("Timeout...");
        debug++;
        break;
      }
    }
    
    Mirf.getData((byte *) &data);
    
    //das Display wird nur dann aktualisiert, wenn sich die Daten ändern um flimmern zu verhindern
    if (data != data_old) {  
        
      lcd.clear();
      
      //AD-Wandler-Daten darstellen
      if (state == 1) {
        lcd.setCursor(0,0);
        lcd.print("AD-Wandler");
        lcd.setCursor(11, 0);
        lcd.print(debug);
        lcd.setCursor(0,1);
        lcd.print(data);
      }
      
      //IO-Daten darstellen
      else {
        lcd.setCursor(0,0);
        lcd.print("Digital IO");
        lcd.setCursor(0,1);
        lcd.print(bitRead(data,0));
        lcd.setCursor(2,1);
        lcd.print(bitRead(data,1));
      }
    
    }
    
    data_old = data;
    delay(250);
  
} 

//wenn Button gedrückt (inkl. Entprellfunktion)  
void buttonPressed() {
  noInterrupts();
  delay(50);
  if (digitalRead(button) == LOW) {
    while(digitalRead(button) == LOW);
   
    if (state == 1) {
      state = 2;        //auf IO umstellen
    }
    
    else {
      state = 1;       //auf ADC umstellen
    }    
    
  }
  interrupts();
}
  
Ich habe mir beim kommentieren des Codes extra Mühe gegeben ;) Includes und void setup() sollten also selbsterklärend sein. Die Interrupt-Funktion prüft, ob der Button gedrückt wurde und der Eingang somit auf LOW gezogen wird. Falls ja, wird die entsprechende Funktion aufgerufen, die den Schalter zuerst entprellt und dann entsprechend die Variable state umstellt. In der Variable wird gespeichert, ob der Nutzer den Wert des ADC oder der digitalen Eingänge des Atmegas sehen möchte. Mit dem Button lässt sich also zwischen den zwei Ansichten umschalten.
Die void loop() beginnt zunächst mit dem "üblichen" Code: übertragen des Wertes der state-Variable an den Atmega und warten auf Antwort. Falls binnen einer Sekunde keine Antwort erfolgt, wird ein Timeout ausgelöst und das Programm beginnt wieder von vorne.
Wenn erfolgreich Daten empfangen wurden, sollen diese auf dem LCD-Display dargestellt werden. Dazu wird zuerst geprüft, ob der Benutzer die Daten des ADC oder der digitalen Eingänge sehen möchte. Entsprechen wird dann die Darstellung auf dem LCD-Display aufgebaut. Zunächst habe ich diesen Schritt bei jedem Programmdurchlauf durchgeführt. Dies führte jedoch zu einem Flimmern des Displays. Daher habe ich zusätzlich eine Überprüfung eingefügt, ob sich die empfangenen Daten überhaupt geändert haben. Nur wenn dies der Fall ist, wird die Darstellung aktualisiert.

Anwendung

Ich habe zu Beginn des Posts ja einen praktischen Nutzen versprochen. Der momentane Aufbau kann das natürlich nicht bieten. Man könnte aber z.B. am Atmega das Poti durch einen Spannungsteiler mit einem Pt100-Widerstand und die Schalter durch Reed-Kontakte ersetzen und so eine Temperatur und den Öffnungszustand eines Fensters übertragen (Stichwort "Home Automation").
Oder Udo könnte sich damit eine Fernsteuerung für seinen Quadrocopter basteln. Das ist ehrlich gesagt auch der Grund, warum er mich dazu "gezwungen" hat, mich mit diesem Thema auseinander zu setzen und die Blog-Beiträge zu verfassen ;)

Samstag, 27. Juli 2013

Atmega/Arduino: Funkmodul nRF24L01+ mit Interrupts

(Englich version) In meinem letzten Beitrag habe ich die Grundlagen zu den nRF24L01+ bzw. nRF24L01P-Modulen erläutert. Wie bereits am Ende des Beitrags erwähnt war die Umsetzung des Codes (vorallem beim Epfänger) nicht optimal. Der Mikrocontroller lief dauerhaft in einer Endlosschleife die nichts anderes tat, als zu prüfen, ob den nun endlich wieder neue Daten empfangen wurden. In der Praxis ist das natürlich eher unpraktisch, der µC soll schließlich auch noch andere Aufgaben übernehmen.

Genau dafür wurden die Interrupts erfunden ;) Im letzten Beitrag habe ich bereits die Interrupt-Funktionen der Funkmodule angesprochen:
"IRQ ist der Interrupt-Pin des Moduls und kann bei 3 verschiedenen Events active low werden: Daten wurden empfangen, Daten wurden gesendet, Senden fehlgeschlagen. Diese Interrupts lassen sich jedoch frei ein- und ausschalten."
Die Interrupts werden über das Register CONFIG gesteuert. Über das Bit 4 steuert man den Interrupt bei fehlgeschlagenem Senden, Bit 5 schaltet den Interrupt bei erfolgreichem Senden und Bit 6 den beim Empfang von Daten. Wird das Bit auf 1 gesetzt ist der jeweilige Interrupt deaktiviert, steht das Bit  auf 0 wird der IRQ-Pin beim Auftreten des entsprechenden Ereignisses auf LOW gesetzt. Die passende Verkabelung habe ich schon beim Aufbau im letzten Beitrag vorgenommen. Der IRQ-Pin muss mit einem der Interrupt-Pins des Mikrocontrollers verbunden werden.

Dies habe ich nun in Code umgesetzt. Die Software auf der Sender-Seite (also auf dem Arduino) wurde nicht geändert. Nur auf der Emfpänger-(Atmega8-)Seite gab es eine kleine Änderung:

#include <SPI.h>
#include <Mirf.h>
#include <nRF24L01.h>
#include <MirfHardwareSpiDriver.h>

void setup(){
  
  //Interrupt initialisieren
  attachInterrupt(0, receive, LOW);
   
  //Funkmodul initialisieren
  Mirf.spi = &MirfHardwareSpi;
  Mirf.init();
  Mirf.setRADDR((byte *)"serv1");
  Mirf.payload = sizeof(unsigned long);
  
  //auf 250kbit/s umstellen (0x26) (2Mbit => 0x0f)
  Mirf.configRegister(RF_SETUP, 0x26);
  
  //Interrupt nur wenn Daten empfangen
  Mirf.configRegister(CONFIG, 0x48);
  
  Mirf.config();

}

void loop(){ 
  //Hier passieren epische Dinge
}

void receive() {
   
  noInterrupts();
  byte data[Mirf.payload];
    
  if(!Mirf.isSending() && Mirf.dataReady()){
    Mirf.getData(data);
    Mirf.setTADDR((byte *)"clie1");
    Mirf.send(data);
  }
  
  interrupts();
  
}

Die setup()-Funktion ist weitestgehend identisch mit der des ursprünglichen Programms Es gibt nur zwei kleine Änderungen: die Funktion attachInterrupt(0, receive, LOW) aktiviert den Interrupt. Wird der Interrupt 0 (Pin 4 beim Atmega8) auf LOW gezogen, dann wird die Funktion receive() aufgerufen.
Die zweite Änderung ist der Befehlt Mirf.configRegister(CONFIG, 0x48). Damit wird das CONFIG-Register so eingestellt, dass nur beim Empfang von Daten ein Interrupt ausgelöst wird.

Das Hauptprogramm (void loop()) ist komplett leer, hier kann jetzt beliebiger Code stehen.

Die Funktion receive() wird bei einem Interrupt aufgerufen und beinhaltet den Code, der zuvor in der loop()-Funktion enthalten war. Auch hier gibt es nur zwei kleine Erweiterungen: der Befehl noInterrupts() deaktiviert zu Beginn der Funktion die Interrupts, um "Überlagerungen" der Interrupts zu vermeiden. Am Ende der Funktion werden sie mit interrupts() wieder aktiviert.

Freitag, 26. Juli 2013

Atmega/Arduino: Funkmodul nRF24L01+ Grundlagen

(English version) Heute gibt es wieder einen Gastbeitrag von mir, dieses mal zum Funkmodul nRF24L01+ (oder nRF24L01P) von Nordic Semiconductor. Das Modul ist für wenige Euro bei Ebay zu haben. Es handelt sich um einen "Ultra low power 2.4GHz RF Transceiver" - es kann also sowohl Senden als auch Empfangen und arbeitet im 2.4GHz-Band. Dabei arbeitet es laut Hersteller besonders sparsam, es zieht einen Maximalstrom von gerade einmal 15mA. Dabei erreicht es Übertragungsraten von bis 2 MBit/s. Angesprochen wird das Modul über den SPI-Bus.

Nun aber genug der Theorie, alle weiteren Details sind dem Datenblatt zu entnehmen. Dieses wird später noch wichtig werden.

Für das nRF24L01P gibt es fertige Libraries für Arduino und den Atmega. Wie ich in meinem letzten Artikel geschrieben habe, benutze ich wegen der einfacheren Bedienung gerne das Arduino-Paket. Daher werde ich im folgenden auch nur erklären, wie man die Module mit einem Arduino-Board bzw. einem "nackten" Atmega8 mit Arduino-Libraries zum Laufen bekommt.

Als einfaches Beispiel zum Einstieg wähle ich das Ping-Programm, dass die Arduino-Lib mitbringt. Der Arduino Uno sendet seine aktuelle "Systemzeit" an den Atmega, dieser sendet sie sofort wieder zurück. Aus der empfangenen Zeit und der aktuellen Zeit kann der Arduino dann die Laufzeit des Signals, also den Pin bestimmen.

Aufbau

Zuerst geht es an die Verkabelung. Die Funkmodule haben ingesamt 8 Anschlüsse: Vcc und GND für die Spannungsversorgung (WICHTIG: die Module verlangen eine Versorgung mit ~3V), MISO, MOSI und SCK für die SPI-Kommunikation, CSN (kündigt dem Modul einen Befehl an) sowie CE und IRQ. IRQ ist der Interrupt-Pin des Moduls und kann bei 3 verschiedenen Events active low werden: Daten wurden empfangen, Daten wurden gesendet, Senden fehlgeschlagen. Diese Interrupts lassen sich jedoch frei ein- und ausschalten.

Die genaue Pinbelegung hängt von der äußeren Beschaltung des Moduls ab, lässt sich jedoch mit ein bisschen googlen schnell heraus finden. Die meisten Module haben folgende Pinbelegung:
IRQ     8  7  MISO
MOSI  6  5  SCK
CSN    4  3  CE
VCC    2  1  GND

Diese Pins müssen nun entsprechend an das Arduino-Board bzw. den Atmega8 angeschlossen werden:
MISO an Pin 12 des Arduino (Pin 18 beim Atmega, Pin 50 beim Arduino Mega)
MOSI an Pin 11 (Pin 17, 51)
SCK an Pin 13 (Pin 19, 52)
IRQ an Pin 2 oder 3 (Pin 4 oder 5, Interrupts gibt es sehr viele beim Arduino Mega, daher gebe ich hier keinen speziellen Pin an)
Folgende Pins sind im Prinzip frei wählbar, die Standardbelegung der Lib ist allerdings
CSN an Pin 7 (Pin 13, 7)
CE an Pin 8 (Pin 14, 8)

Wie bereits erwähnt sind zwingend 3V zur Spannungsversorgung nötig! Die Signal-Pins vertragen jedoch 5V.

Jetzt sollte soweit alles verkabelt sein und wir können uns der Software widmen.

Software Arduino

Für das Beispiel habe ich wie schon gesagt zum Großteil des mitgelieferte Beispiel der Lib übernommen.

Die Library ist HIER erhältlich.

Folgender Code wird auf das Arduino-Board gespielt:

#include <SPI.h>
#include <Mirf.h>
#include <nRF24L01.h>
#include <MirfHardwareSpiDriver.h>

void setup(){
  Serial.begin(9600);
  
  //Falls CE- bzw. CSN-Pin anders belegt
  
  //Mirf.cePin = 7;
  //Mirf.csnPin = 8;
  
  Mirf.spi = &MirfHardwareSpi;
  Mirf.init();
  
  //Empfänder-Adresse einstellen   
  Mirf.setRADDR((byte *)"clie1");
  
  //Payload-Länge einstellen   
  Mirf.payload = sizeof(unsigned long);
  
  //auf 250kbit/s umstellen (0x26) (2Mbit => 0x0f)
  Mirf.configRegister(RF_SETUP, 0x26);
 
  //ggfs. Kanal ändern
  //Mirf.channel = 10;

  //Konfiguration in Modul übernehmen
  Mirf.config();
  
  Serial.println("Beginning ... "); 
}

void loop(){
  unsigned long time = millis();
  
  Mirf.setTADDR((byte *)"serv1");
  
  Mirf.send((byte *)&time);
  
  while(Mirf.isSending()){
  }
  Serial.println("Finished sending");
  delay(10);
  while(!Mirf.dataReady()){
    if ( ( millis() - time ) > 1000 ) {
      Serial.println("Timeout on response from server!");
      return;
    }
  }
  
  Mirf.getData((byte *) &time);
  
  Serial.print("Ping: ");
  Serial.println((millis() - time));
  
  delay(1000);
}  
 
In der setup()-Funktion wird zunächst das Funkmodul mit dem init()-Befehl initialisiert. Dann wird die Adresse eingestellt, unter der das Modul angesprochen werden soll. Die Payload-Länge gibt an, wie viele Daten-Bytes bei jeder Übertragung gesendet werden sollen. Bis zu 32 Bytes sind hier möglich, jedoch sollte man nie mehr wählen als nötig. Um die Reichweite der Module zu erhöhen, habe ich mich dazu entschieden, die Übertragungsrate von 2 Mbit/s auf 250 kbit/s zu ändern. Hierzu ist ein Zugriff auf die Register des Moduls nötig. Dank der Lib geht dies jedoch problemlos mit einer Zeile Code. Der Befehl configRegister(RF_SETUP, 0x26) schreibt den Werte 0x26 in das Register mit dem Namen RF_SETUP und stellt somit die Übertragungsrate ein. Die Namen der Register und ihre genaue Funktion ist dem Datenblatt ab Seite 57 zu entnehmen.
Zum Abschluss wird die Konfiguration mit dem config()-Befehl in das Modul übernommen.

Das Hauptprogramm ist eigentlich recht simpel und selbsterklärend. Mit dem Befehl setTADDR wird eingestellt, an welche Adresse gesendet werden soll. Mit dem send()-Befehl werden dann die gewünschten Daten (hier die Systemzeit) gesendet. Die folgende while-Schleife sorgt dafür, dass der Arduino solange pausiert, bis die Übertragung abgeschlossen ist. Mit der nächsten while-Schleife und der Bedingungen !Mirf.dataReady wartet der µC so lange, bis das Funkmodul Daten (also eine Antwort) empfangen hat.  Der Befehl getData() liest dann die empfangenen Daten aus dem Pufferspeicher des Moduls.

Software Atmega8

#include <SPI.h>
#include <Mirf.h>
#include <nRF24L01.h>
#include <MirfHardwareSpiDriver.h>

void setup(){

  Mirf.spi = &MirfHardwareSpi;
  Mirf.init();
  
  //Empfänder-Adresse einstellen     
  Mirf.setRADDR((byte *)"serv1");
  
  //Payload-Länge einstellen   
  Mirf.payload = sizeof(unsigned long);
  
  //auf 250kbit/s umstellen (0x26) (2Mbit => 0x0f)
  Mirf.configRegister(RF_SETUP, 0x26);
  
  //ggfs. Kanal ändern
  //Mirf.channel = 10;

  //Konfiguration in Modul übernehmen
  Mirf.config();
  
}

void loop(){

  //Pufferspeicher für die Daten
  byte data[Mirf.payload];
   
  if(!Mirf.isSending() && Mirf.dataReady()){
         
    Mirf.getData(data);
         
    Mirf.setTADDR((byte *)"clie1");
   
    Mirf.send(data);
    
  }
}

Die setup()-Funktion des Atmega-Programms ist im Prinzip identisch mit der des Arduino-Programms, ich werde den Code daher nicht näher erläutern.
Das Hauptprogramm ist auch ziemlich simpel. Das Programm läuft die ganze Zeit "im Kreis" und wartet, bis das Funkmodul Daten zur Vefügung stellt (Mirf.dataReady()). Diese werden dann zwischengespeichert, es wird die Adresse des Empfängers gesetzt und die Daten werden sofort wieder zurück gesendet. Um Überschneidungen beim Senden zu verhindern, ist zusätzlich noch die !Mirf.isSending()-Bedigungen in der if-Abfrage eingefügt worden.

Der Code auf der Empfänger-Seite (also auf dem Atmega) ist so natürlich nicht besonders effizient, da der Controller nichts anderes macht als zu warten, ob den nun endlich Daten empfangen wurden. Viel praktischer und schöner lässt sich das mit den erwähnten Interrupts lösen. Wie genau das funktioniert, werde ich in meinem nächsten Beitrag erklären.

Donnerstag, 25. Juli 2013

C# WPF - 3D-Grafik

(English version) Bisher hatte ich es nicht geschafft, 3D-Grafiken zu erzeugen (DirectX, und andere habe ich ausprobiert, von denen hat jedoch nichts schön funktioniert). Heute bin ich per Zufall darauf gestoßen, dass WPF das auch kann. Also hab ich mich hingesetzt und in 2 Stunden war ein kleiner Panzer, den man mit Pfeiltasten fahren kann fertig. Mit WPF 3D-Grafik zu machen ist höchstgradig trivial.

Wie man im Code sehen kann besteht der Hauptteil des Programms aus der Funktion Tank(), welche daraus besteht, dass ich alle Objekte von Hand baue. Das geht prinzipiell auch viel einfacher mit 3D-Editoren oder aus XML-Dateien, für den ersten Test werde ich es aber mal so belassen.

Ansonsten gibt es da noch den moveThread, welcher den Panzer bewegt und dann noch die Funktion RefreshEverything, welche die Position des Panzers verändert. Falls es Fragen zu irgendwas gibt stehe ich gerne Rede und Antwort.
 
Hier nun mein Code von MainWindow.xaml.cs:

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Media.Media3D;
using System.Windows.Navigation;
using System.Windows.Shapes;
using System.Windows.Threading;

namespace Wpf3dTest1
{
    public partial class MainWindow : Window
    {
        Dictionary<Key, Boolean> pressedKeys = new Dictionary<Key, Boolean>();
        bool quit = false;
        public MainWindow()
        {
            InitializeComponent();

            pressedKeys.Add(Key.Left, false);
            pressedKeys.Add(Key.Right, false);
            pressedKeys.Add(Key.Up, false);
            pressedKeys.Add(Key.Down, false);

            this.KeyDown += MainWindow_KeyDown;
            this.KeyUp += MainWindow_KeyUp;

            this.Closing += OnWindowClosing;

            //create a Tank object and add to viewport
            mod = Tank(new Point3D(xPos, 0, 0), 2.2, 1.8, 0.7, 0.3, 0.25, 0.5, 0.25, new DiffuseMaterial(Brushes.Green), new DiffuseMaterial(Brushes.Black));
            viewport3D.Children.Add(mod);

            //start a Thread which moves the object
            Thread thread = new Thread(moveThread);
            thread.Start();
        }

        void MainWindow_KeyUp(object sender, KeyEventArgs e)
        {
            if (pressedKeys.ContainsKey(e.Key))
            {
                pressedKeys[e.Key] = false;
            }
        }

        void MainWindow_KeyDown(object sender, KeyEventArgs e)
        {
            if (pressedKeys.ContainsKey(e.Key))
            {
                pressedKeys[e.Key] = true;
            }
        }

        ModelVisual3D mod;

        private delegate void RefreshEverythingDelegate();
        private void RefreshEverything()
        {
            TranslateTransform3D translate = new TranslateTransform3D();
            translate.OffsetX = xPos;
            translate.OffsetZ = zPos;

            RotateTransform3D rotate = new RotateTransform3D(new AxisAngleRotation3D(new Vector3D(0, 1,0),angle));
            rotate.CenterX = xPos;
            rotate.CenterZ = zPos;


            Transform3DGroup transform = new Transform3DGroup();
            transform.Children.Add(translate);
            transform.Children.Add(rotate);
            mod.Transform = transform;
        }

        //Parameters for turn and move
        private double posIncrementor = 0.02;
        private double xPos = 0;
        private double zPos = 0;

        private double angle = 0;
        private double angleIncrementor = 0.4;

        private void moveThread()
        {
            while (!quit)
            {
                if (pressedKeys[Key.Up])
                {
                    xPos += posIncrementor * Math.Cos(angle * Math.PI/180) ;
                    zPos -= posIncrementor * Math.Sin(angle * Math.PI / 180);
                }
                if (pressedKeys[Key.Down])
                {
                    xPos -= posIncrementor * Math.Cos(angle * Math.PI / 180);
                    zPos += posIncrementor * Math.Sin(angle * Math.PI / 180);
                }
                if (pressedKeys[Key.Left])
                {
                    angle += angleIncrementor;
                }
                if (pressedKeys[Key.Right])
                {
                    angle -= angleIncrementor;
                }
                DispatcherOperation dispOp = this.viewport3D.Dispatcher.BeginInvoke(DispatcherPriority.Normal, new RefreshEverythingDelegate(RefreshEverything));
                Thread.Sleep(10);
            }
        }

        public void OnWindowClosing(object sender, CancelEventArgs e)
        {
            quit = true;
        }

        private ModelVisual3D Tank(Point3D position, double length, double width, double height, double chainWidth, double chainHeight1, double chainHeight2, double chainNotLength, Material bodyMaterial, Material chainMaterial)
        {
            //Tank Body
            MeshGeometry3D bodyMesh = new MeshGeometry3D();
            Point3D a = new Point3D(position.X - length / 2, 0, position.Z + width / 2);
            Point3D b = new Point3D(position.X + length / 2, 0, position.Z + width / 2);
            Point3D c = new Point3D(position.X + length / 2, 0, position.Z - width / 2);
            Point3D d = new Point3D(position.X - length / 2, 0, position.Z - width / 2);
            Point3D e = new Point3D(position.X - length / 2, position.Y + height, position.Z + width / 2);
            Point3D f = new Point3D(position.X + length / 2, position.Y + height, position.Z + width / 2);
            Point3D g = new Point3D(position.X + length / 2, position.Y + height, position.Z - width / 2);
            Point3D h = new Point3D(position.X - length / 2, position.Y + height, position.Z - width / 2);
            BuildRectangle(bodyMesh, a, b, f, e, new Vector3D(0, 0, 1));
            BuildRectangle(bodyMesh, b, c, g, f, new Vector3D(1, 0, 0));
            BuildRectangle(bodyMesh, c, d, h, g, new Vector3D(0, 0, -1));
            BuildRectangle(bodyMesh, d, a, e, h, new Vector3D(-1, 0, 0));
            BuildRectangle(bodyMesh, e, f, g, h, new Vector3D(0, 1, 0));
            BuildRectangle(bodyMesh, a, d, c, b, new Vector3D(0, -1, 0));

            //Build the model object
            GeometryModel3D BodyModel = new GeometryModel3D(
            bodyMesh,
            bodyMaterial);


            //Chain 1
            MeshGeometry3D chain1Mesh = new MeshGeometry3D();//links, also -width
            a = new Point3D(position.X - length / 2 + chainNotLength, 0, position.Z - width / 2 - chainWidth);
            b = new Point3D(position.X - length / 2 + chainNotLength, 0, position.Z - width / 2);
            c = new Point3D(position.X + length / 2 - chainNotLength, 0, position.Z - width / 2 - chainWidth);
            d = new Point3D(position.X + length / 2 - chainNotLength, 0, position.Z - width / 2);
            e = new Point3D(position.X - length / 2, position.Y + chainHeight1, position.Z - width / 2 - chainWidth);
            f = new Point3D(position.X - length / 2, position.Y + chainHeight1, position.Z - width / 2);
            g = new Point3D(position.X + length / 2, position.Y + chainHeight1, position.Z - width / 2 - chainWidth);
            h = new Point3D(position.X + length / 2, position.Y + chainHeight1, position.Z - width / 2);
            Point3D i = new Point3D(position.X - length / 2, position.Y + chainHeight2, position.Z - width / 2 - chainWidth);
            Point3D j = new Point3D(position.X - length / 2, position.Y + chainHeight2, position.Z - width / 2);
            Point3D k = new Point3D(position.X + length / 2, position.Y + chainHeight2, position.Z - width / 2 - chainWidth);
            Point3D l = new Point3D(position.X + length / 2, position.Y + chainHeight2, position.Z - width / 2);

            BuildRectangle(chain1Mesh, a, b, d, c, new Vector3D(0, -1, 0));
            BuildRectangle(chain1Mesh, a, e, f, b, new Vector3D(-1, -1, 0));
            BuildRectangle(chain1Mesh, c, d, h, g, new Vector3D(1, -1, 0));
            BuildRectangle(chain1Mesh, a,c,g,e, new Vector3D(0, 0, -1));
            BuildRectangle(chain1Mesh, i, j, f, e, new Vector3D(-1, 0, 0));
            BuildRectangle(chain1Mesh, k, g, h, l, new Vector3D(1, 0, 0));
            BuildRectangle(chain1Mesh, e,g,k,i, new Vector3D(0, 0, -1));
            BuildRectangle(chain1Mesh, i, k, l, j, new Vector3D(0, 1, 0));

            //Build the model object
            GeometryModel3D Chain1Model = new GeometryModel3D(
            chain1Mesh,
            chainMaterial);


            //Chain 2
            MeshGeometry3D chain2Mesh = new MeshGeometry3D();//rechts, also +width
            a = new Point3D(position.X - length / 2 + chainNotLength, 0, position.Z + width / 2);
            b = new Point3D(position.X - length / 2 + chainNotLength, 0, position.Z + width / 2 + chainWidth);
            c = new Point3D(position.X + length / 2 - chainNotLength, 0, position.Z + width / 2);
            d = new Point3D(position.X + length / 2 - chainNotLength, 0, position.Z + width / 2 + chainWidth);
            e = new Point3D(position.X - length / 2, position.Y + chainHeight1, position.Z + width / 2);
            f = new Point3D(position.X - length / 2, position.Y + chainHeight1, position.Z + width / 2 + chainWidth);
            g = new Point3D(position.X + length / 2, position.Y + chainHeight1, position.Z + width / 2);
            h = new Point3D(position.X + length / 2, position.Y + chainHeight1, position.Z + width / 2 + chainWidth);
            i = new Point3D(position.X - length / 2, position.Y + chainHeight2, position.Z + width / 2);
            j = new Point3D(position.X - length / 2, position.Y + chainHeight2, position.Z + width / 2 + chainWidth);
            k = new Point3D(position.X + length / 2, position.Y + chainHeight2, position.Z + width / 2);
            l = new Point3D(position.X + length / 2, position.Y + chainHeight2, position.Z + width / 2 + chainWidth);

            BuildRectangle(chain2Mesh, a, b, d, c, new Vector3D(0, -1, 0));
            BuildRectangle(chain2Mesh, a, e, f, b, new Vector3D(-1, -1, 0));
            BuildRectangle(chain2Mesh, c, d, h, g, new Vector3D(1, -1, 0));
            BuildRectangle(chain2Mesh, b, f, h, d, new Vector3D(0, 0, 1));
            BuildRectangle(chain2Mesh, i, j, f, e, new Vector3D(-1, 0, 0));
            BuildRectangle(chain2Mesh, k, g, h, l, new Vector3D(1, 0, 0));
            BuildRectangle(chain2Mesh, f, j, l, h, new Vector3D(0, 0, 1));
            BuildRectangle(chain2Mesh, i, k, l, j, new Vector3D(0, 1, 0));

            //Build the model object
            GeometryModel3D Chain2Model = new GeometryModel3D(
            chain2Mesh,
            chainMaterial);


            //Build the whole object
            Model3DGroup model3DGroup = new Model3DGroup();
            model3DGroup.Children.Add(BodyModel);
            model3DGroup.Children.Add(Chain1Model);
            model3DGroup.Children.Add(Chain2Model);

            ModelVisual3D model = new ModelVisual3D();
            model.Content = model3DGroup;
            return model;
        }

        //Funktioniert auch mit nicht-Rechtecken, also wenn nicht alle Innenwinkel 90° sind.
        private void BuildRectangle(MeshGeometry3D geometry,Point3D a, Point3D b, Point3D c, Point3D d,Vector3D normal)
        {
            int baseIndex = geometry.Positions.Count;

            //Add vertices
            geometry.Positions.Add(a);
            geometry.Positions.Add(b);
            geometry.Positions.Add(c);
            geometry.Positions.Add(d);

            //Add normals
            geometry.Normals.Add(normal);
            geometry.Normals.Add(normal);
            geometry.Normals.Add(normal);
            geometry.Normals.Add(normal);

            //Add indices
            geometry.TriangleIndices.Add(baseIndex + 0);
            geometry.TriangleIndices.Add(baseIndex + 2);
            geometry.TriangleIndices.Add(baseIndex + 1);
            geometry.TriangleIndices.Add(baseIndex + 2);
            geometry.TriangleIndices.Add(baseIndex + 0);
            geometry.TriangleIndices.Add(baseIndex + 3);
        }
    }
}


Und hier noch der entsprechende Code für MainWindow.xaml:

<Window x:Class="Wpf3dTest1.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow" Height="350" Width="525">
    <Grid>
        <Viewport3D Name="viewport3D">
            <Viewport3D.Camera>
                <PerspectiveCamera Position="20,10,10"
                                   LookDirection="-20,-10,-10"
                                   UpDirection="0,1,0"
                                   FieldOfView="45"
                                   NearPlaneDistance="1"
                                   FarPlaneDistance="100"></PerspectiveCamera>
            </Viewport3D.Camera>
            <ModelUIElement3D>
                <AmbientLight Color="White"></AmbientLight>
            </ModelUIElement3D>
        </Viewport3D>
    </Grid>
</Window>

Mittwoch, 24. Juli 2013

Windows 7 - Tageszeitabhängiger Hintergrund mit C#

(English version) Ich wollte unbedingt einen tageszeitabhängigen Hintergrund am PC haben. Windows kann das prinzipiell nicht, und ich wollte auch keine fremde Software installieren. Mit ein paar Zeilen C# war es dann jedoch ganz einfach möglich.

Ich habe einen Ordner, in dem 24 Dateien drin sind, 00.jpg, 01.jpg, ... 23.jpg. Diese sind also für von 0 bis 1 Uhr, und so weiter. Über C# wird die Datei dann als Desktophintergrund gesetzt, wie es auch hier beschrieben ist. Mein Code sieht folgendermaßen aus:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Threading;
using System.Runtime.InteropServices;

namespace TimeDependantBg
{
    class Program
    {
        [DllImport("user32.dll")]
        private static extern Int32 SystemParametersInfo(UInt32 uiAction, UInt32 uiParam, String pvParam, UInt32 fWinIni);

        private static UInt32 SPI_SETDESKWALLPAPER = 20;
        private static UInt32 SPIF_UPDATEINIFILE = 0x1;

        static void Main(string[] args)
        {
            int hour;
            string basePath = @"C:\Users\Udo\TimeDependantBg\";
            string fileName;
            while (true)
            {
                hour = DateTime.Now.Hour;

                fileName = basePath + (hour % 24).ToString("00") + ".jpg";

                if (System.IO.File.Exists(fileName))
                {
                    SystemParametersInfo(SPI_SETDESKWALLPAPER, 0, fileName, SPIF_UPDATEINIFILE);
                }
                object lockObj = new object();
                lock (lockObj)
                {
                    Monitor.Wait(lockObj, 100000);// ms, alle 100 Sekunden check
                }
            }
        }
    }
}


Danach wird noch eine Verknüpfung in C:\Users\Udo\AppData\Roaming\Microsoft\Windows\Start Menu\Programs\Startup zu TimeDependantBg.exe angelegt, sodass das Prorgamm automatisch beim Systemstart geöffnet wird.

Sonntag, 21. Juli 2013

ATmega als I²C Master

(English version) Um am ATmega I²C als Master zum laufen zu kriegen war ein riesiger Kampf. Ich bin jedoch auf eine Version gekommen, die KEINE Probleme mit Compiler oder ähnlichem macht. Im Endeffekt habe ich Code aus einer fertigen Implementierung von Manfred Langemann benutzt, diese Implementierung einzubinden klappte bei mir leider nicht, daher habe ich den Code herauskopiert. Um also an einem ATmega I²C benutzen zu können muss man (wenn man es so wie ich macht) die folgenden Zeilen am Anfang des Programmes einfügen:

#define F_CPU 16000000

#define TRUE 1
#define FALSE 0

#include <avr/interrupt.h>

//#include "General.h"
//#include "TWI_Master.h"

/****************************************************************************
  TWI State codes
****************************************************************************/
// General TWI Master staus codes                     
#define TWI_START                    0x08  // START has been transmitted 
#define TWI_REP_START                0x10  // Repeated START has been transmitted
#define TWI_ARB_LOST                0x38  // Arbitration lost

// TWI Master Transmitter staus codes                     
#define TWI_MTX_ADR_ACK                0x18  // SLA+W has been tramsmitted and ACK received
#define TWI_MTX_ADR_NACK            0x20  // SLA+W has been tramsmitted and NACK received
#define TWI_MTX_DATA_ACK            0x28  // Data byte has been tramsmitted and ACK received
#define TWI_MTX_DATA_NACK            0x30  // Data byte has been tramsmitted and NACK received

// TWI Master Receiver staus codes 
#define TWI_MRX_ADR_ACK                0x40  // SLA+R has been tramsmitted and ACK received
#define TWI_MRX_ADR_NACK            0x48  // SLA+R has been tramsmitted and NACK received
#define TWI_MRX_DATA_ACK            0x50  // Data byte has been received and ACK tramsmitted
#define TWI_MRX_DATA_NACK            0x58  // Data byte has been received and NACK tramsmitted

// TWI Slave Transmitter staus codes
#define TWI_STX_ADR_ACK                0xA8  // Own SLA+R has been received; ACK has been returned
#define TWI_STX_ADR_ACK_M_ARB_LOST    0xB0  // Arbitration lost in SLA+R/W as Master; own SLA+R has been received; ACK has been returned
#define TWI_STX_DATA_ACK            0xB8  // Data byte in TWDR has been transmitted; ACK has been received
#define TWI_STX_DATA_NACK            0xC0  // Data byte in TWDR has been transmitted; NOT ACK has been received
#define TWI_STX_DATA_ACK_LAST_BYTE    0xC8  // Last data byte in TWDR has been transmitted (TWEA = “0”); ACK has been received

// TWI Slave Receiver staus codes
#define TWI_SRX_ADR_ACK                0x60  // Own SLA+W has been received ACK has been returned
#define TWI_SRX_ADR_ACK_M_ARB_LOST    0x68  // Arbitration lost in SLA+R/W as Master; own SLA+W has been received; ACK has been returned
#define TWI_SRX_GEN_ACK                0x70  // General call address has been received; ACK has been returned
#define TWI_SRX_GEN_ACK_M_ARB_LOST    0x78  // Arbitration lost in SLA+R/W as Master; General call address has been received; ACK has been returned
#define TWI_SRX_ADR_DATA_ACK        0x80  // Previously addressed with own SLA+W; data has been received; ACK has been returned
#define TWI_SRX_ADR_DATA_NACK        0x88  // Previously addressed with own SLA+W; data has been received; NOT ACK has been returned
#define TWI_SRX_GEN_DATA_ACK        0x90  // Previously addressed with general call; data has been received; ACK has been returned
#define TWI_SRX_GEN_DATA_NACK        0x98  // Previously addressed with general call; data has been received; NOT ACK has been returned
#define TWI_SRX_STOP_RESTART        0xA0  // A STOP condition or repeated START condition has been received while still addressed as Slave

// TWI Miscellaneous status codes
#define TWI_NO_STATE                0xF8  // No relevant state information available; TWINT = “0”
#define TWI_BUS_ERROR                0x00  // Bus error due to an illegal START or STOP condition

#define TWIM_READ    1
#define TWIM_WRITE   0

/*******************************************************
 Public Function: TWIM_Init

 Purpose: Initialise the TWI Master Interface

 Input Parameter:
     - uint16_t    TWI_Bitrate (Hz)

 Return Value: uint8_t
     - FALSE:    Bitrate too high
     - TRUE:        Bitrate OK

*******************************************************/
uint8_t TWIM_Init (uint32_t TWI_Bitrate)
    {
/*
** Set TWI bitrate
** If bitrate is too high, then error return
*/
    TWBR = ((F_CPU/TWI_Bitrate)-16)/2;
    if (TWBR < 11) return FALSE;

    return TRUE;
    }
/*******************************************************
 Public Function: TWIM_Start

 Purpose: Start the TWI Master Interface

 Input Parameter:
     - uint8_t    Device address
     - uint8_t    Type of required Operation:
                TWIM_READ: Read data from the slave
                TWIM_WRITE: Write data to the slave

 Return Value: uint8_t
      - TRUE:        OK, TWI Master accessible
     - FALSE:    Error in starting TWI Master

*******************************************************/
uint8_t TWIM_Start (uint8_t Address, uint8_t TWIM_Type)//1 = read, 0 = write
    {
    uint8_t        twst;
/*
** Send START condition
*/
    TWCR = (1<<TWINT)|(1<<TWSTA)|(1<<TWEN);
/*
** Wait until transmission completed
*/
    while (!(TWCR & (1<<TWINT)));
/*
** Check value of TWI Status Register. Mask prescaler bits.
*/
    twst = TWSR & 0xF8;
    if ((twst != TWI_START) && (twst != TWI_REP_START)) return FALSE;
/*
** Send device address
*/
    TWDR = (Address<<1) + TWIM_Type;
    TWCR = (1<<TWINT)|(1<<TWEN);
/*
** Wait until transmission completed and ACK/NACK has been received
*/
    while (!(TWCR & (1<<TWINT)));
/*
** Check value of TWI Status Register. Mask prescaler bits.
*/
    twst = TWSR & 0xF8;
    if ((twst != TWI_MTX_ADR_ACK) && (twst != TWI_MRX_ADR_ACK)) return FALSE;

    return TRUE;
    }
/*******************************************************
 Public Function: TWIM_Stop

 Purpose: Stop the TWI Master

 Input Parameter: None

 Return Value: None

*******************************************************/
void TWIM_Stop (void)
    {
/*
** Send stop condition
*/
    TWCR = (1<<TWINT)|(1<<TWEN)|(1<<TWSTO);
/*
** Wait until stop condition is executed and bus released
*/
    while (TWCR & (1<<TWINT));
    }
/*******************************************************
 Public Function: TWIM_Write

 Purpose: Write a byte to the slave

 Input Parameter:
     - uint8_t    Byte to be sent

 Return Value: uint8_t
      - TRUE:        OK, Byte sent
     - FALSE:    Error in byte transmission

*******************************************************/
uint8_t TWIM_Write (uint8_t byte)
    {
    uint8_t   twst;
/*
** Send data to the previously addressed device
*/
    TWDR = byte;
    TWCR = (1<<TWINT)|(1<<TWEN);
/*
** Wait until transmission completed
*/
    while (!(TWCR & (1<<TWINT)));
/*
** Check value of TWI Status Register. Mask prescaler bits
*/
    twst = TWSR & 0xF8;
    if (twst != TWI_MTX_DATA_ACK) return 1;

    return 0;
    }
/*******************************************************
 Public Function: TWIM_ReadAck

 Purpose: Read a byte from the slave and request next byte

 Input Parameter: None

 Return Value: uint8_t
      - uint8_t    Read byte

*******************************************************/
uint8_t TWIM_ReadAck (void)
    {
    TWCR = (1<<TWINT)|(1<<TWEN)|(1<<TWEA);
    while (!(TWCR & (1<<TWINT)));   

    return TWDR;
    }
/*******************************************************
 Public Function: TWIM_ReadAck

 Purpose: Read the last byte from the slave

 Input Parameter: None

 Return Value: uint8_t
      - uint8_t    Read byte

*******************************************************/
uint8_t TWIM_ReadNack (void)
    {
    TWCR = (1<<TWINT)|(1<<TWEN);
    while(!(TWCR & (1<<TWINT)));
   
    return TWDR;
    }

Beschleunigungssensor MPU-6050 mit Raspberry PI auslesen

(English version) Der Beschleunigungssensor MPU-6050 ist ziemlich vielseitig, klein und billig. Er kann mit bis zu 6 V betrieben werden und liefert Beschleunigungswerte und sogar Gyroskopwerte (also Drehung in Grad pro Sekunde). So ein Sensor ist also das ideale Kernstück für einen Quadrocopter.

Da man mit einem Raspberry PI deutlich einfacher debuggen kann als mit Atmegas habe ich den Beschleunigungssensor zuerst mal mit dem Raspberry PI angesteuert. Das Grundprinzip ist ziemlich einfach, man muss nur einmal herausfinden wie es geht. Ich benutze am Raspberry PI die bcm2835-Library (http://www.airspayce.com/mikem/bcm2835/).

Das ganze lässt sich mit I²C auch über einen ATmega machen, dafür benutze ich diese Implementierung von I²C, dieser Post hilft da sicherlich weiter.

Um jetzt z.B. die x-Beschleunigung (Am Raspberry PI) auszumessen geht man folgendermaßen vor:

#include <bcm2835.h>
#include <stdio.h>
#include <time.h>
#include <stdlib.h>

int main (int atgc, char** argv)
{
    bcm2835_init();
    bcm2835_i2c_begin();

    char addr = 0x68;

    //I found this address somewhere in the internet...
    char buf[1];
    char regaddr[2];
    int x = 0;
    int ret;

    bcm2835_i2c_setSlaveAddress(addr);

    //disable sleep mode!!!!!

    regaddr[0] = 107;
    regaddr[1] = 0;
    //This is the basic operation to write to an register
    //regaddr[0] is the register address
    //regaddr[1] is the value
    bcm2835_i2c_write(regaddr, 2);
   
    regaddr[0] = 59;
    ret = BCM2835_I2C_REASON_ERROR_DATA;
    while(ret != BCM2835_I2C_REASON_OK)
    {
        //This is the basic operation to read an register
        //regaddr[0] is the register address
        //buf[0] is the value
        bcm2835_i2c_write(regaddr, 1);
        ret = bcm2835_i2c_read(buf, 1);
    }
    x = buf[0]<<8;

    regaddr[0] = 60;
    ret = BCM2835_I2C_REASON_ERROR_DATA;
    while(buf[0] == 99)
    {
        bcm2835_i2c_write(regaddr, 1);
        ret = bcm2835_i2c_read(buf, 1);
    }
    x += buf[0];


    //because of the sign, we have here 32-bit integers,
    //the value is 16-bit signed.

    if (x & 1<<15)
    {
        x -= 1<<16;
    }

    double x_val = x;
    x_val = x_val / 16384;

    //This is only valid if the accel-mode is +- 2g
    //The range can be controlled via the 
    //GYRO_CONFIG and ACCEL_CONFIG registers

    printf("accel: %g\n", x_val);

    bcm2835_i2c_end();
}


Dies kann jetzt natürlich noch beliebig erweitert werden, um Beschleunigungs-Werte und Gyro-Werte auszulesen. Da hiermit aber der grundlegende Zugriff auf die Register geklärt sein sollte, werde ich hier nur auf die Register Map verweisen: http://www.invensense.com/mems/gyro/documents/RM-MPU-6000A.pdf