ON HOW TO BECOME AN EMBEDDED SYSTEM ENGINEER IN LESS THAN 3 HOURS

Learn to Use Gumstix and Garcia Robots
The Garcia Tutorial II

1 Boot the Garcia.

fedora $ scp ./a.txt root@[Garcia IP]:~/a.txt
example 1: scp "a.txt" to the Garcia robot

2 Cross Compiling

The cross-compilers are in the directory:
/net/wbc/src/gumstix/gumstix-oe/tmp/cross/bin/
which contains quite a few binaries, of which you will probably care about
arm-angstrom-linux-gnueabi-gcc
You can use the cross-compilers on bear, or tahoe, or any of the Lab 001 machines.

Write a Hello world program called helloworld.c (it can do anything) and compile like this

fedora $ /net/wbc/src/gumstix/gumstix-oe/tmp/cross/bin/arm-angstrom-linux-gnueabi-gcc -o helloworld helloworld.c
 example 2: cross compiling

3 Get the Brainstem Toolkit SDK (together will our API).

  • A middle layer between programs and the Brainstem board.
  • Download it from here.
  • untar the tar ball.
  • Check your disk quota (quota -v)

3.1 Build basic library of Brainstem in Debug mode.

fedora $ cd brainstem
fedora $ ./build_basic.sh
example 3: building the basis of brainstem, good time for PIZZA break

3.2 Build the first program for the robot, understand the Brainstem Makefile structure

cd to aGarcia/

fedora $ cd aGarcia/
fedora $ ./make_cs23_debug.sh

example 4: Build the cs23Garcia project.

3.3 Here let's try.

  • Goto brainstem/aDebug/aUnix/ARM/,
  • Copy (scp) the cs23Garcia binary to the /home/acroname/aBinary/ directory on your robot
  • Run it using the run script! It takes executable name as first arg and arguments to executable as subsequent args.

3.4 Understand the Project Makfile

#!/bin/sh
# This is a fast tool to launch the building of a Garcia App..
make -f makeCS23.Linux CC=/net/wbc/src/gumstix/gumstix-oe/tmp/cross/bin/arm-angstrom-linux-gnueabi-gcc CCP=/net/wbc/src/gumstix/gumstix-oe/tmp/cross/bin/arm-angstrom-linux-gnueabi-g++ ARCH=ARM debug 
example 5: Code for make_cs23_debug.sh

# makefile: host/aGarcia/makeapp.Linux

SHELL := /bin/sh
CC := gcc
CCP := g++

ROOT := ../
PROGNAME := cs23Garcia

INCLUDES := -IaCommon \
        -IaUnix \
        -IaGarcia \
        -IaGarciaApp \
        -I../aIO/aCommon \
        -I../aIO/aUnix \
        -I../aUI/aCommon \
        -I../aUI/aUnix \
        -I../aStem/aCommon \
        -I../aStem/aUnix \
        -I../aTEAvm/aCommon \
        -I../aTEAvm/aUnix \
        -I../aCommon \
        -I../aDebug/aUnix \
        -I../aDebug/aSystem \
        -I../aUnix

#don't include X11 libraries for ARM
ifeq ($(ARCH),ARM)
LIBRARIES := -laIO \
             -laUI \
             -laStem \
         -laGarcia \
             -lpthread \
         -lm
else
LIBRARIES := -laIO \
             -laUI \
             -laStem \
         -laGarcia \
             -lpthread \
         -lm \
         -L/usr/X11R6/lib \
         -lX11
endif

VPATH := aCommon:aUnix:cs23Garcia:../aCommon:../aTEAvm/aCommon:cs23Garcia/vidcat:cs23Garcia/sf

SOURCES := aAssert.c \
       aMemHandleDebug.c \
       aUtil.c \
       aGarciaGeom.c \
       v4l.c \
           simplevideo.c \
       main.c \
       common.c \
           socketclient.c 
 

CPPSOURCES := acpException.cpp \
          garcia.cpp 

include $(ROOT)make_program_cpp.Linux
example 6: Code for makeCS23.Linux

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <pthread.h>
#include "garcia.h"
#include "./vidcat/simplevideo.h"
#include "./sf/sf_util.h"

int main(int argc, char** argv) {
  init(NULL);
  int i = GarciaTurn(45 / 180.0f * 3.1415926);
  destroy();
}
example 7: main.c

4 Introduce to the ROBOT side API 

4.1 Brainstem side.

You can actually modify garcia.cpp for your own usage. For example you could bypass the acroname API and talk directly to the motors. The following example code shows some more sophisticated interactions with the robot using the API, it is located in main2.c (make sure to change the makefile to use main2.c rather than main.c).
#include "garcia.h"
...
...
// Move around with blocking API

float distance = 0.2;
int i = GarciaWalk(distance); // Walking forward for 20 centimeters
int j = GarciaTurn(20 / 180 * 3.14159);  // Turn right for 20 degrees
if ((!i)&&(!j))
    printf("Succeeded");

// Move around with non blocking API
int i = GarciaWalkN(distance); // Walking forward for 20 centimeters, non blocking
int j = GarciaTurnN(20 / 180 * 3.14159);  // Turn right for 20 degrees  non blocking
GarciaStop();


// Read in sensor data.
float d = GarciaRangeData(BATTERY);
// Read in battery voltage, BATTERY could be 
// FRONT_RANGE_LEFT, FRONT_RANGE_RIGHT......   see garcia.h for details

(Extra Stuff!!!!!!!!!!!!!!!!!!!!!!!! Hack garcia.cpp)
 
example 8: garcia brainstem API

4.1.0 Garcia API.

Following is the Garcia API containing the exposed interfaces of the Garcia robots.
#ifndef GARCIA_UTIL_H
#define GARCIA_UTIL_H

/* This header file defines public interface to control the robot. */
/* The robot is controlled through acpGarcia class provided by */
/* acroname inside the Brainstem API. Here we use pure C function to */
/* take the place of the complicated acpGarcia class to do the same */
/* function. */

/* To use the Garcia robot, user should call init() first in the start */
/* of his code. When user's program is about to quit, user should call destroy() */
/* function. */

/* Any question related to Garcia.cpp and garcia.h should be reported to
   pway@cs.dartmouth.edu(Wei Pan)   */

#ifdef __cplusplus
extern "C" {
#endif

  // This is a blocking function, which means that the function
  // will not return before the robot finish the walk.
  // The float is the distance the user want to robots to move.
  // The unit of the distance is ADJUSTABLE, currently it is centimeter.
  //! param f  the distance to go, negative number for backward.
  //! return a status value, 0 means success, otherwise failure(may be robots are blocked by wall so they can't move)
  int GarciaWalk(float);

  // This is also a blocking function. The robot will turn acording to
  // the angle value the user pass in as a parameter. The current unit is
  // rad. So PI means turn 180 degrees.
  // param f the angel to turn, negative number for counter-clockwise
  // return a status value, 0 means success, otherwise failure(may be robots are blocked by wall so they can't move)
  int GarciaTurn(float);

  // The following two functions is the non-blocking version of the up two
  // functions, which means they will return immediately after you call them.
  // The robot will execute the movement concurrently.
  // If you call them multiple times, the robot will queue each movement and
  // execute them in FIFO order. 
  int GarciaTurnN(float);

  int GarciaWalkN(float);

  // This function is to stop all movements of the robot and clear the movement
  // queue. The robot stop immediately when you call it.
  void GarciaStop();
  
  // NOTICE: The upper two function will return a value indicating the status
  //         of the robots. 0 means works fine. Otherwise there is a problem.
  // The return value and problems has a relation:
  /*
     Please refer to
      brainstem/aCommon/aErr.h
     for full list of error codes meanings.
  */

  // Before the program, please call init(), and pass in the
  // laptop server address, so there is a heart beat thread which will
  // connect to the server address every certain time interval.
  // The two following constant defines the properties of the heart beat thread:  // the HEARTBEAT_PORT is the port for the server to receive the heart beat.
  
#define HEARTBEAT_INTERVAL 60
#define HEARTBEAT_PORT 5656

  //! param c the ip address of the server side code,
  //         ( this IP address will be used to send back a HEARTBEAT signal.)
  void init(char*);
  // Before your program quit, please call destroy()
  void destroy();
  
  // parameters for GarciaRangeData()
#define FRONT_RANGE_LEFT 1
#define FRONT_RANGE_RIGHT 2
#define SIDE_RANGE_LEFT 3
#define SIDE_RANGE_RIGHT 4
#define REAR_RANGE_LEFT 5
#define REAR_RANGE_RIGHT 6
#define BATTERY 7

  // Garcia has 8 rangers, however, we here only use 6 of them. Pass in which ranger
  // you want to set type to REAR_RANGE_LEFT or SIDE_RANGE_LEFT, it will return the range
  // information.
  //! param type see #define
  //! return a reading from sensors
  float GarciaRangeData (int type);  

#ifdef __cplusplus
}
#endif

#endif
example 8: garcia brainstem API

4.2 Camera side

The camera API is just a modified wrapper for vidcat, a utility to capture a picture/image from the webcam. Google to learn more about it if you want. The video architecture for the Gumstix system is known as video4linux,  and vidcat is a utility of video4linux. More information can be found at link.

One important issue is that *YOU DON'T NEED TO* free() the returned pointer once you call this function and finish processing the result. You can just keep calling it again and again to update your picture. The code is at brainstem/aGarcia/cs23Garcia/vidcat/

One major issue we have is that the transimission of video feedback is too slow(300k per frame).  It will be great if you guys would like do an extra credit on video compression using some open source compression code.


v2.0 extra
ppm format and debug with ppm code.

================================>example of a ppm file
P3
320 240
255
RRR GGG BBB    RRR GGG BBB    RRR GGG BBB    RRR GGG BBB
.......
================================>example of a ppm file

See an example.

#include "vidcat/simplevideo.h"
.....
......
//! param device usually is "/dev/video0"
//! param palette usually is VIDEO_PALETTE_YUV420
//  char* OpenDeviceGetImageAndClose(char* device, int palette); 
char* data = OpenDeviceGetImageAndClose("/dev/video0" , VIDEO_PALETTE_YUV420P); 
example 9-1: Video API

#include "vidcat/simplevideo.h"
int cam() {
  char* data;
  FILE *fp;
  int i,j=0,m=0;
  data=OpenDeviceGetImageAndClose("/dev/video0",VIDEO_PALETTE_YUV420P);
  fp=fopen("test.ppm","w");
  fputs("P3\n",fp);
  fputs("320 240\n",fp);
  fputs("255\n",fp);
  for(i=0; i<320*240*3;i+=3) {
    fprintf(fp,"%03d %03d %03d  ",data[i+2], data[i+1],data[i]);
    j++;
    if(j >= 18) {
      fprintf(fp,"\n");
      j=0;
    }
  }
  printf("done!\n");
  fclose(fp);
  return 0;
}
example 9-2:Capture a Picture to a PPM File API

4.3 Sensor side (Serial Forwarder).

The wireless sensor network research has been a hot topic for a very long time. People built sensors like the tMote all over the world, and hope these small, low power consumption sensing devices will be a important component in human activities. These devices do not only sense data (humanity, movement...), they also have built-in CPU and a very short range wireless communication interface.

The wireless sensor is connected to a computer through a USB interface. In detail, the USB interface is treated by the operating system as an additional serial port.  A commonly used program in wireless sensor research is called "Serial Forwarder", which connects to a sensor through this USB interface. Also, a client program could connect to a serial forwarder through TCP/IP sockets. The "Forwarder"'s job is to send everything it receive from the sensor to the clients connected to it through sockets; and send everything clients send to it to the sensor.

All these sensors on the robots play a role as an antenna, they pick what every in the radio and send it to the serial forwarder, and to its clients. Vice versa, they receive packets from serial forwarder and broadcast them over the radio.

The API we will provide on the robot side is a wrapped version of  the serial forwarder a.k.a asf. Download here for asf.

This program, asf, should be run as a daemon (a process runs in the background). Currently the sensor is mounted to /dev/ttyUSB0, the baudrate is 57600, and the platform is telos.

To run asf as a background daemon, simply type
garcia $ ./asf [PORT] /dev/ttyUSB0 57600 > asf.log 2>&1 &
  • Since asf will print stuff out, it will be better to ask asf to print everything to a.log or any other file rather than print on the screen.
  • Choose your favorite port (larger than 5000), and when you write client code, don't forget to connect to the port you specify here.
  • You can actually listen to the packet use sflisten program.
To do so,
fedora $ make
fedora $ sflisten [ROBOT'S IP] [PORT]
example 10: Serial Forwarder Initializer

The basic component to go over the radio for wireless sensors are packets. A packet is a small size array of raw data.
06 01 08 8b ff ff ff ff 04 7d fc 00 fc 00 17 00
      =====                =       [key][key] [magic]
      changes           always 4
example 11:  A Treasure Key Packet  
06 01 08 8b ff ff ff ff 04 7d fc 00 **** 17 00
      =====                =       [key][any] [magic]
      changes           always 4
example 12:  A Treasure Key Packet With the Previous Key

5 Introduce to the CLIENT side API

5.1 Sensor packet reader.

It will be ok if you could implement your own code to connect to the serial forwarder on the robot side, but you could also use the following API, the source code for this API is at here link.

You are supposed to create two threads, one for sending data and one for receiving data from the asf program running on the robot side. Here are examples for these two threads:

#include "sfsource.h"
...
...
//! \brief thread to keep listening to the robot sensor reading.
void* ReadSensorData(void* ptr) {
  int fd = open_sf_source(robot_ip, SENSOR_PORT);  // open a connection to the serial forwarder with the robot_ip and SENSOR_PORT
  while (fd < 0) {
    LOG("Couldn't open serial forwarder\n");
    sleep(5);
    fd = open_sf_source(robot_ip, SENSOR_PORT);
  }
  int i;
  while(1) {  //sniff for ever
    int key = HearKey(fd);
    if (key == -1) {
      //! we lost connection
      LOG("Error in reading sensor data!\n");
      break;
    }
    if (key > 0) {
      //! That's our treasure key.
    }
  }
   close(fd); // never reaches here
}



//! brief broadcast your packet to the sensor in a single thread
void* WriteSensorData(void* ptr) {
  sleep(2);
  int fd = open_sf_source(robot_ip, SENSOR_PORT);
  while (fd < 0) {
    LOG("Couldn't open serial forwarder\n");
    sleep(5);
    fd = open_sf_source(robot_ip, SENSOR_PORT);
  }
  for (;;) {
    BroadcastKey(fd, 10); // 10 is the key to send out
    sleep(5);
  }
}
    

//These are actually the interface for client side sf api.
// This is a modified version from serial forwarder shipped with TinyOS 1.X

int open_sf_source(const char *host, int port);
/* Returns: file descriptor for serial forwarder at host:port, or
sf     -1 for failure
     (see init_sf_source for description of platform handling)
 */

int init_sf_source(int fd);
/* Effects: Checks that fd is following the serial forwarder protocol.
     Use this if you obtain your file descriptor from some other source
     than open_sf_source (e.g., you're a server)
     Sends 'platform' for protocol version '!', and sets 'platform' to
     the received platform value.
   Modifies: platform
   Returns: 0 if it is, -1 otherwise
 */

void *read_sf_packet(int fd, int *len);
/* Effects: reads packet from serial forwarder on file descriptor fd
   Returns: the packet read (in newly allocated memory), and *len is
     set to the packet length
*/

int write_sf_packet(int fd, const void *packet, int len);
/* Effects: writes len byte packet to serial forwarder on file descriptor
     fd
   Returns: 0 if packet successfully written, -1 otherwise
*/


// This is a wrapper for sending packet. It actually calls write_sf_packet().
// param fd the socket opened by open_sf_source
// param key the key to send out
// return 1 if successful, 0 otherwise
int BroadcastKey(int fd, int key); 

// This is a wrapper for receiving packet. It actually call read_sf_packet().
// param fd the socket opened by open_sf_source
// return the key( >0 ) if the program hears a key, -1 means network error
int HearKey(int fd);
example 13:  Serial Forwarder Client Side API

5.2 Treasure Key

The game, and everything.,,

5.3 GTK+,Glade-2 

See Jonghoon's tutorial on GTK here.
There is another demo from last year on GTK+ and Glade-2 here.


This makefile information below is old and may not be necessary if you use pkg-config like Jonghoon says to in his tutorial above.

It may be hard to use auto-conf and auto-make. You can write a make file to compile a GTK program as follows:

# Simple makefile with GTK support
CC=gcc
CFLAGS=-g -ggdb -Wall -DPNG_NO_MMX_CODE -I/usr/include/gtk-2.0 -I/usr/lib/gtk-2.0/include -I/usr/include/atk-1.0 -I/usr/include/cairo -I/usr/include/pango-1.0 -I/usr/include/glib-2.0 -I/usr/lib/glib-2.0/include -I/usr/include/freetype2 -I/usr/include/libpng12 -lc -lgtk-x11-2.0 -lgdk-x11-2.0 -latk-1.0 -lgdk_pixbuf-2.0 -lm -lpangocairo-1.0 -lfontconfig -lXext -lXrender -lXinerama -lXi -lXrandr -lXcursor -lXcomposite -lXdamage -lpango-1.0 -lcairo -lX11 -lXfixes -lgobject-2.0 -lgmodule-2.0 -ldl -lglib-2.0 -lpthread 
SOURCES=src/interface.c src/support.c src/main.c src/callbacks.c src/interface.c
gtk:$(SOURCES)
        $(CC) $(CFLAGS) $(SOURCES) -o $@
example 14: Manually Compile GTK+ Program with Makefile

********
YOU MAY FAIL TO COMPILE YOUR PROGRAM WHEN USING YOUR OWN MAKEFILE, THE PROBLEM IS THAT YOU NEED TO COMMENT OUT A LINE IN MAIN.C OF YOUR GUI CODE, THAT LINE LOOKS LIKE:
  add_pixmap_directory (PACKAGE_DATA_DIR "/" PACKAGE "/pixmaps");

Appendix

Two sensors (treasures) are placed in LAB 001, The key to open the first one (closer to the door) is 10, the key you should get from it is 5; the other one 5 and 12. Once each sensor receives the key, it will keep sending out packets for 5 seconds. These are indentical to sensors we will use in the DEMO session two weeks later.