Project tutorial
Old-School Two-Way Pager with Arduino

Old-School Two-Way Pager with Arduino © GPL3+

Send and receive real SMS text messages with your Arduino! A self-contained texting device with its own SIM card and phone number.

  • 151 respects

Components and supplies

Apps and online services

About this project


Looking to recreate the glory of the late 90s and early 2000s, I thought I would try to make my own two-way pager, just like Jay-Z used to use! But I’ve found this idea to be more useful than at first glance—I designed it so it could be used by children in lieu of a real cell phone, or it could be used as an “SOS” button for someone working alone outdoors or even exercising (if it was a bit smaller). And for the last few weeks, this device has been always on in my living room where my wife and I leave silly messages for each other.

Summary: This project sends and receives text (SMS) messages via an arduino with cellular modem built into a small enclosure with an LCD display and simple control buttons.

Demo of messaging system working to send and then receive a message

Note that the code for this project includes the ability to run the system from the serial interface so you can build and use a version of this project even if you don't have an LCD shield!

Hardware selection

I had an Arduino GSM Shield I wanted to use with this project along with a SIM card from Hologram, so combining with an Arduino UNO I had made a lot of sense. Adafruit makes an LCD display shield with buttons built-in, and because it uses the I2C bus, it uses only two pins on the Arduino for the display and five buttons, leaving lots of room for future customization.

The LCD has a nice blue background color, so I figured combining it with a translucent blue enclosure would look nice. Stacking the three boards on top of each other made for some very clean connections and was no issue electrically because the two shields use different pins from each other.

  • LCD shield: Analog 4 and 5 pins for the I2C bus (along with +5V power and ground [but only the ground next to the Vin pin, not the one next to the 5V pin])
  • GSM shield: pins 2 and 3 for software serial, and pin 7 as a power reset (plus power and ground obviously, even though I never saw any details of this in the documentation)

Everything else is free to expand still! Other devices could even still be added to the I2C bus.

During development, the system ran fine on just USB power from my laptop for sending and receiving SMS, though I suspect it will need more power for using GPRS cellular data reliably.

Cellular communication

Hologram provides a cellular data service that works with any device that accepts a SIM card. After registering my SIM card for service, with just an extra click I was able to assign it a phone number in my area code of choice. Another feature that makes this project great as a kids’ communicator, for example, is that it’s so inexpensive to keep the service active ($0.40/mo) and I can pause it at any time if I stop using the device. Like most cell phone plans, receiving texts is free and sending is $0.19 each. In the future, this cost can be lowered even further by using a data connection to Hologram’s cloud service where a message can be automatically routed to a free SMS service. $0.60 for one megabyte of data will get you a whole lot of tiny text messages.

For whatever reason, even though the GSM shield was ultimately working properly, it would not correctly run the GSMScanner and GSMModem getIMEI() functions designed for initial setup and troubleshooting. Pretty misleading! SMS and GPRS data functions worked fine, so if anyone knows why the modem still can’t tell me its own IMEI number, I’d love to hear your ideas.

Code and Interface

Adafruit has excellent instructions to show the LCD shield in action and Arduino has a good getting-started guide for the GSM shield that showcases the important functions, so most of my code was based on those two bodies of functions, plus the addition of my own user interface.

I had never programmed a user interface before that wasn’t entering a letter into a command line with a keyboard, so I wanted to keep it simple. I decided to make a selection of canned messages to send that could be scrolled through with the up and down keys—actually easier than I thought!

Just by stacking the three boards on top of each other with a working SIM card (even from your own phone!) and flashing the code below, you’ll get a text messaging device that works like this:

  • Power it on, it boots up, and tells you once the network is connected
  • Left button: Go to the “home” screen
  • Right button: Acknowledge in incoming message, allowing the next message to come in if there is one. Also dims the screen if there is no current message.
  • Up/down buttons: Scroll through selection of pre-programmed messages to send. I guess you could put as many as you want into the code; I started with four.
  • Select button: Send the currently selected message

That’s it! Incoming messages are automatically displayed no matter what menu is active on the device, and will even turn the backlight on if it had been off.

The next logical step might be to add a menu of destination numbers for the outgoing messages, or just have it automatically respond to the last message it received (using the remoteNumber() function).

Final build

To cut the openings in the project box, I used a drill for normal holes and then a Dremel multipurpose cutting bit (#561) with guide (#565) for the larger cutouts. Using some extra boards and clamps to make a jig to guide the Dremel would have made for cleaner lines, but I am happy enough with what I did freehand. The bit definitely wanted to wander more than I expected!

The only actual wires in the project are for power to add a 9V battery and switch—without a battery it can be powered from USB or a wall plug and you’re done!

A 9V battery and clip fits nicely next to the Arduino and underneath the GSM shield (which is about the same size as an Arduino Mega). I soldered wires to some header pins to make the power connection easy to attach.

Thankfully the GSM shield had extra long and bendable leads on its pins. I was able to bend them in slightly so that they missed the sockets on the Arduino, leaving those connections free for my power header. I suspect this is why the pins are like that! The LCD shield on top has short and stout pins with no female connections on the front side, so bending pins away is the only method I found to connect things other than the shields in this setup.

Clipping down the USB port solder leads and a few others on the underside of the Arduino allow it to sit flat in the case on some double-stick foam tape. Then the boards stack up to leave the LCD and buttons just barely protruding above the top of the case—perfect!


I’ll keep tinkering with this system and even let my 4-year-old nephew bang it around a little. I think it works pretty well as a messenger, but I was surprised that I like it even more as a tiny billboard in my house to display messages. I’m happy with the aesthetic, which helps there.

Further work on this could it have a second menu for selecting the recipient of a message, automatically dim the display to save power, add lights/buzzers/pager motors for notifications, or any other preferred user interface behavior you could want!

The system could also be fundamentally changed to display notifications from a service like IFTTT, like for if you had a package delivered or someone’s latest tweets. What are you building? How would you improve the system?



You can read more about me and my projects on my blog, including more about this project.

(This project was made possible as part of Hologram's Hacker-In-Residence program. For more information, contact Sheena, Hologram's content coordinator, at


System code for ArduinoArduino
 * Two-way pager system with Arduino and Hologram cellular service
 * Mike Schaus
 * Dec 28, 2016
 * Made as part of the Hologram Hacker-In-Residence program
 * This project sends and receives text (SMS) messages via 
 * an arduino with cellular modem built into a small enclosure 
 * with an LCD display and simple control buttons.
 * Note that code is included to function via the LCD shield OR
 * the serial monitor, so you can use this code even without
 * an LCD shield.

#include <GSM.h>

#define PINNUMBER ""

// include the LCD library code:
#include <Wire.h>
#include <Adafruit_RGBLCDShield.h>
#include <utility/Adafruit_MCP23017.h>

// The shield uses the I2C SCL and SDA pins. On classic Arduinos
// this is Analog 4 and 5 so you can't use those for analogRead() anymore
// However, you can connect other I2C sensors to the I2C bus and share
// the I2C bus.
Adafruit_RGBLCDShield lcd = Adafruit_RGBLCDShield();

// These #defines make it easy to set the backlight color
#define OFF 0x0
#define ON 0x1

// make the arrow special character on the LCD
const byte arrow[8] =
 B00000, B00000, B01000, B01100, B01110, B01100, B01000, B00000

// initialize the GSM library instance
GSM gsmAccess(false); // include a 'true' parameter for debug enabled
GSM_SMS sms;

// char array of the telephone number to send SMS
// change the number 12125551212 to a number
// you have access to
char remoteNumber[20]= "12125551212";

// Array to hold the number a SMS is retreived from
char senderNumber[20];

// char array of the possible outgoing messages to choose from the menu
char* responses[]={"Mike=Awesome!", "Yes", "No", "Howdy!"};
//#define NUMRESPONSES 4 // if someone knows how to calculate this instead, I'm all ears
#define NUMRESPONSES (sizeof(responses)/sizeof(char *)) // thanks to Steve Kemp's comment!

int position=-1; // this way the first button press will always show first option of the menu

int inByte = 0; // incoming serial byte for keyboard interface

boolean backlight = true; // track backlight status for toggling

unsigned long previousMillis = 0; // will store last time messages were checked
#define CHECKINTERVAL 1500 // how often to check for text messages

void setup() {
  // put your setup code here, to run once:

  // initialize serial communications
  Serial.println(F("SMS Message Sender -- starting up..."));

  // set up the LCD's number of columns and rows: 
  lcd.begin(16, 2);

  // Print a message to the LCD
  lcd.print(F("Hello, Hologram!"));
  lcd.setCursor(0, 1);
  lcd.print(F("Starting up..."));

  // set up the arrow character for display
  lcd.createChar(0, arrow);

  // connection state
  boolean notConnected = true;

  // Start GSM shield
  // If your SIM has PIN, pass it as a parameter of begin() in quotes
    if(gsmAccess.begin(PINNUMBER)==GSM_READY) {
      notConnected = false;
      Serial.println(F("GSM is connected because you are so awesome"));
      Serial.println(F("Waiting for messages, or send with \"s\""));


      Serial.println(F("Not connected"));
      lcd.print(F("Not connected"));

// this is the menu system function
void showResponses() {
//  Serial.println(position); // only for debugging menu system

  // make sure cursor position is legal
  if (position<0) position=0;
  if (position>NUMRESPONSES-1) position = NUMRESPONSES-1;

  // write current selection and next option if there is another option
  lcd.write(0); //arrow character
  if (position < NUMRESPONSES-1) {
    lcd.print(" ");

void homeScreen() {
  lcd.print("SMS Messenger!");
  lcd.print("Ready; up/dn snd");

  position=-1; //reset response selection

void receiveSMS(){
  char c;

  // If there are any SMSs available()
  if (sms.available()) {
    Serial.println("Message received from:");

    // Get remote number
    sms.remoteNumber(senderNumber, 20);

    backlight = true;

    // An example of message disposal
    // Any messages starting with # should be discarded
    if (sms.peek() == '#') {
      Serial.println("Discarded SMS");

    // Read message bytes and print them
    // because only returns one character at a time
    int i=0;
    while (c = {
      if (i==17) lcd.setCursor(0, 1); // move to next line if needed
      if (i<33) lcd.print(c); // don't try to print more than 32 chars just in case

    Serial.println("\nEND OF MESSAGE");

    // Delete message from modem memory
    Serial.println("MESSAGE DELETED");

    // wait for right button to acknowlege before letting program continue
    boolean acknowledged = false;
    while(!acknowledged) {
      uint8_t buttons = lcd.readButtons();
      if (buttons & BUTTON_RIGHT) acknowledged = true;
      delay(50); //short delay for troubleshooting -- without this it behaves strangely
    delay(400); // prevent multiple presses in a row

// function to show message options in the serial monitor
void printResponseOptions(){
  for(int i=0; i<NUMRESPONSES; i++){

void sendSMS(const char* txtMsg){

  Serial.print("Message to mobile number: ");

  // print sms text info

  // send the message
  // next, add a signature to the chosen message
  sms.print(" --Be sure to connect with me on my blog");
  // call endSMS function to finish sending; it will return 1 if successful
  if (sms.endSMS()==1) {
  else {

void loop() {
  // put your main code here, to run repeatedly:

  uint8_t buttons = lcd.readButtons();

  if (buttons) {
    if (buttons & BUTTON_UP) {
      backlight = true;
    if (buttons & BUTTON_DOWN) {
      backlight = true;
    if (buttons & BUTTON_LEFT) {
      backlight = true;
    if (buttons & BUTTON_RIGHT) {
      backlight = !backlight; // toggle the backlight state
      if (backlight) lcd.setBacklight(ON);
      else lcd.setBacklight(OFF);
      homeScreen(); // have to write to screen after turning light off, otherwise it goes blank
    if (buttons & BUTTON_SELECT) {
      // make sure cursor selected position is legal
      if (position<0) position=0;
      backlight = true;
    delay(200); // prevent multiple presses in a row

  // this is for serial interface only, not related to LCD and buttons
  // send a message when I type "s" in serial monitor
  // then wait for my selection of the response number
  if (Serial.available() > 0) {
    inByte =; // get incoming byte
    if (inByte == 's') {

      while (Serial.available() > 0) {  // clear the keyboard buffer just in case
        char junk =;
      while (Serial.available() == 0) ;  // Wait here until input buffer has a character
      inByte = Serial.parseInt();
      // would want to check for valid choice here to be more robust

  // check for new messages only once every few seconds to keep interface more responsive
  unsigned long currentMillis = millis();
  if (currentMillis - previousMillis >= CHECKINTERVAL) {
    previousMillis = currentMillis;
    receiveSMS(); // takes about 26ms when there are no messages


Schematic—very simple with shields!
Assembly is actually much simpler than the schematic would imply—most connections are made simply by stacking the two shields on top of the Arduino! Anything beyond that is optional for battery power.


Similar projects you might like

Unicorn Finder (Kid Tracker for Techie Parents)

Project tutorial by Team Hologram

  • 21 respects

Portable Arduino Temp/Humidity Sensor with LCD

Project tutorial by ThothLoki

  • 399 respects

Mobile Vote Box

Project tutorial by benstr 🚀

  • 13 respects

LTE Cellular Shield for Arduino with Voice!

by Timothy Woo

  • 21 respects
Add projectSign up / Login