Shahariar
Published © CC BY

Toothless Bluetooth Keyboard

3 physical buttons only 50% layout Bluetooth Keyboard made with nRF 52840 MCU featuring the most essential Alpha-Numeric Keys & Special Keys

BeginnerFull instructions provided4 hours293
Toothless Bluetooth Keyboard

Things used in this project

Hardware components

nRF52840 Multi-Protocol SoC
Nordic Semiconductor nRF52840 Multi-Protocol SoC
×1
Seeed Studio XIAO nRF52840 Sense (XIAO BLE Sense)
Seeed Studio XIAO nRF52840 Sense (XIAO BLE Sense)
×1
Xenon
Particle Xenon
×1
Adafruit Feather nRF52840 Express
×1
Cherry MX Switch Breakout
SparkFun Cherry MX Switch Breakout
×1
Pushbutton Switch, Momentary Spring Return
Pushbutton Switch, Momentary Spring Return
×3
Maker Essentials - Switches & Potentiometers
Pimoroni Maker Essentials - Switches & Potentiometers
×1

Software apps and online services

Arduino IDE
Arduino IDE
nRF Connect SDK
Nordic Semiconductor nRF Connect SDK

Hand tools and fabrication machines

Soldering iron (generic)
Soldering iron (generic)
Solder Wire, Lead Free
Solder Wire, Lead Free

Story

Read more

Schematics

schematic

Code

main

C/C++
#include <bluefruit.h>
#include <U8g2lib.h>

// Software I2C pins
#define SCL_PIN 10
#define SDA_PIN 9

// <--aint working, direct '\u0008' will be send instead
#define KEY_BACKSPACE 0x2A 

// U8g2 OLED setup with software I2C
U8G2_SSD1306_128X64_NONAME_F_SW_I2C u8g2(U8G2_R0,SCL_PIN,SDA_PIN,U8X8_PIN_NONE);


// pis allocation for feather express nRF52840
// Button definitions
#define BUTTON_SELECT_PIN  11     // red button, it presses the selected key
#define BUTTON_SCROLL_UP_PIN  12  // yellow button, moves cursor left and up
#define BUTTON_SCROLL_DOWN_PIN  13 // blue button, moves cursor right and down

// led glows when any switch press registered

#define LED_SELECT_PIN  A4
#define LED_SCROLL_UP_PIN  A3
#define LED_SCROLL_DOWN_PIN  A2

// pin allocation for particle xenon 
/*
#define BUTTON_SELECT_PIN  11
#define BUTTON_SCROLL_UP_PIN  12
#define BUTTON_SCROLL_DOWN_PIN  3

#define LED_SELECT_PIN  A7
#define LED_SCROLL_UP_PIN  A2
#define LED_SCROLL_DOWN_PIN  A6
*/

// Keyboard key array with max 10 keys per row
// this is the virtual keyboard layout
// use the buttons to select the keys and 
// send it over bluetooth
// Keyboard key array with max 10 keys per row

// press vs press and hold feature added 

const char* keys[50] = {
  "q", "w", "e", "r", "t", "y", "u", "i", "o", "p",
  "a", "s", "d", "f", "g", "h", "j", "k", "l", ",",
  "z", "x", "c", "v", "b", "n", "m", ".", "@", "/",
  "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", 
  "=", "!", "?", "$", "*", "&", "Sp", "Bk", "En", "CL"
};

////////////
//// CL = CAPS LOCK

int keyIndex = 0;
bool CLsLock = false; // CLs Lock status

// Bluetooth setup
BLEHidAdafruit blehid;

// Variables for display refresh/flicker to avoid burnout
unsigned long previousMillis = 0;
const unsigned long interval = 15000;
unsigned long previousMillis2 = 0;
const unsigned long interval2 = 300;


void setup() {
  // Initialize U8g2 display
  u8g2.begin();
  u8g2.clearBuffer();

  // Button setup
  pinMode(BUTTON_SELECT_PIN, INPUT_PULLUP);
  pinMode(BUTTON_SCROLL_UP_PIN, INPUT_PULLUP);
  pinMode(BUTTON_SCROLL_DOWN_PIN, INPUT_PULLUP);
  pinMode(LED_SELECT_PIN, OUTPUT);
  pinMode(LED_SCROLL_UP_PIN, OUTPUT);
  pinMode(LED_SCROLL_DOWN_PIN, OUTPUT);

  // Bluetooth setup
  Bluefruit.begin();
  Bluefruit.setName("Bluefruit52");
  blehid.begin();
  Bluefruit.setName("Toothless Bluetooth Keyboard");
  startAdv();
  pinMode(24, OUTPUT); // blue LED
  pinMode(25, OUTPUT); // red LED
  
  // Initial display update
  updateDisplay();

  while (Bluefruit.connected() == 0) {
    is_not_connected();
  }
  digitalWrite(25, 1);
}

void loop() {
  unsigned long currentMillis = millis();

  if (digitalRead(BUTTON_SCROLL_UP_PIN) == LOW) {
    keyIndex = (keyIndex + 1) % 50;
    digitalWrite(LED_SCROLL_UP_PIN, HIGH);
    delay(30);
    updateDisplay();
    digitalWrite(LED_SCROLL_UP_PIN, LOW);
  }

  if (digitalRead(BUTTON_SCROLL_DOWN_PIN) == LOW) {
    keyIndex = (keyIndex - 1 + 50) % 50;
    digitalWrite(LED_SCROLL_DOWN_PIN, HIGH);
    delay(30);
    updateDisplay();
    digitalWrite(LED_SCROLL_DOWN_PIN, LOW);
  }

  if (digitalRead(BUTTON_SELECT_PIN) == LOW) {
    previousMillis2 = millis();
    digitalWrite(LED_SELECT_PIN, HIGH);
    if (strcmp(keys[keyIndex], "CL") == 0) { // Toggle CLs Lock
      CLsLock = !CLsLock;
      updateDisplay();
      delay(500); // Debouncing delay for CLs Lock
    } else if (strcmp(keys[keyIndex], "EN") == 0) { // Enter key
      sendKey('\n');
    } else if (strcmp(keys[keyIndex], "BK") == 0) { // Back key
      sendSpecialKey(0x2A); // HID code for backspace
    } else {
      sendKey(keys[keyIndex]);
    }
    delay(60);
    digitalWrite(LED_SELECT_PIN, LOW);
  }
//////////////////// press & hold feature /////////////////////////////////
  ////////////////////////////////////////////////////////////////////////////
  while(digitalRead(BUTTON_SELECT_PIN) == LOW && millis()<previousMillis2+interval2)
  {}
  while(digitalRead(BUTTON_SELECT_PIN) == LOW)
  {
     digitalWrite(LED_SELECT_PIN, HIGH);
    if (strcmp(keys[keyIndex], "CL") == 0) { // Toggle CLs Lock
      CLsLock = !CLsLock;
      updateDisplay();
      delay(500); // Debouncing delay for CLs Lock
    } else if (strcmp(keys[keyIndex], "EN") == 0) { // Enter key
      sendKey('\n');
    } else if (strcmp(keys[keyIndex], "BK") == 0) { // Back key
      sendSpecialKey(0x2A); // HID code for backspace
    } else {
      sendKey(keys[keyIndex]);
    }
    delay(60);
    digitalWrite(LED_SELECT_PIN, LOW);
  }

///////////////////////////////////////////////////////////////
  if (Bluefruit.connected() == 1) {
    is_connected();
  }

  if (Bluefruit.connected() == 0) {
    is_not_connected();
  }

  // Refresh display every 5 seconds
  if (currentMillis - previousMillis >= interval) {
    previousMillis = currentMillis;
    refreshDisplay();
  }
}

void startAdv(void) {
  // Advertising packet
  Bluefruit.Advertising.addFlags(BLE_GAP_ADV_FLAGS_LE_ONLY_GENERAL_DISC_MODE);
  Bluefruit.Advertising.addTxPower();
  Bluefruit.Advertising.addAppearance(BLE_APPEARANCE_HID_KEYBOARD);
  
  // Include BLE HID service
  Bluefruit.Advertising.addService(blehid);

  // There is enough room for the dev name in the advertising packet
  Bluefruit.Advertising.addName();
  Bluefruit.Advertising.restartOnDisconnect(true);
  Bluefruit.Advertising.setInterval(40, 244);    // in unit of 0.625 ms
  Bluefruit.Advertising.setFastTimeout(30);      // number of seconds in fast mode
  Bluefruit.Advertising.start(0);                // 0 = Don't stop advertising after n seconds

  while (Bluefruit.connected() == 0) {
    is_not_connected();
  }
  digitalWrite(25, 1);
}

void updateDisplay() {
  u8g2.clearBuffer();
  u8g2.setFont(u8g2_font_6x10_tr); // Small font for the keys
  const int keysPerRow = 10;
  const int keyWidth = 13;
  const int keyHeight = 10;
  const int xOffset = 1;
  const int yOffset = 3;

  for (int i = 0; i < 50; i++) {
    int x = xOffset + (i % keysPerRow) * keyWidth;
    int y = yOffset + (i / keysPerRow) * keyHeight;
    
    if (i == keyIndex) {
      u8g2.drawBox(x - 1, y - 1, keyWidth + 1, keyHeight + 1);
      u8g2.setDrawColor(0); // Invert color for selected key
    } else {
      u8g2.setDrawColor(1);
    }

    String displayString = keys[i];
    if (CLsLock && displayString.length() == 1 && displayString[0] >= 'a' && displayString[0] <= 'z') {
      displayString[0] -= 32; // Convert to uppercase correctly using ASCII
    }
    
    u8g2.setCursor(x, y + 8);
    u8g2.print(displayString);
  }

  u8g2.setDrawColor(1);
  u8g2.setFont(u8g2_font_6x10_tr); // Medium font for the CLs Lock message
  u8g2.setCursor(0, 64);
  u8g2.print(F("Caps Lock: "));
  u8g2.print(CLsLock ? "ON" : "OFF");
  u8g2.sendBuffer();
}

void refreshDisplay() {
  u8g2.setPowerSave(1); // Turn off display
  delay(100);           // Wait for 200 ms
  u8g2.setPowerSave(0); // Turn on display
  updateDisplay();      // Refresh display content
}


void sendKey(const char* key) {
  if (strcmp(key, "Bk") == 0) {
    sendSpecialKey(KEY_BACKSPACE); // Use built-in backspace constant
  } else if (strcmp(key, "En") == 0) {
    blehid.keyPress('\n');
  } else if (strcmp(key, "Sp") == 0) {
    blehid.keyPress(' ');
  } else if (strcmp(key, ",") == 0) {
    blehid.keyPress(',');
  } else if (strcmp(key, ".") == 0) {
    blehid.keyPress('.');
  } else {
    char c = key[0];
    if (CLsLock && c >= 'a' && c <= 'z') {
      c -= 32; // Convert to uppercase correctly using ASCII
    }
    blehid.keyPress(c);
  }
  blehid.keyRelease();
}

void sendSpecialKey(uint8_t keycode) {

  // sending source code style works !!
  // as hid keycode not working for some reason 
  blehid.keyPress('\u0008');
  blehid.keyRelease();
}


void sendKey(char key) {
  char keyStr[2] = {key, '\0'};
  sendKey(keyStr);
}

// Status LED functions
void is_not_connected() {
  digitalWrite(25, 0);
  delay(50);
  digitalWrite(25, 1);
  delay(50); 
}

void is_connected() {
  digitalWrite(24, 0);
  delay(25);
  digitalWrite(24, 1);
  delay(25); 
}

Credits

Shahariar
75 projects • 270 followers
"What Kills a 'Great life' is a 'Good Life', which is Living a Life Inside While Loop"
Contact

Comments

Please log in or sign up to comment.