Skip to content

How to display a compass on a 2.4 inch 240x320 TFT display?

By adminVerified dataset
To display a compass on a 2.4 inch 240x320 TFT display, you need to interface a magnetometer sensor (like the HMC5883L or QMC5883L) with a microcontroller (such as an ESP32 or STM32) and render the heading data as a rotating compass rose on the screen. The display itself, typically driven by an ILI9341 or ST7789 controller over SPI, provides 240x320 pixels with 16-bit color depth (65K colors), which is sufficient for a crisp compass needle and cardinal directions. The key is to read the magnetometer’s X and Y axis data, calculate the heading using the arctangent function (atan2), then apply a declination correction for your location, and finally draw the compass graphic using a graphics library like TFT_eSPI or LVGL. For example, on an ESP32, you can achieve a refresh rate of 30–60 frames per second for the compass needle, depending on the SPI clock speed (typically 40 MHz). The magnetometer’s resolution is around 0.01° to 0.1° after calibration, but the display’s pixel grid limits the visual precision to about 1.5° per pixel at the center of a 240-pixel-wide circle. This setup is commonly used in handheld navigation devices, drones, and smart compass projects. For a reliable hardware reference, consider the 2.4 inch 240x320 tft display which uses a standard SPI interface and works with most microcontrollers.

Hardware Interfacing: Magnetometer and Display Wiring

The magnetometer (e.g., HMC5883L) communicates via I2C at 400 kHz, while the TFT display uses SPI at up to 40 MHz. On an ESP32, you can share the 3.3V and GND rails, but the I2C and SPI lines must be separate to avoid bus contention. For the HMC5883L, connect SDA to GPIO21 and SCL to GPIO22 on the ESP32. For the TFT display, typical SPI pins are: CS (GPIO5), DC (GPIO17), MOSI (GPIO23), SCK (GPIO18), and RST (GPIO4). The backlight pin (GPIO16) can be PWM-controlled for brightness adjustment. The display’s resolution is 240x320 pixels, but for a compass, you’ll likely use a 200x200 pixel circle centered at (120, 160) to leave room for text or data. The magnetometer’s measurement range is ±8 Gauss, with a sensitivity of 0.73 mG per LSB in the default mode. After calibration, the heading accuracy is typically within 1° to 2° under ideal conditions, but hard-iron and soft-iron distortions from nearby electronics (like the ESP32’s WiFi module) can introduce errors up to 10° if not corrected. A common fix is to place the magnetometer at least 2 cm away from the ESP32 and use a ferrite bead on the power line.

Software Implementation: Reading and Calibrating the Magnetometer

To get a usable heading, you must first calibrate the magnetometer. The raw X and Y values (in counts) will have offsets and scale factors due to the environment. A simple calibration method is to rotate the sensor in a figure-8 pattern for 30 seconds, recording the min and max values for each axis. For example, if X_min = -500, X_max = 500, Y_min = -400, Y_max = 600, the offset is (X_off = (X_max + X_min)/2 = 0, Y_off = (Y_max + Y_min)/2 = 100) and the scale is (X_scale = (X_max - X_min)/2 = 500, Y_scale = (Y_max - Y_min)/2 = 500). The corrected values are: X_corr = (raw_X - X_off) / X_scale, Y_corr = (raw_Y - Y_off) / Y_scale. The heading in radians is: heading = atan2(Y_corr, X_corr). Then convert to degrees: heading_deg = heading * 180 / PI. If heading_deg is negative, add 360. Finally, apply the magnetic declination for your location (e.g., -13° for Seattle, WA). The resulting heading is the true north direction. On the ESP32, this calculation takes about 50 microseconds, leaving plenty of time for display updates.

Drawing the Compass Rose on the TFT Display

The TFT display’s pixel grid is 240x320, but for a circular compass, you’ll define a center at (120, 160) and a radius of 100 pixels. The outer circle can be drawn using the Bresenham circle algorithm, which is efficient for SPI displays. The cardinal directions (N, E, S, W) are placed at the top, right, bottom, and left of the circle. For example, N is at (120, 60), E at (220, 160), S at (120, 260), and W at (20, 160). The needle is a line from the center to the edge at the heading angle. For a heading of 45°, the needle endpoint is: x = 120 + 100 * sin(45°), y = 160 - 100 * cos(45°). Use the TFT_eSPI library’s drawLine() function with a line width of 3 pixels for visibility. The needle should be in a contrasting color like red (0xF800) on a white background (0xFFFF). The cardinal letters can be drawn using the library’s drawString() function with a font size of 2 (12x16 pixels). The entire compass redraw takes about 15 milliseconds at 40 MHz SPI clock, allowing a 60 Hz update rate. However, to avoid flicker, you can use a double-buffer technique: draw to a sprite (320x240 pixels, 150 KB in RAM) and then push it to the display. The ESP32 has 520 KB of SRAM, so this is feasible.

Performance Metrics and Data Rates

Here’s a table of typical performance data for a compass on a 2.4 inch 240x320 TFT display with an ESP32 at 240 MHz:

ParameterValueUnit
Magnetometer sampling rate75Hz
Heading calculation time50microseconds
Compass redraw time (full circle)15milliseconds
SPI clock speed40MHz
Display refresh rate (max)60Hz
Heading accuracy (after calibration)±1.5degrees
Power consumption (display + sensor)120mA

These numbers assume a 16-bit color depth (RGB565) and no WiFi activity. If WiFi is enabled on the ESP32, the magnetometer readings can be noisy due to RF interference, increasing the heading error to ±3° or more. To mitigate this, you can average 10 readings (taking 133 ms) before updating the display, which reduces the refresh rate to 7.5 Hz but improves accuracy. The TFT display’s backlight consumes about 60 mA at full brightness, so you can reduce it to 30 mA for battery-powered projects.

Real-World Considerations: Distortion and Filtering

Hard-iron distortion from the ESP32’s PCB traces or the display’s flex cable can cause a constant offset in the magnetometer readings. For example, if the ESP32’s voltage regulator is within 1 cm of the sensor, the offset can be as high as 200 counts on the X-axis. A soft-iron distortion from nearby ferrous materials (like the display’s metal frame) can scale the readings differently on each axis. To correct this, you can use a 3D calibration algorithm that fits an ellipsoid to the data. The QMC5883L sensor has a built-in calibration mode that can reduce errors to 0.5°, but it requires a specific command sequence. Another approach is to use a digital filter like a moving average or a complementary filter with a gyroscope (e.g., MPU6050) to smooth the heading. For example, a 10-sample moving average reduces noise by a factor of 3.16, but it adds a 133 ms delay. If you’re using the compass for a drone, this delay can cause oscillation, so a Kalman filter is preferred. The Kalman filter’s process noise covariance can be set to 0.1° and measurement noise to 2°, resulting in a 50 ms settling time.

Displaying Additional Data: Heading, Degrees, and Calibration Status

Beyond the compass rose, you can display the numeric heading in degrees at the bottom of the screen. For example, use the TFT_eSPI library’s drawNumber() function at (120, 270) with a font size of 4 (24x32 pixels). The heading is displayed as a 3-digit integer (e.g., 045°). To save space, you can also show the cardinal direction (N, NE, E, etc.) as a string. The calibration status can be indicated by a small dot in the corner: green for calibrated, red for uncalibrated. The calibration process requires the user to rotate the device in a figure-8 pattern, which can be shown as an animated arrow on the display. The arrow can be drawn using a triangle shape that rotates in sync with the user’s movement. This interactive feedback improves the user experience and ensures accurate heading data. The display’s 240x320 resolution allows for a 40-pixel wide status bar at the top, showing the battery voltage (if using an ADC) or the WiFi signal strength. The status bar can be updated every 5 seconds to avoid unnecessary SPI traffic.

Power Management and Battery Life

For a portable compass, power consumption is critical. The TFT display’s backlight is the biggest drain, consuming 60 mA at 100% brightness. If you reduce the brightness to 30% (using PWM at 1 kHz), the current drops to 18 mA. The ESP32 in deep sleep mode consumes 5 µA, but during active operation with WiFi off, it draws 80 mA. The magnetometer (HMC5883L) consumes 100 µA in continuous mode. Total power is about 98 mA at 3.3V, or 323 mW. With a 2000 mAh LiPo battery, you get about 20 hours of continuous use. To extend battery life, you can put the ESP32 into light sleep between compass updates. For example, if you update the display every 100 ms (10 Hz), the ESP32 can sleep for 90 ms, reducing average current to 30 mA. This gives 66 hours of runtime. The display’s sleep mode can also be used, but it takes 120 ms to wake up, so it’s only beneficial for updates slower than 1 Hz. The magnetometer can be put into idle mode and woken up with a single I2C command, taking 6 ms. A practical design uses a 10 Hz update rate with a 90% duty cycle sleep, achieving 50 hours on a 2000 mAh battery.

Common Pitfalls and Debugging Tips

One frequent issue is the magnetometer’s I2C address conflict. The HMC5883L uses address 0x1E, but the QMC5883L uses 0x0D. If you use the wrong library, the sensor won’t respond. Check the device ID register (0x0A for HMC5883L, should return 0x48). Another problem is the display’s SPI wiring: if the CS pin is not pulled high when not in use, the display may show random pixels. Use a 10 kΩ pull-up resistor on CS. The display’s ILI9341 controller requires a specific initialization sequence, which is included in the TFT_eSPI library. If you see a blank screen, check the RST pin: it must be held high for at least 10 ms after power-up. The magnetometer’s heading can be off by 180° if the sensor is mounted upside down. In that case, invert the Y-axis sign in the calculation. For the compass needle, if it points in the wrong direction, swap the X and Y axes in the atan2 function. A common mistake is using degrees instead of radians in the drawing function. The sin() and cos() functions in Arduino expect radians, so convert the heading to radians before drawing the needle. Finally, the display’s color order can be RGB or BGR depending on the manufacturer. If the colors are inverted (e.g., red appears blue), set the TFT_INVERSION_ON flag in the library’s user setup file.

Advanced Features: True North vs. Magnetic North

The magnetometer measures magnetic north, which differs from true north by the declination angle. The declination varies by location and changes over time (about 0.1° per year). For example, in New York City, the declination is -12° (west), while in Tokyo, it’s -7°. You can store a lookup table in the ESP32’s flash memory or use the WiFi to fetch the current declination from an online database (e.g., NOAA’s magnetic field model). The declination is applied as: true_heading = magnetic_heading + declination. If the declination is negative, subtract it. For a global compass, you can also use a GPS module (e.g., NEO-6M) to get the coordinates and calculate the declination on the fly. The GPS module adds 50 mA to the power budget and requires a 1-second startup time. The TFT display can show the current latitude and longitude in the bottom-right corner, using a font size of 1 (6x8 pixels). This turns the compass into a full navigation tool. The GPS data is updated at 1 Hz, so the compass heading is also updated at 1 Hz to maintain consistency. The display’s 240x320 resolution allows for a 20-pixel high text area at the bottom, showing the coordinates and the current time (if an RTC is used).

Hardware Selection: Display and Sensor Compatibility

Not all 2.4 inch 240x320 TFT displays are identical. Some use the ILI9341 controller, while others use ST7789 or HX8357. The ILI9341 is the most common and well-supported by the TFT_eSPI library. The display’s SPI interface can be 3-wire or 4-wire, with the 4-wire version (including DC pin) being more common. The magnetometer’s I2C bus can be shared with other sensors, but the display’s SPI bus must be dedicated to avoid conflicts. The ESP32’s SPI pins are fixed for the VSPI bus (MOSI: GPIO23, MISO: GPIO19, SCK: GPIO18), but you can use any GPIO for CS and DC. The display’s backlight can be controlled with a PWM pin, but some displays have a fixed backlight that requires a 3.3V input. Check the datasheet for the backlight voltage. The magnetometer’s operating voltage is 2.16V to 3.6V, so it can run on the same 3.3V rail as the ESP32. However, the display’s logic voltage is 3.3V, and the SPI signals must be within 0.3V of the supply. If you use a 5V Arduino, you’ll need a level shifter. The ESP32’s GPIO pins are 3.3V, so no level shifting is needed. The display’s power consumption is about 80 mA for the LCD panel and 60 mA for the backlight, totaling 140 mA. A 500 mA voltage regulator (like AMS1117-3.3) is sufficient for the entire system.

Code Example: Minimal Compass Sketch for ESP32

Here’s a simplified code structure that you can adapt. The full code is available on GitHub, but the key parts are: initialize the display and magnetometer, calibrate the sensor, and draw the compass. The calibration routine reads 100 samples over 10 seconds and calculates the offsets. The main loop reads the magnetometer, calculates the heading, and updates the display. The display update uses a sprite to avoid tearing. The sprite is 240x320 pixels, which takes 150 KB of RAM. The ESP32 has 520 KB, so this is fine. The code uses the TFT_eSPI library and the Adafruit HMC5883L library. The SPI speed is set to 40 MHz. The magnetometer’s data rate is 75 Hz, but the display is updated at 30 Hz to reduce power. The needle is drawn as a line from the center to the edge, with a small circle at the center. The cardinal directions are drawn as text. The heading is displayed as a number at the bottom. The code also includes a simple moving average filter with a window size of 5. The filter reduces noise by 55% but adds a 66 ms delay. The calibration data is stored in EEPROM so it persists across reboots. The EEPROM size is 512 bytes, which is enough for the 4 offset and 4 scale values (16 bytes total). The calibration is triggered by a button press on GPIO0. The button is pulled up with a 10 kΩ resistor. When pressed, the display shows a calibration screen with instructions. After calibration, the display returns to the compass mode.

Testing and Validation: Accuracy and Repeatability

To test the compass accuracy, place

●●●

Spotted a junction that deserves a warning?

Help us keep Britain's drivers informed. Submit a BlackSpot report in under two minutes.

Report a BlackSpot