AdSense

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:

#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 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>

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.

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.

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;
    }

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.

#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

Saturday 20 July 2013

A small air conditioner

(Deutsche Version) The same cooling method which is used to cool down Bose-Einstein condensates to several nano kelvin is also working in a normal environment. It is called evaporative cooling. Everyone knows this from e.g. a cup of tea, hot water particles evaporate and the temperature drops.

To cool down a room with this principle, you only need water which evaporates. This increases the air humidity but also decreases the temperature. To accelerate the evaporating process, i built the following construction:
A fan (cilence, 92 mm, very silent) is put on stilts in a box of water. The stilts are 6 mm diameter aluminum bars in which I screwed the fan. I would disadvise the usage of wood because it could mould. Afterwards a cloth is wrapped around everything and is secured with a rubber band. The fan pushes air through the wet cloth so the water evaporates. This evaporation needs energy so the temperature drops.

On the right in the image you can see my thermometer I use to measure the air temperature. The temperature of the air coming out of the air conditioner is about 1 degree Centigrade lower than the temperature of the air in my room.

Friday 19 July 2013

Arduino code on the ATMega

(Deutsche Version) The following post is not from Udo. I am honoured to write a guest post for his blog ;)

Programming an ATMega "by Hand" in C can be very time-consuming - especially when you need "special" features as SPI, I2C, etc. You often need to deal with cryptic code and register shifting. It is much simpler if you develop for Arduino. Due to the user-friendly commands, all wishes are easily implemented. Additionally, there are libraries for a lot of external hardware (e.g. ultrasonic modules or radio communication modules). The disadvantage of the Arduino is his high price. You don't want to spend 20 euro to buy a new Arduino for every new project.

It would be nice if the low price and the compact design of the ATMega would be combinable with the user-friendliness of the Arduino. Good news: This is easily possible!

I will explain everything using the example of an ATMega8. First, you need a microcontroller with a suitable circuit (power supply and external quarz). The circuit is explained on this Page.

Additionally, you need a programmer for the ATMega. I use an USBasp, which I bought cheaply on Ebay. The programmer has to be applied to the microcontroller - as described in the link above.

Now we can start with the programming. Therefor you need the Arduino development environment. It can be downloaded here. After the setup, you choose "File - Examples - 01.Basics - Blink". This example flashes a LED every second. To run the code, you have to add the LED to your microcontroller. Be careful, in the code, the pins are numbered for Arduino (in this example: Pin 13). The corresponding pin on the ATMega can be found in this overview. Pin 13 of the Arduino is Pin 19 of the ATMega8, which is PB5. You have to apply the LED to this pin.

Now you can compile the code and flash it onto the microcontroller. At first, you have to select the suitable programmer at "Tools - Programmer", in our case, this is the USBasp. Depending on the used microcontroller, you have to choose the suitable board. I use an ATMega8, so I choose at "Tools - Board" the "Arduino NG with ATMega8". To flash the program onto the microcontroller, I simply click on "File - Upload with programmer". The development environment compiles the code and flashes it onto the ATMega. And now you're done ;)

Following these instructions, you can also create complex programs. I attached radio modules via SPI to the ATMega8. How I did this will be explained in the next post.

Wednesday 17 July 2013

C# Windows Forms resp. WPF - Save window position and size

(Deutsche Version) It can be very useful when all the windows are at the same position and have same size when restarting an application. Therefor, I wrote an own class in C#: SizeSavedWindow. The source code for the class is below. To save the size and position of a window, you have to add  

SizeSavedWindow.addToSizeSavedWindows(this);

in the InitializeComponent(); method.

This function can be called for many different windows. The windows are saved by name into an xml file, if there are no windows which have the same name, everything works fine. This class also exists for WPF, you simply have to change every "Form" to "Window" and the events have to be modified (window_IsVisibleChanged instead of window_Shown). If anyone is interested in the WPF code i can post this code, too. Now the code for the class SizeSavedWindow:

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Runtime.Serialization;
using System.Xml;
using System.Windows.Forms;

namespace KeepSizeTest1
{
    public class SizeSavedWindow
    {
        // To keep a window at the same size and position, just add
        // SizeSavedWindow.addToSizeSavedWindows(this);
        // right after initialieComponent
        public static void addToSizeSavedWindows(Form window)
        {
            window.Shown += window_Shown;
        }

        static void window_Shown(object sender, EventArgs e)
        {
            Form window = (Form)sender;
            if (!window.Visible)
            {
                return;
            }
            if (File.Exists("sizes.xml"))
            {
                var stream = new FileStream("sizes.xml", FileMode.Open);
                var reader = XmlDictionaryReader.CreateTextReader(stream, new XmlDictionaryReaderQuotas());
                var deserializer = new DataContractSerializer(typeof(Dictionary<string, int[]>));
                windows = (Dictionary<string, int[]>)deserializer.ReadObject(reader, true);
                stream.Close();

                foreach (KeyValuePair<string, int[]> pair in windows)
                {
                    if (pair.Key == window.Name)
                    {
                        window.Height = pair.Value[0];
                        window.Width = pair.Value[1];
                        window.Top = pair.Value[2];
                        window.Left = pair.Value[3];
                        break;
                    }
                }
            }
            int[] sizes = new int[4];
            sizes[0] = window.Height;
            sizes[1] = window.Width;
            sizes[2] = window.Top;
            sizes[3] = window.Left;
            if (windows.ContainsKey(window.Name))
            {
                windows.Remove(window.Name);
            }
            windows.Add(window.Name, sizes);
            window.SizeChanged += window_SizeChanged;
            window.LocationChanged += window_LocationChanged;
        }

        static void window_SizeChanged(object sender, EventArgs e)
        {
            Form realSender = (Form)sender;
            if (windows.ContainsKey(realSender.Name))
            {
                windows[realSender.Name][0] = realSender.Height;
                windows[realSender.Name][1] = realSender.Width;

                var writer = new FileStream("sizes.xml", FileMode.Create);
                Type type = windows.GetType();
                var serializer = new DataContractSerializer(type);
                serializer.WriteObject(writer, windows);
                writer.Close();
            }
        }

        static void window_LocationChanged(object sender, EventArgs e)
        {
            Form realSender = (Form)sender;
            if (windows.ContainsKey(realSender.Name))
            {
                windows[realSender.Name][2] = realSender.Top;
                windows[realSender.Name][3] = realSender.Left;

                var writer = new FileStream("sizes.xml", FileMode.Create);
                Type type = windows.GetType();
                var serializer = new DataContractSerializer(type);
                serializer.WriteObject(writer, windows);
                writer.Close();
            }
        }

        static Dictionary<string, int[]> windows = new Dictionary<string, int[]>();
    }
}

Monday 15 July 2013

Visual Studio - Error when publishing a project

(Deutsche Version) The error "The validity of the application could not be verified. The process cannot be continued." happend recently to one of my projects. The solution for this problem is simpel: You have to go to the project settings, security, and then "activate ClickOnce security settings" and then "Fully trustworthy application".

Friday 12 July 2013

C# - Access joystick

(Deutsche Version) In this post I will show how to access a joystick in C#. I use the joystick project from here, I just put everything into a new Windows Forms project into a class Joystick.cs (DirectX had to be included, more information about that is provided here). I modified the project a bit, more about that later.

My program (e.g. to read the x-axis) looks like this:

public partial class Form1 : Form
{
    Joystick.Joystick joystick;
    public Form1()
    {
        InitializeComponent();
        joystick = new Joystick.Joystick(this, true);
        joystick.PushChange += joystick_PushChange;
        joystick.AxeChanged += joystick_AxeChanged;
    }

    void joystick_AxeChanged(int X, int Y, int Z, int Rz)
    {
        System.Console.Write("Axis: {0}\t{1}\t{2}\t{3}\n", X, Y, Z, Rz);
    }

    void joystick_PushChange(int Push)
    {
        System.Console.Write("Push: {0}\n", Push);
    }
}


With this project, you can do lots of things, e.g. control Panzerkampf via joystick. Luckily there are events for everything (Key pressed, axe changed, ...) so you should be able to do everything with this project.

If anyone want to access multiple joysticks, you have to use the modified version of the project: JoystickMultiple. The basic principle is the same, you can use the joystick project the same way as before but you also can read single joysticks. This works like this:

Dictionary<Guid, string> foundSticks = Joystick.Joystick.GetAllJoysticks();
foreach (KeyValuePair<Guid, string> pair in foundSticks)
{
    Joystick.Joystick localStick = new Joystick.Joystick(this, false, pair.Key);
    System.Console.Write("stick: {0}\n", pair.Value);
    localStick.ButtonDown += joystick_ButtonDown;
}


joystick_ButtonDown is the following:

void joystick_ButtonDown(int ButtonNumber, Joystick.Joystick stick)
{
    System.Console.Write("{1} - Button: {0}\n", ButtonNumber, stick.Name);
}


In the modified project, every event also returns the joystick. Similar to DirectSound, the app.config has to be modyfied: <startup> has to be changed to <startup useLegacyV2RuntimeActivationPolicy="true">, otherwise the program will not start.

A small game with PHP

(Deutsche Version) Some time ago, I started to create a small text-based game in PHP. The game is a bit like other games like Legend of the Green Dragon.

Until now, there is not much implemented, you can fight against neutral monsters and buy weapons. The game is accessible via the following link:

Game

Due to my lack of creativity, there is no name for the game yet and the only colour is white. If anyone has any suggestions for the game, just let me know.

Thursday 11 July 2013

Visitor counter with PHP

(Deutsche Version) A visitor counter in PHP is pretty simple to implement. The basic principle is: Everytime when your page is opened, a PHP script is executed which opens a file containing the old visitor number, increments this number and saves the file. For my example, the file "counter.dat" has to be in the same directory as the PHP file. The code for the visitor counter is the following:

$fileName="counter.dat";
$fileHandle=fopen($fileName, 'r') or die("can't open file");
$count = 0;
if(!feof($fileHandle))
{
  $input = fgets($fileHandle, 4096);
  $count = intval($input);
  $count++;
}
fclose($fileHandle);

if ($count != 0)
{
  $fileHandle = fopen($fileName, 'w') or die ("can't open file");
  fwrite($fileHandle, $count);
  fclose($fileHandle);
}
echo('Visitors: '.$count);


Of course you can have a much nicer way of displaying the number of visitors but for the moment, only the basic idea is relevant.

ATMega - A simple and compact thermometer with the DS18S20

(Deutsche Version) I wanted to have a compact, completed project with an ATMega8 and the DS18S20. Therefor, I built a thermometer out of those things. The result:
For the project you need: 1 pinboard, 1 ATMega8, 1 DS18S20, 3 seven-segment displays with decoder, 1 quarz (not nessesary) for the ATMega and lots of cables.

Lets start with the temperature measurement: The ATMega reads the temperature of the DS18S20, as already shown in this post. The wiring is shown in the next image:
The data pin of the DS18S20 is connected to PORTD0 and PORTD1. PORTD0 is output, PORTD1 input. Afterwards, the ATMega converts the temperature into the 3 digits and passes it to the seven-segment decoder.
This is how the pinboard was looking like in the beginning. After taking this picture, I had to space the seven-segment displays a bit because the adaptor cables would not fit otherwise. The code for the ATMega can be downloaded here: Source code. The outputs are the following: PORTC0..3 for the ten digit, PORTB0..3 for the one digit and PORTD4..7 for the position after decimal point. I use 73HC4543 as the seven-segment decoders.

From the decoders there have to be 7 cables to the displays. Applying 21 cables would be very annoying, so I built special adaptor cables which already contain the needed 220 Ohm series resistor. The soldering was a lot of work but afterwards, it was worth it. If anyone has qusetions about this adaptor, i can provide extra information (or write a post), simply ask me in the comments. The adaptor looks like this:


Wednesday 10 July 2013

C# - Access SQL database

(Deutsche Version) Setting up an SQL server is pretty simple, there is a lot of free software (e.g. MySQL) which is not difficult to setup. I assume that you are familiar with SQL and you know what you want to send and receive from the server. To access the server via C# you have to execute the following code:

string server = "serverName";
string database = "databaseName;
string uid = "userName";
string password = "password;
string table = "tableName;


string connectionString = "SERVER=" + server + ";" + "DATABASE=" + database + ";" + "UID=" + uid + ";" + "PASSWORD=" + password + ";";

MySqlConnection connection = new MySqlConnection(connectionString);
connection.Open();

string query = "some SQL query";

MySqlCommand cmd = new MySqlCommand(query, connection);


try
{
  cmd.ExecuteNonQuery();
}
catch (Exception)
{}

Monday 8 July 2013

C# - Serialize class in xml file

(Deutsche Version) To serialize classes into a file without having to care for the file structure, C# provides the so called serializer. This serializer can simply save complete classes (which can also contain sub classes). In a class, every object which shall be saved has to be marked:

[DataContract()]
public class TestClass
{
  [DataMember()]
  public int testInt = 5;
  [DataMember()]
  public string testString = "Test";
}


You have to put [DataContract()] in front of each class and [DataMember()] in front of every object. I also had to add a referency to System.Runtime.Serialization.

To serialize the class testClass wich is of the type TestClass, you only need 5 lines of code:

var writer = new FileStream("outputFileName.xml", FileMode.Create);
Type type = testClass.GetType();
DataContractSerializer serializer = new DataContractSerializer(type);
serializer.WriteObject(writer, testClass);
writer.Close();


The produced file looks like this:

<?xml version="1.0"?>
<TestClass xmlns:i="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://schemas.datacontract.org/2004/07/SerializeTest">
<test1>5</test1>
<test2>Test</test2>
</TestClass>


You can easily see which object has which value, which is not possible when serializing to files in binary format.


To read a serialized file, you have to execute these lines:

var reader = new FileStream("outputFileName.xml", FileMode.Open);
Type type = testClass.GetType();
DataContractSerializer deSerializer = new DataContractSerializer(type);
testClass = (TestClass)deSerializer.ReadObject(reader);
reader.Close();

Sunday 7 July 2013

A helping hand when soldering

(Deutsche Version) Whoever did soldering knows the problem: You have to take the soldering iron, the solder, element A and element B in your hand. Sadly a (normal) human body has too few hands for this. In most cases it is enough to fixate an element. You can buy a so called "third hand" or use a bench vice. If you do not have any of these, here is a simple solution using pliers and a rubber band:
The rubber band simulates a hand which uses the pliers. On the front, you can put in an element (e.g. a multi-pin connector).

Visual Studio - Profiler

(Deutsche Version) Whoever has been programming programs which take a lot of time has asked himself which parts of the program cause the most delay.

A simple solution would be to measure the time in the code. This can be very annoying and take a lot of time. Therefor, there is the feature called "Profiler" in Visual Studio. I will only talk about the profiler in Visual Studio 2012.

The profiler is called by "ANALYSE" - "Launch Performance Wizard". Afterwards the program is started and Visual Studio logs data. When the program has finished, Visual Studio generates a report, this can take some seconds.

When the report is created, an overwiew is shown. Here you can see the CPU usage and below the functions which took the most time.

Saturday 6 July 2013

AntMe

(Deutsche Version) Recently, I found something interesting: AntMe. This is a simulation of ants. Basically, these ants do nothing. You can directly edit C# code to tell the ants what to do. This looks like this:

public override void DestinationReached(Sugar sugar)
{
    Take(sugar);
    GoToBase();
}

This is no pseudo code, AntMe is this simple. Of course you ants can become much more complicated when they set odour marks to communicate with other ants (e.g. "There is sugar here!"). The basic idea of AntMe is simple but if you want to create efficient strategies for your ants, this can become very complex.

You cannot directly action in the simulation, you have to watch the simulation, improve the code and then all over again. This project is suitable for everyone who can program C#.

Wednesday 3 July 2013

ATMega - Disable JTAG

(Deutsche Version) Whoever used an ATMega and wanted to use the channels PC2..5 knows this problem: the channels do not behave as they should. The reason is JTAG which blocks these channels. To disable JTAG, you only have to add the following lines to the beginning of your program:

MCUCSR = (1<<JTD);
MCUCSR = (1<<JTD);


Due to some (mysterious) security issues, the command has to be written twice, otherwise JTAG will not be disabled. Now the channels PC2..5 should behave as they should.