Build an Automatic Computer Display Rotator With an Arduino
Learn how to build a device which senses when your computer monitor is physically rotated, and automatically tells your computer to rotate the display accordingly.
Learn how to build a device which senses when your computer monitor is physically rotated, and automatically tells your computer to rotate the display accordingly.
A lot of digital content these days is narrow and tall (portrait orientation), yet most computer screens are short and long (landscape orientation). This means that a lot of screen's real estate gets wasted. Compare the two screenshots below. Only one and a half posts can be seen in landscape view, whereas 3 can be seen in portrait.
Landscape Mode
Portrait Mode
For this reason, it can be a good idea to rotate computer monitors 90º when browsing social media, however switching your display settings back and forth is tedious. This is especially true if you have multiple monitors or a computer on which the standard keyboard shortcuts do not work. Today I'm going to explain how to build a device to automatically tell your computer to rotate the display when your monitor is physically rotated.
Working Principles
The device will be Arduino based, and will be mounted to the back of your monitor. We'll use an accelerometer to sense direction. When stationary, an accelerometer interprets gravity as an acceleration upwards at 9.8m/s2. The accelerometer will talk to the Arduino with the I2C protocol. The Arduino will be constantly looking at (polling) which direction the accelerometer thinks it's accelerating in. When that changes, it will send a signal to the computer via USB. A python script running on the computer will read the change, and will tell the computer to rotate the display accordingly.
This article explains how to implement this on Windows and Linux. It can probably be done on Mac too. However since I don't own a Mac, I can't try it out. (The only difference would be the system command called by the Python script.)
Materials
You will need:
- Arduino – We used an Uno, but you can use any Arduino type you wish.
- Arduino prototyping shield - any style will do
- MMA7455 Accelerometer
- Sticky mounting pins
- Pushbutton
- 100kΩ Resistor
- A few wires
- A soldering iron and solder
Of course, you'll also need a monitor which can rotate. If you have a non-rotating monitor, check whether it has VESA mounting holes (4 or 8 screw holes in a square(s) on the back). You can buy VESA compatible monitor stands which rotate for about $70.
Construction
Grab your soldering iron, let's get started!
If you have much experience in electronics, some of the pin names on the accelerometer should be familiar. VCC and GND will be tied to the Arduino's power rails. CS means "chip select". Since we only have one chip in this project, we'll physically tie that to 5V. SDA and SCL are the back and forth communication lines used in the I2C protocol. If you're not using an Arduino Uno, the pins for SDA and SCL may be different.
Accelerometer Pin | Arduino Uno Pin |
---|---|
VCC | 5V |
GND | GND |
CS | 5V |
SDA | 4 |
SCL | 5 |
For the pushbutton, we're going to connect it in active-low configuration. Since we're using software debouncing (described later) we don't need any capacitors. The BUTTON_PIN is defined in code as pin 12. You are free to change this if you wish.
Arduino Code
If you have no experience with Arduinos, read our Beginner's Guide to Arduino.
Plug the cape into the Arduino, and plug the Arduino into your computer via USB.
As mentioned before, we can use an accelerometer to detect direction by measuring which direction reads an acceleration of 9.8m/s2. Since the monitor will only ever be vertical or horizontal, this will be easy. (If we needed to account for diagonal orientations it would be more difficult.)
It is good practice when programming to separate the different aspects of your code into different files. We've written a library to sense orientation, based off of the official MMA7455 library. This allows us to abstract away the detail of how this is done, so our main file can remain simple, checking orientation with one function call, and writing a message to the computer when it changes.
Download the following and extract the files from the following zip archive.
Open up monitor.ino in your Arduino IDE. Compile and run the code. (All 3 Arduino files need to be kept in the same directory.) Open your serial monitor with Tools > Serial Monitor, or with ctrl + shift + M.
You should see something similar to the following.
May 2012
The MMA7455 is okay
STATUS : 0
WHOAMI : 55
Assuming the device is Y_POS
Now hold the Arduino so that it is in a vertical plane. Rotate it around (still in a vertical plane). Each time it rotates 90º you should see something like this:
change detected
Rotate Monitor
change detected
Rotate Monitor
change detected
Rotate Monitor
Mount the device to the back of your monitor and plug it into your computer with a USB cable. You can use any method you like to mount it. I used adhesive hooks.
We need to calibrate the accelerometer so that it knows which direction it's initially pointed in when it's turned on. This initialization will run whenever the Arduino is turned on. When in use on your monitor, this may be whenever you turn your computer on. (Depending on whether your USB ports supply power when the computer is off.) So when you turn on your computer this device is expecting your monitor to be in whatever you defined as the default position. If it's not, the Arduino will tell your computer to rotate the display the wrong way, or it will get disoriented and won't tell the computer to rotate at all.
This is why we have the button. When the button is pressed, the Arduino re-runs the setup routine and tells the computer to rotate the display to START_ORIENTATION
.
When you mount the device on the back of your monitor, take a note of which direction is up when your monitor is in its default orientation (the one it will probably be in when you turn on the computer).
There's a line of code in monitor.ino which you need to change to accordingly. If the y arrow on the accelerometer points up, leave it as Y_POS
. If the y arrow points down, change it to Y_NEG
. Similarly for the x-axis, use X_POS
or X_NEG
.
#define START_ORIENTATION Y_POS
If you want to understand what's happening in the code, read on. If you just want a working product, skip to the Computer side code section.
Code Explanation
An understanding of the details of the I2C protocol is not necessary for this project. If you want to learn more, check out our Introduction to the I2C Bus.
Have a look at MMA7455.h.
An enum
is a datatype in C, just like int
or char
. It's used for variables which can be exactly one value from a short list of non-numeric variables. In this case, we're using an enum
when we save the orientation of the device into a variable.
Look at the accelerometer. You'll see an x arrow and a y arrow. When the x arrow points up, we've called that X_POS
. When the x arrow points down, we've called that X_NEG
. The z-axis points out of or into the board. When the board is sitting flat on a table that's Z_POS
.
When mounted on the back of a monitor, the device should only ever have the y-axis or the x-axis up or down. We've created a macro in monitor.ino called VALID_ORIENTATION
to check whether a given orientation matches one of the 4 options we expect. As you can see, we can check the value of an enum just like any other data type.
The setup function is mostly just diagnostics. There are 3 crucial lines.
The above line initiates the serial communication, which is how we talk to the computer.
That initializes the I2C communications, which is how we talk to the accelerometer. It just initializes settings on the Arduino side. We initialize the accelerometer with the following line.
When you press a physical button, the contacts don't close perfectly. They bounce, and the output signal appears as if the user pushed the button several times within a few milliseconds. We're waiting 100ms to make sure our code doesn't interpret those bounces as subsequent pushes. There are two approaches we can take to fix this. We can implement hardware debouncing, or software debouncing. The hardware method involves adding a low pass filter to our circuit, either active or passive. Since this involves buying and soldering extra components I have chosen software debouncing instead.
The accelerometer we are using has some interrupt functionality, but it is not applicable to our use case. That's why we're resorting to polling. (Interrupts are more preferable in terms of performance.) Generally, it's good practice to use interrupts when you want a button to do something. However, since we're polling the accelerometer anyway, we may as well poll the button.
Each time around the loop, we check to see if the button is currently being pressed. If it is, and it wasn't pressed last time around, then we know the user has just pushed the button, so we run the setup routine again, and send START_ORIENTATION
to the computer. We then wait 100ms before continuing. This delay is so that the Arduino waits for the button contacts to settle before it continues. Admittedly, this is a fairly sloppy and lazy approach to software debouncing because the Arduino does nothing during the delay. A more sophisticated approach would involve timers, so that other code can continue to be executed whilst the Arduino waits for the button. In this specific case, the only thing we're missing out on by using a delay instead of a timer is a changed reading from the accelerometer. Since that shouldn't happen when the user presses the button, this sloppy method is acceptable. The only advantage of the delay method is that it's simple. Hardware debouncing requires more components, and writing code for interrupts and delays can get tricky.
&& previous_button_state) // and this is the first time it's been
// pressed
{
// send even if orientation hasn't changed
setup(); //re-calibrate
sendOrientation(START_ORIENTATION);
delay(100); //software debounce
previous_button_state = 0;
}
Computer Side Code
The Arduino will send information to the computer using the same serial interface we use to program and debug it. To avoid clashes, make sure the serial monitor in the Arduino IDE is closed when you run this script. If it's open, some of the Arduino's messages will be gobbled up by the serial monitor, and the script won't see them, or will only see an incomprehensible portion of them.
We're going to be using Python for our computer side code. This is going to listen to what the Arduino writes to the serial line, and will make a system call to rotate the display.
Linux users should skip ahead to the Linux section.
Windows
Download Python 3 for you computer from here. When asked during install whether you want to add python to your path, say yes.
Locate windows-script.py in the zip file you downloaded before.
Open up the Arduino IDE. Click on Tools > Serial Port. Take a look at which ports are listed, such as COM3.
If you see anything other than COM3 Open up windows-script.py in a text editor. Locate the following line, and change it to match what you saw.
Search for the following lines. You will need to change these numbers to suit your setup. The numbers are rotations clockwise in degrees for each possible orientation.
"X_POS":"180",
"X_NEG":"0",
"Y_NEG":"270"}
Now we need to install the pySerial module, which is used to read from the USB port. Open the command prompt. On Windows 8 and later, search for cmd. On Windows 7 and earlier, find the run utility in the start menu and enter cmd.
In the terminal which now appears, type:
Installation of python modules can be finicky. Do not proceed if it did not install successfully.
Now we have one more thing we need to do before running the script. Find the display utility included in the Windows folder of the above zip file (which is from this website).
If you have multiple monitors, do the following. If you only have one monitor, skip this paragraph. Open up the command prompt where you saved display.exe. (Open up a file browser at that location, then shift + right click in the file browser background, and click open command prompt here
.) Enter display.exe /listdevices
into the command prompt. You should see an index for each of your monitors. Take note of what the index is for the one you want to rotate.
The python script will open up a connection to the USB port. When it detects a message from the Arduino, it will send the corresponding command to display.exe, which will rotate the display. Locate the following line, and change the directory to point to display.exe.
For users with multiple monitors, also add /device x (where x is your monitor's index). So something like:
In the command prompt, navigate to the folder where you saved display.exe and windows-script.py. Do this using the cd command. For example, if they're saved in Documents/rotate, use:
Now type dir to see the list of files in that directory. Double check that you see those two files. Now let's run the python script.
Try rotating the device around. The screen should rotate accordingly.
Now we need to tell Windows to run this program in the background every time we start the computer. By default, startup programs get opened visibly. We don't want a visible command window all the time. So instead, we're going to create a batch file, which will start the python script in the background, and then the batch file will exit.
First, open up your startup folder. The location of this folder varies depending on which version of Windows you're using. Press the Windows key, then R. The run window should appear. Type shell:startup
and press enter. Now you should see your startup folder. Create a new text file there called monitor.bat, and paste the following code into that file. Modify the path so that it points to your python script.
START pythonw C:\...\windows-script.py
We are using pythonw instead of python because that is necessary for hiding the command window of the python script.
Now reboot your computer and check that it works. If it does, skip to the end.
Linux
Download Python 3 for you computer from here. You can also do this through your package manager. If asked during install whether you want to add python to your path, say yes.
Install the pySerial module.
Installing python modules can be tricky. Do not proceed if you received an error from the above command.
Open up the Arduino IDE. Click on Tools > Serial Port. Take a look at which ports are listed, such as /dev/ttyACM1
.
Find linux-script.py, which is located in the zip file you downloaded earlier. Open up linux-script.py in a text editor. Locate the following line, and modify it if necessary to add the port you saw.
The command we need to run depends on your specific setup. Open up a terminal (ctrl + alt + T) and enter xrandr
. You should see a display (or multiple), such as eDP1 or HDMI1. Take note of that name. Experiment with the following commands, to see if it rotates your display.
For a single monitor with name HDMI1:
To put the display back,
For two monitors, eDP1 (non-rotating, on the right) and HDMI1 (on the left), try:
To put it back:
Take note of what command worked for your setup. Change the command = "xrandr --output ...
line in your script accordingly, where normal
or left
is replaced with " + translation[direction] + "
(including the double quotes).
Now run the script with:
Try rotating the device around. The screen should rotate accordingly.
If the screen rotates, but the rotation is not the correct one, modify the following line in the python script accordingly:
"X_POS":"right",
"X_NEG":"left",
"Y_NEG":"inverted"}
All that's left now is to start the script automatically on boot. The details of this depend on your distro. Try searching for startup in the start menu. Alternatively, you can add a cron job to do it.
Done!
Now sit back and enjoy your more efficient screen space usage.
Give this project a try for yourself! Get the BOM.
Matthew, could I get the source code for your Arduino project? Thanks
Hey, could you show how you connected pushbutton and resistor, because there’s no photo of this, you only showed, how it looks without a button and resistor.
And would it work without resistor with pins connected like you showed, if I won’t use a button?
I can change code myself for this, but I’m not that good with electric stuff, so I need to be sure that this resistor is only necessary for the button or also for the accelerometer itself.