This article will detail how to write a .NET IoT device driver from scratch using the NV3030B LCD device driver as an example. We will use the Raspberry Pi as the hardware platform and refer to the official .NET IoT Ili934x TFT LCD driver library.
1. Background
In the previous article “Getting Started with .NET IoT Development”, we mentioned methods for developing drivers in .NET IoT. By leveraging cross-disciplinary learning, you can first operate according to these example codes and then rewrite them in .NET. Here, we will take the NV3030B LCD device driver as an example to detail how to write a .NET IoT device driver from scratch.
2. Preparation
Before we start writing the driver, we need to ensure the following:
• Raspberry Pi (this article uses Raspberry Pi Zero 2 W as an example)• Weixue 1.5-inch LCD screen, NV3030B driver chip• SPI wiring• .NET 8.0 or higher• Official Weixue Python driver example code
The 40 Pin wiring of the Raspberry Pi is generally consistent; you can refer to the Raspberry Pi GPIO pinout diagram[1].

The wiring between the display and the Raspberry Pi is as follows:


3. Verify SPI Wiring and Device
Before writing the .NET driver, we first need to verify that the SPI wiring and device are functioning correctly. We can use the example Python code provided by Weixue to perform the test, which includes the Python version of the driver code.
It is important to note: If you have not previously installed the numpy and pillow libraries, you need to install them first:
sudo apt install python3-numpy python3-pillow
Then run the example code and observe whether the display works correctly. If the screen lights up and displays content, it indicates that the wiring and device are functioning properly, and we can proceed to the next step.
4. Writing the .NET Driver
In previous articles, we mentioned that before connecting a device, we should check the support status of the official driver library. If a supported device is available, purchase it; if not, refer to the official driver library or other open-source projects. If all else fails, we can look for similar drivers from the official sources, modify them, and they can still work. Here, I found the official Ili934x TFT LCD driver library[2], which we can use as a reference for designing the NV3030B LCD driver.
Most of the code in this library can be used directly; we only need to modify some initialization and detail codes. We will use this library as a foundation to write the NV3030B LCD driver.
4.1 Quickly Set Up the Project Using GitHub Copilot
Of course, in the era of large models, who still writes code by hand? Writing is impossible; these tedious tasks are left to AI. We just need to tell AI what we want to do, and it will help us generate the code. Now, we directly invoke the GitHub Copilot, asking it to reference the Python driver and help us complete the NV3030B driver based on the <span>Ili934x</span> library implementation:

Of course, the code generated by AI is not 100% accurate; we still need to review it one by one to ensure its correctness. We will not list them all here. Then, based on the AI’s generated code, we will either modify it ourselves or guide AI to generate the correct code.
For GitHub Copilot, I feel that the best IDE to use is still VS Code, at least it provides a better experience than the integrated GitHub Copilot in Visual Studio. Both IDEs have their pros and cons, and I generally use them in conjunction.
Again, it is crucial to conduct thorough reviews; this is a key point that will be tested later.
For this driver, since we are referencing the Ili934x driver library, this library inherits from the <span>GraphicDisplay</span> class, so we will also inherit this class and need to install the following library:
dotnet add package Iot.Device.Bindings.SkiaSharpAdapter
4.2 Areas That Need Modification
Although AI has generated most of the code for us, there are still some areas that need manual modification to meet our preferences and correct some errors. Compared to the original <span>Ili934x</span> driver library, the modifications we need to make are actually few, mainly in the constructor class main file and the <span>Command</span> file.
For the <span>Command</span> file, we need to modify some initialization commands and special commands based on the NV3030B data sheet. Here, we can directly modify according to the initialization commands of the Python driver. Although AI has helped a lot, in the end, due to debugging failures, I had to verify this command and the implementation code one by one several times o(╥﹏╥)o.
Besides the initialization code and basic configuration information, such as frequency and screen size, there are also some detail codes that need modification:
In the <span>SetWindow</span> method, according to the Python driver, there is an offset for <span>y0</span> and <span>y1</span>; we also need to apply the same offset here:
/// <summary>/// Set display window for drawing/// </summary>private void SetWindow(int x0, int y0, int x1, int y1){ y0 += 20; y1 += 20; /* ... */}
Other than that, there is basically no need for modification. Here, we optimized the dimming; the original only had high and low levels, and we added a PWM dimming to better control brightness:
public NV3030B(SpiDevice spiDevice, int dataCommandPin, int resetPin, int backlightPin = -1, int backlight_frequency = 1000, int spiBufferSize = DefaultSPIBufferSize, GpioController? gpioController = null, bool shouldDispose = true){ /* ... */ if (_backlightPin != -1) { _gpioDevice.OpenPin(_backlightPin, PinMode.Output); _backlightChannel = new SoftwarePwmChannel(backlightPin, backlight_frequency,controller: _gpioDevice); _backlightChannel.Start(); SetBacklight(100); } /* ... */}
/// <summary>/// Set backlight brightness (0-100)/// </summary>public void SetBacklight(int brightness){ if (_backlightChannel is null) throw new InvalidOperationException(“Backlight pin not configured”); if (brightness < 0 || brightness > 100) throw new ArgumentOutOfRangeException(nameof(brightness), “Brightness must be between 0 and 100”); double dutyCycle = brightness / 100.0; _backlightChannel.DutyCycle = dutyCycle;}
At this point, we should have made most of the necessary modifications, and we should consider the NV3030B driver writing complete. However, this is not the end; we need to test it.
5. Testing the Driver
Before testing, we need to write a simple test program to initialize the display and show some content, which should include basic graphics drawing and image display tests. The image can directly use the test image from the Python driver <span>LCD_1inch5.jpg</span>.
// Test basic graphicsdisplay.ClearScreen(System.Drawing.Color.Red, true);Task.Delay(10000).Wait();Console.WriteLine("Testing fill rectangle...");display.FillRect(System.Drawing.Color.Blue, 0, 0, 100, 100);display.FillRect(System.Drawing.Color.Green, 100, 0, 100, 100);display.SendFrame(false);Task.Delay(10000).Wait();using var image = BitmapImage.CreateFromFile("LCD_1inch5.jpg");display.DrawBitmap(image);
Deploy the program to the Raspberry Pi, run the test program, and observe whether the display shows content correctly. If everything is normal, congratulations, you have successfully written the NV3030B driver. However, we encountered an unexpected issue:

The entire image display is skewed and fragmented; is this still our square? (▼へ▼メ)
Then, I began a debugging process that lasted four to five days, during which I consulted various large models and tried all the suggestions given, but none resolved the issue. After several tests, I found that by adding data processing after the original <span>LCD_1inch5.py</span> ShowImage method and using .NET to read the binary data directly to send to the screen, it displayed correctly. Based on this breakthrough, I eventually discovered that the issue was still with the <span>SetWindow</span> method. In Python, in addition to the <span>+20</span> adjustment, there is also a <span>-1</span> adjustment, while the original library <span>Ili9341</span> directly sends the <span>byte</span> using the <span>SendBitmapPixelData</span> method, which has a <span>-1</span> operation, so it works correctly.
Finally, the mystery was solved, but this issue had troubled me for several days, and I had several rounds of contention with the large models; sometimes they are really stubborn, acknowledging the problem but refusing to change.
All the code you saved using large models will turn into tears when you encounter pitfalls.
However, it is a good thing; originally, the large model was familiar with this code, and now I am also quite familiar with it. When I have time later, and with multiple displays connected, I will extract a base class to consolidate these common parts and optimize them for quick integration of new displays in the future.
6. Conclusion
After completing the driver writing, we can publish it on NuGet for other developers to use. We will not elaborate on this here; the project is open-source, and those interested can refer to NV3030B[3].
This article detailed how to write a .NET IoT device driver for the NV3030B from scratch. By verifying the SPI wiring and ensuring the device operates correctly, we gradually migrated the Python code to .NET, ultimately achieving the driver writing and testing. I hope this article helps you better understand and master .NET IoT development. If you have any questions or suggestions, feel free to leave a comment.
References
<span>[1]</span> Raspberry Pi GPIO pinout diagram: https://pinout.xyz/<span>[2]</span> Ili934x TFT LCD driver library: https://github.com/dotnet/iot/tree/main/src/devices/Ili934x?wt.mc_id=DT-MVP-5005195<span>[3]</span> NV3030B: https://github.com/sangyuxiaowu/NV3030B?wt.mc_id=DT-MVP-5005195