Game Controller
1. Introduction
A game controller is an important input device for manually controlling a robot. Most game controllers generally have two joysticks, two triggers, two bumpers, four pad buttons, four user buttons, and three special buttons. Some game controllers also have a vibration motor, LEDs, and a speaker. Each joystick has two potentiometers for the X and Y axes and a push button. Each trigger has a potentiometer for its axis.
The Xbox 360 and PlayStation controllers are two of the most popular game controllers. They can be connected to a computer using a USB cable or Bluetooth, and can also be connected to a microcontroller using USB or Bluetooth. In this tutorial, we are going to use a PS4 controller and an ESP32 WROOM32 module. The ESP32 supports both classic Bluetooth and BLE, while the PS4 controller only supports classic Bluetooth.

PS4 Controller (DualShock)

Xbox 360 Controller

PS4 Controller Label

Xbox 360 Controller Label
2. Understanding the flow of Data and Signals from Game Controller to Actuator

Flow of Data and Signals from Game Controller to Actuator
The ESP32 acts as a wireless receiver, parsing HID data from the PS4 controller and converting it into a common JoyData format. This data is then sent to the STM32 via UART, where the STM32 uses the JoyData to control the actuators. The ESP32 can be replaced with a Raspberry Pi Pico W or any other. The standardized JoyData format makes it easy to swap out the receiver as needed.
3. Joystick Data Format from Receiving Controller
Creating a header file for the JoyData structure is a good practice, as it allows you to define the structure in a single place and include it in multiple source files. This makes it easier to include, maintain and update the structure if needed.
1#ifndef _JOY_MSG_HPP
2#define _JOY_MSG_HPP
3
4#include <math.h>
5#include <stdint.h>
6
7#define BUTTONS(button) (1 << button)
8
9namespace myPS4
10{
11 enum Buttons
12 {
13 CROSS,
14 CIRCLE,
15 SQUARE,
16 TRIANGLE,
17 SHARE,
18 PS_BUTTON,
19 OPTION,
20 L3,
21 R3,
22 L1,
23 R1,
24 UP,
25 DOWN,
26 LEFT,
27 RIGHT,
28 TOUCH
29 };
30}
31
32namespace myXBox
33{
34 enum Buttons
35 {
36 A,
37 B,
38 X,
39 Y,
40 BACK,
41 XBOX_BUTTON,
42 START,
43 LSTICK,
44 RSTICK,
45 LB,
46 RB,
47 UP,
48 DOWN,
49 LEFT,
50 RIGHT
51 };
52}
53
54#pragma pack(push, 1)
55struct JoyData
56{
57 int8_t lx;
58 int8_t ly;
59 int8_t rx;
60 int8_t ry;
61 uint8_t lt;
62 uint8_t rt;
63 uint16_t buttons;
64};
65#pragma pack(pop)
66
67#endif // _JOY_MSG_HPP
The total size of JoyData is 8-bytes and contains the following fields:
lx: Left joystick X-axis value
ly: Left joystick Y-axis value
rx: Right joystick X-axis value
ry: Right joystick Y-axis value
lt: Left trigger value
rt: Right trigger value
buttons: Bitmask representing the state of the buttons. Each button is represented by a bit in the bitmask, where 1 indicates that the button is pressed and 0 indicates that it is not pressed.
The buttons are defined in the myPS4 and myXBox namespaces, and each button is assigned a unique value. The buttons are represented as bitmasks, which means that multiple buttons can be pressed at the same time.
The JoyData structure is packed to ensure that the data is aligned correctly in memory. The #pragma pack(push, 1)
directive tells the compiler to align the structure on a 1-byte boundary, which means that there will be no padding between the fields of the structure. The #pragma pack(pop)
directive restores the previous packing alignment.
4. Setting ESP32 as Receiver

DOIT ESP32 DEVKIT V1
There are some Arduino libraries available for interfacing PS4 controller with ESP32. Some examples for ESP32 IDF are also avialable which uses Bluetooth as well as USB. We are going to use Arduino library for this tutorial.
4.1. Installing ESP32 Board by Espressif in Arduino IDE
Open
Arduino IDE
.Search for
ESP32
in the board manager.Install the board by
Espressif
.
4.2. Finding MAC Address of ESP32 and Setting up PS4 Controller
The MAC address is used to pair the PS4 controller with the ESP32 board. Follow the steps below to find the MAC address of the ESP32 board and set up the PS4 controller:
Open
Arduino IDE
and create a new sketch.Copy the following code into the sketch:
esp_mac_loockup.ino1#include "esp_bt_main.h" 2#include "esp_bt_device.h" 3#include "BluetoothSerial.h" 4 5BluetoothSerial SerialBT; 6 7void printDeviceAddress() { 8 const uint8_t* point = esp_bt_dev_get_address(); 9 for (int i = 0; i < 6; i++) { 10 char str[3]; 11 sprintf(str, "%02X", (int)point[i]); 12 Serial.print(str); 13 if (i < 5){ 14 Serial.print(":"); 15 } 16 } 17} 18 19void setup() { 20 Serial.begin(115200); 21 SerialBT.begin("ESP32 Bluetooth"); 22} 23 24void loop() { 25 printDeviceAddress(); 26 delay(1000); 27}
Save the sketch as
esp_mac_loockup.ino
.Select board
DOIT ESP32 DEVKIT V1
fromTools > Board > esp32
.Upload the code to the ESP32 board and open the Serial Monitor. The MAC address will be displayed in the Serial Monitor.
Open
SixaxisPairTool
on Windows. See installation if you do not have. Plug the PS4 controller to PC using USB Cable. Driver for the controller will be installed, if it is first time.Connect the PS4 controller to the PC using a USB cable. The
SixaxisPairTool
will display the MAC address of the PS4 controller. Write the Bluetooth Mac Address of the ESP32 intoMaster MAC Adrress Field
and clickupdate
.
4.3. Using PS4Controller Library by Juan Pablo Marquez
Install the library from Arduino Library Manager or download from here.
Create a new sketch and paste the following contents:
esp_rx_ps4_library.ino1#define DEBUG // Disable it by commenting in actual implementation 2 3#include <PS4Controller.h> 4#include "crc8.hpp" 5#include "joy_msg.hpp" 6using namespace myPS4; 7 8/** @brief Pins assignments of ESP32 */ 9#define IN_BUILT_LED_BLUE 2 10#define RXD2 16 11#define TXD2 17 12 13/** @brief Start byte of packet */ 14#define START_BYTE 0xA5 15 16CRC8 crc(7); 17 18JoyData jdata; 19bool is_flash_set = false; 20bool is_braked; 21 22uint32_t last_transmit = 0; 23 24/** @brief Initialize controller parameters */ 25void setup() { 26 27 Serial.begin(115200, SERIAL_8N1); 28 Serial2.begin(115200, SERIAL_8N1, RXD2, TXD2); 29 30 pinMode(IN_BUILT_LED_BLUE, OUTPUT); 31 digitalWrite(IN_BUILT_LED_BLUE, LOW); 32 33 PS4.attachOnConnect(onConnect); 34 PS4.attachOnDisconnect(onDisConnect); 35 36 PS4.begin(); 37 Serial.print("ESP32 MAC: "); 38 Serial.println(PS4.getAddress()); 39} 40 41/** @brief Operation */ 42void loop() { 43 if (!PS4.isConnected()) 44 return; 45 46 uint32_t now = millis(); 47 if (now - last_transmit < 10) 48 return; 49 50 uint8_t packet[10]; 51 jdata.lx = PS4.LStickX(); 52 jdata.ly = -PS4.LStickY(); 53 jdata.rx = PS4.RStickX(); 54 jdata.ry = -PS4.RStickY(); 55 jdata.lt = PS4.L2Value(); 56 jdata.rt = PS4.R2Value(); 57 58 jdata.buttons = 0; 59 60 if (PS4.Cross()) jdata.buttons |= BUTTONS(CROSS); 61 if (PS4.Circle()) jdata.buttons |= BUTTONS(CIRCLE); 62 if (PS4.Square()) jdata.buttons |= BUTTONS(SQUARE); 63 if (PS4.Triangle()) jdata.buttons |= BUTTONS(TRIANGLE); 64 65 if (PS4.Share()) jdata.buttons |= BUTTONS(SHARE); 66 if (PS4.PSButton()) jdata.buttons |= BUTTONS(PS_BUTTON); 67 if (PS4.Options()) jdata.buttons |= BUTTONS(OPTION); 68 69 if (PS4.L3()) jdata.buttons |= BUTTONS(L3); 70 if (PS4.R3()) jdata.buttons |= BUTTONS(R3); 71 if (PS4.L1()) jdata.buttons |= BUTTONS(L1); 72 if (PS4.R1()) jdata.buttons |= BUTTONS(R1); 73 74 if (PS4.Up()) jdata.buttons |= BUTTONS(UP); 75 if (PS4.UpLeft()) jdata.buttons |= (BUTTONS(UP) | BUTTONS(LEFT)); 76 if (PS4.Left()) jdata.buttons |= BUTTONS(LEFT); 77 if (PS4.DownLeft()) jdata.buttons |= (BUTTONS(LEFT) | BUTTONS(DOWN)); 78 if (PS4.Down()) jdata.buttons |= BUTTONS(DOWN); 79 if (PS4.DownRight()) jdata.buttons |= (BUTTONS(DOWN) | BUTTONS(RIGHT)); 80 if (PS4.Right()) jdata.buttons |= BUTTONS(RIGHT); 81 if (PS4.UpRight()) jdata.buttons |= (BUTTONS(UP) | BUTTONS(RIGHT)); 82 83 if (PS4.Touchpad()) jdata.buttons |= BUTTONS(TOUCH); 84 85 packet[0] = START_BYTE; 86 memcpy((uint8_t*)packet + 1, (uint8_t*)&jdata, sizeof(jdata)); 87 packet[9] = crc.get_hash(packet + 1, sizeof(jdata)); 88 89 // Finally send packet to STM32 through UART2 90 Serial2.write(packet, sizeof(packet)); 91 92 if (PS4.PSButton()) { 93 if (PS4.L1() && PS4.R1()) { 94 if (is_braked) { 95 PS4.setLed(0, 255, 0); 96 PS4.sendToController(); 97 // Serial.println("Brake removed"); 98 is_braked = false; 99 } 100 } else { 101 if (!is_braked) { 102 PS4.setLed(255, 0, 255); 103 PS4.sendToController(); 104 // Serial.println("Braked"); 105 is_braked = true; 106 } 107 } 108 } 109 110#ifdef DEBUG 111 Serial.printf("%lu: %hd %hd %hd %hd %hu %hu %04X\n", 112 now - last_transmit, jdata.lx, jdata.ly, jdata.rx, jdata.ry, jdata.lt, jdata.rt, jdata.buttons); 113#endif 114 115 last_transmit = now; 116} 117 118/** @brief Callback on connect */ 119void onConnect() { 120 digitalWrite(IN_BUILT_LED_BLUE, HIGH); 121 Serial.printf("Battery: %d\n", PS4.Battery()); 122 PS4.setLed(0, 255, 0); 123 PS4.sendToController(); 124 Serial.println("PS4 Connected."); 125} 126 127/** @brief Callback on disconnect */ 128void onDisConnect() { 129 digitalWrite(IN_BUILT_LED_BLUE, LOW); 130 Serial.println("!!PS4 Disconnected!!"); 131}
Save the sketch as
esp_rx_ps4_library.ino
.Create a new file
joy_msg.hpp
in the same directory as the sketch. Copy the contents of the JoyData structure from above into this.Create two files
crc8.hpp
andcrc8.cpp
in the same directory as the sketch. The contents of these files are as follows:
crc8.hpp1/** 2 ******************************************************************************* 3 * @file crc8.hpp 4 * @brief Header file 8-bit crc calculation 5 * @author Robotics Team, IOE Pulchowk Campus 6 ******************************************************************************* 7 */ 8 9#ifndef CRC8_HPP_ 10#define CRC8_HPP_ 11 12#include <stdint.h> 13 14#define WIDTH (8) /**< CRC width */ 15#define CRC_HASH_TABLE_SIZE (256) /**< Size of CRC hash table */ 16#define TOP_BIT (1 << 7) /**< Top bit position */ 17 18/** 19 * @brief Class for CRC-8 checksum calculation. 20 */ 21class CRC8 22{ 23public: 24 /** 25 * @brief Constructor for CRC8 class. 26 * @param polynomial Polynomial value for CRC calculation. 27 */ 28 CRC8(uint8_t polynomial); 29 30 /** 31 * @brief Destructor for CRC8 class. 32 */ 33 ~CRC8() {} 34 35 /** 36 * @brief Calculate CRC-8 checksum for the given data. 37 * @param buf Pointer to the data buffer. 38 * @param len Length of the data buffer. 39 * @return CRC-8 checksum value. 40 */ 41 uint8_t get_hash(uint8_t *buf, uint16_t len); 42 43private: 44 uint8_t table_[CRC_HASH_TABLE_SIZE]; /**< CRC hash table. */ 45}; 46 47#endif // CRC_HPP_crc8.cpp1/** 2******************************************************************************** 3 * @file crc8.cpp 4 * @brief Implementation file for the crc8 class 5 * @author Robotics Team, IOE Pulchowk Campus 6 * @date 2023 7 ******************************************************************************* 8 */ 9 10#include "crc8.hpp" 11 12/** 13 * @brief Constructor for CRC8 class. 14 * 15 * This constructor initializes the CRC8 object with the specified polynomial. 16 * It pre-computes the CRC hash table used for CRC calculation. 17 * 18 * @param poly The polynomial value for CRC calculation. 19 */ 20CRC8::CRC8(uint8_t poly) 21{ 22 uint8_t remainder; 23 int dividend = 0; 24 25 for (; dividend < 256; ++dividend) 26 { 27 remainder = dividend << (WIDTH - 8); 28 29 for (uint8_t b = 8; b > 0; --b) 30 { 31 if (remainder & TOP_BIT) 32 { 33 remainder = (remainder << 1) ^ poly; 34 } 35 else 36 { 37 remainder = (remainder << 1); 38 } 39 } 40 table_[dividend] = remainder; 41 } 42} 43 44/** 45 * @brief Calculate CRC-8 checksum for the given data. 46 * 47 * This method calculates the CRC-8 checksum for the provided data buffer. 48 * 49 * @param buf Pointer to the data buffer. 50 * @param len Length of the data buffer. 51 * @return CRC-8 checksum value. 52 */ 53uint8_t CRC8::get_hash(uint8_t *buf, uint16_t len) 54{ 55 uint8_t data; 56 uint8_t remainder = 0; 57 58 for (uint16_t index = 0; index < len; ++index) 59 { 60 data = buf[index] ^ (remainder >> (WIDTH - 8)); 61 remainder = table_[data] ^ (remainder << 8); 62 } 63 64 return remainder; 65}
Select board
DOIT ESP32 DEVKIT V1
fromTools > Board > esp32
.Select port and upload the sketch to the ESP32 board.
Open the Serial Monitor. Press the
PS button
on the PS4 controller to turn it on. You should see the joystick data changing according to the input from the PS4 controller.
4.4. Using ESP32_Bluepad32
In Arduino IDE, go to
File > Preferences
and add the following URL to theAdditional Board Manager URLs
field:https://raw.githubusercontent.com/ricardoquesada/esp32-arduino-lib-builder/master/bluepad32_files/package_esp32_bluepad32_index.json
Open the Board Manager by going to
Tools > Board > Board Manager
. Search foresp32_bluepad32
by Ricardo Quesda and install the package.Create a new sketch and paste the following contents:
esp_rx_bluepad.ino1#include <Bluepad32.h> 2#include "joy_msg.hpp" 3#include "crc8.hpp" 4 5#define IN_BUILT_LED_BLUE 2 6#define IN_BUILT_LED_BLUE 2 7#define RXD2 16 8#define TXD2 17 9#define START_BYTE 0xA5 10 11bool connected = false; 12 13namespace BPB { 14enum BluePadButtonsMask { 15 Cross = 0x1, 16 Circle = 0x2, 17 Square = 0x04, 18 Triangle = 0x08, 19 Share = 0x02, 20 Power = 0x01, 21 Option = 0x04, 22 L3 = 0x100, 23 R3 = 0x200, 24 L1 = 0x10, 25 R1 = 0x20, 26 Up = 0x01, 27 Down = 0x02, 28 Left = 0x08, 29 Right = 0x04 30 // Touch 31}; 32} 33 34ControllerPtr myControllers[BP32_MAX_GAMEPADS]; 35CRC8 crc(7); 36JoyData jdata; 37bool isBraked = false; 38uint32_t last_transmit = 0; 39 40// This callback gets called any time a new gamepad is connected. 41// Up to 4 gamepads can be connected at the same time. 42void onConnectedController(ControllerPtr ctl) { 43 bool foundEmptySlot = false; 44 // for (int i = 0; i < BP32_MAX_GAMEPADS; i++) { 45 if (myControllers[0] == nullptr) { 46 Serial.printf("CALLBACK: Controller is connected, index=%d\n", 0); 47 // Additionally, you can get certain gamepad properties like: 48 // Model, VID, PID, BTAddr, flags, etc. 49 ControllerProperties properties = ctl->getProperties(); 50 Serial.printf("Controller model: %s, VID=0x%04x, PID=0x%04x\n", ctl->getModelName().c_str(), properties.vendor_id, 51 properties.product_id); 52 myControllers[0] = ctl; 53 foundEmptySlot = true; 54 connected = true; 55 digitalWrite(IN_BUILT_LED_BLUE, HIGH); 56 ctl->setColorLED(0, 255, 0); 57 // break; 58 // } 59 } 60 if (!foundEmptySlot) { 61 Serial.println("CALLBACK: Controller connected, but could not found empty slot"); 62 } 63} 64 65void onDisconnectedController(ControllerPtr ctl) { 66 bool foundController = false; 67 68 // for (int i = 0; i < BP32_MAX_GAMEPADS; i++) { 69 if (myControllers[0] == ctl) { 70 Serial.printf("CALLBACK: Controller disconnected from index=%d\n", 0); 71 myControllers[0] = nullptr; 72 foundController = true; 73 connected = false; 74 digitalWrite(IN_BUILT_LED_BLUE, LOW); 75 // break; 76 // } 77 } 78 79 if (!foundController) { 80 Serial.println("CALLBACK: Controller disconnected, but not found in myControllers"); 81 } 82} 83 84void blinkColorLED(ControllerPtr ctl) { 85 static bool isRed = false; 86 static uint32_t last_blink = 0; 87 uint32_t now = millis(); 88 if (now - last_blink > 1000) { 89 if (isRed) { 90 if (isBraked) { 91 ctl->setColorLED(255, 0, 255); 92 } else { 93 ctl->setColorLED(0, 255, 0); 94 } 95 } else { 96 ctl->setColorLED(50, 0, 0); 97 } 98 99 isRed = !isRed; 100 last_blink = now; 101 } 102} 103 104void dumpGamepad(ControllerPtr ctl) { 105 Serial.printf( 106 "idx=%d, dpad: 0x%02x, buttons: 0x%04x, axis L: %4d, %4d, axis R: %4d, %4d, brake: %4d, throttle: %4d, " 107 "misc: 0x%02x, gyro x:%6d y:%6d z:%6d, accel x:%6d y:%6d z:%6d bat:%d\n", 108 ctl->index(), // Controller Index 109 ctl->dpad(), // D-pad 110 ctl->buttons(), // bitmask of pressed buttons 111 ctl->axisX(), // (-511 - 512) left X Axis 112 ctl->axisY(), // (-511 - 512) left Y axis 113 ctl->axisRX(), // (-511 - 512) right X axis 114 ctl->axisRY(), // (-511 - 512) right Y axis 115 ctl->brake(), // (0 - 1023): brake button 116 ctl->throttle(), // (0 - 1023): throttle (AKA gas) button 117 ctl->miscButtons(), // bitmask of pressed "misc" buttons 118 ctl->gyroX(), // Gyro X 119 ctl->gyroY(), // Gyro Y 120 ctl->gyroZ(), // Gyro Z 121 ctl->accelX(), // Accelerometer X 122 ctl->accelY(), // Accelerometer Y 123 ctl->accelZ(), // Accelerometer Z 124 ctl->battery()); 125} 126 127void dumpJoyData(JoyData &jd) { 128 Serial.printf("JoyData:: lx:%d ly:%d rx:%d ry:%d lt:%u rt:%u bt:%04x\n", 129 jd.lx, 130 jd.ly, 131 jd.rx, 132 jd.ry, 133 jd.lt, 134 jd.rt, 135 jd.buttons); 136} 137 138template<typename T> 139T map(T in, T inMin, T inMid, T inMax, T outMin, T outMid, T outMax) { 140 T out; 141 if (in <= inMid) { 142 // Map from [inMin, inMid] to [outMin, outMid] 143 T inRange = inMid - inMin; 144 T outRange = outMid - outMin; 145 out = (in - inMin) * outRange / inRange + outMin; 146 } else { 147 // Map from [inMid, inMax] to [outMid, outMax] 148 T inRange = inMax - inMid; 149 T outRange = outMax - outMid; 150 out = (in - inMid) * outRange / inRange + outMid; 151 } 152 return out; 153} 154 155void processControllers() { 156 auto myController = myControllers[0]; 157 if (myController && myController->isConnected() && myController->hasData()) { 158 if (myController->isGamepad()) { 159 // dumpGamepad(myController); 160 161 jdata.lx = map(myController->axisX(), -508, -12, 512, -127, 0, 127); 162 jdata.ly = map(myController->axisY(), -508, 8, 512, -127, 0, 127); 163 jdata.rx = map(myController->axisRX(), -508, -32, 512, -127, 0, 127); 164 jdata.ry = map(myController->axisRY(), -508, 18, 512, -127, 0, 127); 165 jdata.lt = map(myController->brake(), 0, 1020, 0, 255); 166 jdata.rt = map(myController->throttle(), 0, 1020, 0, 255); 167 168 jdata.buttons = 0; 169 if (myController->buttons() & BPB::Cross) jdata.buttons |= BUTTONS(myPS4::CROSS); 170 if (myController->buttons() & BPB::Circle) jdata.buttons |= BUTTONS(myPS4::CIRCLE); 171 if (myController->buttons() & BPB::Square) jdata.buttons |= BUTTONS(myPS4::SQUARE); 172 if (myController->buttons() & BPB::Triangle) jdata.buttons |= BUTTONS(myPS4::TRIANGLE); 173 if (myController->miscButtons() & BPB::Share) jdata.buttons |= BUTTONS(myPS4::SHARE); 174 if (myController->miscButtons() & BPB::Power) jdata.buttons |= BUTTONS(myPS4::PS_BUTTON); 175 if (myController->miscButtons() & BPB::Option) jdata.buttons |= BUTTONS(myPS4::OPTION); 176 if (myController->buttons() & BPB::L3) jdata.buttons |= BUTTONS(myPS4::L3); 177 if (myController->buttons() & BPB::R3) jdata.buttons |= BUTTONS(myPS4::R3); 178 if (myController->buttons() & BPB::L1) jdata.buttons |= BUTTONS(myPS4::L1); 179 if (myController->buttons() & BPB::R1) jdata.buttons |= BUTTONS(myPS4::R1); 180 if (myController->dpad() & BPB::Up) jdata.buttons |= BUTTONS(myPS4::UP); 181 if (myController->dpad() & BPB::Down) jdata.buttons |= BUTTONS(myPS4::DOWN); 182 if (myController->dpad() & BPB::Left) jdata.buttons |= BUTTONS(myPS4::LEFT); 183 if (myController->dpad() & BPB::Right) jdata.buttons |= BUTTONS(myPS4::RIGHT); 184 185 if (myController->miscButtons() & BPB::Power) { 186 if (myController->buttons() & BPB::L1 && myController->buttons() & BPB::R1) { 187 myController->setColorLED(0, 255, 0); 188 isBraked = false; 189 } else { 190 myController->setColorLED(255, 0, 255); 191 isBraked = true; 192 } 193 } 194 195 if (myController->battery() < 100) 196 blinkColorLED(myController); 197 } 198 } 199} 200 201void sendPacket() { 202 uint32_t now = millis(); 203 204 if (now - last_transmit >= 10) { 205 uint8_t packet[sizeof(jdata) + 2]; 206 packet[0] = START_BYTE; 207 memcpy((uint8_t *)packet + 1, (uint8_t *)&jdata, sizeof(jdata)); 208 packet[9] = crc.get_hash(packet + 1, sizeof(jdata)); 209 210 // Finally send packet to STM32 through UART2 211 Serial2.write(packet, sizeof(packet)); 212 // Serial.printf("%lu\t", now - last_transmit); 213 last_transmit = now; 214 // dumpJoyData(jdata); 215 } 216} 217// Arduino setup function. Runs in CPU 1 218void setup() { 219 pinMode(IN_BUILT_LED_BLUE, OUTPUT); 220 Serial.begin(115200); 221 Serial2.begin(115200, SERIAL_8N1, RXD2, TXD2); 222 223 Serial.printf("Firmware: %s\n", BP32.firmwareVersion()); 224 const uint8_t *addr = BP32.localBdAddress(); 225 Serial.printf("BD Addr: %2X:%2X:%2X:%2X:%2X:%2X\n", addr[0], addr[1], addr[2], addr[3], addr[4], addr[5]); 226 227 // Setup the Bluepad32 callbacks 228 BP32.setup(&onConnectedController, &onDisconnectedController); 229 230 // "forgetBluetoothKeys()" should be called when the user performs 231 // a "device factory reset", or similar. 232 // Calling "forgetBluetoothKeys" in setup() just as an example. 233 // Forgetting Bluetooth keys prevents "paired" gamepads to reconnect. 234 // But it might also fix some connection / re-connection issues. 235 BP32.forgetBluetoothKeys(); 236 237 238 // Enables mouse / touchpad support for gamepads that support them. 239 // When enabled, controllers like DualSense and DualShock4 generate two connected devices: 240 // - First one: the gamepad 241 // - Second one, which is a "virtual device", is a mouse. 242 // By default, it is disabled. 243 BP32.enableVirtualDevice(false); 244 245 BP32.enableNewBluetoothConnections(false); 246} 247 248// Arduino loop function. Runs in CPU 1. 249void loop() { 250 // This call fetches all the controllers' data. 251 // Call this function in your main loop. 252 if (BP32.update()) { 253 processControllers(); 254 if (connected) 255 sendPacket(); 256 } 257 // The main loop must have some kind of "yield to lower priority task" event. 258 // Otherwise, the watchdog will get triggered. 259 // If your main loop doesn't have one, just add a simple `vTaskDelay(1)`. 260 // Detailed info here: 261 // https://stackoverflow.com/questions/66278271/task-watchdog-got-triggered-the-tasks-did-not-reset-the-watchdog-in-time 262 263 vTaskDelay(1); 264 // delay(150); 265}
Save the sketch as
esp_rx_bluepad.ino
.Create a new file
joy_msg.hpp
in the same directory as the sketch. Copy the contents of the JoyData structure from above into this.Create two files
crc8.hpp
andcrc8.cpp
in the same directory as the sketch. The contents are same as above.Select board
DOIT ESP32 DEVKIT V1
fromTools > Board > esp32_bluepad32
.Select port and upload the sketch to the ESP32 board.
Open the Serial Monitor. Press the
PS button
and theShare button
on the PS4 controller to pair it with the ESP32 board. You should see the joystick data changing according to the input from the PS4 controller.
For more information on the ESP32_Bluepad32 package, see the documentation.
5. Setting Raspberry Pi Pico W as Receiver

Raspberry Pi Pico W
For the Raspberry Pi Pico W, we use Pico SDK
. The Pico SDK is a C/C++ SDK for the Raspberry Pi Pico and Pico W. It provides a set of libraries and tools to help you develop applications for the Pico and Pico W.
Till now, we have provided code straight on the page. Now, we are going to deal with huge code base. So we will provide the link of github repository. Follow the following instructions below to set up the Pico SDK and build the code for receving from PS4 controller:
To install Pico SDK, run
pico_setup.sh
cloning from here.git clone https://github.com/raspberrypi/pico-setup.git cd pico-setup export SKIP_VSCODE=1 && bash pico_setup.sh
Clone this repository and update submodule.
git clone https://github.com/Robotics-Club-Pulchowk/picow_ds4.git cd picow_ds4 git submodule update --init --recursive
Find out
MAC Address
of PS4 Controller. In easy way, connect it to yout PC and see theMAC Address
.Get into the source code folder, open
Src/bt_hid.c
, scroll down a little and replaceremote_addr_string
value by the MAC address of PS4.To print data on
Serial Monitor
, enable stdio usb by editing this line ofSrc/CMakeLists.txt
.pico_enable_stdio_usb(picow_ds4 1)
From the project directory, make build folder, execute
cmake
andmakefile
.cd picow_ds4 git submodule update --init --recursive mkdir build cd build cmake .. # you might use `-DPICO_BOARD=pico_w -DPICO_SDK_PATH=/your/path/to/pico-sdk` if environment is not set make -j4
.uf2
file is used for programmingPico
. You can find it atbuild/src/picow_ds4.uf2
Hold the boot button and connect the Pico W to PC using USB.
Drag and drop the
picow_ds4.uf2
file frombuild/src
to Pico WMass Storage
. You also can use similar command.# Just hit `tab` `tab` after `/media`. cp picow_ds4.uf2 /media/pi/PICOW
Long press
share
andPS4 button
simultaneous untill fast blink of PS4 LED. It will connect to Pico W. Pico W blinks its green LED if no controller is connected and solid green if it detects and connects to a controller.
Pico W sends uart packet same as ESP32 througth its UART0 default Tx and Rx pin i.e. pin 0
and pin 1
.
6. Setting STM32 to Receive Data from ESP32 or Pico W
6.1 STM32CubeMX Configuration
Open CubeMX and generate basic code with:
microcontroller:
stm32f407vgt6
or board:STM32F407VG-DISC1
project name:
stm32_joystick_master
Toolchain/IDE:
CMake
Move to STM32CubeMX
Pinout and Congiguration
. FromCategories
, selectConnectivity > USART4
. Changemode
toAsynchronous
mode. You may change alternate pins from forUSART4_RX
andUSART4_TX
.Under
Configuration > DMA Settings
, addUSART4_RX
DMA request.Set
PD12 Pin
asGPIO Output
. This pin is used to turn on/off the LED.Generate code and open the project.
6.2. Joystick Class with Other Classes and Setups
Create a new file
joy_msg.hpp
insideCore > Inc
. Copy and paste the below contents.joy_msg.hpp1#ifndef _JOY_MSG_HPP 2#define _JOY_MSG_HPP 3 4#include <stdint.h> 5 6#define BUTTONS(button) (1 << button) 7 8namespace PS4 9{ 10 enum Buttons 11 { 12 CROSS, 13 CIRCLE, 14 SQUARE, 15 TRIANGLE, 16 SHARE, 17 PS, 18 OPTION, 19 L3, 20 R3, 21 L1, 22 R1, 23 UP, 24 DOWN, 25 LEFT, 26 RIGHT, 27 TOUCH 28 }; 29} 30 31namespace XBox 32{ 33 enum Buttons 34 { 35 A, 36 B, 37 X, 38 Y, 39 BACK, 40 XBOX, 41 START, 42 LSTICL, 43 RSTICK, 44 LB, 45 RB, 46 UP, 47 DOWN, 48 LEFT, 49 RIGHT 50 }; 51} 52 53#pragma pack(push, 1) 54struct JoyData 55{ 56 int8_t lx; 57 int8_t ly; 58 int8_t rx; 59 int8_t ry; 60 uint8_t lt; 61 uint8_t rt; 62 uint16_t buttons; 63}; 64#pragma pack(pop) 65 66#endif // _JOY_MSG_HPP
This
joy_msg.hpp
has different identifier names for namespaces and some buttons than above.Create a new file
printf_config.h
insideCore > Inc
. Copy and paste the below contents:printf_config.c1#include "stm32f4xx_hal.h" 2 3#ifdef STDIO_USB 4#include "usbd_cdc_if.h" 5 6/*Redirect printf to USB CDC. We can see output on Serial Monitor*/ 7int _write(int file, char *data, int len) 8{ 9 CDC_Transmit_FS((uint8_t *)data, (uint16_t)len); 10 return len; 11} 12 13#else 14/*Redirect printf to ITM. We can see output on Serial Wire Viewer on STM32CubeProgrammer*/ 15int _write(int file, char *data, int len) 16{ 17 for (int i = 0; i < len; ++i) 18 { 19 ITM_SendChar(data[i]); 20 } 21 return len; 22} 23#endif
Create two new files
crc8.hpp
andcrc8.cpp
insideCore > Inc
andCore > Src
respectively. The contents of these files are as follows:crc8.hpp1/** 2 ****************************************************************************** 3 * @file crc8.hpp 4 * @brief Header file 8-bit crc calculation 5 * @author Robotics Team, IOE Pulchowk Campus 6 ****************************************************************************** 7 */ 8 9#ifndef CRC8_HPP_ 10#define CRC8_HPP_ 11 12#include <cstdint> 13 14#ifndef CRC_POLYNOMIAL 15#define CRC_POLYNOMIAL 7 /**< CRC Polynomial **/ 16#endif 17 18// clang-format off 19#define CRC_WIDTH 8 /**< CRC width */ 20#define CRC_HASH_TABLE_SIZE 256 /**< Size of CRC hash table */ 21#define TOP_BIT (1 << 7) /**< Top bit position */ 22// clang-format on 23 24/** 25 * @brief Class for CRC-8 checksum calculation. 26 */ 27class CRC8 28{ 29public: 30 /** 31 * @brief Destructor for CRC8 class. 32 */ 33 ~CRC8() {} 34 35 /** 36 * @brief Calculate CRC-8 checksum for uint8_t type data. 37 * @param buf Pointer to the data buffer. 38 * @param len Length of the data buffer. 39 * @return CRC-8 checksum value. 40 */ 41 uint8_t get_hash(uint8_t *buf, uint16_t len); 42 43 static CRC8 Instance; /**< Singleton instance of CRC8 class. */ 44 45private: 46 static uint8_t polynomial; /** CRC polynomial **/ 47 static uint8_t table_[CRC_HASH_TABLE_SIZE]; /**< CRC hash table. */ 48 49 /** 50 * @brief Private constructor for CRC8 class. 51 */ 52 CRC8(); 53 54 /** 55 * @brief Initialize the CRC hash table. 56 */ 57 static void initialize_table(); 58}; 59 60#endif // CRC_HPP_
crc8.cpp1/*********************************************************************************************** 2 * @file crc8.cpp 3 * @brief Implementation file for the crc8 class 4 * @author Robotics Team, IOE Pulchowk Campus 5 * @date 2024 6 * 7 * For more information on CRC calculation in C, \ 8 * see \link https://barrgroup.com/embedded-systems/how-to/crc-calculation-c-code \endlink. 9 **********************************************************************************************/ 10 11#include "crc8.hpp" 12 13CRC8 CRC8::Instance; 14uint8_t CRC8::polynomial; 15uint8_t CRC8::table_[CRC_HASH_TABLE_SIZE]; 16 17CRC8::CRC8() 18{ 19 polynomial = CRC_POLYNOMIAL; 20 initialize_table(); 21} 22 23void CRC8::initialize_table() 24{ 25 uint8_t remainder; 26 int dividend = 0; 27 28 for (; dividend < 256; ++dividend) 29 { 30#if CRC_WIDTH == 8 31 remainder = dividend; 32#else 33 remainder = dividend << (CRC_WIDTH - 8); 34#endif 35 for (uint8_t b = 8; b > 0; --b) 36 { 37 if (remainder & TOP_BIT) 38 { 39 remainder = (remainder << 1) ^ polynomial; 40 } 41 else 42 { 43 remainder = (remainder << 1); 44 } 45 } 46 table_[dividend] = remainder; 47 } 48} 49 50uint8_t CRC8::get_hash(uint8_t *buf, uint16_t len) 51{ 52 uint8_t data; 53 uint8_t remainder = 0; 54 55 for (uint16_t index = 0; index < len; ++index) 56 { 57#if CRC_WIDTH == 8 58 data = buf[index] ^ remainder; 59#else 60 data = buf[index] ^ (remainder >> (CRC_WIDTH - 8)); 61#endif 62 remainder = table_[data] ^ (remainder << 8); 63 } 64 65 return remainder; 66}
Create two new files
uart_def.h
anduart.hpp
insideCore > Inc
and a new fileuart.cpp
insideCore > Src
respectively. The contents of these files are as follows:uart_def.h1#ifndef UART_DEFINES_H__ 2#define UART_DEFINES_H__ 3 4#ifndef UART_START_BYTE 5#define UART_START_BYTE 0xA5 /**< Start byte of packet */ 6#endif 7 8#ifndef UART_MAX_RX_BUFFER_SIZE 9#define UART_MAX_RX_BUFFER_SIZE 50 /**< Maximum size of receive buffer */ 10#endif 11 12#ifndef UART_MAX_TX_BUFFER_SIZE 13#define UART_MAX_TX_BUFFER_SIZE 50 /**< Maximum size of transmit buffer */ 14#endif 15 16#ifndef UART_CONNCETION_TIMEOUT 17#define UART_CONNCETION_TIMEOUT 100 /**< Timeout for UART connection */ 18#endif 19 20#endif // UART_DEFINES_H__
uart.hpp1/** 2 ****************************************************************************** 3 * @file uart.hpp 4 * @brief Header file for our custom UART Class 5 * @author Robotics Team, IOE Pulchowk Campus 6 ****************************************************************************** 7 */ 8 9#ifndef UART_HPP__ 10#define UART_HPP__ 11 12#include "stm32f4xx_hal.h" 13#include "crc8.hpp" 14#include "uart_def.h" 15 16enum UARTMode 17{ 18 UART_RECEIVING, /**< UART in receiving mode */ 19 UART_TRANSMITTING, /**< UART in transmitting mode */ 20 UART_BOTH /**< UART in both receiving and transmitting mode */ 21}; 22 23/** 24 * @brief Enumeration defining the status of UART reception 25 */ 26enum UARTReceiveStatus 27{ 28 UART_START_BYTE_MATCHED, /**< Start byte of packet matched */ 29 UART_START_BYTE_ERROR, /**< Start byte of packet failed to match */ 30 UART_CRC_MATCHED, /**< CRC hash matched */ 31 UART_CRC_ERROR, /**< CRC hash failed to match */ 32 UART_INTERRUPT_CRASHED /**< UART Interrupt crashed while receiving*/ 33}; 34 35/** 36 * @brief Class representing the UART communication functionality 37 */ 38class UART 39{ 40public: 41 /** 42 * @brief Default constructor for UART class 43 * @warning Be careful, do not call init() if uart handle is not set 44 */ 45 UART(); 46 47 /** 48 * @brief Constructor for UART class 49 * @param[in] _huart Pointer to the UART handle 50 * @param[in] _mode UART Mode 51 * @param[in] _rt_size Size of receiving or transmitting data 52 * @param[in] _t_size Size of transmittin data used if r_size and t_size are different 53 */ 54 UART(UART_HandleTypeDef *_huart, UARTMode _mode, uint16_t _rt_size, uint16_t _t_size = 0); 55 56 /** 57 * @brief Default copy constructor for UART class 58 */ 59 UART(const UART &) = default; 60 61 /** 62 * @brief Default assignment operator for UART class 63 */ 64 UART &operator=(const UART &) = default; 65 66 /** 67 * @brief Default destructor for UART class 68 */ 69 ~UART() = default; 70 71 /** 72 * @brief Initialize UART communication 73 */ 74 bool init(); 75 76 /** 77 * @brief Parse the received data and check for start byte, data, and CRC 78 */ 79 UARTReceiveStatus process_receive_callback(); 80 81 /** 82 * @brief Process transmit callback to update status 83 */ 84 void process_transmit_callback(); 85 86 /** 87 * @brief Copy data from the received data buffer to the provided buffer 88 * @param[in] Pointer to the data to be received 89 */ 90 bool get_received_data(uint8_t *data); 91 92 /** 93 * @brief Transmit data over UART in blocking mode 94 * @param[in] Pointer to the data to be transmitted 95 * @return True if the data is transmitted successfully, false otherwise 96 */ 97 bool transmit(uint8_t *); 98 99 /** 100 * @brief Get the UART instance 101 * @return Pointer to the UART handle 102 */ 103 USART_TypeDef *get_uart_instance(); 104 105 /** 106 * @brief Check if the UART is connected 107 * @return True if connected, false otherwise 108 */ 109 bool connected(); 110 111 /** 112 * @brief Get the tick count of the last received data 113 * @return Tick count of the last received data 114 */ 115 uint32_t get_last_receive_tick(); 116 117 /** 118 * @brief Get the tick count of the last data reception completion 119 * @return Tick count of the last data reception completion 120 */ 121 uint32_t get_last_receive_cplt_tick(); 122 123 /** 124 * @brief Get the tick count of the last data transmission 125 * @return Tick count of the last data transmission 126 */ 127 uint32_t get_last_transmit_tick(); 128 129 /** 130 * @brief Get the tick count of the last data transmission completion 131 * @return Tick count of the last data transmission completion 132 */ 133 uint32_t get_last_transmit_cplt_tick(); 134 135 /** 136 * @brief Get the period of last data reception completion 137 * @return Period of last data reception completion 138 */ 139 uint32_t get_receive_cplt_period(); 140 141 /** 142 * @brief Get the frequency of last data reception completion 143 * @return Frequency(Hz) of last data reception completion 144 */ 145 float get_receive_cplt_frequency(); 146 147 /** 148 * @breie Get Receive Throughput 149 * @param[in] time Time(ms) for bytes count [defualt 1000] 150 * @return Receive Throughput in bytes per time 151 */ 152 uint32_t get_receive_throughput(uint32_t time = 1000); 153 154 /** 155 * @brief Get the sequence number of the received data 156 * @return Sequence number of the received data 157 */ 158 uint32_t get_receive_seq(); 159 160 /** 161 * @brief Get the period of last data transmit completion 162 * @return Period of last data transmit completion 163 */ 164 uint32_t get_transmit_cplt_period(); 165 166 /** 167 * @brief Get the frequency of last data transmit completion 168 * @return Frequency(Hz) of last data transmit completion 169 */ 170 float get_transmit_cplt_frequency(); 171 172 /** 173 * @breie Get Transmit Throughput 174 * @param[in] time Time(ms) for bytes count [defualt 1000] 175 * @return Transmit Throughput in bytes per time 176 */ 177 uint32_t get_transmit_throughput(uint32_t time = 1000); 178 179 /** 180 * @brief Print UART status 181 */ 182 void print_uart_status(); 183 184 /** 185 * @brief Display the latest received packet in hexadecimal format 186 */ 187 void print_receive_buffer(); 188 189 /** 190 * @brief Display the transmitting data in hexadecimal format 191 */ 192 void print_transmit_buffer(); 193 194 /** 195 * @brief Recieve Error handler function 196 */ 197 static void UART_RxErrorCallBack(UART_HandleTypeDef *huart, UARTReceiveStatus status); 198 199 /** 200 * @brief Transmit Error handler function 201 */ 202 static void UART_TxErrorCallBack(UART_HandleTypeDef *huart); 203 204private: 205 UART_HandleTypeDef *huart; /**< Pointer to the UART handle */ 206 207 uint8_t receive_buffer[UART_MAX_RX_BUFFER_SIZE]; /**< Array to receive data */ 208 uint8_t transmit_buffer[UART_MAX_TX_BUFFER_SIZE]; /**< Array to transmit start byte, data, hash */ 209 uint8_t receive_data[UART_MAX_RX_BUFFER_SIZE - 2]; /**< Array to receive data */ 210 uint16_t r_size; /**< Sizes for receiving data */ 211 uint16_t t_size; /**< Sizes for tarnsmitting data */ 212 213 uint32_t last_receive_tick = 0; /**< last receive function call tick */ 214 uint32_t last_transmit_tick = 0; /**< last transmit function call tick */ 215 uint32_t last_receive_cplt_tick = 0; /**< last receive complete tick after crc matched */ 216 uint32_t last_transmit_cplt_tick = 0; /**< last transmit complete tick after transmit cplt callback */ 217 uint32_t receive_cplt_period = 0; /**< Period of last receive complete */ 218 uint32_t transmit_cplt_period = 0; /**< Period of last transmit complete */ 219 220 bool is_waiting_for_start_byte; /**< Current state of receiving */ 221 bool is_transmitting; /**< Current state of transmitting */ 222 UARTMode mode; /**< Current UART mode */ 223 UARTReceiveStatus received_status; /**< last receive status */ 224 uint16_t receive_seq = 0; /**< Sequence number of received data */ 225 uint16_t prev_receive_seq = 0; /**< Previous sequence number of received data */ 226 uint32_t start_byte_error_count = 0; /**< Count of start byte errors */ 227 uint32_t check_error_count = 0; /**< Count of CRC hash errors */ 228 uint32_t transmit_error_count = 0; /**< Count of transmit errors */ 229}; 230 231#endif // UART_HPP__
uart.cpp1/*********************************************************************************************** 2 * @file uart.cpp 3 * @brief Implementation file for the UART class providing communication functionality. 4 * @author Robotics Team, IOE Pulchowk Campus 5 * @date 2023 6 **********************************************************************************************/ 7 8#include <memory.h> 9#include <stdio.h> 10 11#include "uart.hpp" 12 13UART::UART() : huart(nullptr) {} 14 15UART::UART(UART_HandleTypeDef *_huart, UARTMode _mode, uint16_t _rt_size, uint16_t _t_size) 16 : huart(_huart), mode(_mode) 17{ 18 if (_mode == UART_RECEIVING || _mode == UART_BOTH) 19 { 20 r_size = _rt_size; 21 } 22 if (_mode == UART_TRANSMITTING || _mode == UART_BOTH) 23 { 24 t_size = _t_size > 0 ? _t_size : _rt_size; 25 } 26} 27 28bool UART::init() 29{ 30 if (huart == nullptr) 31 return false; 32 33 is_waiting_for_start_byte = true; 34 is_transmitting = false; 35 36 if (mode == UART_RECEIVING || mode == UART_BOTH) 37 { 38 HAL_UART_Receive_DMA(huart, receive_buffer, 1); 39 } 40 41 return true; 42} 43 44/** 45 * This function receives data over UART. It performs the following steps: 46 * - Checks whether the start byte has been received. 47 * - If the start byte is received, proceeds to receive the data payload along with the CRC. 48 * - If the start byte is not received, continues waiting for it. 49 * - Once the data is received, validates the CRC. 50 * - Copies the data to the provided buffer and updates the reception status accordingly. 51 * 52 * @attention This function assumes that the UART has been initialized in RECEIVING mode or BOTH 53 * prior to calling this function. If the UART is initialized in a different mode, 54 * the behavior of this function may not be as expected. 55 */ 56UARTReceiveStatus UART::process_receive_callback() 57{ 58 /* Know the current tick */ 59 uint32_t current_tick = HAL_GetTick(); 60 61 /* Check whether received start byte or data */ 62 if (is_waiting_for_start_byte) 63 { 64 /* If it is start byte, check its value */ 65 if (receive_buffer[0] == UART_START_BYTE) 66 { 67 /* Reset start byte flag for receiving data */ 68 is_waiting_for_start_byte = false; 69 70 /* Trigger DMA for receiving data */ 71 HAL_UART_Receive_DMA(huart, receive_buffer + 1, r_size + 1); 72 73 /* Update received_status for start byte matched */ 74 received_status = UART_START_BYTE_MATCHED; 75 } 76 else /* Start byte failed */ 77 { 78 /* Increase start byte error count for testing*/ 79 start_byte_error_count++; 80 /* Call user defined error callback */ 81 UART_RxErrorCallBack(huart, UART_START_BYTE_ERROR); 82 /* Again try to receive start byte */ 83 HAL_UART_Receive_DMA(huart, receive_buffer, 1); 84 85 /* Update received_status for start byte failure */ 86 received_status = UART_START_BYTE_ERROR; 87 } 88 } 89 else /* Receive data */ 90 { 91 /* Calculate CRC for received data */ 92 uint8_t hash = CRC8::Instance.get_hash(receive_buffer + 1, r_size); 93 94 /* Compare with received Hash */ 95 if (hash == receive_buffer[r_size + 1]) 96 { 97 /* Put data from receive buffer to receive data*/ 98 memcpy(receive_data, receive_buffer + 1, r_size); 99 100 /* Update received_status for CRC Matched */ 101 received_status = UART_CRC_MATCHED; 102 103 /* Update receive_seq, receive_cplt_period and last_receive_cplt_tick as new data is received in user memory */ 104 if (receive_seq == 65535) 105 receive_seq = 0; 106 else 107 receive_seq++; 108 109 receive_cplt_period = current_tick - last_receive_cplt_tick; 110 last_receive_cplt_tick = current_tick; 111 } 112 else /* Hash didn't match */ 113 { 114 /* Increase check error count for testing*/ 115 check_error_count++; 116 /* Call user defined error callback */ 117 UART_RxErrorCallBack(huart, UART_CRC_ERROR); 118 /* Update received_status for CRC failure */ 119 received_status = UART_CRC_ERROR; 120 } 121 122 /* Set start byte flag to receive start byte again for the next packet */ 123 is_waiting_for_start_byte = true; 124 125 /* Trigger DMA for receiving start byte */ 126 HAL_UART_Receive_DMA(huart, receive_buffer, 1); 127 } 128 129 /* Update last_receive_tick to track receive call */ 130 last_receive_tick = current_tick; 131 132 /* Return received_status */ 133 return received_status; 134} 135 136void UART::process_transmit_callback() 137{ 138 /* Update transmit_cplt_period */ 139 uint32_t now = HAL_GetTick(); 140 transmit_cplt_period = now - last_transmit_cplt_tick; 141 last_transmit_cplt_tick = now; 142 is_transmitting = false; 143} 144 145/** 146 * This function receives data over UART. It checks whether the data has been received 147 * and copies the data to the provided buffer. 148 */ 149bool UART::get_received_data(uint8_t *r_data) 150{ 151 if (receive_seq != prev_receive_seq) 152 { 153 /* Copy the data*/ 154 memcpy(r_data, receive_data, r_size); 155 /*Update prev_receive_seq so we can use it in next receive*/ 156 prev_receive_seq = receive_seq; 157 158 return true; 159 } 160 161 return false; 162} 163 164/** 165 * This function transmits data over UART. It prepares the data packet by loading 166 * the start byte, copying the transmitted data, calculating and loading the CRC, 167 * then transmitting the data using DMA. It also updates the transmit tick. 168 * 169 * @note This function assumes that the UART communication has been initialized 170 * prior to calling this function. 171 */ 172bool UART::transmit(uint8_t *t_data) 173{ 174 /* Load start byte */ 175 transmit_buffer[0] = UART_START_BYTE; 176 177 /* Load tx data */ 178 memcpy(transmit_buffer + 1, t_data, t_size); 179 180 /* Calculate and Load CRC */ 181 transmit_buffer[t_size + 1] = CRC8::Instance.get_hash(transmit_buffer + 1, t_size); 182 183 /* Transmit tx data using DMA */ 184 if (HAL_UART_Transmit_DMA(huart, transmit_buffer, t_size + 2) == HAL_OK) 185 { 186 /* Update transmit tick */ 187 last_transmit_tick = HAL_GetTick(); 188 /* Update is_transmitting flag */ 189 is_transmitting = true; 190 return true; 191 } 192 193 /* Increase transmit error count for testing*/ 194 transmit_error_count++; 195 /* Call user defined error callback */ 196 UART_TxErrorCallBack(huart); 197 198 return false; 199} 200 201USART_TypeDef *UART::get_uart_instance() 202{ 203 return huart->Instance; 204} 205 206bool UART::connected() 207{ 208 return (HAL_GetTick() - last_receive_cplt_tick < UART_CONNCETION_TIMEOUT); 209} 210 211uint32_t UART::get_last_receive_tick() 212{ 213 return last_receive_tick; 214} 215 216uint32_t UART::get_last_transmit_tick() 217{ 218 return last_transmit_tick; 219} 220 221uint32_t UART::get_last_receive_cplt_tick() 222{ 223 return last_receive_cplt_tick; 224} 225 226uint32_t UART::get_receive_cplt_period() 227{ 228 return receive_cplt_period; 229} 230 231float UART::get_receive_cplt_frequency() 232{ 233 if (receive_cplt_period == 0) 234 return 0.0f; 235 236 return 1000.0f / receive_cplt_period; 237} 238 239uint32_t UART::get_receive_throughput(uint32_t time) 240{ 241 if (receive_cplt_period == 0 || time == 0) 242 return 0; 243 244 return (r_size + 2) * time / receive_cplt_period; 245} 246 247uint32_t UART::get_receive_seq() 248{ 249 return receive_seq; 250} 251 252uint32_t UART::get_transmit_cplt_period() 253{ 254 return transmit_cplt_period; 255} 256 257float UART::get_transmit_cplt_frequency() 258{ 259 if (transmit_cplt_period == 0) 260 return 0.0f; 261 262 return 1000.0f / transmit_cplt_period; 263} 264 265uint32_t UART::get_transmit_throughput(uint32_t time) 266{ 267 if (transmit_cplt_period == 0 || time == 0) 268 return 0; 269 270 return (t_size + 2) * time / transmit_cplt_period; 271} 272 273void UART::print_uart_status() 274{ 275 printf("========================================\n"); 276 printf("UART Status\n"); 277 printf("========================================\n"); 278 printf("Receive Status:\n"); 279 printf("----------------------------------------\n"); 280 printf("is_waiting_for_start_byte:: %d\n", is_waiting_for_start_byte); 281 printf("Receive Seq:: %hu\n", receive_seq); 282 printf("Start Byte Error Count:: %lu\n", start_byte_error_count); 283 printf("Check Error Count:: %lu\n", check_error_count); 284 printf("Last Receive Tick:: %lu\n", last_receive_tick); 285 printf("Last Transmit Tick:: %lu\n", last_transmit_tick); 286 printf("Last Receive Cplt Tick:: %lu\n", last_receive_cplt_tick); 287 printf("Last Transmit Cplt Tick:: %lu\n", last_transmit_cplt_tick); 288 printf("Receive Cplt Period:: %lu\n", receive_cplt_period); 289 printf("Receive Cplt Frequency:: %.2f\n", get_receive_cplt_frequency()); 290 printf("Receive Throughput:: %lu\n", get_receive_throughput()); 291 printf("----------------------------------------\n"); 292 printf("Transmit Status:\n"); 293 printf("----------------------------------------\n"); 294 printf("is_transmitting:: %d\n", is_transmitting); 295 printf("Transmit Cplt Period:: %lu\n", transmit_cplt_period); 296 printf("Transmit Cplt Frequency:: %.2f\n", get_transmit_cplt_frequency()); 297 printf("Transmit Throughput:: %lu\n", get_transmit_throughput()); 298 printf("========================================\n\n"); 299} 300 301void UART::print_receive_buffer() 302{ 303 printf("UART RX Data::"); 304 for (int i = 0; i <= r_size + 1; ++i) 305 { 306 printf(" %02x", receive_buffer[i]); 307 } 308 printf("\n"); 309} 310 311void UART::print_transmit_buffer() 312{ 313 printf("UART TX Data::"); 314 for (int i = 0; i <= t_size + 1; ++i) 315 { 316 printf("%02x ", transmit_buffer[i]); 317 } 318 printf("\n"); 319} 320 321/*Weakly defined, so user can have its own implementation and donot throw error if not defined*/ 322__weak void UART::UART_RxErrorCallBack(UART_HandleTypeDef *huart, UARTReceiveStatus status) 323{ 324 (void)huart; 325 (void)status; 326} 327 328/*Weakly defined, so user can have its own implementation and donot throw error if not defined*/ 329__weak void UART::UART_TxErrorCallBack(UART_HandleTypeDef *huart) 330{ 331 (void)huart; 332}
Create two new files
joystick.hpp
andjoystick.cpp
insideCore > Inc
andCore > Src
repsectively. Copy and paste the below contents:joystick.hpp1/** 2 ****************************************************************************** 3 * @file joystick.hpp 4 * @brief Header file for supporting joysticks 5 * @author Robotics Team, IOE Pulchowk Campus 6 ****************************************************************************** 7 */ 8 9#ifndef _JOYSTICK_HPP 10#define _JOYSTICK_HPP 11 12#include <math.h> 13 14#include "stm32f4xx_hal.h" 15#include "joy_msg.hpp" 16#include "uart.hpp" 17 18#ifndef JOYSTICK_LONG_PRESS_DURATION 19#define JOYSTICK_LONG_PRESS_DURATION 750 // ms 20#endif 21#ifndef JOYSTICK_DEBOUNCE_DURATION 22#define JOYSTICK_DEBOUNCE_DURATION 30 // ms 23#endif 24 25#if !(defined USE_PS4 || USE_XBOX) 26#define USE_PS4 27#endif 28 29template <typename t> 30inline t map(t x, t in_min, t in_max, t out_min, t out_max) 31{ 32 return (x - in_min) * (out_max - out_min) / (in_max - in_min) + out_min; 33} 34 35template <typename t> 36inline t map(t x, t out_min, t out_max) 37{ 38 return map(x, 0, 1, out_min, out_max); 39} 40 41/** 42 * @brief Base class for joystick functionality. 43 * 44 * This class provides basic functionality for working with joysticks, 45 * including initialization, data retrieval, and printing raw data. 46 */ 47class Joystick 48{ 49private: 50 uint8_t dead_band; /**< Deadband value for joystick axes. */ 51 uint16_t timeout; /**< Timeout value for checking joystick connection. */ 52 JoyData data; /**< Structure to hold joystick data. */ 53 uint16_t prev_buttons = 0; /**< Previous button state. */ 54 uint16_t pressed_buttons = 0; /**< Pressed buttons. */ 55 uint16_t just_pressed_buttons = 0; /**< Just pressed buttons. */ 56 uint16_t just_released_buttons = 0; /**< Just released buttons. */ 57 uint16_t clicked_buttons = 0; /**< Clicked buttons. */ 58 uint16_t long_pressed_buttons = 0; /**< Long pressed buttons. */ 59 uint32_t first_pressed_tick[16] = {0}; 60 uint32_t button_update_tick = 0; 61 62public: 63 UART uart; /**< UART instance for communication. */ 64 65 /** 66 * @brief Default constructor. 67 */ 68 Joystick() = default; 69 70 /** 71 * @brief Constructor with parameters. 72 * 73 * Initializes the joystick with the given UART instance, deadband value, and timeout. 74 * 75 * @param _huart Pointer to the UART_HandleTypeDef for UART communication. 76 * @param _dead_band Deadband value for joystick axes. 77 * @param _timeout Timeout value for checking joystick connection. 78 */ 79 Joystick(UART_HandleTypeDef *_huart, uint8_t _dead_band = 20, uint16_t _timeout = 100); 80 81 Joystick(const Joystick &) = default; 82 83 Joystick &operator=(const Joystick &) = default; 84 85 /** 86 * @brief Default destructor. 87 */ 88 ~Joystick() = default; 89 90 /** 91 * @brief Initialize joystick to start UART communication. 92 */ 93 bool init() 94 { 95 return uart.init(); 96 } 97 98 /** 99 * @brief Update joystick data. 100 * 101 * @return True if data is updated, false otherwise. 102 */ 103 bool update(); 104 105 /** 106 * @brief Print received data from joystick uart. 107 */ 108 void print_received_data(); 109 110 /** 111 * @brief Print data from joystick. 112 */ 113 void print_data(); 114 115 /** 116 * @brief Callback function for UART communication. 117 */ 118 void uart_callback() 119 { 120 uart.process_receive_callback(); 121 } 122 123 /** 124 * @brief Check if joystick is connected. 125 126 * @return True if joystick is connected, false otherwise. 127 */ 128 bool connected() 129 { 130 if ((HAL_GetTick() - uart.get_last_receive_cplt_tick() < timeout) && (uart.get_last_receive_cplt_tick() != 0)) 131 return true; 132 return false; 133 } 134 135 /** 136 * @brief Get joystick data. 137 * 138 * @return JoyData structure containing joystick data. 139 */ 140 JoyData get_data() 141 { 142 return data; 143 } 144 145 /** 146 * @brief Get button state. 147 * 148 * @return 16-bit integer representing button state. 149 */ 150 uint16_t get_buttons() 151 { 152 return data.buttons; 153 } 154 155 /** 156 * @brief Get previous button state. 157 * 158 * @return 16-bit integer representing clicked button flag. 159 */ 160 uint16_t get_clicked_buttons() 161 { 162 return clicked_buttons; 163 } 164 165 /** 166 * @brief Get toggled button state. 167 * 168 * @return 16-bit integer representing toggled button flag. 169 */ 170 uint16_t get_toggled_buttons() 171 { 172 return just_pressed_buttons | just_pressed_buttons; 173 } 174 175 /** 176 * @brief Get long pressed button state. 177 * 178 * @return 16-bit integer representing long pressed button flag. 179 */ 180 uint16_t get_long_pressed_buttons() 181 { 182 return long_pressed_buttons; 183 } 184 185 /** 186 * @brief Get button state. 187 * 188 * @param button Button number. 189 * @return True if button is pressed, false otherwise. 190 */ 191 bool pressed(uint8_t button) 192 { 193 return pressed_buttons & BUTTONS(button); 194 } 195 196 bool only_pressed(uint8_t button) 197 { 198 return (pressed_buttons & BUTTONS(button)) && !(pressed_buttons & ~BUTTONS(button)); 199 } 200 201 /** 202 * @brief Get button state. 203 * 204 * @param button Button number. 205 * @return True if button is released, false otherwise. 206 */ 207 bool released(uint8_t button) 208 { 209 return !(pressed_buttons & BUTTONS(button)); 210 } 211 212 bool just_pressed(uint8_t button) 213 { 214 return just_pressed_buttons & BUTTONS(button); 215 } 216 217 bool just_released(uint8_t button) 218 { 219 return just_released_buttons & BUTTONS(button); 220 } 221 222 /** 223 * @brief Get button state. 224 * 225 * @param button Button number. 226 * @return True if button is clicked, false otherwise. 227 */ 228 bool clicked(uint8_t button) 229 { 230 return clicked_buttons & BUTTONS(button); 231 } 232 233 bool only_clicked(uint8_t button) 234 { 235 return (clicked_buttons & BUTTONS(button)) && !(pressed_buttons & ~BUTTONS(button)); 236 } 237 238 /** 239 * @brief Get button state. 240 * 241 * @param button Button number. 242 * @return True if button is toggled, false otherwise. 243 */ 244 bool toggled(uint8_t button) 245 { 246 return get_toggled_buttons() & BUTTONS(button); 247 } 248 249 /** 250 * @brief Get button state. 251 * 252 * @param button Button number. 253 * @return True if button is long pressed, false otherwise. 254 */ 255 bool long_pressed(uint8_t button) 256 { 257 return long_pressed_buttons & BUTTONS(button); 258 } 259 260 /** 261 * @brief Get left stick x-axis. 262 * 263 * @return float Normalized left stick value of x-axis. 264 */ 265 float lx() 266 { 267 if (abs(data.lx) < dead_band) 268 return 0.0f; 269 return (map<float>((float)data.lx, -127.0f, 127.0f, -1.0f, 1.0f)); 270 } 271 272 /** 273 * @brief Get left stick y-axis. 274 * 275 * @return float Normalized left stick value of y-axis. 276 */ 277 float ly() 278 { 279 if (abs(data.ly) < dead_band) 280 return 0.0f; 281 return (map<float>((float)data.ly, -127.0f, 127.0f, -1.0f, 1.0f)); 282 } 283 284 /** 285 * @brief Get right stick x-axis. 286 * 287 * @return float Normalized right stick value of x-axis. 288 */ 289 float rx() 290 { 291 if (abs(data.rx) < dead_band) 292 return 0.0f; 293 return (map<float>((float)data.rx, -127.0f, 127.0f, -1.0f, 1.0f)); 294 } 295 296 /** 297 * @brief Get right stick y-axis. 298 * 299 * @return float Normalized right stick value of y-axis. 300 */ 301 float ry() 302 { 303 if (abs(data.ry) < dead_band) 304 return 0.0f; 305 return (map<float>((float)data.ry, -127.0f, 127.0f, -1.0f, 1.0f)); 306 } 307 308 /** 309 * @brief Get left trigger. 310 * 311 * @return float Normalized left trigger value. 312 */ 313 float lt() 314 { 315 return (map<float>((float)data.lt, 0.0f, 255.0f, 0.0f, 1.0f)); 316 } 317 318 /** 319 * @brief Get right trigger normalized value. 320 * 321 * @return float Normalized right trigger value. 322 */ 323 float rt() 324 { 325 return (map<float>((float)data.rt, 0.0f, 255.0f, 0.0f, 1.0f)); 326 } 327}; 328 329#endif // _JOYSTICK_HPP
joystick.cpp1/** 2 ****************************************************************************** 3 * @file joystick.cpp 4 * @brief Implementation for supporting joystics 5 * @author Robotics Team, IOE Pulchowk Campus 6 ****************************************************************************** 7 */ 8 9#include <math.h> 10#include <stdio.h> 11 12#include "joystick.hpp" 13 14Joystick::Joystick(UART_HandleTypeDef *_huart, uint8_t _dead_band, uint16_t _timeout) 15 : dead_band(_dead_band), timeout(_timeout), uart(_huart, UART_RECEIVING, 8) {} 16 17bool Joystick::update() 18{ 19 if (!connected()) 20 { 21 prev_buttons = 0; 22 pressed_buttons = 0; 23 just_pressed_buttons = 0; 24 just_released_buttons = 0; 25 clicked_buttons = 0; 26 long_pressed_buttons = 0; 27 return false; 28 } 29 30 just_pressed_buttons = 0; 31 just_released_buttons = 0; 32 clicked_buttons = 0; 33 34 if (!uart.get_received_data((uint8_t *)&data)) 35 return false; 36 37 uint32_t now = HAL_GetTick(); 38 39 if (now - button_update_tick >= JOYSTICK_DEBOUNCE_DURATION) 40 { 41 long_pressed_buttons = 0; 42 pressed_buttons = data.buttons; 43 just_pressed_buttons = (data.buttons & ~prev_buttons); 44 just_released_buttons = (~data.buttons & prev_buttons); 45 46 for (int i = 0; i < 16; ++i) 47 { 48 bool is_pressed = data.buttons & BUTTONS(i); 49 50 if (is_pressed) 51 { 52 if (!(prev_buttons & BUTTONS(i))) 53 { 54 first_pressed_tick[i] = now; 55 } 56 57 if (now - first_pressed_tick[i] >= JOYSTICK_LONG_PRESS_DURATION) 58 { 59 long_pressed_buttons |= BUTTONS(i); 60 } 61 } 62 else 63 { 64 if (prev_buttons & BUTTONS(i)) 65 { 66 if (now - first_pressed_tick[i] < JOYSTICK_LONG_PRESS_DURATION) 67 { 68 clicked_buttons |= BUTTONS(i); 69 } 70 } 71 } 72 } 73 74 prev_buttons = data.buttons; 75 button_update_tick = now; 76 } 77 78 return true; 79} 80 81void Joystick::print_received_data() 82{ 83 printf("Joystick: lx:%d ly:%d rx:%d ry:%d lt:%u rt:%u buttons:%04x\n", 84 data.lx, 85 data.ly, 86 data.rx, 87 data.ry, 88 data.lt, 89 data.rt, 90 data.buttons); 91} 92 93void Joystick::print_data() 94{ 95 printf("Joystick: lx:%f ly:%f rx:%f ry:%f lt:%f rt:%f, prs:%04x, tgl:%04x clk:%04x lng:%04x:\n", 96 lx(), 97 ly(), 98 rx(), 99 ry(), 100 lt(), 101 rt(), 102 pressed_buttons, 103 get_toggled_buttons(), 104 clicked_buttons, 105 long_pressed_buttons); 106}
6.3. Code to Receive Data
Create two new files
App.h
andApp.cpp
insideCore > Inc
andCore > Src
respectively. Copy and paste the below contents:app.h1#ifndef __APP_H 2#define __APP_H 3 4#ifdef __cplusplus 5extern "C" { 6#endif 7 8void setup(); 9 10void loop(); 11 12#ifdef __cplusplus 13} 14#endif 15 16#endif // __APP_H
app.cpp1#include <stdio.h> 2 3#include "usart.h" 4#include "joystick.hpp" 5#include "app.h" 6 7Joystick joystick(&huart4); 8uint32_t last_loop_tick = 0; 9uint32_t last_rx_tick = 0; 10uint32_t last_print_tick = 0; 11 12void setup() 13{ 14 joystick.init(); 15 printf("Joystick initialized\n"); 16 last_loop_tick = HAL_GetTick(); 17} 18 19void loop() 20{ 21 if (HAL_GetTick() - last_loop_tick < 10) 22 return; 23 24 last_loop_tick = HAL_GetTick(); 25 26 // Must call every 10ms or less. 27 bool is_joystick_updated = joystick.update(); 28 29 if (!joystick.connected()) 30 { 31 printf("Joystick not connected\n"); 32 } 33 34 if (is_joystick_updated) 35 { 36 if (HAL_GetTick() - last_print_tick > 100) 37 { 38 last_print_tick = HAL_GetTick(); 39 joystick.print_data(); 40 // joystick.print_received_data(); 41 } 42 } 43 44 float lx, ly, rx, ry, lt, rt; 45 lx = joystick.lx(); 46 ly = joystick.ly(); 47 rx = joystick.rx(); 48 ry = joystick.ry(); 49 lt = joystick.lt(); 50 rt = joystick.rt(); 51 52 bool is_lb_clicked = joystick.clicked(PS4::L1); 53 bool is_rb_clicked = joystick.clicked(PS4::R1); 54 55 // only clicked is true if the button is clicked and no other button is pressed 56 bool is_lb_only_clicked = joystick.only_clicked(PS4::L1); 57 bool is_rb_only_clicked = joystick.only_clicked(PS4::R1); 58 bool is_lb_pressed = joystick.pressed(PS4::L1); 59 bool is_rb_pressed = joystick.pressed(PS4::R1); 60 bool is_lb_long_pressed = joystick.long_pressed(PS4::L1); 61 bool is_rb_long_pressed = joystick.long_pressed(PS4::R1); 62 63 // experiment yourself :) 64} 65 66void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart) 67{ 68 if (huart->Instance == joystick.uart.get_uart_instance()) 69 { 70 joystick.uart_callback(); 71 last_rx_tick = HAL_GetTick(); 72 73 if (HAL_GetTick() - last_rx_tick > 30) 74 { 75 last_rx_tick = HAL_GetTick(); 76 HAL_GPIO_TogglePin(GPIOD, GPIO_PIN_12); 77 } 78 } 79}
Navigate to
Core > Src
and openmain.c
. Includeapp.h
./* USER CODE BEGIN Includes */ #include "app.h" /* USER CODE END Includes */
Call
setup
andloop
inmain
function./* Infinite loop */ /* USER CODE BEGIN WHILE */ while (1) { loop(); /* USER CODE END WHILE */ /* USER CODE BEGIN 3 */ } /* USER CODE END 3 */
6.4. Build and Flash
Now inside
Core
, the structure should look similar to this:Inc\ └── app.h └── crc8.hpp └── dma.h └── gpio.h └── joy_msg.hpp └── joystick.hpp └── main.h └── stm32f4xx_hal_conf.h └── stm32f4xx_it.h └── uart_def.h └── uart.hpp └── usart.h Src\ └── app.cpp └── crc8.cpp └── dma.c └── gpio.c └── joystick.cpp └── main.c └── printf_config.c └── stm32_crc8.cpp └── stm32f4xx_hal_msp.c └── stm32f4xx_it.c └── syscalls.c └── sysmem.c └── system_stm32f4xx.c └── uart.cpp └── usart.c
Update
CMakeLists.txt
as follows:CMakeLists.txt1cmake_minimum_required(VERSION 3.22) 2 3# 4# This file is generated only once, 5# and is not re-generated if converter is called multiple times. 6# 7# User is free to modify the file as much as necessary 8# 9 10# Setup compiler settings 11set(CMAKE_C_STANDARD 11) 12set(CMAKE_C_STANDARD_REQUIRED ON) 13set(CMAKE_C_EXTENSIONS ON) 14 15set(CMAKE_CXX_STANDARD 17) 16set(CMAKE_CXX_STANDARD_REQUIRED ON) 17set(CMAKE_CXX_EXTENSIONS ON) 18 19 20# Define the build type 21if(NOT CMAKE_BUILD_TYPE) 22 set(CMAKE_BUILD_TYPE "Debug") 23endif() 24 25# Set the project name 26set(CMAKE_PROJECT_NAME stm32_joystick_master) 27 28# Include toolchain file 29include("cmake/gcc-arm-none-eabi.cmake") 30 31# Enable compile command to ease indexing with e.g. clangd 32set(CMAKE_EXPORT_COMPILE_COMMANDS TRUE) 33 34# Core project settings 35project(${CMAKE_PROJECT_NAME}) 36message("Build type: " ${CMAKE_BUILD_TYPE}) 37 38# Enable CMake support for ASM and C languages 39enable_language(CXX C ASM) 40 41# Create an executable object type 42add_executable(${CMAKE_PROJECT_NAME}) 43 44# Add STM32CubeMX generated sources 45add_subdirectory(cmake/stm32cubemx) 46 47# Link directories setup 48target_link_directories(${CMAKE_PROJECT_NAME} PRIVATE 49 # Add user defined library search paths 50) 51 52# Add sources to executable 53target_sources(${CMAKE_PROJECT_NAME} PRIVATE 54 # Add user sources here 55 ${CMAKE_SOURCE_DIR}/Core/Src/printf_config.c 56 ${CMAKE_SOURCE_DIR}/Core/Src/crc8.cpp 57 ${CMAKE_SOURCE_DIR}/Core/Src/uart.cpp 58 ${CMAKE_SOURCE_DIR}/Core/Src/joystick.cpp 59 ${CMAKE_SOURCE_DIR}/Core/Src/app.cpp 60) 61 62# Add include paths 63target_include_directories(${CMAKE_PROJECT_NAME} PRIVATE 64 # Add user defined include paths 65) 66 67# Add project symbols (macros) 68target_compile_definitions(${CMAKE_PROJECT_NAME} PRIVATE 69 # Add user defined symbols 70) 71 72# Add linked libraries 73target_link_libraries(${CMAKE_PROJECT_NAME} 74 stm32cubemx 75 76 # Add user defined libraries 77) 78 79# Generate binary 80add_custom_command( 81 OUTPUT ${CMAKE_BINARY_DIR}/${CMAKE_PROJECT_NAME}.bin 82 COMMAND arm-none-eabi-objcopy -O binary ${CMAKE_BINARY_DIR}/${CMAKE_PROJECT_NAME}.elf ${CMAKE_BINARY_DIR}/${CMAKE_PROJECT_NAME}.bin 83 DEPENDS ${CMAKE_PROJECT_NAME} 84 COMMENT "Converting ELF to binary" 85) 86 87# Binary target 88add_custom_target(bin ALL 89 DEPENDS ${CMAKE_BINARY_DIR}/${CMAKE_PROJECT_NAME}.bin 90 COMMENT "Generating binary file" 91)
Build and flash the program.
mkdir build cd build cmake .. make -j st-flash write stm32_joystick_master.bin 0x8000000
Connect the ESP32 or Pico W to the STM32 board througth UART. Also connect PS4 controller.
Open
STM32CubeProgrammer
and connect to the STM32 board. You should see the joystick data changing according to the input from the PS4 controller. The LED onPD12
pin blinks if STM32 is receiving data from the ESP32 or Pico W.
Do your own experiment with the Joystick
class in App.cpp
.