Hardware components | ||||||
| × | 1 | ||||
| × | 1 | ||||
| × | 1 | ||||
| × | 1 | ||||
| × | 1 | ||||
| × | 1 | ||||
| × | 1 | ||||
| × | 1 | ||||
| × | 2 | ||||
Software apps and online services | ||||||
| ||||||
| ||||||
Hand tools and fabrication machines | ||||||
|
It is hard to get real time performance from the Raspberry Pi. It is important to have this when using the HC-SR04 (ultrasonic range finding sensor) as the pulse width determines the range. I figured I might try to create my own i2c slave device for the Raspberry PI to address this issue.
The actual solution was to offload the processing to a ten dollar Launch Pad from Texas Instruments that uses a 35 cent processor. This launchpad has a 16 bit cpu that only has 512 bytes of ram and 16 k bytes of program memory. If you are new to the Launch pad, it is cheap to get into, you don't need to buy any extra software or hardware (except for the Launch Pad itself). It is a great introduction to embedded programming. Obviously the code is included (see below).
The Launch Pad runs a stepper motor to change the sensor's yaw, the ultrasonic sensor (off course) and a uart Bluetooth adapter that I used for debugging. The Bluetooth adapter is completely optional as it just spits out debug info. For that matter so is the stepper motor and driver.
The Raspberry PI interfaces to the Launchpad through i2c. The Launchpad acts as a slave.
By running the timer on the launchpad at 500 kHz, I was able to get really good stability. At ultrasonic distance of 245 mm, I got a standard deviation of 2.5 mm.
On the launch pad, I disconnected Jumpers J3-4, J3-5 and J5-2.
On a final note, even if you are not interested in this specific application, it provides an example framework on how you can create your own Raspberry PI peripheral and make (or loose) a zillion dollars selling it.
I attached the ultrasonic sensor using a hot glue gun.
#include "msp430g2553.h"
#define LED1 BIT0
#define TXD BIT2
#define RXD BIT1
#define SWITCH BIT7
#define STEPPER_orange BIT1
#define STEPPER_yellow BIT2
#define STEPPER_pink BIT3
#define STEPPER_blue BIT4
#define INTERRUPT_SIGNAL BIT5
#define ULTRASONIC_INPUT 0x1
#define ULTRASONIC_OUTPUT 0x10
/*
* main.c
* p1.0 led indicator
* p1.1 uart
* p1.2 uart
* p1.6 i2c SCL clock
* p1.7 i2c SDA data
* p1.3 input switch (stop/start)
* p1.4 ultrasonic trigger
* p2.0 ultrasonic capture timer A1
* p2.1-p2.4 stepper 0-3 timer A0 software gpio
* p1.5 interrupt - data ready
*/
static char tx_buffer[16] = "XXXX_XX\r";
static char i2c_tx_buffer[3] = { 0 };
static char i2c_rx_address = 0;
unsigned int tx_buffer_i = 0;
unsigned int ultrasonic_start = 0;
unsigned int ultrasonic_period = 0;
unsigned int state = 0;
char ConvertIntToAscii(unsigned int v, unsigned int offset)
{
v >>= offset;
v &= 0xF;
if(v < 10)
return v + '0';
else
return (v-10) + 'A';
}
#define DELAY_100ms (50000)
#define DELAY_10ms (5000)
#define DELAY_60ms (30000)
#define DELAY_1ms (500)
#define DELAY_2ms (500)
#define DELAY_100us (50)
void delayUnits(unsigned int delay)
{
TAR = 0;
TACCR0 = delay;
TACCTL0 = CCIE; // Compare-mode interrupt.
TACTL = TASSEL_2 | MC_1 | ID_3; // TACLK = SMCLK, Up mode, 4Mhz / 8 = 500kHz clock
volatile unsigned int tactl;
do
{
__bis_SR_register(CPUOFF + GIE); // Enter LPM0
tactl =(TACTL & MC_3);
}while(tactl != 0);
TACCTL0 &= ~CCIE;
}
#define STEPPER_STEP(on, off, port) ((on) | (port & ~(off)))
void StepClockwise(unsigned int delay)
{
// step 1
P2OUT = STEPPER_STEP(STEPPER_orange, STEPPER_yellow | STEPPER_pink | STEPPER_blue, P2OUT);
delayUnits(delay);
P2OUT = STEPPER_STEP(STEPPER_orange | STEPPER_yellow, STEPPER_pink | STEPPER_blue, P2OUT);
delayUnits(delay);
P2OUT = STEPPER_STEP(STEPPER_yellow, STEPPER_pink | STEPPER_blue | STEPPER_orange, P2OUT);
delayUnits(delay);
P2OUT = STEPPER_STEP(STEPPER_yellow | STEPPER_pink, STEPPER_blue | STEPPER_orange, P2OUT);
delayUnits(delay);
P2OUT = STEPPER_STEP(STEPPER_pink, STEPPER_blue | STEPPER_orange | STEPPER_yellow, P2OUT);
delayUnits(delay);
P2OUT = STEPPER_STEP(STEPPER_pink | STEPPER_blue, STEPPER_orange | STEPPER_yellow, P2OUT);
delayUnits(delay);
P2OUT = STEPPER_STEP(STEPPER_blue, STEPPER_orange | STEPPER_yellow | STEPPER_pink, P2OUT);
delayUnits(delay);
P2OUT = STEPPER_STEP(STEPPER_blue | STEPPER_orange, STEPPER_yellow | STEPPER_pink, P2OUT);
delayUnits(delay);
P2OUT = STEPPER_STEP(0, STEPPER_blue | STEPPER_orange | STEPPER_yellow | STEPPER_pink, P2OUT);
}
void StepAntiClockwise(unsigned int delay)
{
// step 1
P2OUT = STEPPER_STEP(STEPPER_blue, STEPPER_orange | STEPPER_yellow | STEPPER_pink, P2OUT);
delayUnits(delay);
P2OUT = STEPPER_STEP(STEPPER_pink | STEPPER_blue, STEPPER_orange | STEPPER_yellow, P2OUT);
delayUnits(delay);
P2OUT = STEPPER_STEP(STEPPER_pink, STEPPER_blue | STEPPER_orange | STEPPER_yellow, P2OUT);
delayUnits(delay);
P2OUT = STEPPER_STEP(STEPPER_yellow | STEPPER_pink, STEPPER_blue | STEPPER_orange, P2OUT);
delayUnits(delay);
P2OUT = STEPPER_STEP(STEPPER_yellow, STEPPER_pink | STEPPER_blue | STEPPER_orange, P2OUT);
delayUnits(delay);
P2OUT = STEPPER_STEP(STEPPER_orange | STEPPER_yellow, STEPPER_pink | STEPPER_blue, P2OUT);
delayUnits(delay);
P2OUT = STEPPER_STEP(STEPPER_orange, STEPPER_yellow | STEPPER_pink | STEPPER_blue, P2OUT);
delayUnits(delay);
P2OUT = STEPPER_STEP(STEPPER_blue | STEPPER_orange, STEPPER_yellow | STEPPER_pink, P2OUT);
delayUnits(delay);
P2OUT = STEPPER_STEP(0, STEPPER_blue | STEPPER_orange | STEPPER_yellow | STEPPER_pink, P2OUT);
}
void init_I2C()
{
// pin 1.6 and 1.7 as i2C input for scl
P1REN &= ~0xC0;
P1DIR &= ~0xC0;
P1SEL |= 0xC0;
P1SEL2 |= 0xC0;
P1REN &= ~0xC0;
UCB0CTL1 = UCSWRST;
UCB0CTL0 = UCMODE_3 + UCSYNC; // slave mode
UCB0I2COA = 0x48; // address == 0x48
UCB0CTL1 &= ~UCSWRST;
UC0IE |= UCB0RXIE | UCB0TXIE;
UCB0I2CIE |= UCSTTIE;
}
void ProcessState()
{
int j;
if((state < 10) || (state >= 30))
{
for(j=16;j>0;j--)
{
StepClockwise(DELAY_2ms);
}
}
else
{
for(j=16;j>0;j--)
{
StepAntiClockwise(DELAY_2ms);
}
}
state = state + 1;
if(state >= 40)
{
state = 0;
}
}
int main(void) {
WDTCTL = WDTPW | WDTHOLD; // Stop watchdog timer
WDTCTL = WDTPW + WDTHOLD; // Stop WDT
DCOCTL = 0; // Select lowest DCOx and MODx settings
BCSCTL1 = CALBC1_16MHZ; // Set DCO
BCSCTL2 = DIVS1; // SMCLK runs at 16/4 = 4Mhz
DCOCTL = CALDCO_16MHZ;
// configure uart
P2DIR |= 0xFF; // All P2.x outputs
P2OUT &= 0x00; // All P2.x reset
P1SEL |= RXD + TXD ; // P1.1 = RXD, P1.2=TXD
P1SEL2 |= RXD + TXD ; // P1.1 = RXD, P1.2=TXD
//P1DIR |= RXLED + TXLED;
P1OUT &= 0x00;
UCA0CTL1 |= UCSSEL_2; // SMCLK
// pin 1.0 as output led
P1DIR |= LED1;
P1SEL &= ~LED1;
// pin 1.5 interrupt signal data ready
P1DIR |= INTERRUPT_SIGNAL;
P1SEL &= ~INTERRUPT_SIGNAL;
P1SEL2 &= ~INTERRUPT_SIGNAL;
P1OUT &= ~INTERRUPT_SIGNAL;
// pin 1.4 as ultrasonic output trigger
P1DIR |= ULTRASONIC_OUTPUT;
P1OUT &= ~ULTRASONIC_OUTPUT;
P1SEL &= ~ULTRASONIC_OUTPUT;
P1SEL2 &= ~ULTRASONIC_OUTPUT;
// configure switch
P1DIR &= ~SWITCH;
P1OUT |= SWITCH;
P1SEL &= ~SWITCH;
P1SEL2 &= ~SWITCH;
P1REN |= SWITCH;
// pin 2.0 ultrasonic input
P2DIR &= ~ULTRASONIC_INPUT;
P2OUT &= ~ULTRASONIC_INPUT;
P2SEL |= ULTRASONIC_INPUT;
P2SEL2 &= ~ULTRASONIC_INPUT;
P2REN |= ULTRASONIC_INPUT;
// pin 2.1-2.4 stepper
P2DIR |= (STEPPER_orange | STEPPER_yellow | STEPPER_pink | STEPPER_blue);
P2OUT &= ~(STEPPER_orange | STEPPER_yellow | STEPPER_pink | STEPPER_blue);
P2REN &= ~(STEPPER_orange | STEPPER_yellow | STEPPER_pink | STEPPER_blue);
P2SEL2 &= ~(STEPPER_orange | STEPPER_yellow | STEPPER_pink | STEPPER_blue);
P2SEL2 &= ~(STEPPER_orange | STEPPER_yellow | STEPPER_pink | STEPPER_blue);
// uart
// set to 115200 baud, UCBRx = 8, UCBRSx = 0, UCBRFx=11, UCOS16 = 1
//UCA0BR0 = 8;
//UCA0BR1 = 0;
//UCA0MCTL = UCBRF3 | UCBRF1 | UCBRF0 | UCOS16; // UCBRSx = 0, UCBRFx=1, UCOS16 = 1
// set to 115200 baud, UCBRx = 2, UCBRSx = 3, UCBRFx=2, UCOS16 = 1
UCA0BR0 = 2;
UCA0BR1 = 0;
UCA0MCTL = UCBRS1 | UCBRS0 | UCBRF1 | UCOS16; // UCBRSx = 0, UCBRFx=1, UCOS16 = 1
UCA0CTL1 &= ~UCSWRST; // **Initialize USCI state machine**
// ultrasonic capture
TA1CTL = TASSEL_2 | MC_2 | ID_0; // SMCLK, up mode, clock running at 4MHz
TA1CCTL0 = CM_3 | CCIS_0 | SCS | CAP | OUTMOD_0 | CCIE ; // capture both, CCIxA, synchronised capture, capture mode SCCI
init_I2C();
// enable uart interrupts
UC0IE |= UCA0TXIE;
UC0IE |= UCA0RXIE; // Enable USCI_A0 RX interrupt
while (1)
{
// trigger
P1OUT |= ULTRASONIC_OUTPUT;
delayUnits(DELAY_100us);
P1OUT &= ~ULTRASONIC_OUTPUT;
delayUnits(DELAY_100ms);
__disable_interrupt();
i2c_tx_buffer[0] = (ultrasonic_period >> 8);
i2c_tx_buffer[1] = (ultrasonic_period & 0xFF);
i2c_tx_buffer[2] = state;
tx_buffer[0] = ConvertIntToAscii(ultrasonic_period, 12);
tx_buffer[1] = ConvertIntToAscii(ultrasonic_period, 8);
tx_buffer[2] = ConvertIntToAscii(ultrasonic_period, 4);
tx_buffer[3] = ConvertIntToAscii(ultrasonic_period, 0);
tx_buffer[5] = ConvertIntToAscii(state, 4);
tx_buffer[6] = ConvertIntToAscii(state, 0);
if(tx_buffer_i == 0)
UC0IE |= UCA0TXIE;
__enable_interrupt();
// signal data ready
P1OUT |= INTERRUPT_SIGNAL;
delayUnits(DELAY_1ms);
P1OUT &= ~INTERRUPT_SIGNAL;
ProcessState();
}
}
#pragma vector=USCIAB0TX_VECTOR
__interrupt void USCI0TX_ISR(void)
{
// uart
if(UC0IFG & UCA0TXIFG)
{
char txb = tx_buffer[tx_buffer_i++]; // TX next character
UCA0TXBUF = txb;
txb = tx_buffer[tx_buffer_i];
if (txb == 0) // TX over?
{
tx_buffer_i = 0;
UC0IE &= ~UCA0TXIE; // Disable USCI_A0 TX interrupt
}
}
// i2c
if (UC0IFG & UCB0TXIFG)
{
UCB0TXBUF = i2c_tx_buffer[i2c_rx_address];
i2c_rx_address++;
if(i2c_rx_address >= 3)
{
i2c_rx_address = 0;
}
}
if (UC0IFG & UCB0RXIFG)
{
char rx = UCB0RXBUF;
}
}
#pragma vector=USCIAB0RX_VECTOR
__interrupt void USCI0RX_ISR(void)
{
// uart
if(UC0IFG & UCA0RXIFG)
{
char rx_char = UCA0RXBUF;
//if (rx_char == 'r') // 'r' received?
//{
// tx_buffer_i = 0;
// UC0IE |= UCA0TXIE; // Enable USCI_A0 TX interrupt
//}
}
// i2c
if (UCB0STAT & UCSTTIFG)
{
i2c_rx_address = 0;
UCB0STAT &= ~UCSTTIFG;
}
if(UCB0STAT & UCSTPIFG)
{
UCB0STAT &= ~UCSTPIFG;
}
}
#pragma vector=TIMER1_A0_VECTOR
__interrupt void TIMER1_A0_ISR(void)
{
volatile unsigned int tacct = TA1CCTL0;
if(tacct & COV) // overflow
{
// just clear it
TA1CCTL0 &= ~COV;
}
if((tacct & CCI) != 0)
{
// start of ultrasound
ultrasonic_start = TA1CCR0;
P1OUT |= 1;
}
else
{
ultrasonic_period = TA1CCR0 - ultrasonic_start;
P1OUT &= ~1;
}
}
#pragma vector=TIMER0_A0_VECTOR
__interrupt void ta0_isr(void)
{
TACTL &= ~MC_3;
__bic_SR_register_on_exit(CPUOFF);
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Windows.Devices.Enumeration;
using Windows.Devices.I2c;
using Windows.Devices.Gpio;
namespace LaunchPadMsp430
{
public class SonarEventArgs : EventArgs
{
public double Range { get; set; }
public double Yaw { get; set; }
}
public partial class LaunchPadMsp430 : IDisposable
{
public event EventHandler<SonarEventArgs> SensorInterruptEvent;
#region Constants
public const byte ADDRESS = 0x48;
#endregion
private const Int32 INTERRUPT_PIN = 18; // GPIO_GEN1)
I2cDevice _launchPadDevice = null;
private GpioController IoController;
private GpioPin InterruptPin;
#region 12c
private byte ReadByte(byte regAddr)
{
byte[] buffer = new byte[1];
buffer[0] = regAddr;
byte[] value = new byte[1];
_launchPadDevice.WriteRead(buffer, value);
return value[0];
}
private byte[] ReadBytes(byte regAddr, int length)
{
byte[] values = new byte[length];
byte[] buffer = new byte[1];
buffer[0] = regAddr;
_launchPadDevice.WriteRead(buffer, values);
return values;
}
void WriteByte(byte regAddr, byte data)
{
byte[] buffer = new byte[2];
buffer[0] = regAddr;
buffer[1] = data;
_launchPadDevice.Write(buffer);
}
void writeBytes(byte regAddr, byte[] values)
{
byte[] buffer = new byte[1 + values.Length];
buffer[0] = regAddr;
Array.Copy(values, 0, buffer, 1, values.Length);
_launchPadDevice.Write(buffer);
}
#endregion
public async void InitHardware()
{
try
{
IoController = GpioController.GetDefault();
InterruptPin = IoController.OpenPin(INTERRUPT_PIN);
InterruptPin.Write(GpioPinValue.Low);
InterruptPin.SetDriveMode(GpioPinDriveMode.Input);
InterruptPin.ValueChanged += Interrupt;
string aqs = I2cDevice.GetDeviceSelector();
DeviceInformationCollection collection = await DeviceInformation.FindAllAsync(aqs);
I2cConnectionSettings settings = new I2cConnectionSettings(ADDRESS);
settings.BusSpeed = I2cBusSpeed.FastMode; // 400kHz clock
settings.SharingMode = I2cSharingMode.Exclusive;
_launchPadDevice = await I2cDevice.FromIdAsync(collection[0].Id, settings);
await Task.Delay(100); // wait power up sequence
}
catch (Exception ex)
{
string error = ex.ToString();
}
}
private void Interrupt(GpioPin sender, GpioPinValueChangedEventArgs args)
{
if (_launchPadDevice != null)
{
SonarEventArgs ea = new SonarEventArgs();
ea.Range = 0;
ea.Yaw = 0;
byte[] data = ReadBytes(0x5A, 3);
int raw = (data[0] << 8) + data[1];
double microSeconds = raw / 4;
ea.Range = microSeconds / 5800;
ea.Yaw = data[2] * 5.625/64;
if(SensorInterruptEvent != null)
{
SensorInterruptEvent(this, ea);
}
}
}
#region IDisposable Support
private bool disposedValue = false; // To detect redundant calls
protected virtual void Dispose(bool disposing)
{
if (!disposedValue)
{
InterruptPin.Dispose();
if (_launchPadDevice != null)
{
_launchPadDevice.Dispose();
_launchPadDevice = null;
}
disposedValue = true;
}
}
~LaunchPadMsp430()
{
// Do not change this code. Put cleanup code in Dispose(bool disposing) above.
Dispose(false);
}
// This code added to correctly implement the disposable pattern.
public void Dispose()
{
// Do not change this code. Put cleanup code in Dispose(bool disposing) above.
Dispose(true);
GC.SuppressFinalize(this);
}
#endregion
}
}
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices.WindowsRuntime;
using Windows.Foundation;
using Windows.Foundation.Collections;
using Windows.UI.Core;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Controls.Primitives;
using Windows.UI.Xaml.Data;
using Windows.UI.Xaml.Input;
using Windows.UI.Xaml.Media;
using Windows.UI.Xaml.Navigation;
// The Blank Page item template is documented at http://go.microsoft.com/fwlink/?LinkId=402352&clcid=0x409
namespace LaunchPadMsp430
{
/// <summary>
/// An empty page that can be used on its own or navigated to within a Frame.
/// </summary>
public sealed partial class MainPage : Page
{
LaunchPadMsp430 msp430 = new LaunchPadMsp430();
double[] value = new double[256];
int i = 0;
public MainPage()
{
this.InitializeComponent();
msp430.InitHardware();
msp430.SensorInterruptEvent += Msp430_SensorInterruptEvent;
}
private void Msp430_SensorInterruptEvent(object sender, SonarEventArgs e)
{
var task = Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () =>
{
textBlock.Text = String.Format("stats: {0} {1}", e.Range, e.Yaw);
value[i++] = e.Range;
if(i >= value.Length)
{
double mean = 0;
foreach (double v in value)
mean += v;
mean /= value.Length;
double sd = 0;
foreach (double v in value)
sd += (v - mean) * (v - mean);
sd /= value.Length;
sd = Math.Sqrt(sd);
i = 0;
}
});
}
}
}
Comments