Skip to content

Comments

drivers/lcd: Add ST7796 TFT LCD framebuffer driver#18301

Merged
acassis merged 1 commit intoapache:masterfrom
vrmay23:up_st7796_driver
Feb 19, 2026
Merged

drivers/lcd: Add ST7796 TFT LCD framebuffer driver#18301
acassis merged 1 commit intoapache:masterfrom
vrmay23:up_st7796_driver

Conversation

@vrmay23
Copy link
Contributor

@vrmay23 vrmay23 commented Feb 1, 2026

Add support for ST7796 TFT LCD controller (320x480). The driver implements the NuttX framebuffer interface for SPI-connected displays.

Features:

  • SPI interface with CONFIG_SPI_CMDDATA for D/C pin control
  • RGB565 (16-bit) and RGB666 (18-bit) color formats
  • Runtime rotation support (0, 90, 180, 270 degrees) via MADCTL
  • Board-provided configuration via st7796_config_s structure
  • Partial screen update via updatearea for efficient rendering
  • Persistent swap buffer to avoid per-frame allocations
  • Proper ST7796S initialization sequence with documented timing

The driver uses a board-provided configuration structure allowing flexible setup of resolution, SPI frequency, color depth, and initial MADCTL value without requiring Kconfig options in the generic driver.

Tested with LVGL graphics library on STM32H7 nucleo0h753zi platform.

Note: Please adhere to Contributing Guidelines.

Summary

Add ST7796 TFT LCD framebuffer driver for 320x480 SPI-connected displays.
The ST7796 is a popular TFT controller used in 3.5" IPS LCD modules. This driver implements the NuttX framebuffer interface (fb_vtable_s) allowing integration with graphics libraries like LVGL.
Key features:

SPI interface using CONFIG_SPI_CMDDATA for D/C pin control
RGB565 (16-bit) and RGB666 (18-bit) color formats
Runtime rotation support (0, 90, 180, 270 degrees) via MADCTL register
Board-provided configuration via st7796_config_s structure
Partial screen update via updatearea for efficient rendering
Persistent swap buffer to avoid per-frame memory allocations

The driver follows the same pattern as existing LCD drivers (ST7789, GC9A01) and does not introduce board-specific dependencies in the generic driver code.

Impact

New driver: drivers/lcd/st7796.c
New header: include/nuttx/lcd/st7796.h
Updated: drivers/lcd/Kconfig, Make.defs, CMakeLists.txt
No breaking changes to existing code
Requires CONFIG_SPI_CMDDATA enabled for D/C pin control

Testing

Hardware: STM32H753ZI Nucleo + 3.5" ST7796 IPS LCD (SPI)
NuttX version: 12.12
Tested with:

fb example (framebuffer color test)
LVGL graphics library (animations, widgets)
Runtime rotation via FBIOSET_ROTATION ioctl

Build configurations: nsh, fb, lvgl
PRs without testing information will not be accepted. We will
request test logs.

@github-actions github-actions bot added Area: Drivers Drivers issues Area: Graphics Size: XL The size of the change in this PR is very large. Consider breaking down the PR into smaller pieces. labels Feb 1, 2026
@vrmay23
Copy link
Contributor Author

vrmay23 commented Feb 2, 2026

So, let me organize the needs:

1 - Remove the duplicated guards for SPID_CMDDATA; # error "CONFIG_SPI_CMDDATA must be enabled for ST7796"
AND put it at beggining.

2 - Remove the redundancy check "#ifdef CONFIG_LCD_ST7796".

3 - Fix Kconfig:
bool "ST7796 LCD Display"
default n
depends on SPI
depends on LCD_FRAMEBUFFER

4 - removing from public api to .c:

ST7796_NOP
ST7796_SWRESET
ST7796_RDDID
ST7796_RDDST
ST7796_SLPIN
ST7796_SLPOUT
ST7796_PTLON
ST7796_NORON
ST7796_RDIMGFMT
ST7796_RDSELFDIAG
ST7796_INVOFF
ST7796_INVON
ST7796_GAMMASET
ST7796_DISPOFF
ST7796_DISPON
ST7796_CASET
ST7796_RASET
ST7796_RAMWR
ST7796_RAMRD
ST7796_PTLAR
ST7796_VSCRDEF
ST7796_TEOFF
ST7796_TEON
ST7796_MADCTL
ST7796_VSCRSADD
ST7796_PIXFMT
ST7796_WRDISPBRIGHT
ST7796_RDDISPBRIGHT
ST7796_WRCTRLD
ST7796_RDCTRLD
ST7796_WRCABC
ST7796_RDCABC
ST7796_WRCABCMIN
ST7796_RDCABCMIN
ST7796_INVCTR
ST7796_DFC
ST7796_PWCTRL1
ST7796_PWCTRL2
ST7796_PWCTRL3
ST7796_PWCTRL4
ST7796_PWCTRL5
ST7796_VCOM
ST7796_PWCTRL6
ST7796_GAMMAPOS
ST7796_GAMMANEG
ST7796_DOCA
ST7796_CSCON
ST7796_MADCTL_MY
ST7796_MADCTL_MX
ST7796_MADCTL_MV
ST7796_MADCTL_ML
ST7796_MADCTL_BGR
ST7796_MADCTL_MH
struct st7796_cmd_s

4.1 - keep in public api

ST7796_XRES_RAW
ST7796_YRES_RAW
ST7796_SPI_MAXFREQUENCY
ST7796_MADCTL_PORTRAIT
ST7796_MADCTL_PORTRAIT_BGR
ST7796_MADCTL_RPORTRAIT
ST7796_MADCTL_RPORTRAIT_BGR
ST7796_MADCTL_LANDSCAPE
ST7796_MADCTL_LANDSCAPE_BGR
ST7796_MADCTL_RLANDSCAPE
ST7796_MADCTL_RLANDSCAPE_BGR
struct st7796_config_s

5 - Let me share also images and logs.

image image image

@vrmay23 vrmay23 requested a review from michallenc February 2, 2026 19:42
jerpelea
jerpelea previously approved these changes Feb 3, 2026
@vrmay23
Copy link
Contributor Author

vrmay23 commented Feb 11, 2026

Hello. I have made the changes and proceed with the tests as below:

image image image image

@vrmay23 vrmay23 requested a review from linguini1 February 11, 2026 20:19
acassis
acassis previously approved these changes Feb 12, 2026
@acassis
Copy link
Contributor

acassis commented Feb 12, 2026

@michallenc PTAL

Copy link
Contributor

@michallenc michallenc left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My original objection still stands - I don't think creating the controller with direct framebuffer support is the right approach, it goes against the current practices in LCD driver codes. It's unusable on embedded devices with small RAMs (primary NuttX targets) and even in a violation with the documentation

Finally, the LCD screen drivers are usually available at drivers/lcd/ and implement the callbacks defined at include/nuttx/lcd/lcd.h
include/nuttx/lcd/lcd.h provides structures and APIs needed to work with LCD screens, whether using the framebuffer adapter or the LCD Character Drivers;

Though yes, there is the word usually. As I mentioned in my review, the controller should provide LCD interface and thus support BOTH framebuffer approach and LCD chardev approach.

I am not gonna block this if you want to merge it, but adding a flawned design to the codebase is a dangerous slippery slope.

@michallenc michallenc dismissed their stale review February 12, 2026 13:16

As described above.

@acassis
Copy link
Contributor

acassis commented Feb 12, 2026

My original objection still stands - I don't think creating the controller with direct framebuffer support is the right approach, it goes against the current practices in LCD driver codes. It's unusable on embedded devices with small RAMs (primary NuttX targets) and even in a violation with the documentation

Finally, the LCD screen drivers are usually available at drivers/lcd/ and implement the callbacks defined at include/nuttx/lcd/lcd.h
include/nuttx/lcd/lcd.h provides structures and APIs needed to work with LCD screens, whether using the framebuffer adapter or the LCD Character Drivers;

Though yes, there is the word usually. As I mentioned in my review, the controller should provide LCD interface and thus support BOTH framebuffer approach and LCD chardev approach.

I am not gonna block this if you want to merge it, but adding a flawned design to the codebase is a dangerous slippery slope.

@michallenc understood your point, could you please provide some driver reference that he could use? And explain which modification he needs to do (since he explained doesn't have experience with this kind of driver and is new to NuttX) ?
Thank you for understanding

@michallenc
Copy link
Contributor

My original objection still stands - I don't think creating the controller with direct framebuffer support is the right approach, it goes against the current practices in LCD driver codes. It's unusable on embedded devices with small RAMs (primary NuttX targets) and even in a violation with the documentation

Finally, the LCD screen drivers are usually available at drivers/lcd/ and implement the callbacks defined at include/nuttx/lcd/lcd.h
include/nuttx/lcd/lcd.h provides structures and APIs needed to work with LCD screens, whether using the framebuffer adapter or the LCD Character Drivers;

Though yes, there is the word usually. As I mentioned in my review, the controller should provide LCD interface and thus support BOTH framebuffer approach and LCD chardev approach.
I am not gonna block this if you want to merge it, but adding a flawned design to the codebase is a dangerous slippery slope.

@michallenc understood your point, could you please provide some driver reference that he could use? And explain which modification he needs to do (since he explained doesn't have experience with this kind of driver and is new to NuttX) ? Thank you for understanding

Sure, I already mentioned ST7789 controller, among others there are ST7735 or SSDxxxx controllers. I think the ST7796 driver in this MR is good, it just needs the putrun(), getrun(), putarea() instead of direct control by framebuffer IOCTL. I know @vrmay23 had valid reasons why do it with framebuffer because of API, but that API can be used with LCD char dev as well as I described before.

I wouldn't oppose to this if it would be fixable in subsequent patches, but that would mean removing st7796_fbinitialize and thus breaking boards already using it - not as problematic as API change, but still nasty.

@vrmay23
Copy link
Contributor Author

vrmay23 commented Feb 12, 2026

Well, I kind of understand what you mean. Basically is up to the user to decides either to use a FB or a LCD driver. This is clear.

However, please, don't get me wrong, but if we think carefully: The "RAM" argument is self-defeating (or my understanding can be wrong, for sure and if it is the case, correct me). But let's see what I thought:

1 - Small display means small framebuffer that fits in small MCU RAM.

2 - Large display already requires a capable MCU by nature (SPI bandwidth, processing power, peripheral complexity). So, any one pairing a 320x480 display with a 20KB MCU (without external ram) would face bigger problems than the framebuffer size the system simply wouldn't work for any meaningful use case.

3 - The lcd_dev_s line-by-line approach on a small MCU with a large display is technically possible but practically useless, because you cannot render anything meaningful on 320x480 without a proper buffer.

Therefore, the "low RAM" advantage of lcd_dev_s only applies to small displays, where the framebuffer is also small and
fits in RAM anyway. Hence, the argument defeats itself.

I can for sure improve it, that's the idea in the end. Work as much as possible to support NuttX to grow, but not only grow (become better). I just need more time and knowledge :)

@michallenc
Copy link
Contributor

2 - Large display already requires a capable MCU by nature (SPI bandwidth, processing power, peripheral complexity). So, any one pairing a 320x480 display with a 20KB MCU (without external ram) would face bigger problems than the framebuffer size the system simply wouldn't work for any meaningful use case.

That is good point, however the size of framebuffer of 320x480 takes about 300 kB of RAM - that's an extreme value even if we don't consider really small MCUs with let's say 20 kB RAM (you would probably even have problem running NuttX on it unless you only need shell and one or two peripherals). But a lot of MCUs used in embedded environment don't have mega bytes of RAM, but rather hundreds of kBs - SAM series has up to 384 kBs, the new PIC series from Microchip usually 512 kBs, lot of STM32 chips still have somewhat between 300-500 kBs (some exceptions with 1 MB) and the same goes for ESP32-C series if I remember correctly. I think these are all MCUs that are considered capable, are widely used yet still barely have enough RAM for 300 kBs large framebuffer.

I am talking from first hand experience here, we use SAMv7 MCU in our company embedded products and we used to run LVGL with framebuffer API on ST7789. It was quite ok with 135x240 display, but we have one product with 320*240 display as well and that is where we hit the limit. Yes, the frambuffer fits into the memory, but consumes almost half of it (plus LVGL itself is pretty hungry RAM considered). And we switched to LCD chardev instead, that is also supported by LVGL for NuttX (LV_USE_NUTTX_LCD), so it was pretty straightforward.

3 - The lcd_dev_s line-by-line approach on a small MCU with a large display is technically possible but practically useless, because you cannot render anything meaningful on 320x480 without a proper buffer.

LVGL allows you to initialize it's own buffer (LV_NUTTX_LCD_BUFFER_SIZE and LV_NUTTX_LCD_BUFFER_COUNT),. We set it to cache about 80 or something lines and it gets you the smoother redraw. It's on 320x240 display, not 320x480, but it's pretty good on it.

I can for sure improve it, that's the idea in the end. Work as much as possible to support NuttX to grow, but not only grow (become better). I just need more time and knowledge :)

Sure, I don't want to be mean or something like that, you did a really good job with the driver. And I understand it's tiring and time consuming to be stuck on a merge request that you might need to get merged because other development depends on it - I've been there as well. I suppose I don't mind merging it and changing it later, the only nasty thing is the function st7796_fbinitialize would probably be removed/changed and that would break board support package API. That still isn't so critical as normal API and it wouldn't affect many people since the driver is new now (likely it would affect just you) so I suppose I am fine with that.

This merge request also shown the lack of proper documentation and that's on us. We should probably have driver interface section in the docs as well, because API itself doesn't tell you how to implement the controller itself.

@vrmay23
Copy link
Contributor Author

vrmay23 commented Feb 15, 2026

@linguini1 it is done!
I have moved FBIOSET_ROTATION as value 0x1e to the fb.h file.
Please, kindle review it again.

BR,
Vinicius

image image

testing the rotation
image

image

Add support for ST7796 TFT LCD controller (320x480). The driver
implements the NuttX framebuffer interface for SPI-connected displays.

Features:
- SPI interface with CONFIG_SPI_CMDDATA for D/C pin control;
- RGB565 (16-bit) and RGB666 (18-bit) color formats;
- Runtime rotation support (0, 90, 180, 270 degrees) via MADCTL;
- Board-provided configuration via st7796_config_s structure;
- Partial screen update via updatearea for efficient rendering;
- Persistent swap buffer to avoid per-frame allocations;
- Proper ST7796S initialization sequence with documented timing;

The driver uses a board-provided configuration structure allowing
flexible setup of resolution, SPI frequency, color depth, and
initial MADCTL value without requiring Kconfig options in the
generic driver.

Architecture changes in this revision as per request:

- Moved internal register commands to .c file (private)
- Moved MADCTL bit definitions to .c file (private)
- Moved struct st7796_cmd_s to .c file (private)
- Converted MADCTL orientation macros to absolute values
- Moved CONFIG_SPI_CMDDATA error check to beginning of file
- Removed duplicated CONFIG_SPI_CMDDATA guards
- Public header contains only board configuration API
- Changed the Kconfig auto-select FB from 'select' to 'depends on'

Tested with LVGL graphics library on STM32H753ZI board (my own port).

Signed-off-by: Vinicius May <vmay.sweden@gmail.com>
@vrmay23
Copy link
Contributor Author

vrmay23 commented Feb 15, 2026

I guess it is all done from my side, right @acassis ?
Thanks

@acassis
Copy link
Contributor

acassis commented Feb 18, 2026

@michallenc PTAL

@acassis acassis merged commit 96a49bc into apache:master Feb 19, 2026
64 of 77 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Area: Drivers Drivers issues Area: Graphics Area: Video Size: XL The size of the change in this PR is very large. Consider breaking down the PR into smaller pieces.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

7 participants