How to rotate the display on a 2.4 inch 240x320 screen?
How to rotate the display on a 2.4 inch 240x320 screen
To rotate the display on a 2.4 inch 240x320 ips display, you need to adjust the orientation settings in your microcontroller code, typically by modifying the MADCTL register (0x36) in the ILI9341 or ST7789 driver, or by swapping the X and Y coordinates in your graphics library. For most common drivers like ILI9341, sending the command 0x36 followed by a byte value like 0xE0 for landscape mode or 0x70 for portrait mode will rotate the display 90 degrees. If you're using a library like Adafruit_GFX or TFT_eSPI, you can call setRotation() with a parameter from 0 to 3 to cycle through four orientations. The exact method depends on your specific driver chip—ILI9341, ST7789, or HX8357—and the interface (SPI or parallel). For a 2.4 inch 240x320 ips display with MCU SPI interface, you'll typically use the writecommand() and writedata() functions to send the rotation command. Below, I'll break down the technical details, register values, and code examples for multiple drivers, so you can get it working on any hardware setup.
First, let's talk about the hardware. The 2.4 inch 240x320 ips display is a common TFT LCD module that uses a resolution of 240 pixels in width and 320 pixels in height. Most of these modules are based on the ILI9341 controller, but some use ST7789 or even HX8357. The IPS (In-Plane Switching) technology gives you wide viewing angles—typically 80 degrees in all directions—and better color accuracy compared to TN panels. The display usually communicates via SPI (Serial Peripheral Interface) with a maximum clock speed of 40 MHz, though some modules support 60 MHz. The MCU interface means you're driving it directly from a microcontroller like ESP32, STM32, or Arduino. The SPI interface uses 4 wires: MOSI, MISO, SCLK, and CS (chip select), plus DC (data/command) and RST (reset). The pixel format is 16-bit RGB565, which gives 65,536 colors. The display's native orientation is portrait mode (240x320), but rotating it to landscape (320x240) is common for user interfaces.
The key to rotation lies in the MADCTL register (Memory Access Control) at address 0x36. This register controls how the display's framebuffer is mapped to the physical pixels. It has 8 bits, each controlling a specific aspect of orientation:
| Bit | Name | Description |
|---|---|---|
| 7 | MY | Row address order swap (vertical mirror) |
| 6 | MX | Column address order swap (horizontal mirror) |
| 5 | MV | Row/Column exchange (swap X and Y) |
| 4 | ML | Vertical refresh order (bottom to top) |
| 3 | BGR | RGB/BGR order (usually set to 0 for RGB) |
| 2 | MH | Horizontal refresh order (right to left) |
| 1-0 | Reserved | Typically set to 0 |
For a standard 2.4 inch 240x320 ips display with ILI9341, the default orientation (portrait, no rotation) uses a MADCTL value of 0x48. This sets MY=0, MX=0, MV=0, and BGR=1 (since the display expects BGR color order). To rotate to landscape (90 degrees), you set MV=1, MX=1, and MY=0, which gives 0xE8. For 180 degrees rotation, set MY=1, MX=1, MV=0, giving 0x88. For 270 degrees, set MV=1, MX=0, MY=1, giving 0x78. Here's a table of common values:
| Rotation | MADCTL Value (ILI9341) | Orientation |
|---|---|---|
| 0 (default) | 0x48 | Portrait, 240x320 |
| 1 (90°) | 0xE8 | Landscape, 320x240 |
| 2 (180°) | 0x88 | Portrait inverted, 240x320 |
| 3 (270°) | 0x78 | Landscape inverted, 320x240 |
But wait—if your display uses the ST7789 driver, the MADCTL values are different. ST7789 is common on smaller IPS modules, but some 2.4-inch variants use it. For ST7789, the default orientation is often 0x00 for portrait, but you need to set the RGB order bit. A typical rotation sequence for ST7789 is:
| Rotation | MADCTL Value (ST7789) | Orientation |
|---|---|---|
| 0 | 0x00 | Portrait |
| 1 | 0x60 | Landscape |
| 2 | 0xC0 | Portrait inverted |
| 3 | 0xA0 | Landscape inverted |
For the HX8357 driver, which is less common but still used in some 2.4-inch modules, the MADCTL register works similarly but with different default values. Typically, you use 0x70 for portrait and 0xE0 for landscape. Always check your module's datasheet or the driver chip's part number printed on the back of the PCB. If you're unsure, you can read the driver ID by sending command 0x04 (Read Display ID) or 0xD3 (Read ID4) over SPI. For ILI9341, the ID is usually 0x9341; for ST7789, it's 0x7789.
Now, let's get into the code. If you're using the Adafruit_ILI9341 library on an Arduino or ESP32, rotation is handled by the setRotation() function. This function internally sends the MADCTL command. Here's a practical example:
#include <Adafruit_ILI9341.h>
#define TFT_CS 10
#define TFT_DC 9
#define TFT_RST 8
Adafruit_ILI9341 tft = Adafruit_ILI9341(TFT_CS, TFT_DC, TFT_RST);
void setup() {
tft.begin();
tft.setRotation(1); // 90 degrees, landscape
tft.fillScreen(ILI9341_BLACK);
}
The setRotation() parameter maps to the MADCTL values internally. For Adafruit_ILI9341, rotation 0 uses 0x48, rotation 1 uses 0xE8, rotation 2 uses 0x88, and rotation 3 uses 0x78. But if you're using a different library like TFT_eSPI (common on ESP32), the rotation function is similar but allows you to define custom rotation matrices. TFT_eSPI is highly optimized for ESP32, using DMA and dual-core processing. To rotate in TFT_eSPI, you call tft.setRotation(1) as well, but you can also modify the User_Setup.h file to change the default rotation. For example, in User_Setup.h, you can set #define TFT_ROTATION 1 to start in landscape mode.
If you're writing raw SPI commands without a library, you need to send the MADCTL command manually. Here's a typical sequence for ILI9341:
void setRotation(uint8_t r) {
uint8_t madctl = 0x48; // default
switch (r) {
case 0: madctl = 0x48; break;
case 1: madctl = 0xE8; break;
case 2: madctl = 0x88; break;
case 3: madctl = 0x78; break;
}
writeCommand(0x36);
writeData(madctl);
// Also set column and page addresses for the new orientation
if (r == 0 || r == 2) {
// Portrait: 240x320
writeCommand(0x2A); // Column address
writeData(0x00); writeData(0x00); // Start column
writeData(0x00); writeData(0xEF); // End column (239)
writeCommand(0x2B); // Page address
writeData(0x00); writeData(0x00); // Start page
writeData(0x01); writeData(0x3F); // End page (319)
} else {
// Landscape: 320x240
writeCommand(0x2A);
writeData(0x00); writeData(0x00);
writeData(0x01); writeData(0x3F); // End column (319)
writeCommand(0x2B);
writeData(0x00); writeData(0x00);
writeData(0x00); writeData(0xEF); // End page (239)
}
writeCommand(0x2C); // Memory write
}
Notice that you also need to update the column (0x2A) and page (0x2B) address registers. If you don't, the display will still draw in the wrong area, causing artifacts. The column address defines the X range (0 to 239 for portrait, 0 to 319 for landscape), and the page address defines the Y range (0 to 319 for portrait, 0 to 239 for landscape). This is a common mistake—people only change MADCTL but forget to adjust the address windows, resulting in partial or garbled output.
Another critical detail is the RGB vs BGR color order. The ILI9341 expects BGR order by default, but some modules are wired for RGB. If your colors look wrong after rotation (e.g., red appears blue), you need to flip the BGR bit (bit 3) in the MADCTL register. For ILI9341, if you're using a module that expects RGB, set the MADCTL value to 0x40 for portrait instead of 0x48. For landscape, use 0xE0 instead of 0xE8. Check your module's documentation—most 2.4 inch 240x320 ips display modules from reputable manufacturers use BGR, but cheap clones might vary.
Let's talk about performance implications. Rotating the display doesn't change the physical pixel count, but it does affect how you draw graphics. In landscape mode, you have more horizontal pixels (320 vs 240), which is better for text-heavy UIs or graphs. However, the SPI bus speed becomes a bottleneck. For a 240x320 display, the total pixel count is 76,800 pixels. At 16-bit color, that's 153,600 bytes per frame. At 40 MHz SPI, a full frame update takes about 3.8 ms, but with overhead, you're looking at 10-15 ms per frame. If you're updating the display at 60 fps, you'll use most of the SPI bandwidth. Rotating the display doesn't change this, but if you're using software rendering (like Adafruit_GFX), the library has to recalculate coordinates for each pixel when rotated, which adds CPU overhead. On an ESP32 at 240 MHz, this is negligible, but on an Arduino Uno (16 MHz), it can slow down your frame rate by 20-30%.
For touchscreen integration, if your 2.4-inch module includes a resistive touch overlay (like the XPT2046 controller), you also need to rotate the touch coordinates to match the display orientation. The touch controller returns raw ADC values (typically 0-4095 for X and Y). You need to map these to the display's pixel coordinates. For example, if the display is rotated 90 degrees, you swap the X and Y touch values and invert one axis. A common mapping function is:
void getRotatedTouch(uint16_t *x, uint16_t *y, uint8_t rotation) {
uint16_t rawX = readTouchX();
uint16_t rawY = readTouchY();
switch (rotation) {
case 0: // Portrait
*x = map(rawX, TOUCH_MIN_X, TOUCH_MAX_X, 0, 239);
*y = map(rawY, TOUCH_MIN_Y, TOUCH_MAX_Y, 0, 319);
break;
case 1: // Landscape
*x = map(rawY, TOUCH_MIN_Y, TOUCH_MAX_Y, 0, 319);
*y = map(rawX, TOUCH_MAX_X, TOUCH_MIN_X, 0, 239);
break;
case 2: // Portrait inverted
*x = map(rawX, TOUCH_MAX_X, TOUCH_MIN_X, 0, 239);
*y = map(rawY, TOUCH_MAX_Y, TOUCH_MIN_Y, 0, 319);
break;
case 3: // Landscape inverted
*x = map(rawY, TOUCH_MAX_Y, TOUCH_MIN_Y, 0, 319);
*y = map(rawX, TOUCH_MIN_X, TOUCH_MAX_X, 0, 239);
break;
}
}
You need to calibrate the touch controller first by reading the minimum and maximum ADC values for each axis. These values depend on the touch panel's resistance and the ADC reference voltage. For a typical XPT2046 on a 2.4-inch display, TOUCH_MIN_X might be around 200, TOUCH_MAX_X around 3800, and similar for Y. The exact values vary by module, so you should run a calibration routine during setup.
Another factor is display driver initialization. Some modules require a specific initialization sequence before rotation works. For example, the ILI9341 needs to be taken out of sleep mode (command 0x11) and have its display turned on (command 0x29) before you can send MADCTL. If you skip these steps, the display might not respond to rotation commands. Here's a minimal initialization sequence for ILI9341:
void initDisplay() {
reset(); // Toggle RST pin low for 10ms
writeCommand(0x01); // Software reset
delay(5);
writeCommand(0x11); // Sleep out
delay(120);
writeCommand(0x36); // MADCTL
writeData(0x48); // Default orientation
writeCommand(0x3A); // Pixel format
writeData(0x55); // 16-bit RGB565
writeCommand(0x29); // Display on
delay(20