(Deutsche Version)
no translation yet.
A collection of my projects in the areas of physics, electronics and information science.
AdSense
Wednesday, 31 July 2013
Saturday, 27 July 2013
Friday, 26 July 2013
Atmega/Arduino: nRF24L01+ basics
(Deutsche Version)
Today I will write another "guest post". This time, I'm going to present the wireless modules called nRF24L01+ (or nRF24L01P) by Nordic Semiconductor. These modules can be bought very cheap on ebay. The modules are "Ultra low power 2.4GHz RF Transceivers" - so the can send AND receive and they work in 2.4GHz band. According to Nordic Semiconductor, they are very energy efficient and consume only 15mA. The transfer rate is up to 2MBit/s. The modules are interfaced using the SPI bus.
That's enough of theory, further information can be found in the datasheet, which is going to be important later.
For the nRF24L01+ there are "ready to use" libraries for Arduino and Atmega. As described in my last post I prefer to use the Arduino IDE. So I'm going to explain how to use the modules with an Arduino board or an Atmega8 with Arduino libraries.
As an easy example, I will use the ping programm included in the Arduino library. The Arduino Uno sends its "system time" to the Atmega, which sends it back immediately. Then the Uno calculates the ping from its current "system time" and the received time.
Hardware
At first, we have to wire everything. The modules have 8 pins: VCC and GND for voltage supply (IMPORTANT: the modules need a supply voltage of ~3V), MISO, MOSI and SCK for SPI communication, CSN (tells the module that there is going to be a new command), CE and IRQ. IRQ is an interrupt pin and can be active low on 3 events: data has been received, data was send and ending failed. These interrupts can be (de)activated as desired.
Most of the modules have te following pin mapping:
IRQ 8 7 MISO
MOSI 6 5 SCK
CSN 4 3 CE
VCC 2 1 GND
These pins have to be connected to the Arduino Uno or the Atmega:
MISO to pn 12 of the Arduino (pin 18 of the Atmega, pin 50 for the Arduino Mega)
MOSI to pin 11 (pin 17, 51)
SCK to pin 13 (pin 19, 52)
IRQ to pin 2 or 3 (pin 4 or 5, there are too many interrupts for the Arduino Mega, so I will not specify a certain pin)
CSN to pin 7 (pin 13, 7)
CE to in 8 (pin 14, 8)
As alreday mentioned, the supply voltage has to be 3V. The signal pins accept 5V.
Software Arduino
For this example I used the example included in the library.
The library can be found HERE.
The following code runs on the Arduino board:
At the end of the setup, the settings are applied using the config() command.
The loop() is easy and self-explaining. With the setTADDR command you can choose to which address the data is sent. The send() command send the desired data (here it's the system time). The while loop let's the Arduino do nothing until transmission is completed. With the next while loop and the condition !Mirf.dataReady, the µC waits until the wireless modul hast received some data. getData() reads this data from the module.
Software Atmega8
The loop() is very short. The programm is running in a loop and waits for the wireless modul to receive some data (Mirf.dataReady()). The data is written to a cache, the target address is set and the data is sent back. The !Mirf.isSending() condition prevents overlapping on sending.
Of course, the code on the Atmega is not very efficient because the controller doesn't do anything but waiting for new data. It would be much better to use interrupts. I'm going to explain the usage of interrupts in my next post.
Today I will write another "guest post". This time, I'm going to present the wireless modules called nRF24L01+ (or nRF24L01P) by Nordic Semiconductor. These modules can be bought very cheap on ebay. The modules are "Ultra low power 2.4GHz RF Transceivers" - so the can send AND receive and they work in 2.4GHz band. According to Nordic Semiconductor, they are very energy efficient and consume only 15mA. The transfer rate is up to 2MBit/s. The modules are interfaced using the SPI bus.
That's enough of theory, further information can be found in the datasheet, which is going to be important later.
For the nRF24L01+ there are "ready to use" libraries for Arduino and Atmega. As described in my last post I prefer to use the Arduino IDE. So I'm going to explain how to use the modules with an Arduino board or an Atmega8 with Arduino libraries.
As an easy example, I will use the ping programm included in the Arduino library. The Arduino Uno sends its "system time" to the Atmega, which sends it back immediately. Then the Uno calculates the ping from its current "system time" and the received time.
Hardware
At first, we have to wire everything. The modules have 8 pins: VCC and GND for voltage supply (IMPORTANT: the modules need a supply voltage of ~3V), MISO, MOSI and SCK for SPI communication, CSN (tells the module that there is going to be a new command), CE and IRQ. IRQ is an interrupt pin and can be active low on 3 events: data has been received, data was send and ending failed. These interrupts can be (de)activated as desired.
Most of the modules have te following pin mapping:
IRQ 8 7 MISO
MOSI 6 5 SCK
CSN 4 3 CE
VCC 2 1 GND
These pins have to be connected to the Arduino Uno or the Atmega:
MISO to pn 12 of the Arduino (pin 18 of the Atmega, pin 50 for the Arduino Mega)
MOSI to pin 11 (pin 17, 51)
SCK to pin 13 (pin 19, 52)
IRQ to pin 2 or 3 (pin 4 or 5, there are too many interrupts for the Arduino Mega, so I will not specify a certain pin)
CSN to pin 7 (pin 13, 7)
CE to in 8 (pin 14, 8)
As alreday mentioned, the supply voltage has to be 3V. The signal pins accept 5V.
Software Arduino
For this example I used the example included in the library.
The library can be found HERE.
The following code runs on the Arduino board:
#include <SPI.h> #include <Mirf.h> #include <nRF24L01.h> #include <MirfHardwareSpiDriver.h> void setup(){ Serial.begin(9600); //if CE and CSN pins aren't connected to the standard pins //Mirf.cePin = 7; //Mirf.csnPin = 8; Mirf.spi = &MirfHardwareSpi; Mirf.init(); //set receive address Mirf.setRADDR((byte *)"clie1"); //set payload length Mirf.payload = sizeof(unsigned long); //switch to 250kbit/s (0x26) (2Mbit => 0x0f) Mirf.configRegister(RF_SETUP, 0x26); //change channel //Mirf.channel = 10; //write settings to configuration 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 the setup() function, the modul is initialised using the init() command. Then the address of the module and the payload length are set. The payload length tells how many data bytes are send when transmitting. Up to 32 bytes are possible but you shouldn't choose more than necessary. To increase the range of the modules I have set the transmission rate from 2MBit/s to 250kBit/s. Therefor the registers of the modules have to be accessed. Thanks to the library this can be done with only one line of code. The command configRegister(RF_SETUP, 0x26) writes the value 0x26 to the register RF_SETUP and sets the transmission rate. The names of the registers and their functions can be found in the datasheet (page 57 and following).
At the end of the setup, the settings are applied using the config() command.
The loop() is easy and self-explaining. With the setTADDR command you can choose to which address the data is sent. The send() command send the desired data (here it's the system time). The while loop let's the Arduino do nothing until transmission is completed. With the next while loop and the condition !Mirf.dataReady, the µC waits until the wireless modul hast received some data. getData() reads this data from the module.
Software Atmega8
#include <SPI.h> #include <Mirf.h> #include <nRF24L01.h> #include <MirfHardwareSpiDriver.h> void setup(){ Mirf.spi = &MirfHardwareSpi; Mirf.init(); //set receive address Mirf.setRADDR((byte *)"serv1"); //set payload length Mirf.payload = sizeof(unsigned long); //switch to 250kbit/s (0x26) (2Mbit => 0x0f) Mirf.configRegister(RF_SETUP, 0x26); ///change channel //Mirf.channel = 10; //write settings to configuration Mirf.config(); } void loop(){ //cache for received data byte data[Mirf.payload]; if(!Mirf.isSending() && Mirf.dataReady()){ Mirf.getData(data); Mirf.setTADDR((byte *)"clie1"); Mirf.send(data); } }The setup() function is almost the same as in the Arduino code, so I'm not going to explain it again.
The loop() is very short. The programm is running in a loop and waits for the wireless modul to receive some data (Mirf.dataReady()). The data is written to a cache, the target address is set and the data is sent back. The !Mirf.isSending() condition prevents overlapping on sending.
Of course, the code on the Atmega is not very efficient because the controller doesn't do anything but waiting for new data. It would be much better to use interrupts. I'm going to explain the usage of interrupts in my next post.
Thursday, 25 July 2013
C# WPF - 3D graphics
(Deutsche Version) Until now, I was not able to create 3D graphics. Today I found out that WPF is able to do this, too. Within 2 hours, I was able to create a small tank who can be controlled with the arrow keys. Creating 3D graphics with WPF is extremely simpel.
As you can see in the code, the main part of the program is the function
Other functions I use are
And here the code for MainWindow.xaml:
As you can see in the code, the main part of the program is the function
Tank(), which creates all objects by hand. Basically, it is much easier to do these things with a 3D editor but for the moment, this will do fine.Other functions I use are
moveThread, this function moves the the tank and the function RefreshEverything which changes the position of the displayed tank. Here is the code for 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);
}
}
}
And here the code for 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>
Labels:
C#,
Panzerkampf,
WPF
Wednesday, 24 July 2013
Windows 7 - Desktop background depending on time of day with C#
(Deutsche Version) I wanted to have a desktop background which changes depending on the time of day. Windows is not able to do this and I did not want to install software. Luckily, I found a solution with C#.
I created a folder which contains 24 files: 00.jpg, 01.jpg, ... 23.jpg. These files are for the corresponding hours during the day (00.jpg is for 0 to 1 o'clock). The desktop background is set via C#, as described here. This is my code:
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.
Afterwards, a shortcut to TimeDependantBg.exe is created in C:\Users\Udo\AppData\Roaming\Microsoft\Windows\Start Menu\Programs\Startup so the program will be started when the computer is starting.
I created a folder which contains 24 files: 00.jpg, 01.jpg, ... 23.jpg. These files are for the corresponding hours during the day (00.jpg is for 0 to 1 o'clock). The desktop background is set via C#, as described here. This is my code:
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.
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, check every 100 seconds
}
}
}
}
}
Afterwards, a shortcut to TimeDependantBg.exe is created in C:\Users\Udo\AppData\Roaming\Microsoft\Windows\Start Menu\Programs\Startup so the program will be started when the computer is starting.
Labels:
C#
Sunday, 21 July 2013
Using an ATMega as I2C master
(Deutsche Version) Using an ATMega as I2C master was definitely not easy. In the end, I was using an implementation of Manfred Langemann from which I copied the code right into my *.c file for the AVR Studio. To use an ATMega for I2C, you only have to copy the following lines to the beginning of the program:
#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;
}
Labels:
ATMega
Read acceleration sensor MPU-6050 with Raspberry PI
(Deutsche Version) To read the MPU-6050 via a Raspberry PI, a I²C library is needed. I used the bcm2835 library (http://www.airspayce.com/mikem/bcm2835/). To read the acceleration, i used the following C code. It is very important to disable the sleep mode (set register 107 to 0), otherwise the sensor will not work properly.
If you want to use an ATMega instead of a Raspberry PI, see this post.
All other applications should be simple with this code, the Register Map will provide further information: http://www.invensense.com/mems/gyro/documents/RM-MPU-6000A.pdf
If you want to use an ATMega instead of a Raspberry PI, see this post.
#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();
} All other applications should be simple with this code, the Register Map will provide further information: http://www.invensense.com/mems/gyro/documents/RM-MPU-6000A.pdf
Subscribe to:
Posts (Atom)