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; //; // 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] =;     } 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.


Popular posts from this blog

Arduino Regular Expressions

Unicode output from an Arduino.

ESP32 Camera