Hi, im building a gamepad, im using an ESP32 connected via Bluetooth.
Wiring is simple, the pins are connected to vcc with 10k resistors, and to 22,23 pins, common to GND
This is the code I have:
#include <ESP32Encoder.h> // https://github.com/madhephaestus/ESP32Encoder/
#include <BleGamepad.h> // https://github.com/MagnusThome/ESP32-BLE-Gamepad
BleGamepad bleGamepad("Wheel", "Arduino", 100);
#define MAXENC 1
uint8_t uppPin[MAXENC] = {22};
uint8_t dwnPin[MAXENC] = {23};
uint8_t encoderUpp[MAXENC] = {4};
uint8_t encoderDwn[MAXENC] = {5};
ESP32Encoder encoder[MAXENC];
unsigned long holdoff[MAXENC] = {0};
int32_t prevenccntr[MAXENC] = {1};
bool prevprs[MAXENC] = {0};
#define HOLDOFFTIME 150 // TO PREVENT MULTIPLE ROTATE "CLICKS" WITH CHEAP ENCODERS WHEN ONLY ONE CLICK IS INTENDED
void setup() {
Serial.begin(115200);
for (uint8_t i = 0; i < MAXENC; i++) {
encoder[i].clearCount();
encoder[i].attachSingleEdge(dwnPin[i], uppPin[i]);
}
//customKeypad.setHoldTime(1);
bleGamepad.begin();
Serial.println("Booted!");
}
void loop() {
unsigned long now = millis();
// -- ROTARY ENCODERS : ROTATION -- //
for (uint8_t i = 0; i < MAXENC; i++) {
int32_t cntr = encoder[i].getCount();
if (cntr != prevenccntr[i]) {
if (!holdoff[i]) {
if (cntr > prevenccntr[i]) {
sendKey(encoderUpp[i]);
}
if (cntr < prevenccntr[i]) {
sendKey(encoderDwn[i]);
}
holdoff[i] = now;
if (holdoff[i] == 0) holdoff[i] = 1; // SAFEGUARD WRAP AROUND OF millis() (WHICH IS TO 0) SINCE holdoff[i]==0 HAS A SPECIAL MEANING ABOVE
}
else if (now - holdoff[i] > HOLDOFFTIME) {
prevenccntr[i] = encoder[i].getCount();
holdoff[i] = 0;
}
}
}
}
void sendKey(uint8_t key) {
uint32_t gamepadbutton = key;
Serial.print("pulse\t");
Serial.println(key);
if (bleGamepad.isConnected()) {
bleGamepad.press(gamepadbutton);
delay(150);
bleGamepad.release(gamepadbutton);
}
}
The problem with that code is that jstest-gtk shows, when rotating CW, a good input, but, when clicking CCW, every.... 20 clicks, it sends the CW input.
Is a debounce problem? a broken encoder?
Also I tried this to use interrupts (I prefer use them ESP32 has a lot of interrupt pins, and I think its better that keep scanning a matrix or array of encoders)
#include "Arduino.h"
#include "NewEncoder.h"
#include <BleGamepad.h>
#ifndef ESP32
#error ESP32 Only
#endif
void handleEncoder(void *pvParameters);
void ESP_ISR callBack(NewEncoder *encPtr, const volatile NewEncoder::EncoderState *state, void *uPtr);
QueueHandle_t encoderQueue;
volatile int16_t prevEncoderValue;
volatile int16_t prevEncoderValue1;
#define numOfButtons 16
#define numOfHatSwitches 2
byte previousButtonStates[numOfButtons];
byte currentButtonStates[numOfButtons];
BleGamepad bleGamepad;
BleGamepadConfiguration bleGamepadConfig;
uint8_t newMACAddress[] = {0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF - 0x02};
void setup() {
Serial.begin(115200);
delay(1000);
BaseType_t success = xTaskCreatePinnedToCore(handleEncoder, "Handle Encoder", 1900, NULL, 2, NULL, 0);
if (!success) {
printf("Failed to create handleEncoder task. Aborting.\n");
while (1) {
yield();
}
}
bleGamepadConfig.setAutoReport(false);
bleGamepadConfig.setControllerType(CONTROLLER_TYPE_GAMEPAD); // CONTROLLER_TYPE_JOYSTICK, CONTROLLER_TYPE_GAMEPAD (DEFAULT), CONTROLLER_TYPE_MULTI_AXIS
bleGamepadConfig.setButtonCount(numOfButtons);
bleGamepadConfig.setHatSwitchCount(numOfHatSwitches);
bleGamepadConfig.setVid(0xe502);
bleGamepadConfig.setPid(0xabcd);
bleGamepad.begin(&bleGamepadConfig);
esp_base_mac_addr_set(&newMACAddress[0]);
}
void loop() {
}
void sendKey(uint8_t key) {
uint32_t gamepadbutton = key;
Serial.print("pulse\t");
Serial.println(key);
if(bleGamepad.isConnected()) {
bleGamepad.press(gamepadbutton);
}
}
void handleEncoder(void *pvParameters) {
NewEncoder::EncoderState currentEncoderstate;
NewEncoder::EncoderState currentEncoderstate1;
int16_t currentValue;
int16_t currentValue1;
encoderQueue = xQueueCreate(1, sizeof(NewEncoder::EncoderState));
if (encoderQueue == nullptr) {
printf("Failed to create encoderQueue. Aborting\n");
vTaskDelete(nullptr);
}
// This example uses Pins 25 & 26 for Encoder. Specify correct pins for your ESP32 / Encoder setup. See README for meaning of constructor arguments.
// Use FULL_PULSE for encoders that produce one complete quadrature pulse per detnet, such as: https://www.adafruit.com/product/377
// Use HALF_PULSE for endoders that produce one complete quadrature pulse for every two detents, such as: https://www.mouser.com/ProductDetail/alps/ec11e15244g1/?qs=YMSFtX0bdJDiV4LBO61anw==&countrycode=US¤cycode=USD
NewEncoder *encoder1 = new NewEncoder(25, 26, -100, 100, 0, FULL_PULSE);
NewEncoder *encoder2 = new NewEncoder(22, 23, -100, 100, 0, FULL_PULSE);
if (encoder1 == nullptr) {
printf("Failed to allocate NewEncoder object. Aborting.\n");
vTaskDelete(nullptr);
}
if (!encoder1->begin()) {
printf("Encoder Failed to Start. Check pin assignments and available interrupts. Aborting.\n");
delete encoder1;
vTaskDelete(nullptr);
}
encoder1->getState(currentEncoderstate);
prevEncoderValue = currentEncoderstate.currentValue;
printf("Encoder Successfully Started at value = %d\n", prevEncoderValue);
encoder1->attachCallback(callBack);
if (encoder2 == nullptr) {
printf("Failed to allocate NewEncoder object. Aborting.\n");
vTaskDelete(nullptr);
}
if (!encoder2->begin()) {
printf("Encoder Failed to Start. Check pin assignments and available interrupts. Aborting.\n");
delete encoder2;
vTaskDelete(nullptr);
}
encoder2->getState(currentEncoderstate1);
prevEncoderValue1 = currentEncoderstate1.currentValue;
printf("Encoder Successfully Started at value = %d\n", prevEncoderValue1);
encoder2->attachCallback(callBack);
for (;;) {
xQueueReceive(encoderQueue, ¤tEncoderstate, portMAX_DELAY);
printf("Encoder: ");
currentValue = currentEncoderstate.currentValue;
if (currentValue != prevEncoderValue) {
switch (currentEncoderstate.currentClick) {
case NewEncoder::UpClick:
sendKey(4);
break;
case NewEncoder::DownClick:
sendKey(5);
break;
default:
break;
}
if (currentButtonStates != previousButtonStates)
{
for (byte currentIndex = 0; currentIndex < numOfButtons; currentIndex++)
{
previousButtonStates[currentIndex] = currentButtonStates[currentIndex];
}
bleGamepad.sendReport();
}
prevEncoderValue = currentValue;
} else {
}
}
vTaskDelete(nullptr);
}
void ESP_ISR callBack(NewEncoder*encPtr, const volatile NewEncoder::EncoderState *state, void *uPtr) {
BaseType_t pxHigherPriorityTaskWoken = pdFALSE;
xQueueOverwriteFromISR(encoderQueue, (void * )state, &pxHigherPriorityTaskWoken);
if (pxHigherPriorityTaskWoken) {
portYIELD_FROM_ISR();
}
}
That second code, "works" as is... but, i need to release the key (it keeps pressed before the first rotation CW and CCW) so the logic editing is:
void sendKey(uint8_t key) {
uint32_t gamepadbutton = key;
Serial.print("pulse\t");
Serial.println(key);
if(bleGamepad.isConnected()) {
bleGamepad.press(gamepadbutton);
delay(150);
bleGamepad.release(gamepadbutton);
}
}
But it doesnt work, now, when rotating, nothing is clicked.
So, if I need to choose, i prefer to solve that second problem, i want to use interrupts... but if its not possible... well, if you know why a rotary messes clicks CCW but no CW and how to solve it... will be very thankful.
Thanks!