Sonar Scanner Part 2
The second iteration of the sonar scanner (part 1 is here) has seen a switch
to an Arduino Nano as the target board. This required soldering some pins to a
piece of strip-board to allow multiple devices to take power from the limited
5v output available on the Nano. This allowed some simplification to the “rats
nest” and the set-up as a “chip on
the old block” as a precursor to any installation. The current layout made
testing the revised software somewhat easier.
Here is a rather thrown together diagram of the connections.
Not up to publication standards but hopefully clear enough for anyone
interested.
The Arduino sonar scanner support software is now
transformed into a state machine where each “task” set by a controlling
processor over an I2C serial connection switches between states. The
controlling (master) board can also request data from the Nano with the data
returned being dependent upon the current state.
The idea is that it should be straightforward to introduce
new options (as states) as and when the requirement develops. The basic
principal clearly works with state switching being managed from an external
device.
The draft code
The Nano code has a similar set of constants and pins set up
as the initial trial version. The pin numbers have been changed to accommodate the
nano layout. A set of state function prototypes are declared and then gathered
up into an array of function pointers. Switching between states just required
running the appropriate function in this array. The setup() function starts the
I2C serial interface and then sets a timer to allow the DHT11 module to stabilise
before setting the speed of sound value.
The loop() function simply calls the relevant function based
upon the current value of the command struct. Each of the state functions runs
continuously until a global bool (changeState) indicates that they should exit.
This returns control to the loop() function that simply calls the next state
based upon the new value in the struct commandC.
Each state does what it does.
State1 currently takes distance measurements in a single
direction using the ultrasonic module every half a second until told to stop.
State2 scans an arc, reading and storing the distances
measured.
State3 takes distance measurements in a set direction
relative to forward but will trigger an alarm if the distance is less than or
equal to a defined value. The alarm is triggered by pulling a digital pin LOW.
This change can be detected by an interrupt on another board.
Further states have been outlined but not yet implemented.
The current code also implements a response to a request for
information arriving over the I2C link. If State1 is current then the last
measured distance value is returned over the I2C interface.
#include <Servo.h>
#include <Wire.h>
#include "DHT.h"
#define SERVOFORWARD 80
#define SWEEPINTERVAL 16
#define DHTTYPE DHT11
#define I2C_ADDRESS 42
#define ALARM_WAIT 500
template<class T> inline Print &operator <<(Print &obj, T arg) { obj.print(arg); return obj; }
Servo mServo;
const uint8_t tPin = 7;
const uint8_t rPin = 8;
const uint8_t dhtPin = 4;
const uint8_t servoPin = 5;
const uint8_t sclPin = A5;
const uint8_t sdaPin = A4;
const uint8_t alarmPin = 9;
volatile float soundSpeed = 29.1; //331.3;
uint32_t curMillis, lastMillis;
DHT dht(dhtPin, DHTTYPE);
uint8_t dMap[180];
volatile bool changeState = false;
//int lastTest, testCount;
void state1();
void state2();
void state3();
void (*states[])(void) = {state1, state2, state3};
typedef struct {
uint8_t command;
int8_t angle;
int8_t rel;
uint8_t distance;
}Command;
volatile Command activeC = {0,0,0,0};
void setup() {
Serial.begin(115200); // for test/debug only
for(uint8_t i = 0; i < 180; i++) {
dMap[i] = 0;
}
Wire.begin(I2C_ADDRESS);
Wire.onReceive(receiveData);
Wire.onRequest(sendData);
pinMode(tPin, OUTPUT);
pinMode(rPin, INPUT);
pinMode(alarmPin, OUTPUT);
digitalWrite(alarmPin, HIGH);
setTimer();
mServo.attach(servoPin);
}
// State functions
void state1() {
// read distances in set direction every half second
Serial << "Entering state 1\n";
changeState = false;
uint8_t angle = SERVOFORWARD + activeC.angle;
if (angle < 0){
angle = 0;
} else if(angle > 180) {
angle = 180;
}
mServo.write(angle); // point the servo
while (!changeState){
dMap[angle] = getDistance();
curMillis = lastMillis = millis();
while(curMillis - lastMillis < 500 && !changeState) {
curMillis = millis();
}
}
}
void state2() {
// scan angle either side of a relative angle to forward.
// ?? should relative be confined to a range???
Serial << "Entering state 2\n";
changeState = false;
uint8_t angle = activeC.angle;
uint8_t relative = activeC.rel + SERVOFORWARD;
if((relative + angle) > 180) {
activeC.angle = angle = 180 - relative;
} else if((relative - angle) < 0) {
activeC.angle = angle = relative;
}
int16_t pos = relative; //mServo.read(); // read the start position
mServo.write(pos);
int16_t lmt;
memset(&dMap[relative - angle], '\0', angle * 2);
while (!changeState){
for(lmt = relative + angle; pos <= lmt; pos++) {
mServo.write(pos);
lastMillis = curMillis;
while(curMillis - lastMillis < SWEEPINTERVAL) {
curMillis = millis();
}
dMap[pos] = getDistance();
if(changeState) {return;}
}
for(lmt = relative - angle; pos >= lmt; pos--) {
mServo.write(pos);
lastMillis = curMillis;
while(curMillis - lastMillis < SWEEPINTERVAL) {
curMillis = millis();
}
dMap[pos] = getDistance();
if(changeState) {return;}
}
}
}
void state3() {
// alarm on distance < x on set relative bearing
// question - should alarms repeat (can be managed by receiver
// if alarm state should we re-measure before firing alarm?
Serial << "Entering state 3\n";
changeState = false;
uint8_t range = activeC.distance;
uint32_t alarmMillis = 0;
uint8_t angle = SERVOFORWARD + activeC.angle;
if (angle < 0){
angle = 0;
} else if(angle > 180) {
angle = 180;
}
mServo.write(angle); // point the servo
while (!changeState){
dMap[angle] = getDistance();
if(dMap[angle] <= range) {
digitalWrite(alarmPin, LOW);
delayMicroseconds(10);
digitalWrite(alarmPin, HIGH);
}
curMillis = lastMillis = millis();
while(curMillis - lastMillis < 500 && !changeState) {
curMillis = millis();
}
}
}
void state4() {
// scan but alarm at distance < x
changeState = false;
}
void state5() {
//scan but alarm at forward distance < x only
}
void state6() {
// alarm on distance >= x on set relative bearing (backing up)
changeState = false;
}
// I2C interrupt handlers
void receiveData(int byteCount) {
// new command so state change event
uint8_t buff[4];
for(uint8_t b = 0; b < 4; b++) {
if(Wire.available()) {
buff[b] = Wire.read();
} else {
// diagnostic only
Serial << "byte not available\n";
}
}
activeC.command = buff[0];
activeC.angle = buff[1];
activeC.rel = buff[2];
activeC.distance = buff[3];
Serial << (int)activeC.command << ", " << (int)activeC.angle << ", "
<< (int)activeC.rel << ", " << (int)activeC.distance << '\n';
changeState = true;
}
void sendData() {
// what gets sent is based upon current state
// ** remember Wire library has a 32 byte buffer which limits things **
switch (activeC.command) {
case 0:
uint8_t range = dMap[SERVOFORWARD + activeC.angle];
Wire.write(range);
break;
}
}
// service functions
int16_t getDistance() {
// Send sound pulse
digitalWrite(tPin, LOW);
delayMicroseconds(2);
digitalWrite(tPin, HIGH);
delayMicroseconds(10);
digitalWrite(tPin, LOW);
int32_t pulseWidth = pulseIn(rPin, HIGH);
return (int16_t)(pulseWidth/soundSpeed) / 2; // nearest cm
}
//set timer to read tempetature
void setTimer(){
// set time1 overflow to avoid clash with
// Servo.h library vector
noInterrupts();
TCCR1A = 0;
TCCR1B = 0;
TCNT1 = 3036;
TCCR1B |= (1 << (CS12 & CS10));
TIMSK1 |= (1 << TOIE1);
interrupts();
}
ISR(TIMER1_OVF_vect) {
float t = dht.readTemperature();
if(t >= 0 && t <= 50) {
// t is in valid range
soundSpeed = 10000 / (331.3 + t * 0.606); // adjusted time for 1K x 10
}
TIMSK1 ^= (1 << TOIE1);
}
void loop() {
states[activeC.command](); // starts at default
}
The sonar scanner project is now beginning to look like a plug and play component for a larger project - which is the intention.Test Master
The test Master device is an Arduino Uno. It has connections
for the I2C interface and one for any incoming alarm interrupt signal. The test
code here is basic and rather contrived.
I should probably have added an earth connection
between the two devices but as both are running from the same USB hub while I
develop the code I think that was OK to be omitted. The target project will implement
a common earth for all connected devices.
Both the Sonar Scanner code and the test Master code send diagnostic
output over the USB serial link. I made use of PuTTY to connect to the Arduino
Uno during testing and left the Arduino IDE serial monitor connected to the
Arduino Nano.
My book "Practical Arduino C" has all the details on programming support for I2C interfaces, timers and servos.
My book "Practical Arduino C" has all the details on programming support for I2C interfaces, timers and servos.
Comments
Post a Comment