DC Motor high-pitched noise

I have one Arduino nano as the receiver and one ESP32 as transceiver, both with a nRF24L01+. The motor in question is connected to a L298 mini H-Bridge. But I don't know why it makes a weird high-pitched sound when the transceiver joystick is centered.

I know the issue is on the receiver code, because the previous version didn't make this weird noise. But I unfortunately lost it.

Transceiver code:

#include <Arduino.h>
#include <SPI.h>
#include <U8g2lib.h>
#include "nRF24L01.h"
#include "RF24.h"

#define LCDWidth u8g2.getDisplayWidth()
#define ALIGN_CENTER(t) ((LCDWidth - (u8g2.getUTF8Width(t))) / 2)
#define ALIGN_RIGHT(t) (LCDWidth - u8g2.getUTF8Width(t))
#define ALIGN_LEFT 0

U8G2_SSD1306_128X64_NONAME_F_HW_I2C u8g2(U8G2_R0);  // All Boards without Reset of the Display

// 'Max', 20x10px
const unsigned char maxbat[] PROGMEM = {
  0xfc, 0xff, 0x0f, 0x04, 0x00, 0x08, 0xf7, 0xde, 0x0b, 0xf1, 0xde, 0x0b, 0xf1, 0xde, 0x0b, 0xf1,
  0xde, 0x0b, 0xf1, 0xde, 0x0b, 0xf7, 0xde, 0x0b, 0x04, 0x00, 0x08, 0xfc, 0xff, 0x0f
};
// 'Medium', 20x10px
const unsigned char medbat[] PROGMEM = {
  0xfc, 0xff, 0x0f, 0x04, 0x00, 0x08, 0x07, 0xde, 0x0b, 0x01, 0xde, 0x0b, 0x01, 0xde, 0x0b, 0x01,
  0xde, 0x0b, 0x01, 0xde, 0x0b, 0x07, 0xde, 0x0b, 0x04, 0x00, 0x08, 0xfc, 0xff, 0x0f
};
// 'Low', 20x10px
const unsigned char lowbat[] PROGMEM = {
  0xfc, 0xff, 0x0f, 0x04, 0x00, 0x08, 0x07, 0xc0, 0x0b, 0x01, 0xc0, 0x0b, 0x01, 0xc0, 0x0b, 0x01,
  0xc0, 0x0b, 0x01, 0xc0, 0x0b, 0x07, 0xc0, 0x0b, 0x04, 0x00, 0x08, 0xfc, 0xff, 0x0f
};

// 'ConfigIcon', 13x13px
const unsigned char configIcon[] PROGMEM = {
  0x00, 0x00, 0x4e, 0x04, 0x4a, 0x04, 0x4e, 0x04, 0x44, 0x0e, 0x44, 0x0a, 0x44, 0x0e, 0x44, 0x04,
  0xe4, 0x04, 0xa4, 0x04, 0xe4, 0x04, 0x44, 0x04, 0x00, 0x00
};

// 'JogosIcon', 10x10px
const unsigned char JogosIcon[] PROGMEM = {
  0x02, 0x01, 0xfd, 0x02, 0x01, 0x02, 0xcd, 0x02, 0x45, 0x02, 0x01, 0x02, 0x79, 0x02, 0x85, 0x02,
  0x85, 0x02, 0x02, 0x01
};

// 'VoltarIcon', 10x10px
const unsigned char VoltarIcon[] PROGMEM = {
  0x30, 0x00, 0x18, 0x00, 0x0c, 0x00, 0xfe, 0x00, 0x0c, 0x01, 0x18, 0x02, 0x30, 0x02, 0x00, 0x01,
  0xfc, 0x00, 0x00, 0x00
};

// 'JoysticksIcon', 10x10px
const unsigned char JoysticksIcon[] PROGMEM = {
  0x30, 0x00, 0x78, 0x00, 0xcc, 0x00, 0xcc, 0x00, 0x78, 0x00, 0x30, 0x00, 0x30, 0x00, 0x78, 0x00,
  0xfe, 0x01, 0xff, 0x03
};

// 'PowerSavingIcon', 10x10px
const unsigned char PowerSavingIcon[] PROGMEM = {
  0x30, 0x00, 0xfc, 0x00, 0x84, 0x00, 0x94, 0x00, 0xa4, 0x00, 0x94, 0x00, 0xa4, 0x00, 0x94, 0x00,
  0x84, 0x00, 0xfc, 0x00
};

// 'ControlesIcon', 10x10px
const unsigned char ControlesIcon[] PROGMEM = {
  0x78, 0x00, 0x86, 0x01, 0x32, 0x01, 0x49, 0x02, 0x49, 0x02, 0x79, 0x02, 0x49, 0x02, 0x4a, 0x01,
  0x86, 0x01, 0x78, 0x00
};

const uint8_t *bitmaps[] = { VoltarIcon, ControlesIcon, PowerSavingIcon, JogosIcon, JoysticksIcon, VoltarIcon, VoltarIcon };
// ============================================================ //

RF24 radio(4, 5);

int MidpointEsqX = 119;
int MidpointEsqY = 119;
int MidpointDirX = 119;
int MidpointDirY = 119;
uint8_t Tolerance = 5;

int joyAX = 0; // Joystick Esquerdo X (0-255) (Midpoint: 119)
int joyAY = 0; // Joystick Esquerdo Y (0-255) (Midpoint: 119)
int joyBX = 0; // Joystick Direito X (0-255) (Midpoint: 119)
int joyBY = 0; // Joystick Direito Y (0-255) (Midpoint: 119)

struct MeuControleRemoto {
  boolean BotEsqCim = 0;
  boolean BotEsqEsq = 0;
  boolean BotEsqDir = 0;
  boolean BotEsqBai = 0;
  boolean BotDirCim = 0;
  boolean BotDirEsq = 0;
  boolean BotDirDir = 0;
  boolean BotDirBai = 0;
  boolean Mts1 = 0;
  boolean Mts2 = 0;
  boolean Mts3 = 0;
  boolean Mts4 = 0;
  byte Pot1 = 0;
  byte Pot2 = 0;
  byte Pot3 = 0;
  byte Pot4 = 0;
  byte JoyDireitaX = 127;
  byte JoyDireitaY = 127;
  byte JoyEsquerdaX = 127;
  byte JoyEsquerdaY = 127;
  byte rcBattery = 50; // 3.7v -> 5v (dividir por 10)
};

MeuControleRemoto ControleRemoto;

byte joyAXp = 27; 
byte joyAYp = 26; 
byte joyBXp = 25;
byte joyBYp = 33;

// #define joyAXp = 0; 
// #define joyAYp = 0; 
// #define joyBXp = 0;
// #define joyBYp = 0;
#define BotEsqCimPin = 0;
#define BotEsqEsqPin = 0;
#define BotEsqDirPin = 0;
#define BotEsqBaiPin = 0;
#define BotDirCimPin = 0;
#define BotDirEsqPin = 0;
#define BotDirDirPin = 0;
#define BotDirBaiPin = 0;
#define Mts1Pin = 0;
#define Mts2Pin = 0;
#define Mts3Pin = 0;
#define Mts4Pin = 0;
#define Pot1Pin = 0;
#define Pot2Pin = 0;
#define Pot3Pin = 0;
#define Pot4Pin = 0;


const int numReadings = 10;  // Número de leituras para calcular a média
const int numJoysticks = 4;  // Número de entradas de joystick

// Pinos dos joysticks
const int joystickPins[numJoysticks] = {joyAXp, joyAYp, joyBXp, joyBYp};

struct Joystick {
  int pin;
  int readings[numReadings];
  int readIndex;
  long total;
  int count;
};

Joystick joysticks[numJoysticks];

int getSmoothedJoystickValue(Joystick &joystick) {
  joystick.total -= joystick.readings[joystick.readIndex];
  int newValue = analogRead(joystick.pin);
  joystick.readings[joystick.readIndex] = newValue;
  joystick.total += newValue;
  joystick.readIndex = (joystick.readIndex + 1) % numReadings;
  if (joystick.count < numReadings) {
    joystick.count++;
  }
  return joystick.total / joystick.count;
}

const unsigned char *batLevel;
const unsigned char *RCbatLevel;
char *rcName = "TESTE";
int canal = 123;
bool botaoPressionado = false;

uint8_t lastRcBattery = ControleRemoto.rcBattery;
uint8_t remoteBattery = 45;
uint8_t lastRemoteBattery = 45; // ficticia

unsigned long startMillis;  // Millis para os loops em geral
unsigned long currentMillis;  // Millis para os loops em geral
const int period = 5000;  //the value is a number of milliseconds

bool menuAtivo = false;
int selected;  // Para os menus

bool rodou = 0; // Para o display saber se ja atualizou a tela

void setup(void) {
  Serial.begin(115200);
  u8g2.begin();

  startMillis = millis();  //initial start time

  pinMode(14, INPUT_PULLUP);
  pinMode(13, INPUT_PULLUP);
  pinMode(12, INPUT_PULLUP);

  for (int i = 0; i < numJoysticks; i++) {
    joysticks[i] = {joystickPins[i], {0}, 0, 0, 0};
    pinMode(joysticks[i].pin, INPUT);

  }

  if (radio.isChipConnected())
    Serial.println("\n\nTransmitter NF24 connected to SPI");
  else Serial.println("\n\nNF24 is NOT connected to SPI");

  radio.begin();                          // Inicializando o MÓDULO RF24l01 para comunicação.
  radio.setAutoAck(false);                // Desativando pacotes ACK (Pacote de Confirmação de Recebimento de Mensagem)
  radio.setChannel(100);                  // Configurando Módulo para operar no canal número 1 (você pode escolher um canal de 0 a 127) (Canal Padrão é o 76)
  radio.setDataRate(RF24_250KBPS);        // Configurando Módulo para operar em uma taxa de 250kbps (Menor taxa de dados possível para um maior alcance do rádio)
  radio.setPALevel(RF24_PA_HIGH);         // Configurando Módulo para transmitir em potência máxima
  radio.powerUp();                        // Ativando Módulo, caso entre em estado de baixo consumo.
  radio.openWritingPipe(0xE8E8F0F0E1LL);  // Abrindo o meio de comunicação entre transmissor e receptor e configurando endereço de comunicação (0xE8E8F0F0E1LL)
  radio.stopListening();                  // Interrompendo mecanismos de recepção "escuta" do Módulo

}

// int x = 0;

void loop() 
{

  if (menuAtivo) {
    menu();
    return;
  }

  joyAX = map(getSmoothedJoystickValue(joysticks[0]), 0, 4095, 0, 255);
  joyAY = map(getSmoothedJoystickValue(joysticks[1]), 0, 4095, 0, 255);
  joyBX = map(getSmoothedJoystickValue(joysticks[2]), 0, 4095, 0, 255);
  joyBY = map(getSmoothedJoystickValue(joysticks[3]), 0, 4095, 0, 255);

  if ((joyAX != ControleRemoto.JoyEsquerdaX) || (joyAY != ControleRemoto.JoyEsquerdaY) ||
      (joyBX != ControleRemoto.JoyDireitaX) || (joyBY != ControleRemoto.JoyDireitaY)) {
    
    if ((joyAX > (MidpointEsqX + Tolerance)) || ((joyAX < (MidpointEsqX - Tolerance)))) {
      ControleRemoto.JoyEsquerdaX = joyAX;
    }
    
    if ((joyAY > (MidpointEsqY + Tolerance)) || ((joyAY < (MidpointEsqY - Tolerance)))) {
      ControleRemoto.JoyEsquerdaY = joyAY;
    }

    if ((joyBX > (MidpointDirX + Tolerance)) || ((joyBX < (MidpointDirX - Tolerance)))) {
      ControleRemoto.JoyDireitaX = joyBX;
    }
    
    if ((joyBY > (MidpointDirY + Tolerance)) || ((joyBY < (MidpointDirY - Tolerance)))) {
      ControleRemoto.JoyDireitaY = joyBY;
    }

    /*
    Serial.print("joyAX: ");
    Serial.print(joyAX);
    Serial.print(" joyAY: ");
    Serial.print(joyAY);
    Serial.print(" | ");
    Serial.print("joyBX: ");
    Serial.print(joyBX);
    Serial.print(" joyBY: ");
    Serial.println(joyBY);
    */
  }

  
  if ((ControleRemoto.rcBattery != lastRcBattery || remoteBattery != lastRemoteBattery)/* && (rodou == 0)*/)
  {
    rodou = 1;
    Serial.println("Atualizou");
    u8g2.clearBuffer();  // clear the internal memory
    bateria();
    u8g2.setFont(u8g2_font_amstrad_cpc_extended_8u);
    u8g2.setCursor(1, 13);
    u8g2.print(F("CANAL: "));
    u8g2.setCursor(55, 13);
    u8g2.print(canal);

    u8g2.setFont(u8g2_font_t0_11_mr);
    u8g2.setCursor(ALIGN_CENTER(rcName), 30);
    u8g2.print(rcName);

    // Ícones
    u8g2.drawXBMP(107, 3, 20, 10, batLevel);
    u8g2.drawXBMP(107, 52, 20, 10, RCbatLevel);
    u8g2.drawXBMP(1, 50, 13, 13, configIcon);

    u8g2.drawHLine(0, 15, 128);
    u8g2.drawHLine(0, 47, 128);

    u8g2.sendBuffer();  // transfer internal memory to the display

    //u8g2.setPowerSave(1);
    //delay(5000);
    //u8g2.setPowerSave(0);
    //x += 1;
  }
  

  /*
  if (x == 5) {
    menuAtivo = true;
  }
  */

  for (int i = 0; i < 5; i++) {
    radio.write(&ControleRemoto, sizeof(ControleRemoto));
    // Serial.println(ControleRemoto.JoyDireitaX);
  }
}

void Controles() {}

void PowerSaving() {}

void Jogos() {}

void Debug() {}

// =========MENU========= //
typedef void (*FuncPtr)();

void menu() 
{
  const char *options[5] = {
    " Voltar",
    " Controles",
    " Power Saving",
    " Jogos",
    " Debug"
  };

  FuncPtr funcoesMenu[5] = {
    loop,
    Controles,
    PowerSaving,
    Jogos,
    Debug
  };

  const int menuLength = sizeof(options) / sizeof(options[0]);
  const unsigned long period = 20;  // Intervalo em milissegundos
  int start = 0;
  selected = 0;  // Começa no item "Voltar"

  bool last12State = HIGH; // Invertido por causa do INPUT_PULLUP
  bool last13State = HIGH;
  bool last14State = HIGH;

  while (menuAtivo) {

    if (digitalRead(14) != last14State) {

      if (digitalRead(14) == LOW) {
        last14State = LOW;
        botaoPressionado = true;

        selected += 1;

        if (selected >= menuLength) {
          selected = 0;  // Volta para o primeiro item
          start = 0;
        }

        if (selected >= start + 3) {
          start += 1;  // Ajuste o início para rolar para baixo
        }
      }
      delay(100);
    }

    last14State = digitalRead(14);

// Atualiza o estado anterior do botão

    if (digitalRead(12) == LOW) {
      botaoPressionado = true;
      
      if (selected == 0) {
        selected = menuLength - 1;  // Volta para o último item
        start = menuLength - 3;     // Ajuste o início para mostrar os últimos itens
      } else {
        selected -= 1;
      }

      if (selected < start) {
        start -= 1;  // Ajuste o início para rolar para cima
      }
    }

    if (digitalRead(13) == LOW) {
      botaoPressionado = true;
      
      if (selected == 0) {
        menuAtivo = false;
        return;
      }

      funcoesMenu[selected]();  // Chame a função correspondente ao item selecionado
    }

    if (botaoPressionado) {
      botaoPressionado = false;

      u8g2.clearBuffer();
      u8g2.setCursor(ALIGN_CENTER("MENU"), 13);
      u8g2.print(F("MENU"));
      u8g2.drawHLine(0, 15, 128);

      for (int i = start; i < start + 5 && i < menuLength; i++) {
        int yPos = 15 + (i - start) * 12;
        if (i == selected) {
          u8g2.setDrawColor(1);
          u8g2.drawBox(0, yPos + 6, 128, 10);
          u8g2.setDrawColor(0);
        } else {
          u8g2.setDrawColor(1);
        }
        u8g2.drawXBMP(0, yPos + 6, 10, 10, bitmaps[i]);
        u8g2.setCursor(14, yPos + 15);
        u8g2.print(options[i]);
      }

      u8g2.sendBuffer();
    }
  }
}

void bateria() 
{
  // Lê a V. bateria
  int x = 44;  // V. Ficticia (Controle)
  int y = ControleRemoto.rcBattery;  // V. Ficticia (Carrinho)

  if (x >= 50) {
    batLevel = maxbat;
  } else if (x > 43) {
    batLevel = medbat;
  } else {
    batLevel = lowbat;
  }

  if (y >= 50) {
    RCbatLevel = maxbat;
  } else if (y > 43) {
    RCbatLevel = medbat;
  } else {
    RCbatLevel = lowbat;
  }
}

Receiver code:

#include "RF24.h"
#include "nRF24L01.h"
#include "Servo.h"
#include <SPI.h>

struct MeuControleRemoto {
  boolean BotEsqCim = 0;
  boolean BotEsqEsq = 0;
  boolean BotEsqDir = 0;
  boolean BotEsqBai = 0;
  boolean BotDirCim = 0;
  boolean BotDirEsq = 0;
  boolean BotDirDir = 0;
  boolean BotDirBai = 0;
  boolean Mts1 = 0;
  boolean Mts2 = 0;
  boolean Mts3 = 0;
  boolean Mts4 = 0;
  byte Pot1 = 0;
  byte Pot2 = 0;
  byte Pot3 = 0;
  byte Pot4 = 0;
  byte JoyDireitaX = 127;
  byte JoyDireitaY = 127;
  byte JoyEsquerdaX = 127;
  byte JoyEsquerdaY = 127;
  byte rcBattery = 50; // 3.7v -> 5v (dividir por 10)
};

MeuControleRemoto ControleRemoto;
Servo servo;
RF24 radio(7, 8);

const byte PonteH1 = 5;
const byte PonteH2 = 6;

uint8_t midpointAX;
uint8_t midpointAY;
uint8_t midpointBX;
uint8_t midpointBY;
uint8_t LastServoPos = 90;

void setup() {
  // Serial.begin(9600);
  servo.attach(9);
  pinMode(PonteH1, OUTPUT);
  pinMode(PonteH2, OUTPUT);

  radio.begin();                             //Inicialisando o MÓDULO RF24l01 para comunicação.
  radio.setAutoAck(false);                   // Desativando pacotes ACK (Pacote de Confirmação de Recebimento de Mensagem)
  radio.setChannel(100);                     // Configurando canal de transmissão do rádio (você pode escolher um canal de 0 a 127)
  radio.setDataRate(RF24_250KBPS);           // Configurando Módulo para operar em uma taxa de 250kbps (Menor taxa de dados possível para um maior alcance do rádio)
  radio.setPALevel(RF24_PA_HIGH);            // Configurando Módulo para transmitir em potência máxima (a título de conhecimento, pois aqui o rádio está configurado apenas como receptor)
  radio.powerUp();                           // Ativando Módulo, caso entre em estado de baixo consumo.
  radio.startListening();                    //Inicializando o MÓDULO para 'escutar' as requisições enviadas pelo transmissor.
  radio.openReadingPipe(1, 0xE8E8F0F0E1LL);  // Abrindo o meio de comunicação entre transmissor e receptor, também configurando endereço de comunicação(0xE8E8F0F0E1LL)

  midpointAY = 114;
  
  // if (radio.isChipConnected())
  //   Serial.println("\n\nTransmitter NF24 connected to SPI");
  // else Serial.println("\n\nNF24 is NOT connected to SPI");
}

int x = 0;
int y = 0;

void loop() {

  /*
  Serial.print("1a: ");
  Serial.print(ControleRemoto.JoyEsquerdaY);
  Serial.print(" | ");
  Serial.print("2a: ");
  Serial.println(map(ControleRemoto.JoyEsquerdaY, midpointAY,0, 0, 255));
  */

  if (radio.available()) {  //verificando se há dados enviados pelo tranmissor.
    radio.read(&ControleRemoto, sizeof(ControleRemoto));  // Recebendo dados enviado pelo transmissor

  
    if (ControleRemoto.JoyDireitaX != LastServoPos) {
      //Serial.println(ControleRemoto.JoyDireitaX);
      servo.write(map(ControleRemoto.JoyDireitaX, 0,255, 60,120));
      LastServoPos = map(ControleRemoto.JoyDireitaX, 0,255, 60,120);
    }

    if (ControleRemoto.JoyEsquerdaY > midpointAY + 2) {
      
      analogWrite(PonteH1, map(ControleRemoto.JoyEsquerdaY, midpointAY + 2,255, 20, 255));
      digitalWrite(PonteH2, LOW);
    }

    if (ControleRemoto.JoyEsquerdaY < midpointAY - 2) {
      analogWrite(PonteH2, map(ControleRemoto.JoyEsquerdaY, midpointAY - 2,0, 20, 255));
      digitalWrite(PonteH1, LOW);
    }

    Serial.println(map(ControleRemoto.JoyEsquerdaY, midpointAY + 2,255, 20, 255));
    Serial.println(map(ControleRemoto.JoyEsquerdaY, midpointAY - 2,0, 20, 255));

  }
}

analogWrite() isn't true analog. It's PWM (for good reasons) and default PWM frequency is about 500Hz (in the lower-middle of the audio range). There is usually some vibration at the PWM frequency. It should be the worst around 50%.

There is a way to increase the PWM frequency and I think it can be above the audible range, but I don't know how to do that. And changing the frequency MIGHT foul-up other timing-related parts of your program.

For changing the PWM frequency
See:-
Secrets of Arduino pwm

1 Like