yahboom
HomeLearn
Solution
Blog
Online Store
About Us
EN/中文
Self-balancing Robot Car📖
00.Get started quickly
  • 01.Balance car manual
  • 02.Balancing car assembly
  • 03.Get started quickly
  • 04.Balance car interface description
  • 05.Common Problem Analysis
  • 06.Balance Car V2 Library Function Development Manual
01.Environment development
  • 01.MDK-ARM installation
  • 02.STM32CubeIDE installation
  • 03.Common driver installation
  • 04.Download the program
  • 05.MDK-ARM project usage
  • 06.Program simulation
  • 07. VSCode Installation
02.STM32 getting started course
  • 01.Project creation
  • 02.LED Control(GPIO)
  • 03.Buzzer control(GPIO)
  • 04.Button control(EXTI)
  • 05.Serial communication(USART)
  • 06.Voltage detection(ADC)
  • 07.Basic timer(TIM)
  • 08.Universal timer(TIM)
  • 09.Advanced Timer(TIM)
  • 10.I2C Communication(I2C)-OLED
  • 11.SPI communication(SPI)-2.4G handle
  • 12.Independent Watchdog (IWDG)
  • 13.Window Watchdog (WWDG)
  • 14.DMA-USART
  • 15.DMA-I2C
03.STM32 expansion course
  • 01.Battery voltage detection (ADC)
  • 02.Ultrasonic module-measuring distance (TIM)
  • 03.Motor drive+encoder (TIM)
  • 04.OLED Data-Display(I2C)
  • 05.MPU6050-Data Read (I2C)
  • 06.Bluetooth module-Data reading (USART)
  • 07.2.4G handle-Data reading (SPI)
  • 08.Tracking module-Read status (GPIO)
  • 09.CCD module-Reading data (ADC)
  • 10.Electromagnetic module-Reading data (ADC)
  • 11.K210 module-Serial communication (USART)
  • 12.Tmini-Plus lidar-Reading data (USART)
04.Robot car PID control course
  • 01.PID basic concept
  • 02.PID example analysis
  • 03.P, PI, PD controller theory
  • 04.Position PID
  • 05.Incremental PID
  • 06.Cascade PID
  • 07.Balance principle of car
  • 08.Car upright control (PD)
  • 09.Car speed ​​control (PI)
  • 10.Car steering control (PD)
  • 11.Got angle and angular velocity (DMP algorithm)
  • 12.Got angle and angular velocity (Kalman filter algorithm)
  • 13.Got angle and angular velocity(Complementary filter algorithm)
05.Robot car basic course
  • 01.Car parameter adjustment
  • 02.Ultrasonic obstacle avoidance
  • 03.Ultrasonic follow
  • 04.Bluetooth remote control
  • 05.Load balance
06.Robot car advanced course
  • 01.4-channel tracking
  • 02.4-channel tracking avoid
  • 03.2.4G handle control
  • 04.CCD tracking
  • 05.CCD tracking avoid
  • 06.Electromagnetic tracking
  • 07.K210-QR code recognition
  • 08.K210-Color line tracking
  • 09.K210-Color follow
  • 10.K210-Self learning
  • 11.K210-Number recognition
  • 12.Lidar avoid
  • 13.Lidar guard
  • 14.Lidar follow
  • 15.Lidar patrol
  • 16.Lidar wall tracking-straight line
  • 17.Lidar wall tracking-multiple walls
  • 18. DIY Automatic driving car
07.Robot car LQR control
  • Robot car LQR control
08.Circuit design
  • 01.Overall circuit design
  • 02.STM32 minimum system
  • 03.Serial port circuit design
  • 04.Power circuit design
  • 05.MPU6050 Circuit Design
  • 06.Motor drive circuit design
09.Mathematical model
  • Mathematical model
10.Expand course
  • Autonomous Driving
Download
  • APP(Android)
  • Codes
  • Hardware-Info
  • SCH
  • Software Tools
  • STM32_Chip_DataSheet
  • 3D_Model_File
  • Video_PPT
Download ZIP

Welcome to Self-balancing Robot Car repository

07.2.4G handle-Data reading (SPI)

2.4G handle-Data reading (SPI)

2.4G handle-Data reading (SPI)Hardware connectionControl PrincipleSoftware configurationPin definitionSoftware codeControl functionExperimental phenomenon

The tutorial combines OLED and serial port to display the key data of 2.4G wireless controller.

The tutorial only introduces the standard library project code

Hardware connection

image-20240825205618171

Since we have configured a special connection line, we only need to install it to the corresponding interface:

PeripheralsDevelopment Board
PS2: CSPB12
PS2: SCKPB13
PS2: MISOPB14
PS2: MOSIPB15

Control Principle

  • 2.4G Wireless Controller

The 2.4G wireless controller is mainly composed of a controller and a receiver (the receiver adapter board is convenient for pin connection with the development board).

Controller

Send key information to the receiver.

Receiver

Receives data sent by the controller and passes it to the development board; the development board can also configure the controller's sending mode through the data sent by the controller.

image-20240807212212404

Handle Button Description

image-20240819100651601

Handle ButtonProgram Function DescriptionProgram Definition (Part)
Direction Key (①): △ForwardPSB_PAD_UP
Direction Key (①): ▽BackwardPSB_PAD_DOWN
Direction Key (①): ◁Turn LeftPSB_PAD_LEFT
Direction key (①): ▷Turn rightPSB_PAD_RIGHT
Function key (②): ▲ForwardPSB_TRIANGLE/PSB_GREEN
Function key (②): ×BackwardPSB_CROSS/PSB_BLUE
Function key (②): ☐Turn leftPSB_SQUARE/PSB_PINK
Function key (②): ◯Turn rightPSB_CIRCLE/PSB_RED
Joystick: ③Control direction (valid in dual light mode)PSB_L3/PSS_LX/PSS_LY
Joystick: ④Control direction (valid in dual light mode)PSB_R3/PSS_RX/PSS_RY
Button (⑤): L1/L2AcceleratePSB_L1/PSB_L2
Button (⑥): R1/R2Slow downPSB_R1/PSB_R2
Button (⑦): STARTTurn off power saving modePSB_START
Button (⑧): MODESwitch mode (indicator light display: single light and dual light mode) 
Button (⑨): SELECTNot usedPSB_SELECT
  • Receiver and adapter board pin description

Receiver pinDescription
DI/DATSignal flow, from handle to host, this signal is an 8-bit serial data, synchronously transmitted on the falling edge of the clock. The signal is read when the clock changes from high to low.
DI/DATSignal flow, from host to handle, this signal is opposite to DI, the signal is an 8-bit serial data, synchronously transmitted on the falling edge of the clock.
NCEmpty port.
GNDReceiver working power supply, power supply range 3~5V.
VDDUsed to provide the handle trigger signal. During communication, it is at a low level.
CS/SELSignal flow, from the host to the handle, this signal is relative to DI, the signal is an 8-bit serial data, synchronously transmitted on the falling edge of the clock.
CS/SELClock signal, sent by the host, used to keep data synchronization.
NCEmpty port.
ACKAcknowledgement signal from the handle to the host. This signal goes low at the last cycle of each 8-bit data transmission and CS remains low. If the CS signal does not go low, the PS host will try another peripheral in about 60 microseconds. The ACK port is not used during programming. (Can be ignored)

image-20240807214832385

Software configuration

Pin definition

Main control chipPinMain function (after reset)Default multiplexing functionRedefine function
STM32F103RCT6PB12PB12SPI2_NSS/I2C2_SMBAI/USART3_CK/TIM1_BKIN 
STM32F103RCT6PB13PB13SPI2_SCK/USART3_CTS/ TIM1_CH1N 
STM32F103RCT6PB14PB14SPI2_MISO/USART3_RTS/TIM1_CH2N 
STM32F103RCT6PB15PB15SPI2_MOSI/TIM1_CH3N 

Software code

Since the default function of the pin is the normal IO pin function, we need to use the multiplexing function.

xxxxxxxxxx
Product supporting materials source code path: Attachment → Source code summary → 2.Extended_Course → 7.Control_Handle

Control function

The tutorial only briefly introduces the code, you can open the project source code to read it in detail.

PS2_Init

​x
void PS2_Init(void)
{
    GPIO_InitTypeDef GPIO_InitStructure; //Define GPIO_InitStructure structure
    //DI
    RCC_APB2PeriphClockCmd(PS_RCC_DI,ENABLE);
    GPIO_InitStructure.GPIO_Mode=GPIO_Mode_IPU; //Pull-up, pull-down and floating input modes will cause abnormalities when the handle is not connected
    GPIO_InitStructure.GPIO_Pin=PS_PIN_DI;
    GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz; //50MHZ
    GPIO_Init(PS_PORT_DI,&GPIO_InitStructure);

    //DO
    RCC_APB2PeriphClockCmd(PS_RCC_DO,ENABLE); //Turn on GPIOB clock
    GPIO_InitStructure.GPIO_Mode=GPIO_Mode_Out_PP; //Multiplexed push-pull output mode
    GPIO_InitStructure.GPIO_Pin=PS_PIN_DO; //DO port
    GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz; //50MHZ
    GPIO_Init(PS_PORT_DO,&GPIO_InitStructure);

    //CS
    RCC_APB2PeriphClockCmd(PS_RCC_CS,ENABLE); //Turn on GPIOB clock
    GPIO_InitStructure.GPIO_Mode=GPIO_Mode_Out_PP; //Multiplexed push-pull output mode
    GPIO_InitStructure.GPIO_Pin=PS_PIN_CS; //CS port
    GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz; //50MHZ
    GPIO_Init(PS_PORT_CS,&GPIO_InitStructure);

    //SCK
    RCC_APB2PeriphClockCmd(PS_RCC_CLK,ENABLE); //Turn on GPIOB clock
    GPIO_InitStructure.GPIO_Mode=GPIO_Mode_Out_PP; //Multiplexed push-pull output mode
    GPIO_InitStructure.GPIO_Pin=PS_PIN_CLK; //CLK port
    GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz; //50MHZ
    GPIO_Init(PS_PORT_CLK,&GPIO_InitStructure);
}

PS2_Cmd

xxxxxxxxxx
void PS2_Cmd(u8 CMD)
{
    volatile u16 ref=0x01;
    Data[1] = 0;
    for(ref=0x01;ref<0x0100;ref<<=1)
    {
        if(ref&CMD)
        {
            DO_H;
        }
        else DO_L;
        CLK_H;
        DELAY_TIME;
        CLK_L;
        DELAY_TIME;
        CLK_H;
        if(DI)
            Data[1] = ref|Data[1];
    }
    delay_us(16);
}

PS2_RedLight

xxxxxxxxxx
u8 PS2_RedLight(void)
{
    CS_L;
    PS2_Cmd(Comd[0]);
    PS2_Cmd(Comd[1]);
    CS_H;
    if( Data[1] == 0X73)   return 0 ;
    else return 1;
}

PS2_ReadData

xxxxxxxxxx
void PS2_ReadData(void)
{
    volatile u8 byte=0;
    volatile u16 ref=0x01;
    CS_L;
    PS2_Cmd(Comd[0]);
    PS2_Cmd(Comd[1]);
    for(byte=2;byte<9;byte++)
    {
        for(ref=0x01;ref<0x100;ref<<=1)
        {
            CLK_H;
            DELAY_TIME;
            CLK_L;
            DELAY_TIME; 
            CLK_H;          
              if(DI)
              Data[byte] = ref|Data[byte];
        }
        delay_us(16);
    }
    CS_H;
}

PS2_DataKey

xxxxxxxxxx
u8 PS2_DataKey()
{
    u8 index;
    PS2_ClearData();
    PS2_ReadData();
    Handkey=(Data[4]<<8)|Data[3];
    for(index=0;index<16;index++)
    {       
        if((Handkey&(1<<(MASK[index]-1)))==0)
        return index+1;
    }
    return 0;
}

PS2_AnologData

xxxxxxxxxx
u8 PS2_AnologData(u8 button)
{
    return Data[button];
}

PS2_ClearData

xxxxxxxxxx
void PS2_ClearData()
{
    u8 a;
    for(a=0;a<9;a++)
        Data[a]=0x00;
}

PS2_Vibration

xxxxxxxxxx
void PS2_Vibration(u8 motor1, u8 motor2)
{
    CS_L;
    delay_us(16);
    PS2_Cmd(0x01);
    PS2_Cmd(0x42);
    PS2_Cmd(0X00);
    PS2_Cmd(motor1);
    PS2_Cmd(motor2);
    PS2_Cmd(0X00);
    PS2_Cmd(0X00);
    PS2_Cmd(0X00);
    PS2_Cmd(0X00);
    CS_H;
    delay_us(16);  
}

PS2_ShortPoll

xxxxxxxxxx
void PS2_ShortPoll(void)
{
    CS_L;
    delay_us(16);
    PS2_Cmd(0x01);  
    PS2_Cmd(0x42);  
    PS2_Cmd(0X00);
    PS2_Cmd(0x00);
    PS2_Cmd(0x00);
    CS_H;
    delay_us(16);   
}

PS2_EnterConfing

xxxxxxxxxx
void PS2_EnterConfing(void)
{
    CS_L;
    delay_us(16);
    PS2_Cmd(0x01);  
    PS2_Cmd(0x43);  
    PS2_Cmd(0X00);
    PS2_Cmd(0x01);
    PS2_Cmd(0x00);
    PS2_Cmd(0X00);
    PS2_Cmd(0X00);
    PS2_Cmd(0X00);
    PS2_Cmd(0X00);
    CS_H;
    delay_us(16);
}

PS2_TurnOnAnalogMode

xxxxxxxxxx
void PS2_TurnOnAnalogMode(void)
{
    CS_L;
    PS2_Cmd(0x01);  
    PS2_Cmd(0x44);  
    PS2_Cmd(0X00);
    PS2_Cmd(0x01);
    PS2_Cmd(0xEE);
    PS2_Cmd(0X00);
    PS2_Cmd(0X00);
    PS2_Cmd(0X00);
    PS2_Cmd(0X00);
    CS_H;
    delay_us(16);
}

PS2_VibrationMode

xxxxxxxxxx
void PS2_VibrationMode(void)
{
    CS_L;
    delay_us(16);
    PS2_Cmd(0x01);  
    PS2_Cmd(0x4D);  
    PS2_Cmd(0X00);
    PS2_Cmd(0x00);
    PS2_Cmd(0X01);
    CS_H;
    delay_us(16);   
}

PS2_ExitConfing

xxxxxxxxxx
void PS2_ExitConfing(void)
{
    CS_L;
    delay_us(16);
    PS2_Cmd(0x01);  
    PS2_Cmd(0x43);  
    PS2_Cmd(0X00);
    PS2_Cmd(0x00);
    PS2_Cmd(0x5A);
    PS2_Cmd(0x5A);
    PS2_Cmd(0x5A);
    PS2_Cmd(0x5A);
    PS2_Cmd(0x5A);
    CS_H;
    delay_us(16);
}

PS2_SetInit

xxxxxxxxxx
void PS2_SetInit(void)
{
    PS2_ShortPoll();
    PS2_ShortPoll();
    PS2_ShortPoll();
    PS2_EnterConfing();
    PS2_TurnOnAnalogMode();
    //PS2_VibrationMode();
    PS2_ExitConfing();
}

PS2_Data_Show

xxxxxxxxxx
void PS2_Data_Show(void)
{
    g_PS2_LX = PS2_AnologData(PSS_LX);
    g_PS2_LY = PS2_AnologData(PSS_LY);
    g_PS2_RX = PS2_AnologData(PSS_RX);
    g_PS2_RY = PS2_AnologData(PSS_RY);
    g_PS2_KEY = PS2_DataKey();

    
    if ((g_PS2_LX == 255) && (g_PS2_LY == 255) && (g_PS2_RX == 255) && (g_PS2_RY == 255))
    {
        if (g_flag == 1)
        {
            printf("PS2 mode is RED or GREEN mode \r\n");
            printf("Or PS2 disconnect! \r\n");
            g_flag = 0;
        }
    }

    else
    {
        if (g_flag == 0)
        {
            printf("PS2 mode is RED^GREEN mode \r\n");
            g_flag = 1;
        }
        // Only the red green mode has the correct joystick value
        if (g_PS2_LX > 130 || g_PS2_LX < 100)
            printf("PS2_LX = %d \r\n", g_PS2_LX);
        if (g_PS2_LY > 130 || g_PS2_LY < 100)
            printf("PS2_LY = %d \r\n", g_PS2_LY);
        if (g_PS2_RX > 130 || g_PS2_RX < 100)
            printf("PS2_RX = %d \r\n", g_PS2_RX);
        if (g_PS2_RY > 130 || g_PS2_RY < 100)
            printf("PS2_RY = %d \r\n", g_PS2_RY);
    }

    switch (g_PS2_KEY)
    {
    case PSB_SELECT:
        printf("key = PSB_SELECT\r\n");
        OLED_Draw_Line("key = PSB_SELECT", 1, true, true);
        break;
    case PSB_L3:
        printf("key = PSB_L3\r\n");
        OLED_Draw_Line("key = PSB_L3", 1, true, true);
        break;
    case PSB_R3:
        printf("key = PSB_R3\r\n");
        OLED_Draw_Line("key = PSB_R3", 1, true, true);
        break;
    case PSB_START:
        printf("key = PSB_START\r\n");
        OLED_Draw_Line("key = PSB_START", 1, true, true);
        break;
    case PSB_PAD_UP:
        printf("key = PSB_PAD_UP\r\n");
        OLED_Draw_Line("key = PSB_PAD_UP", 1, true, true);
        break;
    case PSB_PAD_RIGHT:
        printf("key = PSB_PAD_RIGHT\r\n");
        OLED_Draw_Line("key = PSB_PAD_RIGHT", 1, true, true);
        break;
    case PSB_PAD_DOWN:
        printf("key = PSB_PAD_DOWN\r\n");
        OLED_Draw_Line("key = PSB_PAD_DOWN", 1, true, true);
        break;
    case PSB_PAD_LEFT:
        printf("key = PSB_PAD_LEFT\r\n");
        OLED_Draw_Line("key = PSB_PAD_LEFT", 1, true, true);
        break;
    case PSB_L2:
        printf("key = PSB_L2\r\n");
        OLED_Draw_Line("key = PSB_L2", 1, true, true);
        break;
    case PSB_R2:
        printf("key = PSB_R2\r\n");
        OLED_Draw_Line("key = PSB_R2", 1, true, true);
        break;
    case PSB_L1:
        printf("key = PSB_L1\r\n");
        OLED_Draw_Line("key = PSB_L1", 1, true, true);
        break;
    case PSB_R1:
        printf("key = PSB_R1\r\n");
        OLED_Draw_Line("key = PSB_R1", 1, true, true);
        break;
    case PSB_GREEN:
        printf("key = PSB_GREEN\r\n");
        OLED_Draw_Line("key = PSB_GREEN", 1, true, true);
        break;
    case PSB_RED:
        printf("key = PSB_RED\r\n");
        OLED_Draw_Line("key = PSB_RED", 1, true, true);
        break;
    case PSB_BLUE:
        printf("key = PSB_BLUE\r\n");
        OLED_Draw_Line("key = PSB_BLUE", 1, true, true);
        break;
    case PSB_PINK:
        printf("key = PSB_PINK\r\n");
        OLED_Draw_Line("key = PSB_PINK", 1, true, true);
        break;
    }
    delay_ms(100);
}

Experimental phenomenon

The Control_Handle.hex file generated by the project compilation is located in the OBJ folder of the Control_Handle project. Find the Control_Handle.hex file corresponding to the project and use the FlyMcu software to download the program to the development board.

After the program is successfully downloaded: the serial port prints and the OLED displays the key value pressed by the wireless handle.

xxxxxxxxxx
When using the serial port debugging assistant, you need to pay attention to the serial port settings. If the settings are wrong, the phenomenon may be inconsistent.

image-20240808193904123

Bluetooth module-Data reading (USART)
Tracking module-Read status (GPIO)

COMPANY

  • About us
  • Team Style
  • Blog

LEARNING & EXPLORATION

  • Tutorials
  • Edge AI hardware
  • Educational robots
  • Embedded intelligent hardware

WHERE TO BUY

  • Yahboom Online Store
  • Amazon
  • Alibaba
  • AliExpress
  • Mercado

TECHNOLOGY SUPPORT

  • Email: support@yahboom.com

BE A DISTRIBUTOR

  • Email: sales@yahboom.com
QR Code

Copyright © 2017-2026 Yahboom. All Rights Reserved.

EN/中文