You are viewing outdated content for BUG. If you have a BUG Y.T. edition or 2.0 series device, please visit our updated wiki: http://wiki.buglabs.net
Interfacing with the vonHippel in C
From BUG Wiki
Contents |
Before You Begin
Disclaimer
Misuse of the vonHippel module has the potential to damage both it and the BUGbase. Proceed at your own risk.
Introduction
Why use C rather than Java to interface with the vonHippel module?
- You can directly access additional functionality (the ADC temperature sensor, the SPI bus, etc)
- You can fine tune things to your needs (set the ADC gain, for example)
- You'll learn a lot (there is very little between your code and the hardware)
Things You'll Need
You will need to gather some things before you can jump in to the wonderful world of programming the vonHippel in C. You will need the BUG toolchain and the vonHippel header file.
To get the toolchain, follow the instructions on this page, under the heading "toolchain" (for 1.4 only. Stay tuned for 2.0 instructions).
To get the vonHippel header file, you will need to download the kernel source (this is for 1.4 only, again):
svn co svn://svn.buglabs.net/bug/branches/R1.4/qa/bug-linux-2.6.27.2
Try to get one that matches the kernel that you will be using, if you can - although the header files don't change very frequently, so you shouldn't encounter too much trouble if it doesn't quite match.
Compiling a Test Program
Before continuing, it's a good idea to ensure that the toolchain and such are set up properly
#include <stdio.h>
#include <linux/bmi/bmi_vh.h>
int main( int argc, char* argv[] ){
printf( "Hello, world." );
return 0;
}
Be sure to include that extra header file - that's the magical vonHippel header file.
Compile this program with the cross-compiler - that's "arm-poky-linux-gnueabi-gcc". (if you followed the instructions in the link above, then it should already be in $PATH).
You'll notice that it complained that the magical header file is missing - to solve this, be sure to compile with the option "-I /path/to/kernel/source/include/". It should compile without complaint.
Ioctl and Friends
ioctl
Ioctl is a C function for interfacing with IO devices for which the "byte stream" metaphor that is assumed by most other IO functions is not adequate. It is the primary method of communicating with the vonHippel module in C. It has the following prototype:
int ioctl( int device_file_descriptor, int request, void* data ); (actually this isn't quite its prototype, but this is how it behaves)
You may benefit from looking at the ioctl manpage at this point.
bmi_vh.h
Now let's have a look at that magical header file. You'll be able to find it at
/path/to/kernel/source/include/linux/bmi_vh.h
There's some pretty standard header file stuff here - the defined constants and structs are to make filling the "data" argument of ioctl easier.
At the bottom of the file, you may notice that a bunch of things are defined to some crazy macros. Don't worry too much about how these work - the important thing to know is that they serve as the "request" argument of ioctl. They "automagically" ensure that your data goes to the right place in the vonHippel. Also, note that the third argument of these macros is always some data type. This data type should go in the third argument of ioctl.
Examples
Connecting to the vonHippel
In this example, we will attempt to connect to the vonHippel module and read some data from it.
#include <fcntl.h>
#include <linux/bmi/bmi_vh.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/ioctl.h>
#include <sys/unistd.h>
//Buffer sizes
#define VH_NAME_SIZE 32
//VonHippel port configuration
#define PORT_1 1
#define PORT_2 2
#define PORT_3 3
#define PORT_4 4
#define MIN_PORT PORT_1
#define MAX_PORT PORT_4
int setup( int port );
int main( int argc, char* argv[] ){
int port;
int rc;
int vh_fd;
int vh_data;
if( argc != 2 ){
fprintf( stderr, "ERROR: Run as %s <port number (1 to 4)>.\n", argv[0] );
return EXIT_FAILURE;
}
rc = sscanf( argv[1], "%i", &port );
if( rc == EOF ){
fprintf( stderr, "ERROR: Could not parse argument.\n" );
return EXIT_FAILURE;
}
if( port < MIN_PORT || port > MAX_PORT ){
fprintf( stderr, "ERROR: Invalid port - must be in range 1 to 4.\n" );
return EXIT_FAILURE;
}
vh_fd = setup( port );
//Attempt to poll the status of the vonHippel. Virtually every read or write operation will take
//this form.
rc = ioctl( vh_fd, BMI_VH_GETSTAT, &vh_data );
if( rc < 0 ){
fprintf( stderr, "ERROR: Could not poll vonHippel." );
return EXIT_FAILURE;
}
printf( "IOX data = 0x%x\n", vh_data & 0x00FF );
printf( "GPIO data = 0x%x\n", ( vh_data >> 8 ) & 0x000F );
return EXIT_SUCCESS;
}
int setup( int port ){
int vh_fd;
char vh_name[ VH_NAME_SIZE ];
int rc;
//Generate the device name
rc = snprintf( vh_name,
VH_NAME_SIZE,
"/dev/bmi_vh_control_m%d",
port );
if( rc >= VH_NAME_SIZE ){
fprintf( stderr, "ERROR: File name too long." );
return EXIT_FAILURE;
}
printf( "Opening vonHippel...\n" );
//Open the vonHippel
vh_fd = open( vh_name, O_WRONLY, 0666 );
if( vh_fd == -1 ){
fprintf( stderr, "ERROR: Could not open vonHippel" );
return EXIT_FAILURE;
}
printf( "Success!\n" );
return vh_fd;
}
Hey! That was easy. See? You had nothing to worry about! Now just compile it as before, scp it over to your BUG and try running it. You'll notice that it doesn't work - it can't connect to the vonHippel. This is because Concierge is blocking access to the vonHippel. You'll have to shut off Concierge to make this work:
/etc/init.d/concierge stop
If you want to start Concierge, just execute:
/etc/init.d/concierge start
You may notice that some bizarre error messages show up when Concierge is stopped and you execute this program. Simply disconnect and re-connect the vonHippel and they will go away.
Setting and Reading the RDAC
The RDAC (digital potentiometer) output appears on the vonHippel as "VAD" in the "power" section. It is just some voltage.
#include <fcntl.h>
#include <linux/bmi/bmi_vh.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/ioctl.h>
#include <sys/unistd.h>
typedef double voltage;
//Buffer sizes
#define VH_NAME_SIZE 32
//VonHippel port configuration
#define PORT_1 1
#define PORT_2 2
#define PORT_3 3
#define PORT_4 4
#define MIN_PORT PORT_1
#define MAX_PORT PORT_4
//RDAC
#define GET_RDAC_INPUT( x ) ( (uint8_t)( -40*(x) + 306 ) )
#define GET_RDAC_OUTPUT( x ) ( (voltage)( ( (x) - 306 ) / -40.0 ) )
//Odds and ends
#define OUTPUT_HELP_MESSAGE printf( "Instructions:\n s <decimal number> - set the RDAC voltage.\n r - read the RDAC voltage.\n h - display this message.\n q - quit.\n" )
//This is no masterpiece of elegance
#define FLUSH_INPUT_BUFFER \
ungetc( 'a', stdin ); \
while( 1 ){ \
int ch = getchar(); \
if( ch == EOF || ch == '\n' ){ \
break; \
} \
}
int setup( int port );
void interactive_mode( int vh_fd );
void set_rdac( int vh_fd, voltage new_voltage );
voltage read_rdac( int vh_fd );
int main( int argc, char* argv[] ){
int port;
int rc;
int vh_fd;
int vh_data;
if( argc != 2 ){
fprintf( stderr, "ERROR: Run as %s <port number (1 to 4)>.\n", argv[0] );
return EXIT_FAILURE;
}
rc = sscanf( argv[1], "%i", &port );
if( rc == EOF ){
fprintf( stderr, "ERROR: Could not parse argument.\n" );
return EXIT_FAILURE;
}
if( port < MIN_PORT || port > MAX_PORT ){
fprintf( stderr, "ERROR: Invalid port - must be in range 1 to 4.\n" );
return EXIT_FAILURE;
}
vh_fd = setup( port );
interactive_mode( vh_fd );
return EXIT_SUCCESS;
}
void interactive_mode( int vh_fd ){
OUTPUT_HELP_MESSAGE;
while( 1 ){
char input;
voltage voltage_data;
scanf( "%c", &input );
switch( input ){
case 's':
//Set
scanf( "%lf", &voltage_data );
set_rdac( vh_fd, voltage_data );
break;
case 'r':
//Read
printf( "RDAC voltage is: %f.\n", read_rdac( vh_fd ) );
break;
case 'q':
//Quit
return;
break;
default:
OUTPUT_HELP_MESSAGE;
break;
}
FLUSH_INPUT_BUFFER;
}
return;
}
int setup( int port ){
int vh_fd;
char vh_name[ VH_NAME_SIZE ];
int rc;
//Generate the device name
rc = snprintf( vh_name,
VH_NAME_SIZE,
"/dev/bmi_vh_control_m%d",
port );
if( rc >= VH_NAME_SIZE ){
fprintf( stderr, "ERROR: File name too long." );
return EXIT_FAILURE;
}
//Open the vonHippel
vh_fd = open( vh_name, O_WRONLY, 0666 );
if( vh_fd == -1 ){
fprintf( stderr, "ERROR: Could not open vonHippel" );
return EXIT_FAILURE;
}
return vh_fd;
}
void set_rdac( int vh_fd, voltage new_voltage ){
int rc;
uint8_t dac_input;
dac_input = GET_RDAC_INPUT( new_voltage );
rc = ioctl( vh_fd, BMI_VH_SETRDAC, dac_input );
if( rc == -1 ){
fprintf( stderr, "ERROR: Could not set RDAC.\n" );
}
sleep( 1 );
return;
}
voltage read_rdac( int vh_fd ){
int rc;
uint8_t dac_output;
ioctl( vh_fd, BMI_VH_RDRDAC, &dac_output );
if( rc == -1 ){
fprintf( stderr, "ERROR: Could not read RDAC.\n" );
return EXIT_FAILURE;
}
return GET_RDAC_OUTPUT( dac_output );
}
You're probably wondering what those wacky macros (wackroes?) are doing. Hold on. I'll bring them down here for you:
//RDAC #define GET_RDAC_INPUT( x ) ( (uint8_t)( -40*(x) + 306 ) ) #define GET_RDAC_OUTPUT( x ) ( (voltage)( ( (x) - 306 ) / -40.0 ) )
What's going on here? Well, it turns out that there are only two _IOXs defined for the RDAC: BMI_VH_SETRDAC and BMI_VH_RDRDAC. These do not send raw I2C packets (the RDAC is an I2C device), but rather are configured to send and receive data that equates directly to the RDAC value. This data, as it turns out, is rather strange: it is a byte that corresponds to the output voltage. If it has a value of less than 107, then the VAD voltage will be at a maximum (~4.91V). Above 107, the VAD voltage will decrease linearly until about 1.3V. Those macroes simply scale the input value accordingly. Note that because of the way they function, the program will behave oddly if the input voltage is greater than ~4.91 or less than ~1.3 (nominal range is 1.5V to 4.5V). Also, keep in mind that this is kind of a hack - in an ideal world, it would be possible to directly interface with the RDAC through I2C and read the calibration data from its EEPROM to set it properly (this is entirely possible, and may very well be the next example here...). Consequently, these macros may need to be adjusted to get optimal performance with a particular vonHippel module.
