Hi all
If the filesystem wasn’t weird enough, I have yet another piece (a rather large piece) of code to share with you. During this year I implemented a kind of Plug and Play interface for controllers. Essentially, it tries to mimic the behaviour of controllers on a PC. What I mean is, you can plug in a set of controllers into any port on the OI and without any recompiling your code, and your controllers will do the same thing they did before even if they weren’t plugged in the same spot. :ahh:
But there is more, the code also detects whether buttons are pressed or released or are being held. It also provides a way to quickly switch between controller configurations for different drivers. So if I want to use an XBOX controller, but my friend wants to use 2 joysticks, the framework allows you to program in two different configurations and switch between them on the fly. Also, it contains a configurable analog noise filter and a configurable deadband.
Of course, adding this framework to your project might be slightly difficult as it requires a few low level things to be done. I highly recommend you make a backup of your code before trying to add this to your project as we will be changing a lot of the “Do not change” sections.
Warning: If you have data in your EEPROM it will be erased as the filesystem must be used with HAL. See my thread on the filesystem to learn how it works and how to use it.
Prerequisites:
- Kevin’s Serial Port Code
- Kevin’s EEPROM Code
Installation Guide:
- Download the zip file below and unzip the files into a new folder.
- Open your MPLAB project
- Open ifi_aliases.h and delete all the #defines as listed in the Removed ifi_aliases.txt file given to you
- Open ifi_default.h, and find the rx_data_record structure
- Replace it with the structure found in the given Rx_Data_Record.txt file
- Add all the other *.c and *.h files to your project
- Open Timer Interrupt Handler.txt and copy the code into your InterruptHandlerLow(). Be sure to #include “timers.h” into the file containing you InterruptHandleLow() routine.
- Remove your slow/fast loops, they are no longer needed and will break the new code. Get/Putdata are handled in the timer interrupt. See below for timing help.
Configuring the Code:
When the robot starts up, it has to initialize the HAL subsystem and it has to find a writable filesystem on the EEPROM. The following code will initalize the filesystem and HAL.
#include "hal.h"
#include "filesystem.h"
#include "timers.h"
RETURN_CODE err;
err = FS_Init();
if( err == FS_ERROR_NO_FILESYSTEM ){
FS_Format(FALSE); /* Use TRUE instead of FALSE for a full format */
err = FS_Init();
if( err != FS_ERROR_SUCCESS ){
printf("FS format and initialize failed."); /* This should never happen */
}
}
Timer_Initialize();
HAL_Initialize();
if( (rxdata.oi_sw_bytes[0] | rxdata.oi_sw_bytes[1]) != NULL)
{ HAL_Reconfigure(5); }
The HAL also needs to be reconfigured everytime a controller change is made, the easiest way to do this was to see if a button was being pressed on startup. If a button is pressed on any controller on any port at startup it will enter the configuration which will guide you through it over the serial console.
By default HAL has builtin drivers for a joystick and an xbox controller. It also provides support for custom control boxes, but for that you will have to write your own driver for that. If you just use joysticks, the hard part is done, if you use the USB chicklet however, you must tell HAL your USB chicklet mappings. To do this, open xbox.c and find:
/* Enables the use of the Analog aux bytes */
#define MAP_ANALOG_AUX
/* USB Chicklet mappings of digital buttons */
#define MAP_DIGI_BIT_1 XBOX_BUTTON_X
#define MAP_DIGI_BIT_2 XBOX_BUTTON_Y
#define MAP_DIGI_BIT_3 XBOX_BUTTON_A
#define MAP_DIGI_BIT_4 XBOX_BUTTON_B
/* USB chicklet mappings of analog aux 1 byte */
#define MAP_AUX1_BIT_1 XBOX_SHOULDER_LEFT_1
#define MAP_AUX1_BIT_2 XBOX_SHOULDER_LEFT_2
#define MAP_AUX1_BIT_3 XBOX_SHOULDER_RIGHT_1
#define MAP_AUX1_BIT_4 XBOX_SHOULDER_RIGHT_2
/* USB Chicklet mappings of analog aux 2 byte */
#define MAP_AUX2_BIT_1 XBOX_PAD_UP
#define MAP_AUX2_BIT_2 XBOX_PAD_DOWN
#define MAP_AUX2_BIT_3 XBOX_PAD_LEFT
#define MAP_AUX2_BIT_4 XBOX_PAD_RIGHT
The first thing to tell the driver is whether you use all the analog channels for buttons or not. To use the analog channels as buttons, make sure #define MAP_ANALOG_AUX is not commented out. Next you must tell HAL which button presses are associated with the data the chicklet gives it. For an XBOX controller, you’re given 12 buttons. When you configure the chicklet make a note of the order in which you press the buttons, that order is the same as the list above. The buttons that the xbox driver can recognize are in xbox.h and are listed below:
#define XBOX_BUTTON_LOGO CONTROLLER_TYPE_XBOX | 0x00
#define XBOX_BUTTON_X CONTROLLER_TYPE_XBOX | 0x01
#define XBOX_BUTTON_Y CONTROLLER_TYPE_XBOX | 0x02
#define XBOX_BUTTON_A CONTROLLER_TYPE_XBOX | 0x03
#define XBOX_BUTTON_B CONTROLLER_TYPE_XBOX | 0x04
#define XBOX_BUTTON_START CONTROLLER_TYPE_XBOX | 0x05
#define XBOX_BUTTON_BACK CONTROLLER_TYPE_XBOX | 0x06
#define XBOX_SHOULDER_LEFT_1 CONTROLLER_TYPE_XBOX | 0x07
#define XBOX_SHOULDER_LEFT_2 CONTROLLER_TYPE_XBOX | 0x08
#define XBOX_SHOULDER_RIGHT_1 CONTROLLER_TYPE_XBOX | 0x09
#define XBOX_SHOULDER_RIGHT_2 CONTROLLER_TYPE_XBOX | 0x0A
#define XBOX_PAD_UP CONTROLLER_TYPE_XBOX | 0x0B
#define XBOX_PAD_DOWN CONTROLLER_TYPE_XBOX | 0x0C
#define XBOX_PAD_LEFT CONTROLLER_TYPE_XBOX | 0x0D
#define XBOX_PAD_RIGHT CONTROLLER_TYPE_XBOX | 0x0E
#define XBOX_POV_LEFT CONTROLLER_TYPE_XBOX | 0x0F
So, as an example, if the fifth button you pressed was the big X, you’de change (in xbox.c)
#define MAP_AUX1_BIT_1 XBOX_SHOULDER_LEFT_1
to
#define MAP_AUX1_BIT_1 XBOX_BUTTON_LOGO
Your input drivers are now configured. The next thing you must do is assign tasks to your buttons. This is done in hal_out.c. This file contains all the output drivers. An output driver is essentially a controller configuration. If you wanted two configurations you’de have two output drivers. Three drivers are provided by default. They are: a null output driver that does nothing, HAL_Out_AAP, the driver we used during the competition, and HAL_Out_Analog, the analog output driver.
To create your own output driver you must make a function with the following prototype:
void HAL_Out_MyDriver(unsigned char exec, unsigned char subid){...}
In that function you are given two pieces of data, the exec byte and the subid byte. The exec byte contains information on what button was pressed and on what controller. It also contains data on whether the button was pressed or released. The subid tells you which controller number was responsible for the press. This is only useful if you have more than one of the same type type of controller. For example, if you have two joysticks, the sub id will be 0 if a button on the first one was pressed and so on.
To act on a certain button press, an if statement can be used to compare the exec byte to the defined buttons in joystick.h and xbox.h. Examples follow bellow:
void HAL_Out_MyDriver(unsigned char exec, unsigned char subid){
/* Check if XBOX A button was pressed */
if( exec == (XBOX_BUTTON_A | PRESSED) ) { /* do stuff */ }
/* Check if 2nd XBOX controller B button was released */
if( subid == 1 ){
if ( exec == (XBOX_BUTTON_B | RELEASED) ) { /*do stuff */ }
}
/* Check if joystick hat is pointing up */
if ( exec == ( JOYSTICK_HAT_UP | PRESSED ) ) { /* do stuff */ }
}
Once you created your output driver, register it into the HAL framework. In hal.c find the line:
const rom hal_output_driver drivers_out[STUDENT_DRIVERS] = { &HAL_Out_AAP };
and change it to
const rom hal_output_driver drivers_out[2] = { &HAL_Out_AAP, &HAL_Out_MyDriver };
Now tell HAL to use that driver somewhere in your code:
HAL_Load_Student_Driver(1);
You should also now change the analog output driver. Only one is provided for all drivers so you only need to edit HAL_Out_Ana(two_axis_pad pad) in hal_out.c. You are given a structure called pad which provides an X and a Y axis (pad.x and pad.y respectively).
The last step is to call the HAL function everytime new data is sent to the controller. Ideally you’de call HAL() every 26ms, but I’de recommend 13ms for safety. You’ll notice HAL() takes one argument, simply send NULL to it since that was used for our task scheduler code. Your function call should look like this:
HAL(NULL);
You can now compile the code and download it. At first startup a few things will happen. Your EEPROM will be formatted to the Inverse Paradox filesystem. HAL will be initialized, but won’t find a configuration so it will automatically kick you into the text based configurator. Simply follow the on screen instructions and enjoy the light show. It will then save the configuration to the EEPROM and will let your code execute.
When you restart the robot the configuration will be read out of the EEPROM and you will never have to configure again unless you change your controllers around. To force a reconfiguration, press any button on any controller and reset the robot.
You’re now done!
Timing Issues:
As you noticed, this code requires our timer code to be used. This code will get rid of the need for slow/fast loops since it handles Get/Putdata for you. This may make timing things like HAL a little tricky. The solution is to use our dynamic timer support. Simply put, dynamic timers run off the main system timer and can have custom intervals set to them. A simple timing example is presented below for HAL.
/* Done at initialization time */
/* Set the first timer to have a tick interval of 13 seconds */
Timer_Set_Interval(0, 13);
Timer_Reset(0);
/* done in main loop */
static unsigned long last_tick;
if ( last_tick < Timer_Ticks(0) ){
last_tick = Timer_Ticks(0);
HAL(NULL);
/* Code in here will only run every 13ms since the timer will only increase its tick count every 13ms */
}
Another timing tool is the Kernel_Sleep() function which will sleep for the number of milliseconds specified. This is blocking code and should be used sparingly.
Even more configuration Options:
In hal.h, you are given 7 defines that let you enable/disable/tune the analog noise/deadband filters. These are:
/* Deadband filter options */
#define ANALOG_DEADBAND /* Comment to disable */
#define ANALOG_DEADBAND_TOP 140
#define ANALOG_DEADBAND_BOTTOM 114
#define ANALOG_DEADBAND_MIDDLE 127
/* Noise filter options */
#define ANALOG_NOISE_FILTER /* Comment to disable */
#define ANALOG_NOISE_FILTER_POWER_X 4
#define ANALOG_NOISE_FILTER_POWER_Y 4
If you’re using our filesystem for more than one file, make sure you don’t overwrite your other files by assigning
#define HAL_CONFIG_FILE 0
the proper file number.
HAL will choose a default output drive at startup, this can be changed by changing:
#define HAL_DEFAULT_DRIVER 0
to the correct output driver.
Other defines that are important, but work well with the defaults are:
/* Default time to wait for controller responses (seconds) */
#define HAL_CONFIG_TIMEOUT 5
/* Time to wait before config starts */
#define HAL_WAIT_ENTER_CONFIG 2000
/* Time to wait for a button to be released to move on */
#define HAL_WAIT_BUTTON_RELEASE 1000
/* HAL polling time, poll at this time (in ms) if you are using timer based get/putdata... otherwise call HAL process in slow loop */
#define HAL_POLLING_INTERVAL 13
Okay… that was a long post… but I hope you guys have fun with this code and tinker with it. I had lots of fun writing it so I hope you have fun using it. Btw, this code was used during competition time and has been put through its paces so it should not do anything weird. But as always, use caution when trying new control systems with heavy robots.
P.S. You’ll notice I included our modified ifi_* files… Use them at your own risk, they’ve been heavily modified to save on codespace and to make our systems work better for us.
P.P.S. I apologize if I forgot to tell you something important.
Inverse Paradox HAL.zip (25.6 KB)
Inverse Paradox HAL.zip (25.6 KB)