Hackster is hosting Hackster Holidays, Ep. 4: Livestream & Giveaway Drawing. Start streaming on Wednesday!Stream Hackster Holidays, Ep. 4 on Wednesday!
Novachris
Published © CC BY-NC-SA

Portal 2 Turret - Master Turret Control of Portal 2 Turrets

The Master Turret Control for controlling multiple Portal turrets per my other build.

AdvancedFull instructions provided4,250
Portal 2 Turret - Master Turret Control of Portal 2 Turrets

Things used in this project

Hardware components

Arduino Nano R3
Arduino Nano R3
×1

Hand tools and fabrication machines

3D Printer (generic)
3D Printer (generic)

Story

Read more

Custom parts and enclosures

Controller+-+Bottom+v1.stl

Controller+-+Top+Plate+Yellow+on+top.stl

Controller+-+Screen+strap.stl

Controller+-+Top+Plate+Yellow+on+bottom.stl

Controller parts

The top plate ( 2 versions), bottom housing and little strap for LCD screen.

Schematics

Wiring Schematic - MTC

The wiring schematic I made to build the MTC

Code

MTC code

C/C++
My code for the MTC
/* Master Turret Control - Actual
 *  Chris Nowak - MAR 2019
 * This is the handheld MTC module.  
 *  https://www.instructables.com/member/ChrisN219/
 *  https://create.arduino.cc/projecthub/Novachris/talking-portal-2-turret-gun-637bf3
 * 
 *  This code includes all the features needed to control up to three turrets,
 *  perform the Cara Mia Opera, Chat Time and Manual mode.
 *  The turrets are featured on another build.  The turret
 *  will operate autonomously using this code without the MTC.
 *  
 *  The code is built to work with three turrets, but each will operate independently.
 *  Lots of debugging code left in.  Use or clean up as desired.  It's mostly debugged,
 *  but isn't perfect.  Given the price you paid, I figure that's ok! ;)
*/

#include <RF24.h>
#include <RF24Network.h>
#include <SPI.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>

Adafruit_SSD1306 display(4);
#define LOGO32_GLCD_HEIGHT 32 
#define LOGO32_GLCD_WIDTH  32 
static const unsigned char PROGMEM logo32_glcd_bmp[] =
{ B00000000, B00000001, B10000000, B00000000,
  B00000000, B00000110, B01100000, B00000000,
  B00000000, B00011000, B00011000, B00000000,
  B00000000, B00010000, B00001000, B00000000,
  B00000000, B00110000, B00001100, B00000000,
  B00001100, B00001000, B00010000, B00011000,
  B00110000, B00011000, B00011000, B00001100,
  B01110000, B00010001, B10001000, B00001110,
  B01100000, B00110011, B11001100, B00000110,
  B01100000, B00100011, B11000100, B00000110,
  B11000000, B00110001, B10001100, B00000011,
  B11000000, B00100000, B00000100, B00000011,
  B11000000, B00110000, B00001100, B00000011,
  B11000000, B00010000, B00001000, B00000011,
  B01100000, B00010000, B00001000, B00000110,
  B00110000, B00001100, B00110000, B00001100,

  B00011000, B00000011, B11000000, B00011000,
  B00000110, B00001101, B10110000, B01100000,
  B00000000, B00110001, B10001100, B00000000,
  B00000000, B11000001, B10000011, B00000000,
  B00000001, B10000001, B10000001, B10000000,
  B00000110, B00000001, B10000000, B01100000,
  B00011000, B00000001, B10000000, B00011000,
  B00110000, B00000001, B10000000, B00001100,
  B01100000, B00000001, B10000000, B00000110,
  B01100000, B00000001, B10000000, B00000110,
  B11000000, B00000001, B10000000, B00000011,
  B11000000, B00000001, B10000000, B00000011,
  B11000000, B00000001, B10000000, B00000011,
  B11000000, B00000001, B10000000, B00000011,
  B11000000, B00000001, B10000000, B00000011,
  B11000000, B00000000, B00000100, B00000011 };

RF24 radio(10, 9);                // nRF24L01 (CE,CSN)
RF24Network network(radio);
const uint64_t this_node = 00;    // 
const uint64_t RED = 01;       // turret #1 - Red Turret
const uint64_t WHT = 02;       // turret #2 - White Turret
const uint64_t BLU = 03;       // turret #3 - Blue Turret 

byte pb1 = A2; // Select (GRN) "X"
byte pb2 = A1; // Select 2 (WHT) "T"
byte pb3 = A0; // Break (Red) "<"
byte joyX=A6;
byte joyY=A7;
byte joyButton = A3; // joystick press

byte ledManual = 5; // LED for signalling Manual Mode (BLUE)
byte ledNotReady = 6; // LED for signalling that turrets busy (RED)
byte ledOkToGo = 7; // LED signalling ready for action (GREEN)

byte power = 8; // Through a pinout to show Nano powered and working
int goState; // Aria =1, Chatty = 2, Manual = 3
int pb1State;
int pb2State;
int pb3State;
byte joyButtonState;
byte busyWhite = 0;
byte busyRed = 0;
byte busyBlue = 0;
byte notReady;
byte DataRead;
int x=0;
int y=0;
int z=0;
byte MainBusy = 1;
byte MainAria = 0;
byte MainChat = 0;
byte MainManual = 0;
byte MenuSelection = 0;
int Rx = 0;
int Wx = 0;
int Bx = 0;
byte RedHere = 0;
byte WhiteHere = 0;
byte BlueHere = 0;
byte selectTurret;

/////////////////////////////////////////////////////////////////// 
//============================ SETUP ============================//
///////////////////////////////////////////////////////////////////
void setup() {
  Serial.begin(9600);//
  SPI.begin();
  radio.begin();
  radio.setPALevel(RF24_PA_LOW);
  radio.setDataRate(RF24_2MBPS);
  radio.enableAckPayload();
  network.begin(70, this_node);  //(channel, node address)
  pinMode(pb1, INPUT_PULLUP);
  pinMode(pb2, INPUT_PULLUP);
  pinMode(pb3, INPUT_PULLUP);
  pinMode(joyButton, INPUT_PULLUP);
  pinMode(ledManual, OUTPUT);
  pinMode(ledNotReady, OUTPUT);
  pinMode(ledOkToGo, OUTPUT);
  pinMode(power, OUTPUT);
  radio.printDetails();
  digitalWrite (power, HIGH);
  display.begin(SSD1306_SWITCHCAPVCC, 0x3C); // initialize with the I2C addr 
  display.clearDisplay(); 
  display.display();
  display.setRotation(2); // yellow band on bottom. 0 is for yellow on top
  Splash();
  delay (5000); // Display spash screen for 5 seconds.
  display.clearDisplay();
  MenuSelection = 1;
  for (int i=0; i <= 20; i++){ // quick net check to see which turrets around
    ReadNet();
    TurretCheck();
  }
}
/////////////////////////////////////////////////////////////////// 
//========================= MAIN LOOP ===========================//
///////////////////////////////////////////////////////////////////
void loop() { 
  //=====  No turrets connected =====//
  while ((RedHere == 0) && (WhiteHere == 0) && (BlueHere == 0)){
    ReadNet();
    display.setTextColor(WHITE);
    display.setTextSize(0);
    display.setCursor(20,0); display.print("No Turrets!");
    display.setCursor(0,8); display.print("Check to make sure ");
    display.setCursor(0,16); display.print("Turrets powered up");
    display.setCursor(0,24); display.print("and ready to go...");
    display.display();
    display.clearDisplay ();
  }

  //=====  Turret Busy somewhere =====//
  while ((busyWhite == 1) || (busyRed == 1) || (busyBlue == 1)) {
    BusyScreen();
    ReadNet();
    digitalWrite (ledOkToGo, LOW);
    digitalWrite (ledNotReady, HIGH);
  }
  //=====  Read inputs  =====//
  x=analogRead(joyX);
  y=analogRead(joyY);
  pb1State = digitalRead(pb1); // "X"
  pb2State = digitalRead(pb2); // "T"
  pb3State = digitalRead(pb3); // "<"
  joyButtonState = digitalRead(joyButton); // "."
  
  //=====  Main Menu loop - Wait for input or Turret to get busy =====//
  if ((busyWhite == 0) && (busyRed == 0) && (busyBlue == 0)) {
    digitalWrite (ledOkToGo, HIGH);
    digitalWrite (ledNotReady, LOW);
    ReadNet();
    WriteNet();
    goState = 0;
    MainBusy = 0;
  }
  //=====  Use Joystick for menu selection  =====//
  if (MainBusy == 0) {
    if (y < 300) {--MenuSelection;}
    if (y > 700) {MenuSelection++;}
    if (MenuSelection > 3) {MenuSelection = 1;}
    if (MenuSelection < 1) {MenuSelection = 3;}
    MainMenu();
    }
  //=====  Check turret status  =====//
  if (pb2State == 0) {
    do {
      TurretCheck(); 
      pb3State = digitalRead(pb3);
      WriteNet();
      ReadNet();
      Serial.println();   
    }
    while (pb3State != 0); // press '<' button to exit status screen
  }
  //=====  Go to ARIA Mode  =====//
  if (((pb1State == 0)||(joyButtonState == 0)) && (MenuSelection == 1)){
    MainBusy = 1;
    goState = 1;
    digitalWrite (ledOkToGo, LOW);
    digitalWrite (ledNotReady, HIGH);
    AriaMode();
  }  
  //=====  Go to CHAT Mode  =====//
  if (((pb1State == 0)||(joyButtonState == 0)) && (MenuSelection == 2)) {
    MainBusy = 1; 
    goState = 2;
    digitalWrite (ledOkToGo, LOW);
    digitalWrite (ledNotReady, LOW);
    chatMode(); 
  }
  //=====  Go to MANUAL CONTROL Mode  =====//
  if (((pb1State == 0)||(joyButtonState == 0)) && (MenuSelection == 3)) {
    MainBusy = 1; 
    goState = 3;
    digitalWrite (ledOkToGo, LOW);
    digitalWrite (ledNotReady, LOW);
    digitalWrite (ledManual, HIGH);
    manualControl();
    } 
  else {
    digitalWrite(ledManual, LOW);
  }
  //====== Debugging Monitor ========//
  Serial.println();
  Serial.print(F("-MTC-  "));
  Serial.print(F("goState: "));Serial.print (goState); Serial.print ("   ");
/*  Serial.print(F("White :"));Serial.print (busyWhite); Serial.print ("   ");
  Serial.print(F("Red :"));Serial.print (busyRed); Serial.print ("   ");
  Serial.print(F("Blue :"));Serial.print (busyBlue); Serial.print ("   ");
  Serial.print(F("RedHr :"));Serial.print (RedHere); Serial.print ("   ");
  Serial.print(F("WhtHr :"));Serial.print (WhiteHere); Serial.print ("   ");
  Serial.print(F("BluHr :"));Serial.print (BlueHere); Serial.print ("   ");
  Serial.print(F("x :"));Serial.print (x); Serial.print ("\t");
  Serial.print(F("y :"));Serial.print (y); Serial.print ("\t");
*/
  Serial.print(F("pb1 :"));Serial.print (pb1State); Serial.print ("   ");
  Serial.print(F("pb2 :"));Serial.print (pb2State); Serial.print ("   ");
  Serial.print(F("pb3 :"));Serial.print (pb3State); Serial.print ("   ");
  Serial.print(F("jy4 :"));Serial.print (joyButtonState); Serial.print ("   ");
}
/////////////////////////////////////////////////////////////////// 
//======================= DISPLAY SCREENS =======================//
///////////////////////////////////////////////////////////////////

//------------- Opening Splash Screen ---------------//
void Splash (){
  display.clearDisplay();
  display.setTextColor(WHITE);
  display.drawBitmap(0, 0,  logo32_glcd_bmp, 32, 32, 1);
  display.drawBitmap(46, 0,  logo32_glcd_bmp, 32, 32, 1);
  display.drawBitmap(92, 0,  logo32_glcd_bmp, 32, 32, 1);
  display.fillRect(0,24,124,8, BLACK);
  display.setCursor(4,24); display.print("== Turret Control ==");
  display.display();
}
//------------- Main Menu ---------------//
void MainMenu () { // display menu
  display.setTextSize(0); // display text on screen
  display.setTextColor(WHITE);
  if (MenuSelection == 1) {
    display.setTextSize(0);
    display.setCursor(0,24); display.print("----- SELECTION -----");
    display.setCursor(24,0); display.print(" == ARIA ==");
    display.setCursor(24,8); display.print("    Chat");
    display.setCursor(24,16); display.print("   Manual");}
  if (MenuSelection == 2) {
    display.setCursor(0,24); display.print("----- SELECTION -----");
    display.setCursor(24,0); display.print("    Aria");
    display.setCursor(24,8); display.print(" == CHAT ==");
    display.setCursor(24,16); display.print("   Manual");}
  if (MenuSelection == 3) {
    display.setCursor(0,24); display.print("----- SELECTION -----");
    display.setCursor(24,0); display.print("    Aria");
    display.setCursor(24,8); display.print("    Chat");
    display.setCursor(24,16); display.print("== MANUAL ==");}
  display.display();
  display.clearDisplay ();
}
//------------- Busy Screen ---------------//
void BusyScreen () { // display menu
  display.setTextSize(0); // display text on screen
  display.setTextColor(WHITE);
  display.setTextSize(0);
  display.setCursor(20,24); display.print("BUSY RIGHT NOW");
  display.setCursor(0,0); display.print("Sorry, I'm busy ");
  display.setCursor(0,8); display.print("right now, dealing");
  display.setCursor(0,16); display.print("with you people...");
  display.display();
  display.clearDisplay ();
}

//-------- Turret Check Screen ---------------//
void TurretCheck() { 
    display.setTextColor(WHITE);
    display.setTextSize(0);
    display.setCursor(26,24); display.print("Turret Status");
    
    display.setCursor(0,0); display.print("Red: ");
    if (busyRed == 1) {display.setCursor(38,0); display.print ("Busy");}
    else if (RedHere == 1) {display.setCursor(38,0); display.print ("Ready");}
    else {display.setCursor(38,0); display.print ("not available");} 
     
    display.setCursor(0,8); display.print("White: ");
    if (busyWhite == 1) {display.setCursor(38,8); display.print ("Busy");}
    else if (WhiteHere == 1) {display.setCursor(38,8); display.print ("Ready");}
    else {display.setCursor(38,8); display.print ("not available");} 

    display.setCursor(0,16); display.print("Blue: ");
    if (busyBlue == 1) {display.setCursor(38,16); display.print ("Busy");}
    else if (BlueHere == 1) {display.setCursor(38,16); display.print ("Ready");}
    else {display.setCursor(38,16); display.print ("not available");} 
    
    display.display();
    display.clearDisplay ();
}
/////////////////////////////////////////////////////////////////// 
//======================= ARIA MODE =============================//
///////////////////////////////////////////////////////////////////
void AriaMode () { 
  for (int i=0; i <= 10; i++){ // Make sure the turrets get the message.
    WriteNet();
   }
  MenuSelection = 1;
  int i=0;
  goState = 0;
  do {
    i++;
    display.setTextSize(2); // display text on screen
    display.setTextColor(WHITE);
    display.setCursor(8,8); display.print("ARIA MODE");
    display.fillRect(0,24,120,4, WHITE); // 
    display.fillRect(0+(i),2,40,4, WHITE);;
    display.display();
    display.clearDisplay ();
    pb3State = digitalRead(pb3);
    ReadNet();
    WriteNet();
    if (i > 80) (i = 0);
    if ((busyWhite == 0) && (busyRed == 0) && (busyBlue == 0)) {
      return;
    }
  }
  while (pb3State != 0); 
  for (int i=0; i <= 10; i++){
    WriteNet();
  }
}
/////////////////////////////////////////////////////////////////// 
//======================= CHAT MODE =============================//
///////////////////////////////////////////////////////////////////
void chatMode() {
   for (int i=0; i <= 10; i++){ // Make sure the turrets get the message.
    WriteNet();
  }
  Serial.println();
  digitalWrite (ledOkToGo, LOW);
  digitalWrite (ledNotReady, HIGH);
  MenuSelection = 2;
  int i=0;
  goState = 0;
  do {
    WriteNet();
    ReadNet();
    Serial.println();
    i++;
    display.setTextSize(2); // display text on screen
    display.setTextColor(WHITE);
    display.setCursor(8,8); display.print("CHAT MODE");
    display.fillRect(0,24,120,4, WHITE); // 
    display.fillRect(0+i,2,40,4, WHITE);
    display.display();
    display.clearDisplay ();
    pb3State = digitalRead(pb3);
    if (i > 80) (i = 0);
    if ((busyWhite == 0) && (busyRed == 0) && (busyBlue == 0)) {
      return;
    }
  }
  while (pb3State != 0);
  for (int i=0; i <= 10; i++){
    WriteNet();
  }
}
/////////////////////////////////////////////////////////////////// 
//======================= MANUAL CONTROL ========================//
///////////////////////////////////////////////////////////////////
void manualControl() {
  WriteNet();
  Serial.println();
  selectTurret = 2;
  MenuSelection = 3;
  display.setTextSize(2); // display text on screen
  display.setTextColor(WHITE);
  display.setCursor(0,8); display.print("MANUAL CTL");
  display.fillRect(0,24,128,4, WHITE); // 
  display.fillRect(0,2,128,4, WHITE);;
  display.display();
  do {
    x=analogRead(joyX);
    y=analogRead(joyY);
    pb1State = digitalRead(pb1); // Fire (GRN)
    pb2State = digitalRead(pb2); // Say random comment (WHT)
    pb3State = digitalRead(pb3); // Break (RED)
    WriteNet();
    Serial.println();
  }
  while (pb3State != 0);
  goState = 0;
  display.clearDisplay ();
  ReadNet();
  for (int i=0; i <= 10; i++){
    WriteNet();
  }
  Serial.println();
}

/////////////////////////////////////////////////////////////////// 
//========================= RECEIVING ===========================//
///////////////////////////////////////////////////////////////////
void ReadNet(){
  network.update();
  for (int i=0; i <= 10; i++){
    if ( network.available() )  {
      RF24NetworkHeader header;
      network.peek(header);
      network.read(header, &DataRead, sizeof(DataRead));
      if (header.from_node == 01){  // get data from RED
        busyRed = DataRead; 
        RedHere = 1;} // RED is recognized
      if (header.from_node == 02){
        busyWhite = DataRead;
        WhiteHere = 1;}      
      if (header.from_node == 03){
        busyBlue = DataRead;
        BlueHere = 1;}   
    } 
  }
}
/////////////////////////////////////////////////////////////////// 
//========================= SENDING ===========================//
///////////////////////////////////////////////////////////////////
void WriteNet(){
  network.update();
  int payload [] = {x, y, pb1State, pb2State, pb3State, goState}; 

  RF24NetworkHeader header14(RED);
  bool ok14 = network.write(header14, &payload, sizeof(payload));
  if (ok14) {
    Rx = 0;
    Serial.print("RED ok14   ");}
  else {
    Rx++;
    Serial.print("--------   ");}
     
  RF24NetworkHeader header16(WHT);
  bool ok16 = network.write(header16, &payload, sizeof(payload));
  if (ok16) {
    Wx = 0;
    Serial.print("WHT ok16   ");}
  else {
    Wx++;
    Serial.print("--------   ");}

  RF24NetworkHeader header18(BLU);
  bool ok18 = network.write(header18, &payload, sizeof(payload));
  if (ok18) {
    Bx = 0;
    Serial.print("BLU ok18   ");}
  else {
    Bx++;
    Serial.print("--------   ");}
    
  if (Rx >= 10){RedHere = 0;}  // to check if communication lost and record it.
  else {RedHere = 1;}
  if (Wx >= 10){WhiteHere = 0;}
  else WhiteHere = 1;
  if (Bx >= 10){BlueHere = 0;}
  else {BlueHere = 1;}
  
   Serial.print(F("Rx :"));Serial.print (Rx); Serial.print ("   ");
   Serial.print(F("RedHere :"));Serial.print (RedHere); Serial.print ("   ");
   Serial.print(F("Wx :"));Serial.print (Wx); Serial.print ("   ");
   Serial.print(F("WhiteHere :"));Serial.print (WhiteHere); Serial.print ("   ");
   Serial.print(F("Bx :"));Serial.print (Bx); Serial.print ("   ");
   Serial.print(F("BlueHere :"));Serial.print (BlueHere); Serial.print ("   ");
}

Credits

Novachris

Novachris

0 projects • 18 followers

Comments