Getting Started

1. Introduction

This document describes the basic usage of the pysherpa client library for uSherpa.

2. Prerequisites

It is assumed, that the following is in place:

  • Python >= 2.6 (including pyserial)
  • A copy of the uSherpa pysherpa library distribution
  • A copy of the uSherpa firmware for the TI Launchpad
  • A TI Launchpad equipped with the MSP430G2553 connected to the USB port of your PC, and the serial port it is using is known.

3. Flashing the Firmware to your Launchpad

See the documentation from the uSherpa firmware.

4. Installing the uSherpa pysherpa Library

Get yourself a copy of the pysherpa Python library. The easiest way is to git clone form the pysherpa repository:

git clone https://github.com/wendlers/usherpa-pysherpa.git

This will create a new directory “usherpa-pysherpa” for you. Change into this directory and issue the following to install the pysherpa library:

sudo python setup.py install

1. Check the Installation

You could check if everything went well by entering a Python shell and check if the uSherpa python libraries could be imported:

python

Now check if you are able to import the pysherpa libraries:

import usherpa.comm
import usherpa.api

Both commands should be silently acceded by the Python interpreter (no errors reported).

5. Basic API Usage

In the following sub-section it will be shown, how the API basics work. As an example, we write a python-script which blinks the build in LED on pin 1.0 of the Launchpad.

1. Import “uSherpa” Functionality

Every program that wishes to use uSherpa functionality must import the corresponding librares. This is done by using the following statements:

from usherpa.api import *
from usherpa.serialcomm import *

The first one imports the uSherpa API, the second one imports the transport needed to access the Launchpad through its serial line. The transport is separated form the API since it is planned to have different means of physical transport in the future (e.g. SPI, I2C, …).

2. Create an API Instance

Next an API instance to work on is needed. The instance needs to know which transport to use for communicating with the MCU and thus, a packet stream for the serial line has to be created first:

ps = SerialPacketStream("/dev/ttyACM0")

The packet stream takes the port where your Launchpad is connected to as input. After the transport instance has been created, the receiver thread for incomingpackets needs to be started:

ps.start()

Now the API instance could be created with the following statement:

us = uSherpa(ps)

As an input, the API takes a successfully created transport instance.

3. Configure PIN Functionality

After a connection to the MCU is established, we need to tell the MCU what pins we like to use, and what functionality we intend these pins to perform.

The following code snipped shows how to configure the build in LED on pin 1.0 of the Launchpad as an digital output:

us.pinMode(uSherpa.PIN_1_0, uSherpa.OUTPUT)

The API operation to configure pin functionality is “pinMode”. It takes two parameters: the pin to configure and the function which should be set for that pin (the available functions are described in the following chapters).

4. Perform an Operation on a PIN

With our pin configured successfully as digital output, its state (high/low) could be changed by using the “digitalWrite” function:

# set pin 1.0 to low
us.digitalWrite(uSherpa.PIN_1_0, uSherpa.LOW)

# toggle LED a few times
for i in range(0, 10):
	us.digitalWrite(uSherpa.PIN_1_0, uSherpa.TOGGLE)
	time.sleep(0.25);

“digitalWrite” takes two parameters: a pin to which to write, and the status to assign to that pin. The status is one of “uSherpa.HIGH”, “uSherpa.LOW” or “uSherpa.TOGGLE”.

5. The Complete Script

Our complete script, including basic error handling, looks like this:

# Import usherpa stuff
from usherpa.api import *
from usherpa.serialcomm import *

# Serial packet stream instance
ps = None

try:

	# Create serial packet stream that connects to MCU running uSherpa via serial 
	# line on port '/dev/ttyACM0'.
	ps = SerialPacketStream("/dev/ttyACM0")

	# Start the packet streams receiver thread
	ps.start()

	# Create uSherpa API instance using serial packet stream as transport
	us = uSherpa(ps)

	# Configure pin 1.0 (internal LED on Launchpad) for output
	us.pinMode(uSherpa.PIN_1_0, uSherpa.OUTPUT)

	# Set pin 1.0 to low
	us.digitalWrite(uSherpa.PIN_1_0, uSherpa.LOW)

	# toggle LED a few times
	for i in range(0, 10):
		us.digitalWrite(uSherpa.PIN_1_0, uSherpa.TOGGLE)
		time.sleep(0.25);

except Exception as e:
	print e 

finally:
	if not ps == None:
		# Stop packet streams receiver thread
		ps.stop()	

6. Digital Input

In the previous section we configured a pin for digital output. Now we will have a look on how to use digital input. We are going to configure pin 1.3 (the build in user button on the Launchpad) as digital input, and read in its current state.

Note: Create the API instance the same way as show in the first example.

7. Configure the PIN Functionality

For configuring the functionality, again “pinMode” is used. This time we put the pin 1.3 into mode “uSherpa.INPUT”:

us.pinMode(uSherpa.PIN_1_3, uSherpa.INPUT)

uSherpa knows three different input types that could be assign to a pin:

INPUT    : It is assumed, that an external pull-up/down resistor is provided. 
           This mode is often referred to as floating. The internal button on P1.3
           on the Launchpad has an external pull-up resistor (as for Rev. 1.4).  

PULLUP   : The internal pull-up resistor is enabled. Thus, initial state is high, 
           and a connected button must shorten to ground if closed. 

PULLDOWN : The internal pull-down resistor is enabled. Thus, initial state is low, 
           and a connected button must shorten to VCC if closed. 

1. Perform an Operation on a PIN

The operation that could be performed on a pin configured as digital input is “digitalRead”. The function takes as argument a pin for which to perform the read and returns the current state of the pin (high or low). E.g. to wait for the button at pin 1.3 to be pressed, we could do the following:

while us.digitalRead(uSherpa.PIN_1_3) == uSherpa.HIGH:
	pass

Note: polling the pin is very inefficient. For a more efficient method see the
chapter about external interrupts.

2. The Complete Script

The complete script which checks if button pin 1.3 was pressed looks like this:

from usherpa.api import *
from usherpa.serialcomm import *

# Serial Packet stream instance
ps = None

try:
	ps = SerialPacketStream("/dev/ttyACM0")
	ps.start()

	us = uSherpa(ps)

	# configure pin 1.3 (internal button on Launchpad) for input 
	us.pinMode(uSherpa.PIN_1_3, uSherpa.INPUT)

	print "waiting for button pressed"

	# read pin 1.3 until state changed form high to low
	while us.digitalRead(uSherpa.PIN_1_3) == uSherpa.HIGH:
		pass

	print "button pressed"

except Exception as e:
	print e 

8. External Interrupts

Since polling the state of an digital input is not very elegant nor very efficient, one could configure uSherpa to send and interrupt every time a pin of interest changes its state. To receive such interrupts, the only thing one has to do is register a handler function and enable external interrupts for the pin of interest.

Note: Create the API instance the same way as show in the first example.

1. Write the Interrupt Handler

Writing the interrupt handler is as simple as providing a new function which takes two arguments: the message and the packet received from the MCU:

def exti(msg, packet):
	print "Received external interrupt: ", msg, packet

2. Register the Interrupt Handler

The handler then needs to be registered by assigning it to the “evHandler” attribute of the packet stream created earlier:

ps.evHandler = exti

3. Configure the PIN Functionality

Pin 1.3 has to be configured to be an input (as one would do for a normal input pin):

us.pinMode(uSherpa.PIN_1_3, uSherpa.INPUT)

Additionally “externalInterrupt” is used to request interrupts for this pin. “externalInterrupt” in its simplest form takes two arguments. The pin for which to receive interrupts, and the edge transition:

us.externalInterrupt(uSherpa.PIN_1_3, uSherpa.EDGE_HIGHLOW);

The API knows the following edge transitions:

EDGE_HIGHLOW   state on input changed from high to low

EDGE_LOWHIGH   state on input changed from low to high

EDGE_NONE      disables previously enabled interrupting 

4. The Complete Script

Our complete script which handles presses on button P1.3 may look like this:

from usherpa.api import *
from usherpa.serialcomm import *

ps = None

# Interrupt handler
def exti(msg, packet):
	print "Received external interrupt: ", msg, packet

try:
	ps = SerialPacketStream("/dev/ttyACM0")
	
	# register call-back handler for external interrupts
	ps.evHandler = exti

	# start packet streams reader thread 
	ps.start()

	us = uSherpa(ps)

	# configure pin 1.3 (internal button on Launchpad) for input 
	us.pinMode(uSherpa.PIN_1_3, uSherpa.INPUT)

	# for pin 1.3 enable external interrupt for high-to-low transitions
	us.externalInterrupt(uSherpa.PIN_1_3, uSherpa.EDGE_HIGHLOW);

	# wait for key to exit program 
	print "Press ENTER to exit"
	raw_input()

except Exception as e:
	print e 

5. More Advanced Interrupt Handling

In the case you only want to receive an interrupt call-back after a defined edge transition was detected a given number of times, a third parameter, the trigger count could be passed to the “externalInterrupt” method. Specifying a trigger count > 0 will send an interrupt only if the defined edge transition was detected the given number of trigger counts. E.g. if you want to get an interrupt only after the button on pin 1.3 was pressed 3 times, the following statement could be used:

# For pin 1.3 enable external interrupt for high-to-low transitions, after
# the transition occurred 3 times
us.externalInterrupt(uSherpa.PIN_1_3, uSherpa.EDGE_HIGHLOW, 3);

With the above definition, your call-back handler will be called ONCE after the button at P1.3 was pressed 3 times. After the interrupt was fired, the external interrupt for that pin is removed on the MCU side! Thus, if you like to receive an other interrupt for that pin, you have to call “externalInterrupt” again.

The described behavior comes in handy e.g. in conjunction with wheel encoders. Lets say, if every edge transition of your wheel-encoder signals a movement of 1cm, defining an “externalInterrupt” which calls you back after 10 triggers, gives you a notification when the wheels made a distance of 10cm.

9. Analog Input

With a pin configured for analog input, it is possible to measure a voltage on that pin between 0 and VCC (where VCC is ~3.3V on the Launchpad powered through USB). For example an analog input could be used to measure the position of a potentiometer. For this example, the output of a 10k potentiometer needs to be connected to pin 1.5. The other two pins of the potentiometer are connected between VCC and GND.

Note: Create the API instance the same way as show in the first example.

1. Configure the PIN Functionality

The pin is set to analog input by using the “pinMode” method and assigning uSherpa.ANALOG to the pin:

us.pinMode(uSherpa.PIN_1_5, uSherpa.ANALOG)

Note: not all pins of the Launchpad are able to perform analog readings. Only pins 1.0 to 1.7 are allowed for analog input. Choosing anything else will raise an exception.

2. Perform an Operation on a PIN

Next we could use “analogRead” to make the MCU sample the input and return th value. “analogRead” takes a single parameter, the pin for which to perform the read:

a = us.analogRead(uSherpa.PIN_1_5);

The MSP430 has an 10Bit ADC (Analog Digital Converter), thus the maximum value it could provide is 2^10 = 1024. To convert that value to a voltage, the following calculation could be used (assuming Vmax is about 3.3V):

# Convert value from analog read to volts: 
# - assuming Vmax is 3.3V
# - assuming max value from analog read is 1024
v = (3.3 / 1024.0) * a

3. The Complete Script

Our complete script which samples pin 1.5 once looks like this:

from usherpa.api import *
from usherpa.serialcomm import *

# Serial Packet stream instance
ps = None

try:

	print "uSherpaAnalogRead"

	ps = SerialPacketStream("/dev/ttyACM0")
	ps.start()

	us = uSherpa(ps)

	# configure pin 1.5 for analog input 
	us.pinMode(uSherpa.PIN_1_5, uSherpa.ANALOG)

	# perform analog read on pin 1.5
	a = us.analogRead(uSherpa.PIN_1_5);

	# convert value from analog read to volts: 
	# - assuming Vmax is 3.3V
	# - assuming max value from analog read is 1024
	v = (3.3 / 1024.0) * a

	print "~ volts " + `v` + " (" + `a` + ")"
	
except Exception as e:
	print e 

10. Pulse Width Modulation

With pulse width modulation, it is possible to produce a signal on an output witch switches from high to low within a defined period for a defined time. This could be
used e.g. to control the speed of DC motors, operate servos or dim the light of an LED.

Lets first have a look at the output signal produced by an PWM to understand the basic terms we are going to use later on:

     | dc |  - = 10usec.
     |    |       |
HIGH +----+    +----+    +----+    +----+
     |    |    |    |    |    |    |    |
LOW  +    +----+    +----+    +----+    +
     |         |
     | period  | 

The time after which the output wave form starts repeating is called “period”. The period in uSherpa is defined in usec. The time for which the output is kept high within the period is called duty-cycle (dc) and it is given in percent (of the period) in uSherpa. The above figure shows a PWM with an period of 80usec. and a duty-cycle of 50% which is 40usec. Thus a dc of 0% means the output is completely switched off, and a dc of 100% means, the output is switched on permanently. The effect for all between 0 and 100% is, that e.g. a DC motor or a LED sees the average voltage which results form the on/off ratio. If, for example we have an HIGH voltage of 3.3V, and a duty-cycle of 50%, this looks for a LED like 1.65V. If the period is chosen small enough, the eye for example will not realize, that the LED in reality is switched on and off very fast (on the other hand, if you choose the period very big, the on/off will be realized as blinking).

In the next example we will dim the build in LED on pin 1.6 up and down (to get a pulse effect).

Note: Create the API instance the same way as show in the first example.

1. Configure the PIN Functionality

us.pinMode(uSherpa.PIN_1_6, uSherpa.PWM)

Note,that only a few pins on the MSP430G2553 are able to perform hardware PWM: P1.2, P1.6, P2.1 and P2.2. Trying to configure any other pin for PWM will result in an exception. Also be aware of the fact, that for each port (1 or 2) only one pin out of the two available could produce a PWM at a time. Thus, if you configured P2.1 for PWM, you could not configure P2.2 as PWM at the same time. But it is valid, to e.g. configure P1.6 and P2.2 for PWM at the same time.

2. Set PWM Period

Next, we need to configure the PWM period. This is done through the “pwmPeriod” function which takes two arguments: the pin for which to set the period, and the periods duration in usec.

# set PWM period to 1000us on pin 1.6 
us.pwmPeriod(uSherpa.PIN_1_6, 1000)

3. Set PWM Duty-Cycle

To adjust the PWM duty-cycle the “pwmDuty” operation is used. This operation takes the pin for which to configure the duty-cycle, and the duty-cycle in percent.

# set duty cycle to 50%
us.pwmDuty(uSherpa.PIN_1_6, 0x80);

Note, that the duty cycle percentage goes from 0 to 0xFF, and that 0xFF means 100%.Thus, the value to pass to “pwmDuty” is: int(percentage * (0xFF / 100)).

4. The Complete Script

The following script fades the build in LED on P1.6 up/down by using hardware PWM:

from usherpa.api import *
from usherpa.serialcomm import *

# Serial Packet stream instance
ps = None

# duty cycle
dc 		= 0

# direction for cycling (+5/-5)
dcDir 	= 5

try:

	ps = SerialPacketStream("/dev/ttyACM0")
	ps.start()

	us = uSherpa(ps)

	# configure pin 1.6 (internal LED on Launchpad) for PWM output
	us.pinMode(uSherpa.PIN_1_6, uSherpa.PWM)

	# set PWM period to 1000us on pin 1.6 
	us.pwmPeriod(uSherpa.PIN_1_6, 1000)

	try:

		while True:
			# modify duty-cycle
			dc = dc + dcDir

		    # if max. duty cycle (0xFF) reached, reverse direction
			if dc >= 0xFF:
				dcDir = -dcDir
       			# make sure DC is still valid
				dc = 0xFF
			# if min. duty cycle (0x00) reached, reverse direction
			elif dc <= 0x00:
				dcDir = -dcDir
       			# make sure DC is still valid
				dc = 0x00
   
			# write modified DC   
			us.pwmDuty(uSherpa.PIN_1_6, dc)

	except KeyboardInterrupt: 
		pass

except Exception as e:
	print e 

11. Reset the MCU

Sometimes one needs to reset the MCU through software. E.g. it would be a good idea to reset the MCU when the PWM LED script was terminated through an keyboard interrupt. Otherwise the hardware PWM will keep on going for ever.

1. Perform a software reset

A software reset is performed by calling the reset operation:

us.reset()

Note: if you intend to reconnect to the MCU after a reset, give the MCU a short amount of time to boot up fully.

2. The Complete Script

The following script revisited the PWM LED example from before and adds a reset call before terminating:

from usherpa.api import *
from usherpa.serialcomm import *

# Serial Packet stream instance
ps = None

# duty cycle
dc 		= 0

# direction for cycling (+5/-5)
dcDir 	= 5

try:

	ps = SerialPacketStream("/dev/ttyACM0")
	ps.start()

	us = uSherpa(ps)

	# configure pin 1.6 (internal LED on Launchpad) for PWM output
	us.pinMode(uSherpa.PIN_1_6, uSherpa.PWM)

	# set PWM period to 1000us on pin 1.6 
	us.pwmPeriod(uSherpa.PIN_1_6, 1000)

	try:

		while True:
			# modify duty-cycle
			dc = dc + dcDir

		    # if max. duty cycle (0xFF) reached, reverse direction
			if dc >= 0xFF:
				dcDir = -dcDir
       			# make sure DC is still valid
				dc = 0xFF
			# if min. duty cycle (0x00) reached, reverse direction
			elif dc <= 0x00:
				dcDir = -dcDir
       			# make sure DC is still valid
				dc = 0x00
   
			# write modified DC   
			us.pwmDuty(uSherpa.PIN_1_6, dc)

	except KeyboardInterrupt: 
		pass

except Exception as e:
	print e 

12. Pulse Length Read

If you need to know the length of a pulse on a input pin, you could use the "pulselengthRead" method. When called, the method waits until the state of the given input changes, then again waits until the state goes back to what it was. The time it takes from the first state change to the second state change then is returned. If no state change was detected after a timeout, an uSherpaExeption is thrown.

Note: The times measured for the period, and assumed for the timeout are up to now just "SOMETHING" (means, they are not secs., msecs., usecs"). This has to be fixes in the future ...

In the following example we will see, how the length of a button press on pin 1.3 could be measured through the "pulselenghtRead" functionality.

Examples:

HIGH        +-----------------------+
            |                       |
LOW   ------+                       +-------
            | <- length measured -> |
            |                       |
       1st change               2nd change


HIGH  ------+                       +-------
            |                       |
LOW         +-----------------------+       
            | <- length measured -> |
            |                       |
       1st change               2nd change

1. Configure the PIN Functionality

The pin has to be configured as input:

us.pinMode(uSherpa.PIN_1_3, uSherpa.INPUT)

2. Read the Pulse Length for a PIN

The "pulselengthRead" function takes one argument which is the input pin for which to read the pulse length. It then will return the length of the detected pulse in "SOMETHING", or throw an "uSherpaException" if the read timed out:

pl = us.pulselengthRead(uSherpa.PIN_1_3)

3. The Complete Script

The complete script looks like this:

from usherpa.api import *
from usherpa.serialcomm import *

# Serial Packet stream instance
ps = None

try:

	ps = SerialPacketStream("/dev/ttyACM0")
	ps.start()

	us = uSherpa(ps)

	# configure pin 1.3 (internal button on Launchpad) for input 
	us.pinMode(uSherpa.PIN_1_3, uSherpa.INPUT)

	try:

		# read pulse length on pin 1.3
		pl = us.pulselengthRead(uSherpa.PIN_1_3)

		print "Pulse-lenght was: " + `pl`

	except uSherpaException:

		print "Timeout while waiting for pulse on pin 1.3"

except Exception as e:
	print e 

4. More advanced pulse length reading

Beside the pulse length reading method descried above, there is a more advanced method. This method reconfigures the given pin to an output, drives it high for at least 10us, then changes it back to whatever input it was, and then reads the pulse length. This method comes in handy when using different kind of digital range finders (like e.g. the SFR05, the PING sensor ...) which use a single line for measuring. To use the advanced method, set the second (optional) parameter of "pulselengthRead" to True (the parameter is called "driveHighFirst"). E.g. to get the range from a SRF05 ultrasonic range finder, the following could be used:

# Configure pin where SFR05 is connected to as input
us.pinMode(uSherpa.PIN_2_0, uSherpa.INPUT)

# Read pulse-lenght, but drive pin high first
pl = us.pulselengthRead(uSherpa.PIN_2_0, True) 

print "Pulse-lenght was: " + `pl`

The following picture shows what will happen on the pin:

        configure as
       output   input
            |   |
HIGH        +---+     ------------------------+
            |   |     |                       |
LOW   ------+   +-----+                       +-------
           10us high  | <- length measured -> |
                      |                       |
                   1st change               2nd change

Leave a Reply

You must be logged in to post a comment.