Skip to main content

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.

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
image

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.

  1. 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.

    1. 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

    2. 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.

      image
      image

      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>;
      };
      };
      };
    3. 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
      image

      Additionally, certain pins, as shown in the following image, may have conflicting assignments and need to be configured as regular GPIO pins:

      image

    4. 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:
      image

      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>;
      };
      };
      };
    5. 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:
      image

  2. Compile by selecting the branch and specifying the development board model

    luckfox@luckfox:~/luckfox-pico$ ./build.sh lunch
  3. Compile

    luckfox@luckfox:~/Luckfox-Pico/luckfox-pico$ ./build.sh
  4. 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.

  1. Pin Definitions

    1. 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 103
    2. Add 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)
    3. Initialize GPIO in DEV_GPIO_Init Function

      static 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);
      }
    4. 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 */
      }
      }
  2. SPI/I2C Communication

    1. 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 0
    2. Request 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;
      }
    3. Initialization

      Call the OLED_1in3_C_Init function to initialize the OLED in the main function.

      void OLED_1in3_C_Init()
      {
      //Hardware reset
      OLED_Reset();

      //Set the initialization register
      OLED_InitReg();
      }
    4. Sending Data

      In the OLED_WriteReg function, pull the OLED_DC pin low to indicate command transmission, then use the DEV_SPI_WriteByte or I2C_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 the DEV_SPI_WriteByte or I2C_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
      }
  3. Cross Compilation

    1. 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 the Makefile 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)
    2. 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

  1. 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 /
  2. Running the Program

    After adjusting the permissions of the main file, execute the program:

    #cd c/
    #chmod 777 main
    #./main
  3. Experimental Observations

    GUI Interface 1
    image

    GUI Interface 2
    image

    GUI Interface 3
    image

    Image Display
    image

    Key Operations
    image