SPI/I2C Drive OLED (C)
This article aims to provide a detailed guide on how to use Luckfox Pico Plus to drive a 1.3-inch screen (the Pico version differs in right-side pin numbering). We are using Waveshare's Pico-OLED-1.3. For specific screen parameters, you can refer to the Pico-OLED-1.3 product wiki. You can download the image file and sample program to use directly or follow the steps below to configure it yourself.
- Example Program:Pico OLED Code
- Image file:Pico Image File & Plus Image File & Pro/Max Image File
- Device Tree File:Pico Device Tree File & Plus Device Tree File & Pro/Max Device Tree File
1. Pin Mapping
Ensuring that all pins used for OLED communication function properly is a basic requirement for driving the OLED. Therefore, we need to configure pin functionality in the device tree file. Before that, we need to determine the pins on Luckfox Pico Plus to which the Pico-OLED-1.3 is connected. Let's take the example of user button 0 on Pico-OLED-1.3, which corresponds to pin GP15. On Luckfox Pico Plus, its pin number is 20, GPIO pin name is GPIO1_D1_d, and its pin number is 57.
Pico-OLED-1.3
Luckfox Pico
Luckfox Pico Plus
Luckfox Pico Pro/Max
2. Modify Device Tree
You can see that the default function of GPIO1_D1_d is UART3 in the device tree file. However, in our program, we want to detect button states by reading the level of this pin. Therefore, we need to configure this pin as a regular GPIO.
Modify the Device Tree File
In the device tree file, we need to add definitions for OLED-related pins and enable SPI/I2C functionality. Let's take a look at how to modify the device tree file.
Device tree file paths
Luckfox Pico:
<SDK directory>/sysdrv/source/kernel/arch/arm/boot/dts/rv1103g-luckfox-pico.dts
Luckfox Pico Plus:
<SDK directory>/sysdrv/source/kernel/arch/arm/boot/dts/rv1103g-luckfox-pico-plus.dts
Define GPIO
Defining a GPIO typically requires adding two sections of code. Below is an example showing how to define the GPIO1_D1_d pin in the device tree.
The code segment to add is as follows:
/{
gpio1pd1:gpio1pd1 {
compatible = "regulator-fixed";
pinctrl-names = "default";
pinctrl-0 = <&gpio1_pd1>;
regulator-name = "gpio1_pd1";
regulator-always-on;
};
};
&pinctrl {
gpio1-pd1 {
gpio1_pd1:gpio1-pd1 {
rockchip,pins = <1 RK_PD1 RK_FUNC_GPIO &pcfg_pull_up>;
};
};
};Comment Out Peripheral Function of Pins
Disabling the peripheral function of a pin can be achieved by commenting out the corresponding peripheral node in the device tree. Here's an example showing how to disable the PWM function for the GPIO1_D1_d pin in the device tree
Additionally, certain pins, as shown in the following image, may have conflicting assignments and need to be configured as regular GPIO pins:
Configure SPI
While configuring SPI, GPIO1_C2_d is used as the reset pin for the LCD. We need to configure it as a regular IO pin here, so it won't be configured as the MISO pin for SPI, as shown in the image below (please refer to the comment at line 153). To make SPI work properly, you also need to configure the SPI controller and define the necessary SPI pins in the device tree. Here's an example showing how to configure SPI in the device tree:
The code segment to add is as follows:
&pinctrl {
spi0 {
/omit-if-no-ref/
spi0m0_pins: spi0m0-pins {
rockchip,pins =
/* spi0_clk_m0 */
<1 RK_PC1 4 &pcfg_pull_none>,
/* spie_miso_m0 */
/* <1 RK_PC3 6 &pcfg_pull_none>,*/
/* spi_mosi_m0 */
<1 RK_PC2 6 &pcfg_pull_none>;
};
};
};Configure I2C
We are using the I2C3 interface to communicate with the OLED. I2C3 is already enabled by default in the device tree. Here's the code snippet from the device tree that enables I2C3:
Compile by selecting the branch and specifying the development board model
luckfox@luckfox:~/luckfox-pico$ ./build.sh lunch
Compile
luckfox@luckfox:~/Luckfox-Pico/luckfox-pico$ ./build.sh
Reflash the firmware
After compiling, reflash the firmware.
3. Example Program
Pico-OLED-1.3 can achieve display and key detection functions. To implement these features, we need to define pins and implement SPI/I2C communication in the program, and then compile the program using cross-compilation tools. Next, let's take a look at the specific implementation steps.
Pin Definitions
Define the pin numbers in
DEV_Config.h
#define OLED_DC_PIN 34
#define OLED_RST_PIN 51
/*PICO*/
// #define KEY0_PIN 57
// #define KEY1_PIN 136
/*PLUS*/
#define KEY0_PIN 57
#define KEY1_PIN 103Add Read Pin Level Macro Definitions in
DEV_Config.h
#define GET_KEY0 DEV_Digital_Read(KEY0_PIN)
#define GET_KEY1 DEV_Digital_Read(KEY1_PIN)Initialize GPIO in
DEV_GPIO_Init
Functionstatic void DEV_GPIO_Init(void)
{
DEV_GPIO_Mode(OLED_RST_PIN, 1);
DEV_GPIO_Mode(OLED_DC_PIN, 1);
DEV_GPIO_Mode(KEY0_PIN, 0);
DEV_GPIO_Mode(KEY1_PIN, 0);
}Evaluate Pin Levels in
OLED_1in3_c_test.c
/* Key */
while(1)
{
/* Check if the pin level is low */
if(GET_KEY1 == 0){
/* Add operations to perform when user presses key 1 here */
}else{
/* Add operations to perform when user releases key 1 here */
}
if(GET_KEY0 == 0){
/* Add operations to perform when user presses key 0 here */
}else{
/* Add operations to perform when user releases key 0 here */
}
}
SPI/I2C Communication
Select the communication method in
DEV_Config.h
. By default, SPI communication is used. When using I2C communication, you need to modify the resistors on the back of Pico-OLED-1.3.#define USE_SPI 1
#define USE_IIC 0Request SPI/I2C resources in the
DEV_ModuleInit
function.UBYTE DEV_ModuleInit(void)
{
#ifdef USE_DEV_LIB
DEV_GPIO_Init();
#if USE_SPI
printf("USE_SPI\r\n");
DEV_HARDWARE_SPI_beginSet("/dev/spidev0.0",SPI_MODE_3,10000000);
#elif USE_IIC
printf("USE_IIC\r\n");
OLED_DC_0;
OLED_CS_0;
DEV_HARDWARE_I2C_begin("/dev/i2c-3");
DEV_HARDWARE_I2C_setSlaveAddress(0x3c);
#endif
#endif
return 0;
}Initialization
Call the
OLED_1in3_C_Init
function to initialize the OLED in themain
function.void OLED_1in3_C_Init()
{
//Hardware reset
OLED_Reset();
//Set the initialization register
OLED_InitReg();
}Sending Data
In the
OLED_WriteReg
function, pull the OLED_DC pin low to indicate command transmission, then use theDEV_SPI_WriteByte
orI2C_Write_Byte
function to send data to the OLED.static void OLED_WriteReg(uint8_t Reg)
{
#if USE_SPI
OLED_DC_0;
DEV_SPI_WriteByte(Reg);
#elif USE_IIC
I2C_Write_Byte(Reg,IIC_CMD);
#endif
}In the
OLED_WriteData
function, set the OLED_DC pin high to indicate data transmission, then use theDEV_SPI_WriteByte
orI2C_Write_Byte
function to send data to the OLED.static void OLED_WriteData(uint8_t Data)
{
#if USE_SPI
OLED_DC_1;
DEV_SPI_WriteByte(Data);
#elif USE_IIC
I2C_Write_Byte(Data,IIC_RAM);
#endif
}
Cross Compilation
Specify the Cross Compilation Tool
Users should move the entire "c" folder to the virtual machine and edit the
Makefile
file within the "c" folder. Modify the content after CC= in theMakefile
to specify the cross-compilation tool.Replace
<SDK Directory>
with your own SDK path in the Makefile, for example,/home/luckfox/luckfox-pico/
.DIR_Config = ./lib/Config
DIR_EPD = ./lib/OLED
DIR_FONTS = ./lib/Fonts
DIR_GUI = ./lib/GUI
DIR_Examples = ./examples
DIR_BIN = ./bin
OBJ_C = $(wildcard ${DIR_EPD}/*.c ${DIR_Config}/*.c ${DIR_GUI}/*.c ${DIR_Examples}/*.c ${DIR_FONTS}/*.c)
OBJ_O = $(patsubst %.c,${DIR_BIN}/%.o,$(notdir ${OBJ_C}))
TARGET = main
USELIB = USE_DEV_LIB
DEBUG = -D $(USELIB)
ifeq ($(USELIB), USE_DEV_LIB)
LIB = -lpthread -lm
endif
CC = <SDK Directory>/tools/linux/toolchain/arm-rockchip830-linux-uclibcgnueabihf/bin/arm-rockchip830-linux-uclibcgnueabihf-gcc
MSG = -g -O0 -Wall
CFLAGS += $(MSG) $(DEBUG)
${TARGET}:${OBJ_O}
$(CC) $(CFLAGS) $(OBJ_O) -o $@ $(LIB)
${DIR_BIN}/%.o:$(DIR_Examples)/%.c
$(CC) $(CFLAGS) -c $< -o $@ -I $(DIR_Config) -I $(DIR_GUI) -I $(DIR_EPD)
${DIR_BIN}/%.o:$(DIR_EPD)/%.c
$(CC) $(CFLAGS) -c $< -o $@ -I $(DIR_Config)
${DIR_BIN}/%.o:$(DIR_FONTS)/%.c
$(CC) $(CFLAGS) -c $< -o $@
${DIR_BIN}/%.o:$(DIR_GUI)/%.c
$(CC) $(CFLAGS) -c $< -o $@ -I $(DIR_Config) -I $(DIR_EPD) -I $(DIR_Examples)
${DIR_BIN}/%.o:$(DIR_Config)/%.c
$(CC) $(CFLAGS) -c $< -o $@ $(LIB)
clean :
rm $(DIR_BIN)/*.*
rm $(TARGET)Compile the Program
After editing the
Makefile
, use the 'make' command to cross-compile the program.luckfox@luckfox:~/c$ make
Once cross-compilation is successful, an executable file named
main
will be generated in the current directory.luckfox@luckfox:~/c$ ls
bin examples lib main Makefile pic readme_CN.txt readme_EN.txt
4. Achieving the Desired Outcomes
Transferring Compiled Files to the Development Board
To start, transfer the entire "c" folder from the virtual machine to your Windows computer. Next, use either TFTP or ADB to move the files to the development board. Here are the steps for using ADB to transfer files from Windows to the development board:
adb push [path_to_files] [board_storage_path]
Example: (Transferring the "c" folder from the current directory to the root directory of the development board)
adb push c /Running the Program
After adjusting the permissions of the
main
file, execute the program:#cd c/
#chmod 777 main
#./mainExperimental Observations
GUI Interface 1
GUI Interface 2
GUI Interface 3
Image Display
Key Operations