Software
IntroductionAs seen in the firmware section, it is possible to control RoboVero with a simple serial terminal application. However, for anything but the simplest of tasks, this can quickly become unwieldy. The solution is a language interface that allows RoboVero functions to be called from within a program. One such API is the RoboVero Python Client Library. Python has a comprehensive standard library and online documentation making it an accessible and powerful language for robotics applications. Source
Overo COM HostDepending on your distribution and image you might need to install python and some modules. On Angstrom,
$ opkg update $ opkg install python $ opkg install python-modules $ opkg install python-pyserial You also need to download the Python Client Library. Because git doesn't exist on Angstrom, you will need to maintain it yourself. You can mount the SD Card to copy the PCL over, or use cURL:
$ opkg install curl $ curl -L -k https://github.com/robovero/python/tarball/master | tar zx Note the RoboVero HubCommander Interface. For the Overo to see the RoboVero, you must disconnect the USB mini-B. SSH to your Overo to send commands. LinuxThe Python Client Library is available via github. If you don't already use git you will need to install it.
$ sudo apt-get install git Now you can get the most up to date version of the library.
$ cd ~/robovero $ git clone git://github.com/robovero/python.git The repository will be stored in ~/robovero/python. Before moving on, let's make sure everything is up to date. $ sudo apt-get update $ sudo apt-get install python python-serial MacIf you are using Mac, install git from here. Now you can get the most up to date version of the library.
$ cd ~/robovero $ git clone git://github.com/robovero/python.git Python should already be installed on your Mac. To get pyserial, download the archive from http://pypi.python.org/pypi/pyserial. Unpack the archive, enter the directory in Terminal and run $ python setup.py install WindowsNOTE: The installation instructions for Windows are in progress. Visit the Question and Answer section if you need help installing on Windows. You may also need to update your firmware to get it to work.
$ git clone git://github.com/robovero/python.git Python also needs to be installed on your Windows computer. Get Python 2.7 from http://python.org/download/. After that, to get pyserial, download the archive from http://pypi.python.org/pypi/pyserial. Unpack the archive. Set your Environment Variables by following instructions http://docs.python.org/using/windows.html#excursus-setting-environment-variables. Enter the directory in Command Prompt and run $ python setup.py install ExamplesThe easiest way to get started with the Python Client Library is to try some of the included examples. Make sure that your RoboVero is powered, USB is connected, and the device has been reset (see pitfalls). $ cd ~/robovero/python Analog-to-Digital ConverterThis examples measures a voltage on AD0_0.
$ python adc.py 1748 756 883 749 795 ^C keyboard interrupt: how rude! Inertial MeasurementThis example displays x, y, and z readings for the on-board accelerometer, compass, and gyro.
$ python IMU.py a [x, y, z]: [-880, -640, -16704] c [x, y, z]: [-257, -257, -257] g [x, y, z]: [-257, -257, -257] a [x, y, z]: [-832, -608, -16672] c [x, y, z]: [-257, -257, -257] g [x, y, z]: [-257, -257, -257] ^CTraceback (most recent call last): File "IMU.py", line 130, in <module> time.sleep(1) KeyboardInterrupt ServoThis example requires an RC servo connected to PWM1.
$ python servo.py
New angle: 10
New angle: 100
enter an angle between 0 and 90 degrees
New angle: 20
New angle: 0
New angle: ^CTraceback (most recent call last):
File "servo.py", line 65, in <module>
match_value = getServoAngle()
File "servo.py", line 22, in getServoAngle
user_angle = raw_input("New angle: ")
KeyboardInterrupt
Update your Python Client Library to access the latest examples. $ git pull Usage
For the most part, you can call Python Client Library functions the same way as C Peripheral Driver functions - python will even forgive you if your function call is followed by a semi-colon. Some key differences are outlined below using the peripheral pin select as an example.
Includes#include "lpc17xx_pinsel.h" /* C */ import robovero.lpc17xx_pinsel # Python Additionally, unless you want to append each function call with the name of the module containing it, you need to import functions and types individually by name.
from robovero.lpc17xx_pinsel import PINSEL_CFG_Type, PINSEL_ConfigPin EnumsC enums are represented by classes in Python. The enumerators are class variables. Here is the declaration of a function that accepts an enumerated type as its sole argument.
void PINSEL_ConfigTraceFunc (FunctionalState NewState); And here is how it is used in python and C.
PINSEL_ConfigTraceFunc(ENABLE); /* C */ from robovero.lpc_types import FunctionalState # Python PINSEL_ConfigTraceFunc(FunctionalState.ENABLE); StructsStructs in C are treated as classes in Python. The main differences are declaration and referencing. Declaration
PINSEL_CFG_Type PinCfg; /* C */ PinCfg = PINSEL_CFG_Type() # Python Referencing
PINSEL_ConfigPin(&PinCfg); /* C */ PINSEL_ConfigPin(PinCfg.ptr) # Python ArraysYou can use lists, tuples, strings, etc. to store data in memory on the machine where python is running. In certain cases you need to store an array of data in RoboVero memory. One such case is to use the function UART_SEND which expects a pointer to an array of data. Here is one of many ways this can be achieved in C.
#include "string.h" #include "lpc17xx_uart.h" #include "lpc_types.h" #include "LPC17xx.h" #include "extras.h" char msg[] = "RoboVero smash!"; roboveroConfig(); UART_Send(LPC_UART1, msg, strlen(msg), BLOCKING); The robovero.extras module contains a class called Array. The initialization functions takes the number of elements, width of each element, and initialization values (optional) as arguments. Values can be a string, list, or single value to copy to RoboVero memory. In case a single value is provided, it is used for each element in the array.
from robovero.extras import Array, roboveroConfig from robovero.LPC17xx import LPC_UART1 from robovero.lpc17xx_uart import UART_Send from robovero.lpc_types import TRANSFER_BLOCK_Type _msg = "RoboVero smash!" msg = Array(len(_msg), 1, _msg) roboveroConfig() UART_Send(LPC_UART1, msg.ptr, msg.length, TRANSFER_BLOCK_Type.BLOCKING) There is a documentation site for robovero python library at Robovero Python. Advanced Workflow
Python can be used for developing your robotics applications quickly and interactively. Follow these steps if you find yourself limited by USB latency. These four functions are often called sequentially to enable PWM in counter mode on a given channel.
PWM_ChannelCmd(LPC_PWM1, 1, FunctionalState.ENABLE) PWM_ResetCounter(LPC_PWM1) PWM_CounterCmd(LPC_PWM1, FunctionalState.ENABLE) PWM_Cmd(LPC_PWM1, FunctionalState.ENABLE) Step 1: Create a python functiondef pwmCounterState(PWMChannel, NewState): PWM_ChannelCmd(LPC_PWM1, PWMChannel, NewState) PWM_ResetCounter(LPC_PWM1) PWM_CounterCmd(LPC_PWM1, NewState) PWM_Cmd(LPC_PWM1, NewState) pwmCounterState(1, FunctionalState.ENABLE) Step 2: Port your function to COnce you're satisfied with your new function, convert it to C and add it to extras.c Remember to include any headers that you reference. #include "lpc17xx_pwm.h"
int _PWM_ChannelCmd(uint8_t * args)
{
uint8_t * arg_ptr;
LPC_PWM_TypeDef* PWMx;
uint8_t PWMChannel;
FunctionalState NewState;
if ((arg_ptr = (uint8_t *) strtok(NULL, " ")) == NULL) return 1;
PWMx = (LPC_PWM_TypeDef*) strtoul((char *) arg_ptr, NULL, 16);
if ((arg_ptr = (uint8_t *) strtok(NULL, " ")) == NULL) return 1;
PWMChannel = (uint8_t) strtoul((char *) arg_ptr, NULL, 16);
if ((arg_ptr = (uint8_t *) strtok(NULL, " ")) == NULL) return 1;
NewState = (FunctionalState) strtoul((char *) arg_ptr, NULL, 16);
PWM_ChannelCmd(PWMx, PWMChannel, NewState);
return 0;
}
Which is fairly similar to our new function.
int _pwmCounterState(uint8_t * args)
{
uint8_t * arg_ptr;
uint8_t PWMChannel;
FunctionalState NewState;
if ((arg_ptr = (uint8_t *) strtok(NULL, " ")) == NULL) return 1;
PWMChannel = (uint8_t) strtoul((char *) arg_ptr, NULL, 16);
if ((arg_ptr = (uint8_t *) strtok(NULL, " ")) == NULL) return 1;
NewState = (FunctionalState) strtoul((char *) arg_ptr, NULL, 16);
PWM_ChannelCmd(LPC_PWM1, PWMChannel, NewState);
PWM_ResetCounter(LPC_PWM1);
PWM_CounterCmd(LPC_PWM1, NewState);
PWM_Cmd(LPC_PWM1, NewState);
return 0;
}
Let's take a closer look. First, prepend the function name with an underscore as a reminder that the function is intended to be called by the USB interface only. The arguments and return type are mandatory. int _pwmCounterState(uint8_t * args) Arguments are passed to the function in a string separated by spaces. This first variable declared below is used as a pointer to the individual arguments. The other variables are used to store the arguments. uint8_t * arg_ptr; uint8_t PWMChannel; FunctionalState NewState; Now the arguments are separated, converted from string to unsigned long, and then typecast. If an argument is missing the function returns 1. if ((arg_ptr = (uint8_t *) strtok(NULL, " ")) == NULL) return 1; PWMChannel = (uint8_t) strtoul((char *) arg_ptr, NULL, 16); if ((arg_ptr = (uint8_t *) strtok(NULL, " ")) == NULL) return 1; NewState = (FunctionalState) strtoul((char *) arg_ptr, NULL, 16); And finally, call the functions PWM_ChannelCmd(LPC_PWM1, PWMChannel, NewState); PWM_ResetCounter(LPC_PWM1); PWM_CounterCmd(LPC_PWM1, NewState); PWM_Cmd(LPC_PWM1, NewState); return 0; The return value is used to indicate the status of the operation. If your function needs to return data, it needs to be sent via USB. The following snippet is from _PWM_GetCaptureValue which wraps the library function PWM_GetCaptureValue and returns an unsigned int. sprintf((char *) str, "%x\r\n", (unsigned int) PWM_GetCaptureValue(PWMx, CaptureChannel)); writeUSBOutString(str); return 0; Step 3: Append the driver function tableAdd this entry to the driver function table in src/table.c. The first parameter is the command that causes your function to be invoked. The second parameter is the pointer to your function.
{(uint8_t *) "pwmCounterState", _pwmCounterState},
int pwmCounterState(uint8_t * args); Step 4: Replace the python functionAdd this new function to extras.py
def pwmCounterState(PWMChannel, NewState):
return robocaller("pwmCounterState", "void", PWMChannel, NewState)
+ from robovero.extras import pwmCounterState - def pwmCounterState(PWMChannel, NewState): - PWM_ChannelCmd(LPC_PWM1, PWMChannel, NewState) - PWM_ResetCounter(LPC_PWM1) - PWM_CounterCmd(LPC_PWM1, NewState) - PWM_Cmd(LPC_PWM1, NewState) That's it! We've just reduced four USB function calls to one. Tipsextras.c and extras.py contain more examples of this process. If you absolutely must use interrupts, there is no need to ditch the Python Client Library for a pure C firmware implementation. Simply write and test your interrupt service routine - you might be able to do so in python before porting to C - and then add it to the vector table in startup.c. Now, you can enable the interrupt through your python application and expect it to be handled with minimal latency. PitfallsHere are some hangups to watch out for when developing with RoboVero. > There is currently no mechanism to synchronize firmware and software versions. If an example isn't working for you, it is possible that it was written for a more recent revision than that which shipped with your RoboVero. Update your firmware to the most recent version. > When you first connect to RoboVero it asks you to press enter to begin. This is used to detect the line termination that your host machine is using - whether it be an Overo COM, desktop PC, etc. It is possible that PCL sends different line terminators than your favorite serial application (Kermit, Minicom, etc.). If such is the case, you will have to perform a hard reset when switching from Kermit to your program or vice-versa. |