Meghdoot07Moinak Ghosh
Created June 2, 2021 © GPL3+

RoboDoctor

A Robotic frontline warrior to limit medical personnel contact with COVID patients

AdvancedFull instructions providedOver 1 day140

Things used in this project

Story

Read more

Custom parts and enclosures

Initial Mechanical Design planned

Schematics

Circuit image for robot

Fritzing project for bot circuit

Code

Locomotion Code

C/C++
Code for the bot locomotion on the Arduino MKR1010 Wifi
#define motor1_en 10      // motor 1 enable pin 
#define motor2_en 11      // motor 2 enable pin 
#define motor1_dir1 4     // motor 1 input1 (InputA) 
#define motor1_dir2 5     // motor 1 input2 (InputA)
#define motor2_dir1 6     // motor 2 input1 (InputB)
#define motor2_dir2 7    // motor 2 input2  (InputB)

void setup() 
   {
      // put your setup code here, to run once:
       
      Serial.begin(9600);   //  start serial communication using 9600 baudrate 
      for(unsigned int i=4;i<8;i++)
         {
             pinMode(i,OUTPUT); // declaring input pins of motor1 and motor2 as a output pin 
         }
      pinMode(motor1_en,OUTPUT);  // declaring enable pins of motor as a output 
      pinMode(motor2_en,OUTPUT);
  //--------------------------timer setup
  noInterrupts();           // disable all interrupts
  TCCR1A = 0;
  TCCR1B = 0;
  timer1_counter = 59286;   // preload timer 65536-16MHz/256/2Hz (34286 for 0.5sec) (59286 for 0.1sec)

  
  TCNT1 = timer1_counter;   // preload timer
  TCCR1B |= (1 << CS12);    // 256 prescaler 
  TIMSK1 |= (1 << TOIE1);   // enable timer overflow interrupt
  interrupts();             // enable all interrupts
  //--------------------------timer setup
  
  while (!Serial) {
    ; // wait for serial port to connect. Needed for native USB port only
  }

  analogWrite(pin_pwm,0);   //stop motor
  digitalWrite(pin_fwd,0);  //stop motor
  digitalWrite(pin_bwd,0);  //stop motor
}
       
        
    }

void loop() 
     {
        if (GamePad.isUpPressed()) // if UP is pressed in the gamepad then move robot forward
          {
             Serial.print("UP");
             forward();
          }

        else if (GamePad.isDownPressed()) // if DOWN is pressed in the gamepad then move robot backward
          {
             Serial.print("DOWN");
             backward();
          }

        else  if (GamePad.isLeftPressed()) // if LEFT is pressed in the gamepad then move robot LEFT
           {
              Serial.print("Left");
              left();
           }

        else  if (GamePad.isRightPressed()) // if RIGHT is pressed in the gamepad then move robot RIGHT
           {
               Serial.print("Right");
               right();
           }

        else // stop the robot
           {
            Serial.println("strop");
            Stop();
           }


     }

     void forward()  // function for robot forward movement 
     {
        analogWrite(motor1_en,255);
        analogWrite(motor2_en,255);
        digitalWrite(motor1_dir1,HIGH);
        digitalWrite(motor1_dir2,LOW);
        digitalWrite(motor2_dir1,HIGH);
        digitalWrite(motor2_dir2,LOW);
      
     }
     void backward() // function for robot backward movement 
     {
        analogWrite(motor1_en,255);
        analogWrite(motor2_en,255);
        digitalWrite(motor1_dir1,LOW);
        digitalWrite(motor1_dir2,HIGH);
        digitalWrite(motor2_dir1,LOW);
        digitalWrite(motor2_dir2,HIGH);
      
     }
     void left() // function for robot left movement 
     {
        analogWrite(motor1_en,255);
        analogWrite(motor2_en,255);
        digitalWrite(motor1_dir1,LOW);
        digitalWrite(motor1_dir2,HIGH);
        digitalWrite(motor2_dir1,HIGH);
        digitalWrite(motor2_dir2,LOW);
      
     }
     void right() // function for robot right movement 
     {
        analogWrite(motor1_en,255);
        analogWrite(motor2_en,255);
        digitalWrite(motor1_dir1,HIGH);
        digitalWrite(motor1_dir2,LOW);
        digitalWrite(motor2_dir1,LOW);
        digitalWrite(motor2_dir2,HIGH);
      
     }
     void Stop() // // function for no movement
     {
        analogWrite(motor1_en,0);
        analogWrite(motor2_en,0);
        digitalWrite(motor1_dir1,LOW);
        digitalWrite(motor1_dir2,LOW);
        digitalWrite(motor2_dir1,LOW);
        digitalWrite(motor2_dir2,LOW);
      
     }
String mySt = "";
char myChar;
boolean stringComplete = false;  // whether the string is complete
boolean motor_start = false;
const byte pin_a = 2;   //for encoder pulse A
const byte pin_b = 3;   //for encoder pulse B
const byte pin_fwd = 4; //for H-bridge: run motor forward
const byte pin_bwd = 5; //for H-bridge: run motor backward
const byte pin_pwm = 6; //for H-bridge: motor speed
int encoder = 0;
int m_direction = 0;
int sv_speed = 100;     //this value is 0~255
double pv_speed = 0;
double set_speed = 0;
double e_speed = 0; //error of speed = set_speed - pv_speed
double e_speed_pre = 0;  //last error of speed
double e_speed_sum = 0;  //sum error of speed
double pwm_pulse = 0;     //this value is 0~255
double kp = 0;
double ki = 0;
double kd = 0;
int timer1_counter; //for timer
int i=0;

void loop() {
  if (stringComplete) {
    // clear the string when COM receiving is completed
    mySt = "";  //note: in code below, mySt will not become blank, mySt is blank until '\n' is received
    stringComplete = false;
  }

  //receive command from Visual Studio
  if (mySt.substring(0,8) == "vs_start"){
    digitalWrite(pin_fwd,1);      //run motor run forward
    digitalWrite(pin_bwd,0);
    motor_start = true;
  }
  if (mySt.substring(0,7) == "vs_stop"){
    digitalWrite(pin_fwd,0);
    digitalWrite(pin_bwd,0);      //stop motor
    motor_start = false;
  }
  if (mySt.substring(0,12) == "vs_set_speed"){
    set_speed = mySt.substring(12,mySt.length()).toFloat();  //get string after set_speed
  }
  if (mySt.substring(0,5) == "vs_kp"){
    kp = mySt.substring(5,mySt.length()).toFloat(); //get string after vs_kp
  }
  if (mySt.substring(0,5) == "vs_ki"){
    ki = mySt.substring(5,mySt.length()).toFloat(); //get string after vs_ki
  }
  if (mySt.substring(0,5) == "vs_kd"){
    kd = mySt.substring(5,mySt.length()).toFloat(); //get string after vs_kd
  }  
}

void detect_a() {
  encoder+=1; //increasing encoder at new pulse
  m_direction = digitalRead(pin_b); //read direction of motor
}
ISR(TIMER1_OVF_vect)        // interrupt service routine - tick every 0.1sec
{
  TCNT1 = timer1_counter;   // set timer
  pv_speed = 60.0*(encoder/200.0)/0.1;  //calculate motor speed, unit is rpm
  encoder=0;
  //print out speed
  if (Serial.available() <= 0) {
    Serial.print("speed");
    Serial.println(pv_speed);         //Print speed (rpm) value to Visual Studio
    }


  //PID program
  if (motor_start){
    e_speed = set_speed - pv_speed;
    pwm_pulse = e_speed*kp + e_speed_sum*ki + (e_speed - e_speed_pre)*kd;
    e_speed_pre = e_speed;  //save last (previous) error
    e_speed_sum += e_speed; //sum of error
    if (e_speed_sum >4000) e_speed_sum = 4000;
    if (e_speed_sum <-4000) e_speed_sum = -4000;
  }
  else{
    e_speed = 0;
    e_speed_pre = 0;
    e_speed_sum = 0;
    pwm_pulse = 0;
  }
  

  //update new speed
  if (pwm_pulse <255 & pwm_pulse >0){
    analogWrite(pin_pwm,pwm_pulse);  //set motor speed  
  }
  else{
    if (pwm_pulse>255){
      analogWrite(pin_pwm,255);
    }
    else{
      analogWrite(pin_pwm,0);
    }
  }
  
}
void serialEvent() {
  while (Serial.available()) {
    // get the new byte:
    char inChar = (char)Serial.read();
    // add it to the inputString:
    if (inChar != '\n') {
      mySt += inChar;
    }
    // if the incoming character is a newline, set a flag
    // so the main loop can do something about it:
    if (inChar == '\n') {
      stringComplete = true;
    }
  }
}

Sheet Data collector

JavaScript
Google Sheets script for getting values
/*
Created by : Meghdoot Ghosh 
following template provided on arduino.cc by marcopas
*/


// get active spreasheet
var ss = SpreadsheetApp.getActiveSheet().getSheetByName('Dr_Robo_Data');

// get sheet named Dr_Robo_Data
var sheet = ss.getSheetByName('Dr_Robo_Data');


var MAX_ROWS = 1440;     // max number of data rows to display
// 3600s / cloud_int(30s) * num_ore(12h)
var HEADER_ROW = 1;     // row index of header
var TIMESTAMP_COL = 1;  // column index of the timestamp column

function doPost(e) {  
  var cloudData = JSON.parse(e.postData.contents); // this is a json object containing all info coming from IoT Cloud
  //var webhook_id = cloudData.webhook_id; // really not using these three
  //var device_id = cloudData.device_id;
  //var thing_id = cloudData.thing_id;
  var values = cloudData.values; // this is an array of json objects
  
  // store names and values from the values array
  // just for simplicity
  var incLength = values.length;
  var incNames = [];
  var incValues = [];
  for (var i = 0; i < incLength; i++) {
    incNames[i] = values[i].name;
    incValues[i] = values[i].value;
  }
  
  // read timestamp of incoming message
  var timestamp = values[0].updated_at;          // format: yyyy-MM-ddTHH:mm:s
.mmmZ
  var date = new Date(Date.parse(timestamp)); 
  
  /*
  This if statement is due to the fact that duplicate messages arrive from the cloud!
  If that occurs, the timestamp is not read correctly and date variable gets compromised.
  Hence, execute the rest of the script if the year of the date is well defined and it is greater
  then 2018 (or any other year before)
  */
  if (date.getYear() > 2018) {
  
    // discard all messages that arrive 'late'
    if (sheet.getRange(HEADER_ROW+1, 1).getValue() != '') { // for the first time app is run
      var now = new Date(); // now
      var COMM_TIME = 5; // rough overestimate of communication time between cloud and app
      if (now.getTime() - date.getTime() > COMM_TIME * 1000) {
        return;
      }
    }
    
    // this section write property names 
    sheet.getRange(HEADER_ROW, 1).setValue('timestamp');
    for (var i = 0; i < incLength; i++) {
      var lastCol = sheet.getLastColumn(); // at the very beginning this should return 1 // second cycle -> it is 2
      if (lastCol == 1) {
        sheet.getRange(HEADER_ROW, lastCol + 1).s
etValue(incNames[i]);
      } else {
        // check if the name is already in header
        var found = 0;
        for (var col = 2; col <= lastCol; col++) {
          if (sheet.getRange(HEADER_ROW, col).getValue() == incNames[i]) {
            found = 1;
            break;
          }
        }
        if (found == 0) {
          sheet.getRange(HEADER_ROW, lastCol+1).setValue(incNames[i]);
        }
      }
    }
    
    // redefine last coloumn and last row since new names could have been added
    var lastCol = sheet.getLastColumn();
    var lastRow = sheet.getLastRow();
    
    // delete last row to maintain constant the total number of rows
    if (lastRow > MAX_ROWS + HEADER_ROW - 1) { 
      sheet.deleteRow(lastRow);
    }
    
    // insert new row after deleting the last one
    sheet.insertRowAfter(HEADER_ROW);
    
    // reset style of the new row, otherwise it will inherit the style of the header row
    var range = sheet.getRange('A2:Z2');
    //range.setBackground('#ffffff');
    range.setFontColor('#000000');
    range.setFontSize(10);
    range.setFontWeight('normal');
    
    // write the timestamp
    sheet.getRange(HEADER_ROW+1, TIMESTAMP_COL).setValue(date).setNumberFormat("yyyy-MM-dd HH:mm:ss");
    
    // write values in the respective columns
    for (var col = 1+TIMESTAMP_COL; col <= lastCol; col++) {
      // first copy previous values
      // this is to avoid empty cells if not all properties are updated at the same time
      sheet.getRange(HEADER_ROW+1, col).setValue(sheet.getRange(HEADER_ROW+2, col).getValue());
      for (var i = 0; i < incLength; i++) {
        var currentName = sheet.getRange(HEADER_ROW, col).getValue();
        if (currentName == incNames[i]) {
          // turn boolean values into 0/1, otherwise google sheets interprets them as labels in the graph
          if (incValues[i] == true) {
            incValues[i] = 1;
          } else if (incValues[i] == false) {
            incValues[i] = 0;
          }
          sheet.getRange(HEADER_ROW+1, col).setValue(incValues[i]);
        } 
      }
    }  
  
  } // end if (date.getYear() > 2018)
}

Wifi_secrets.h file

C Header File
For storing Wifi credentials
#define SECRET_SSID "RoboMob"
#define SECRET_PASS "iotrocks"

Thing`s properties

JSON
COde for linking Arduino IoT clioud with local code
#include <ArduinoIoTCloud.h>
#include <Arduino_ConnectionHandler.h>


const char THING_ID[] = "a88dcdb6-b155-44e6-a7e7-1e229309cc20";

const char SSID[]     = SECRET_SSID;    // Network SSID (name)
const char PASS[]     = SECRET_PASS;    // Network password (use for WPA, or use as key for WEP)


float wd_hum;
float wd_temp;
float batt_percent;

void initProperties(){

  ArduinoCloud.setThingId(THING_ID);
  ArduinoCloud.addProperty(wd_hum, READ, 30 * SECONDS, NULL);
  ArduinoCloud.addProperty(wd_temp, READ, 30 * SECONDS, NULL);
  ArduinoCloud.addProperty(batt_percent, READ, 30 * SECONDS, NULL);

}

WiFiConnectionHandler ArduinoIoTPreferredConnection(SSID, PASS);

Sketch main

JSON
{
  "cpu": {
    "fqbn": "arduino:samd:mkrwifi1010",
    "name": "Arduino MKR WiFi 1010",
    "type": "serial"
  },
  "secrets": [
    {
      "name": "SECRET_SSID",
      "value": ""
    },
    {
      "name": "SECRET_PASS",
      "value": ""
    }
  ],
  "included_libs": []
}

Final Code

Arduino
#include "arduino_secrets.h"
/* 
  Sketch generated by the Arduino IoT Cloud Thing "Global_Status"
  https://create.arduino.cc/cloud/things/a88dcdb6-b155-44e6-a7e7-1e229309cc20 

  Arduino IoT Cloud Properties description

  The following variables are automatically generated and updated when changes are made to the Thing properties

  float wd_hum;
  float wd_temp;
  float batt_percent;

  Properties which are marked as READ/WRITE in the Cloud Thing will also have functions
  which are called when their values are changed from the Dashboard.
  These functions are generated with the Thing and added at the end of this sketch.
*/

#include "thingProperties.h"


void setup() {
  // Initialize serial and wait for port to open:
  Serial.begin(9600);
  // This delay gives the chance to wait for a Serial Monitor without blocking if none is found
  delay(1500); 
  batt_percent=93;
  // Defined in thingProperties.h
  initProperties();

  // Connect to Arduino IoT Cloud
  ArduinoCloud.begin(ArduinoIoTPreferredConnection);
  
  /*
     The following function allows you to obtain more information
     related to the state of network and IoT Cloud connection and errors
     the higher number the more granular information youll get.
     The default is 0 (only errors).
     Maximum is 4
 */
  setDebugMessageLevel(2);
  ArduinoCloud.printDebugInfo();
}

void loop() {
  wd_temp = random(24,28);
  wd_hum = random(55,62);
  batt_percent = batt_percent - 0.023;
  ArduinoCloud.update();
  // Your code here 
  
  
}

Credits

Meghdoot07

Meghdoot07

8 projects • 16 followers
IoT developer with interest in robotics, automation and electric vehicles and specialising in FPGA based vision applications at the edge
Moinak Ghosh

Moinak Ghosh

11 projects • 38 followers
IoT enthusiast experienced with autonomous drones and vehicles, FPGA-based vision applications, MCU programming/Embedded/ML/AI

Comments