40Pin Extension

The Quectel Pi H1 single board computer provides a standard 40PIN GPIO expansion interface, supporting various peripheral interfaces such as GPIO, I2C, SPI, UART, PWM, etc. The following sections will introduce how to test these interface functions.

Pin Definition


Interface Configuration

The configuration file for some low-speed interfaces (i2c9/spi10/uart12_2w/i2c13/spi14) in the 40PIN is located at /etc/quecpi_config/quecpi_config.ini.

Configuration Steps:

  1. Modify the quecpi_config.ini configuration file
  2. Apply the configuration
quecpi_config 40pin set
  1. Restart the system to make the configuration take effect

GPIO Testing

This section uses pin3 of the 40PIN as an example to demonstrate how to use the GPIO function. The gpio_num corresponding to pin3 is 36.

Hardware Connection

Method 1: Measure voltage with a multimeter
Connect pin3 (GPIO_36) to the positive terminal of a multimeter, and pin6 (GND) to the negative terminal of the multimeter. The multimeter can measure the voltage value output by the pin to verify whether the GPIO function is working properly.

Method 2: Use GPIO terminal expansion board LEDs
You can also plug a Raspberry Pi 4B/3B GPIO terminal expansion board into the 40‑Pin expansion header to test GPIO high/low levels. This expansion board breaks out each GPIO pin and provides corresponding indicator LEDs. Configure the target GPIO pin as an output and connect it to the expansion board; when the GPIO outputs a high level, the LED turns on, and when it outputs a low level, the LED turns off, allowing you to visually observe whether the GPIO level changes as expected.
For more information about this expansion board, refer to the GPIO terminal expansion board entry in the accessories chapter (see: Accessories).

Method 1: Using SHELL Commands

The lgpiod service is enabled by default in the system. Execute the following commands in sequence:

Step 1: Open GPIO device

rgs c 999 go 4

Use the go command to open the file /dev/gpiochip4

Step 2: Set GPIO mode

rgs c 999 gso 0 36

Use the gso command to set gpio 36 to output mode. The 0 in this command is the return value from the previous command, modify it according to the actual situation.

Step 3: Set low level

rgs c 999 gw 0 36 0

Set gpio 36 to low level. At this time, the test pin voltage value is 0V.

Step 4: Set high level

rgs c 999 gw 0 36 1

Set gpio 36 to high level. At this time, the test pin voltage value is 3.3V.

Method 2: Using Python/C Code

If you need to use programming languages for GPIO control, please refer to the following documents:

These documents provide complete code examples, compilation methods, and testing steps.

I2C Testing

Pin3 and pin5 of the 40PIN are the data and clock pins of I2C by default, corresponding to the device node /dev/i2c9.

Tip
If the device node does not appear, you can first use quecpi_config mentioned at the beginning of this chapter to configure and enable it.

Test Preparation

This test uses the Waveshare Environmental Sensor Expansion Board (BME280), connected through the 40PIN interface.

Hardware Connection Diagram:

Image 1

Waveshare Environmental Sensor Expansion Board

Image 2

QuecPi 40PIN Pins

Image 3

Quectel Pi H1 with Environmental Sensor Expansion Board

Using C Code

Prerequisites
If the lgpio library is not available in the system, please refer to the C/C++ GPIO Development document to install the lgpio library.

Step 1: Create source file

Create a new envtest.c file with the following content:

Click to expand/collapse complete code
  #include <stdio.h>
  #include <lgpio.h>

  #define I2C_DEV_NUM 9
  #define BME280_ADDR 0x76

  int32_t digT[3],digP[9],digH[6];
  int32_t t_fine = 0.0;

  double compensate_P(int32_t adc_P)
  {
      double pressure = 0.0;
      double v1,v2;
      v1 = (t_fine / 2.0) - 64000.0;
      v2 = (((v1 / 4.0) * (v1 / 4.0)) / 2048) * digP[5];
      v2 = v2 + ((v1 * digP[4]) * 2.0);
      v2 = (v2 / 4.0) + (digP[3] * 65536.0);
      v1 = (((digP[2] * (((v1 / 4.0) * (v1 / 4.0)) / 8192)) / 8)  + ((digP[1] * v1) / 2.0)) / 262144;
      v1 = ((32768 + v1) * digP[0]) / 32768;
      if(v1 == 0)
          return 0;
      pressure = ((1048576 - adc_P) - (v2 / 4096)) * 3125;
      if (pressure < 0x80000000)
          pressure = (pressure * 2.0) / v1;
      else
          pressure = (pressure / v1) * 2;
      v1 = (digP[8] * (((pressure / 8.0) * (pressure / 8.0)) / 8192.0)) / 4096;
      v2 = ((pressure / 4.0) * digP[7]) / 8192.0;
      pressure = pressure + ((v1 + v2 + digP[6]) / 16.0) ;
      return (pressure/100);
  }

  double compensate_T(int32_t adc_T)
  {
      double temperature = 0.0;
      double v1,v2;
      v1 = (adc_T / 16384.0 - digT[0] / 1024.0) * digT[1];
      v2 = (adc_T / 131072.0 - digT[0] / 8192.0) * (adc_T / 131072.0 - digT[0] / 8192.0) * digT[2];
      t_fine = v1 + v2;
      temperature = t_fine / 5120.0;
      return temperature;
  }

  double compensate_H(int32_t adc_H)
  {       
      double var_h = t_fine - 76800.0;
      if (var_h == 0)
          return 0;
      var_h = (adc_H - (digH[3] * 64.0 + digH[4]/16384.0 * var_h)) *
          (digH[1] / 65536.0 * (1.0 + digH[5] / 67108864.0 * var_h * (1.0 + digH[2] / 67108864.0 * var_h)));
      var_h = var_h * (1.0 - digH[0] * var_h / 524288.0);
      if (var_h > 100.0)
          var_h = 100.0;
      else if (var_h < 0.0)
          var_h = 0.0;
      return var_h;
  } 

  void get_calib_param(int handle)
  {
      uint8_t calib[32];
      for(int i=0;i<24;i++) {   
          calib[i] = lgI2cReadByteData(handle, 0x88 + i); 
      }   
      calib[24] = lgI2cReadByteData(handle, 0xA1);
      for(int i=25,o=0;i<32;i++,o++) {   
          calib[i] = lgI2cReadByteData(handle, 0xE1 + o); 
      }   
      digT[0] = (calib[1] << 8) | calib[0];
      digT[1] = (calib[3] << 8) | calib[2];
      digT[2] = (calib[5] << 8) | calib[4];
      digP[0] = (calib[7] << 8) | calib[6];
      digP[1] = (calib[9] << 8) | calib[8];
      digP[2] = (calib[11] << 8) | calib[10];
      digP[3] = (calib[13] << 8) | calib[12];
      digP[4] = (calib[15] << 8) | calib[14];
      digP[5] = (calib[17] << 8) | calib[16];
      digP[6] = (calib[19] << 8) | calib[18];
      digP[7] = (calib[21] << 8) | calib[20];
      digP[8] = (calib[23] << 8) | calib[22];
      digH[0] = calib[24];
      digH[1] = (calib[26] << 8) | calib[25];
      digH[2] = calib[27];
      digH[3] = (calib[28] << 4) | (0x0f & calib[29]);
      digH[4] = (calib[30] << 4) | ((calib[29] >> 4) & 0x0f);
      digH[5] = calib[31];
      for(int i=1;i<2;i++)
          if((digT[i] & 0x8000) != 0) digT[i] = (-digT[i] ^ 0xFFFF) + 1;
      for(int i=1;i<8;i++)        
          if ((digP[i] & 0x8000) != 0)    digP[i]=(-digP[i] ^ 0xFFFF) + 1 ;    
      for(int i=0;i<6;i++)    
          if ((digH[i] & 0x8000) != 0) digH[i] = (-digH[i] ^ 0xFFFF) + 1;
  }

  int main(int argc, char **argv)
  {
      uint8_t data[8];
      double value[3];
      int handle = lgI2cOpen(I2C_DEV_NUM, BME280_ADDR, 0);

      lgI2cWriteByteData(handle, 0xF2, 0x01);
      lgI2cWriteByteData(handle, 0xF4, 0x27);
      lgI2cWriteByteData(handle, 0xF5, 0xA0);
      get_calib_param(handle);

      for(int i=0;i<8;i++) {
          data[i] = lgI2cReadByteData(handle, 0xF7 + i); 
      }
      value[0] = (data[0] << 12) | (data[1] << 4) | (data[2] >> 4); 
      value[1] = (data[3] << 12) | (data[4] << 4) | (data[5] >> 4); 
      value[2] = (data[6] << 8)  |  data[7];
      value[0] = compensate_P(value[0]);
      value[1] = compensate_T(value[1]);
      value[2] = compensate_H(value[2]);

      printf("pressure:    %7.2f hPa\n", value[0]);
      printf("temperature: %7.2f C\n"  , value[1]);
      printf("humidity:    %7.2f %\n"  , value[2]);
      lgI2cClose(handle);
  }

Step 2: Compile the program

gcc -o envtest envtest.c -llgpio

Step 3: Run the test

sudo ./envtest
Note
If a "Permission denied" error occurs, you need to run the program with sudo.

After successful execution, the output contains the collected pressure, temperature, and humidity information:

pressure:    1013.25 hPa
temperature:   25.36 C
humidity:     45.67 %

SPI Testing

The SPI function in the 40PIN corresponds to the device node /dev/spi10.

Tip
If the device node does not appear, you can first use quecpi_config mentioned at the beginning of this chapter to configure and enable it.

SPI Loopback Test

This test controls the SPI interface through SHELL commands to verify data transmission and reception functions.

Step 1: Open SPI device

rgs c 999 SPIO 10 0 10000000 0

Use the SPIO command to open /dev/spidev10.0 with a baud rate of 10000000. The command returns a file handle (usually 0).

Step 2: Perform SPI data transmission test

Short data test:

rgs c 999 SPIX 0 1 2 3 4

Long data test:

rgs c 999 SPIX 0 1 2 3 4 0 1 2 3 4 0 1 2 3 4 0 1 2 3 4 0 1 2 3 4 0 1 2 3 4 0 1 2 3 4 0 1 2 3 4 0 1 2 3 4 0 1 2 3 4

Use the SPIX command for SPI data transmission, where 0 is the file handle returned by the previous command, and the following numbers are the data to be sent.

Test Results:

  • When MISO and MOSI are shorted (loopback mode), returns the sent data:
4 1 2 3 4

The first number 4 indicates the number of data bytes received.

  • When MISO and MOSI are not shorted, returns all 255 (indicating no valid data received):
4 255 255 255 255

UART Testing

Pin8 and pin10 of the 40PIN are configured as UART function by default, corresponding to the device node /dev/ttyHS2. If the node does not appear, you can first use quecpi_config mentioned at the beginning of this chapter to configure and enable it.

View Serial Port Devices
You can use the following command to view all serial port devices in the system:
ls /dev/tty*

UART Loopback Test

This test verifies whether the serial port transmission and reception functions are working properly by shorting pin8 and pin10.

Hardware Connection: Short pin8 (TX) and pin10 (RX) of the 40PIN.

Prerequisites
The python3-serial library needs to be installed:
apt-get update
apt-get install python3-serial

Step 1: Create test script

Create a new uart_loop.py file with the following content:

Click to expand/collapse complete code
import serial
import time

def serial_loopback_test(port='/dev/ttyHS2', baudrate=115200, timeout=1):
    try:
        # Open serial port
        ser = serial.Serial(
            port=port,
            baudrate=baudrate,
            parity=serial.PARITY_NONE,
            stopbits=serial.STOPBITS_ONE,
            bytesize=serial.EIGHTBITS,
            timeout=timeout
        )

        if not ser.is_open:
            print(f"Unable to open serial port {port}")
            return

        print(f"Serial port {port} opened, starting loopback test (Press Ctrl+C to exit)...")
        test_data = b"Hello, Serial Loopback!"  # Test data

        while True:
            # Send data
            ser.write(test_data)
            print(f"Sent: {test_data.decode('utf-8')}")

            # Read loopback data
            time.sleep(0.1)  # Wait for data reception
            received = ser.read(len(test_data))

            # Verify results
            if received == test_data:
                print(f"Received: {received.decode('utf-8')} → Test passed\n")
            else:
                print(f"Receive exception: Sent[{len(test_data)}] vs Received[{len(received)}] → Test failed\n")

            time.sleep(1)  # Repeat test every 1 second

    except serial.SerialException as e:
        print(f"Serial port error: {e}")
    except KeyboardInterrupt:
        print("\nUser interrupted test")
    finally:
        if 'ser' in locals() and ser.is_open:
            ser.close()
            print(f"Serial port {port} closed")

if __name__ == "__main__":
    # Modify serial port and baud rate according to actual situation
    serial_loopback_test(port='/dev/ttyHS2', baudrate=115200)

Step 2: Run the test

python3 uart_loop.py

Test Results:

When pin8 and pin10 are correctly shorted, the program will continuously send data and verify whether the received data matches:

Serial port /dev/ttyHS2 opened, starting loopback test (Press Ctrl+C to exit)...
Sent: Hello, Serial Loopback!
Received: Hello, Serial Loopback! → Test passed

Sent: Hello, Serial Loopback!
Received: Hello, Serial Loopback! → Test passed

Sent: Hello, Serial Loopback!
Received: Hello, Serial Loopback! → Test passed

Press Ctrl+C to exit the test program.

PWM Testing

Pin33 of the 40Pin is configured as PWM function by default. Here, we choose Waveshare's 4pin PWM protocol speed control fan as the test device.

Hardware Connection

PWM Fan Wiring Definition:

Pin Function Wire colour 40PIN Connection
1 +5V Red pin2 or pin4 (5V)
2 PWM Blue pin33 (GPIO_78)
3 GND Black pin6/9/14/20/25/30/34/39 (any GND)
4 Tach Yellow pin32 (GPIO_76)

Connection Diagram:

PWM Fan Interface Definition

Fan Connected to 40PIN

Fan Connected to 40PIN

PWM Fan Interface Definition

Note
  • The Tach pin is used to read fan speed and is an optional connection
  • If you don't need to read speed information, the Tach pin can be left unconnected

Using C Code

  • Create a pwm.c file with the following content:

    #include <stdio.h>
    #include <lgpio.h>
    int main(int argc, char **argv)
    {
          int h;
          int gpio = 78; 
          float pwmFrequency = 1000;
          float pwmDutyCycle = 50; 
    
          h = lgGpiochipOpen(4);  // Open /dev/gpiochip4 device
          if (h < 0) {
              printf("ERROR: %s (%d)\n", lguErrorText(h), h); 
              return 1;
          }   
    
          int e = lgGpioClaimOutput(h, 0, gpio, 0); 
          if (e < 0) {
              printf("ERROR: %s (%d)\n", lguErrorText(e), e); 
              return 1;
          }   
    
          e = lgTxPwm(h, gpio, pwmFrequency, pwmDutyCycle, 0, 0); 
          if (e < 0) {
              printf("ERROR: %s (%d)\n", lguErrorText(e), e); 
              return 1;
          }   
    
          lguSleep(5);
          lgGpioFree(h, gpio);
          lgGpiochipClose(h);
          return 0;
    }
    
  • Compile:

    gcc pwm.c -o pwm -llgpio
    
  • Execute:

    sudo ./pwm
    

    The fan will run at medium speed. You can adjust the pwmDutyCycle value in the code (range 0-100) to adjust the fan speed.