/* generated by ziphu for our prototype boards first useful deployment application into the market locally
* ESP32-S3 Ultrasonic Rodent Repeller
* Features:
* 1. Generates 20kHz - 60kHz ultrasonic sweep using LEDC (Hardware PWM).
* 2. Modulates the signal (Pulses it) at a lower frequency to prevent habituation.
* 3. Randomizes the sweep speed and center frequency slightly.
*/
// --- Configuration ---
const int PWM_PIN = 5; // GPIO pin connected to the transducer (+)
const int PWM_CHANNEL = 0; // LEDC channel (0-15)
// Frequency Settings
const int FREQ_MIN = 20000; // 20 kHz (Lower bound of rodent hearing)
const int FREQ_MAX = 60000; // 60 kHz (Upper effective range)
// Modulation (Pulsing) Settings
// This determines how fast the sound "blinks" on and off (e.g., 4 Hz = 4 times a second)
const int PULSE_RATE_MIN = 2; // Hz
const int PULSE_RATE_MAX = 10; // Hz
// Timing
unsigned long previousMillis = 0;
unsigned long sweepPrevMillis = 0;
// State Variables
int currentFreq = FREQ_MIN;
int freqDirection = 1; // 1 for going up, -1 for going down
int pulseInterval = 250; // ms (starts at 4Hz)
bool isSoundOn = true;
void setup() {
Serial.begin(115200);
// Configure LEDC for the PWM channel
// 13-bit resolution gives us steps = 8192.
// At 60kHz, this is still plenty of resolution.
ledcSetup(PWM_CHANNEL, currentFreq, 13);
// Attach the channel to the GPIO pin
ledcAttachPin(PWM_PIN, PWM_CHANNEL);
// Random Seed using floating pin A0 (if available) or time
// If your board doesn't have A0, just use randomSeed(analogRead(0));
// On S3, usually randomSeed(millis()) is fine if no noise pin is connected.
randomSeed(analogRead(0));
Serial.println("Rodent Repeller Started");
}
void loop() {
unsigned long currentMillis = millis();
// --- 1. Handle Frequency Sweeping (The "Screech") ---
// We change the frequency every X milliseconds to sweep through the range
int sweepSpeed = random(10, 50); // Change freq every 10 to 50ms (makes it erratic)
if (currentMillis - sweepPrevMillis >= sweepSpeed) {
sweepPrevMillis = currentMillis;
// Update frequency
currentFreq += (100 * freqDirection); // Jump 100Hz per step
// Reverse direction at limits
if (currentFreq >= FREQ_MAX) {
currentFreq = FREQ_MAX;
freqDirection = -1;
} else if (currentFreq <= FREQ_MIN) {
currentFreq = FREQ_MIN;
freqDirection = 1;
// When we hit the bottom, maybe randomize the pulse rate for variety
randomizePulseRate();
}
// Update the Hardware PWM
ledcWriteTone(PWM_CHANNEL, currentFreq);
}
// --- 2. Handle Modulation / Pulsing (The "Stutter") ---
// We toggle the sound on and off to mimic erratic noise
if (currentMillis - previousMillis >= pulseInterval) {
previousMillis = currentMillis;
isSoundOn = !isSoundOn;
if (isSoundOn) {
ledcWrite(PWM_CHANNEL, 4095); // 50% Duty Cycle (Max volume for square wave)
} else {
ledcWrite(PWM_CHANNEL, 0); // Mute
}
}
}
void randomizePulseRate() {
// Calculate a new random pulse interval in milliseconds based on Hz
int randomHz = random(PULSE_RATE_MIN, PULSE_RATE_MAX);
pulseInterval = 1000 / randomHz;
// Debug output (optional)
// Serial.print("New Pulse Rate: ");
// Serial.print(randomHz);
// Serial.println(" Hz");
}