Page 1 of 1

Alternatives to Streamdeck for OBS

Posted: Fri Mar 26, 2021 12:43 am
by M0PIT
Having seen the article ‘Alternatives to Streamdeck’ in CQ-TV271, I’ve realised there maybe interest in a lockdown project of mine. It uses an Arduino UNO and a MCUfriend 3.5” TFT LCD screen to give a 15 LCD key keypad to interface with OBS. All the parts I had to hand, but it shouldn’t cost more than £20 for the TFT and the Arduino UNO (16U2).

tft.png
tft.png (95.43 KiB) Viewed 7053 times

I’m no programmer, so my code may not be perfect, but it works for me.

--------------Copy and Paste Code into IDE-------------------------------------------------------

/*
*
*
* OBS-15-Keypad-V1-0.ino
*
* 15 keypad application base, by Phil Hayes M0PIT 2021.
*
* License: GNU General Public License Version 3.0.
*
* Copyright (C) 2020-2021. All rights reserved.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see: http://www.gnu.org/licenses/
*
*
*/


#if 1

#include <Adafruit_GFX.h>
#include <MCUFRIEND_kbv.h>
MCUFRIEND_kbv tft;
#include <TouchScreen.h>
#include <Keyboard.h>

#define MINPRESSURE 200
#define MAXPRESSURE 1000

// ALL Touch panels and wiring is DIFFERENT
// copy-paste results from TouchScreen_Calibr_native.ino

//******* Two lines modified for calibration
const int XP = 7, XM = A1, YP = A2, YM = 6; //ID=0x9341
const int TS_LEFT = 131, TS_RT = 914, TS_TOP = 95, TS_BOT = 936;
//******** End of calibation

TouchScreen ts = TouchScreen(XP, YP, XM, YM, 300);

Adafruit_GFX_Button SW1_btn, SW2_btn, SW3_btn, SW4_btn, SW5_btn, SW6_btn, SW7_btn, SW8_btn, SW9_btn, SW10_btn, SW11_btn, SW12_btn, SW13_btn, SW14_btn, SW15_btn;

int pixel_x, pixel_y; //Touch_getXY() updates global vars
bool Touch_getXY(void)
{
TSPoint p = ts.getPoint();
pinMode(YP, OUTPUT); //restore shared pins
pinMode(XM, OUTPUT);
digitalWrite(YP, HIGH); //because TFT control pins
digitalWrite(XM, HIGH);
bool pressed = (p.z > MINPRESSURE && p.z < MAXPRESSURE);
if (pressed) {
pixel_x = map(p.x, TS_LEFT, TS_RT, 0, tft.width()); //.kbv makes sense to me
pixel_y = map(p.y, TS_TOP, TS_BOT, 0, tft.height());
}
return pressed;
}

#define BLACK 0x0000
#define BLUE 0x001F
#define RED 0xF800
#define GREEN 0x07E0
#define CYAN 0x07FF
#define MAGENTA 0xF81F
#define YELLOW 0xFFE0
#define WHITE 0xFFFF


//------------------------------Define Key Output-------------------------------------------

#define KEY_N1 89 //** 89 = Numberpad 1
#define KEY_N2 90 //** 90 = Numberpad 2
#define KEY_N3 91 //** 91 = Numberpad 3
#define KEY_N4 92 //** 92 = Numberpad 4
#define KEY_N5 93 //** 93 = Numberpad 5
#define KEY_N6 94 //** 94 = Numberpad 6
#define KEY_N7 95 //** 95 = Numberpad 7
#define KEY_N8 96 //** 96 = Numberpad 8
#define KEY_N9 97 //** 97 = Numberpad 1

#define KEY_F1 58 //** 58 = F1
#define KEY_F2 59 //** 59 = F2
#define KEY_F3 60 //** 60 = F3
#define KEY_F4 61 //** 61 = F4
#define KEY_F5 62 //** 62 = F5
#define KEY_F6 63 //** 63 = F6

//------------------------------End Keys Output-------------------------------------------

uint8_t buf[8] = {
0 }; //Keyboard Report Buffer


void setup(void)
{

Serial.begin(9600);
uint16_t ID = tft.readID();
if (ID == 0xD3D3) ID = 0x9486; // write-only shield
tft.begin(ID);
tft.setRotation(0); //PORTRAIT
tft.fillScreen(BLACK);


//------------------------------Draw Keys-----------------------------------------

SW1_btn.initButton(&tft, 55, 50, 90, 85, WHITE, MAGENTA, BLACK, "SCR1", 3);
SW2_btn.initButton(&tft, 160, 50, 90, 85, WHITE, MAGENTA, BLACK, "SCR2", 3);
SW3_btn.initButton(&tft, 265, 50, 90, 85, WHITE, MAGENTA, BLACK, "SCR3", 3);

SW4_btn.initButton(&tft, 55, 145, 90, 85, WHITE, BLUE, BLACK, "SCR4", 3);
SW5_btn.initButton(&tft, 160, 145, 90, 85, WHITE, BLUE, BLACK, "SCR5", 3);
SW6_btn.initButton(&tft, 265, 145, 90, 85, WHITE, BLUE, BLACK, "SCR6", 3);

SW7_btn.initButton(&tft, 55, 240, 90, 85, WHITE, GREEN, BLACK, "SCR7", 3);
SW8_btn.initButton(&tft, 160, 240, 90, 85, WHITE, GREEN, BLACK, "SCR8", 3);
SW9_btn.initButton(&tft, 265, 240, 90, 85, WHITE, GREEN, BLACK, "SCR9", 3);

SW10_btn.initButton(&tft, 55, 335, 90, 85, WHITE, CYAN, BLACK, "WIPE", 3);
SW11_btn.initButton(&tft, 160, 335, 90, 85, WHITE, CYAN, BLACK, "CUT", 3);
SW12_btn.initButton(&tft, 265, 335, 90, 85, WHITE, CYAN, BLACK, "LUM", 3);

SW13_btn.initButton(&tft, 55, 430, 90, 85, WHITE, YELLOW, BLACK, "BOT", 3);
SW14_btn.initButton(&tft, 160, 430, 90, 85, WHITE, YELLOW, BLACK, "TOP", 3);
SW15_btn.initButton(&tft, 265, 430, 90, 85, WHITE, YELLOW, BLACK, "EXT", 3);

//-----------------------------------------------------------------------------------

SW1_btn.drawButton(false);
SW2_btn.drawButton(false);
SW3_btn.drawButton(false);
SW4_btn.drawButton(false);
SW5_btn.drawButton(false);
SW6_btn.drawButton(false);
SW7_btn.drawButton(false);
SW8_btn.drawButton(false);
SW9_btn.drawButton(false);
SW10_btn.drawButton(false);
SW11_btn.drawButton(false);
SW12_btn.drawButton(false);
SW13_btn.drawButton(false);
SW14_btn.drawButton(false);
SW15_btn.drawButton(false);

releaseKey();
}


void loop(void)
{
bool down = Touch_getXY();

//---------------------------------------------------------------------------------

SW1_btn.press(down && SW1_btn.contains(pixel_x, pixel_y));
SW2_btn.press(down && SW2_btn.contains(pixel_x, pixel_y));
SW3_btn.press(down && SW3_btn.contains(pixel_x, pixel_y));
SW4_btn.press(down && SW4_btn.contains(pixel_x, pixel_y));
SW5_btn.press(down && SW5_btn.contains(pixel_x, pixel_y));
SW6_btn.press(down && SW6_btn.contains(pixel_x, pixel_y));
SW7_btn.press(down && SW7_btn.contains(pixel_x, pixel_y));
SW8_btn.press(down && SW8_btn.contains(pixel_x, pixel_y));
SW9_btn.press(down && SW9_btn.contains(pixel_x, pixel_y));
SW10_btn.press(down && SW10_btn.contains(pixel_x, pixel_y));
SW11_btn.press(down && SW11_btn.contains(pixel_x, pixel_y));
SW12_btn.press(down && SW12_btn.contains(pixel_x, pixel_y));
SW13_btn.press(down && SW13_btn.contains(pixel_x, pixel_y));
SW14_btn.press(down && SW14_btn.contains(pixel_x, pixel_y));
SW15_btn.press(down && SW15_btn.contains(pixel_x, pixel_y));

//--------------------------------------------------------------

if (SW1_btn.justReleased())
SW1_btn.drawButton();

if (SW2_btn.justReleased())
SW2_btn.drawButton();

if (SW3_btn.justReleased())
SW3_btn.drawButton();

if (SW4_btn.justReleased())
SW4_btn.drawButton();

if (SW5_btn.justReleased())
SW5_btn.drawButton();

if (SW6_btn.justReleased())
SW6_btn.drawButton();

if (SW7_btn.justReleased())
SW7_btn.drawButton();

if (SW8_btn.justReleased())
SW8_btn.drawButton();

if (SW9_btn.justReleased())
SW9_btn.drawButton();

if (SW10_btn.justReleased())
SW10_btn.drawButton();

if (SW11_btn.justReleased())
SW11_btn.drawButton();

if (SW12_btn.justReleased())
SW12_btn.drawButton();

if (SW13_btn.justReleased())
SW13_btn.drawButton();

if (SW14_btn.justReleased())
SW14_btn.drawButton();

if (SW15_btn.justReleased())
SW15_btn.drawButton();

// --------------Button Output to USB------------------------

if (SW1_btn.justPressed()) {
SW1_btn.drawButton(true);
buf[2] = KEY_N1;
Serial.write(buf,8);
releaseKey();
}

if (SW2_btn.justPressed()) {
SW2_btn.drawButton(true);
buf[2] = KEY_N2;
Serial.write(buf,8);
releaseKey();
}

if (SW3_btn.justPressed()) {
SW3_btn.drawButton(true);
buf[2] = KEY_N3;
Serial.write(buf,8);
releaseKey();
}

if (SW4_btn.justPressed()) {
SW4_btn.drawButton(true);
buf[2] = KEY_N4;
Serial.write(buf,8);
releaseKey();
}

if (SW5_btn.justPressed()) {
SW5_btn.drawButton(true);
buf[2] = KEY_N5;
Serial.write(buf,8);
releaseKey();
}

if (SW6_btn.justPressed()) {
SW6_btn.drawButton(true);
buf[2] = KEY_N6;
Serial.write(buf,8);
releaseKey();
}

if (SW7_btn.justPressed()) {
SW7_btn.drawButton(true);
buf[2] = KEY_N7;
Serial.write(buf,8);
releaseKey();
}

if (SW8_btn.justPressed()) {
SW8_btn.drawButton(true);
buf[2] = KEY_N8;
Serial.write(buf,8);
releaseKey();
}

if (SW9_btn.justPressed()) {
SW9_btn.drawButton(true);
buf[2] = KEY_N9;
Serial.write(buf,8);
releaseKey();
}

if (SW10_btn.justPressed()) {
SW10_btn.drawButton(true);
buf[2] = KEY_F1;
Serial.write(buf,8);
releaseKey();
}

if (SW11_btn.justPressed()) {
SW11_btn.drawButton(true);
buf[2] = KEY_F2;
Serial.write(buf,8);
releaseKey();
}

if (SW12_btn.justPressed()) {
SW12_btn.drawButton(true);
buf[2] = KEY_F3;
Serial.write(buf,8);
releaseKey();
}

if (SW13_btn.justPressed()) {
SW13_btn.drawButton(true);
buf[2] = KEY_F4;
Serial.write(buf,8);
releaseKey();
}

if (SW14_btn.justPressed()) {
SW14_btn.drawButton(true);
buf[2] = KEY_F5;
Serial.write(buf,8);
releaseKey();
}

if (SW15_btn.justPressed()) {
SW15_btn.drawButton(true);
buf[2] = KEY_F6;
Serial.write(buf,8);
releaseKey();
}

}

void releaseKey ()
{
delay(150);

buf[0] =0;
buf[2] =0;
Serial.write(buf, 8); //Release Key ********************

}

#endif

----------------------------End of Code----------------------------------------

I do need to point out that after the code has been uploaded to the UNO, the USB interface of the UNO has to be reprogrammed for it to be recognised as a HID keyboard device. Only UNOs using the 16U2 interface chip can be reprogrammed, the HID keyboard firmware and Howto is available from the Arduino website.

Phil, M0PIT

Re: Alternatives to Streamdeck for OBS

Posted: Fri Mar 26, 2021 7:13 pm
by G3GJA
Phil

This should be written up for a CQ-TV article. As the owner of a Streamdeck (a gratefully received Christmas present) I appreciate how useful they are and your design offers as good performance at a fraction of the cost.

Definitely deserves wider publicity.

Clive G3GJA

Re: Alternatives to Streamdeck for OBS

Posted: Wed Apr 14, 2021 7:39 pm
by VK3IE
Thanks Phil...
First time Arduino user..
Getting there and looking good

Robert