Skip to content

Instantly share code, notes, and snippets.

@46cv8
Last active May 16, 2025 23:35
Show Gist options
  • Save 46cv8/c3f152743faadd8a839f0ce3831e9ca9 to your computer and use it in GitHub Desktop.
Save 46cv8/c3f152743faadd8a839f0ce3831e9ca9 to your computer and use it in GitHub Desktop.
Receiver firmware for cigman CM-701 10khz pwm laser level using atmega32u4 and opt101 photodiode
/*--------------------------------------------------------------------
For CIGMAN CM-701, but may work with other laser units if you change the frequency to match.
You should use a frequency for the ADC exactly 4x the frequency of the laser PWM of our leveling unit.
Laser-Level Receiver → ATmega32U4 ADC @40 kHz → 4-sample
min/max with accumulation → EMA(high,low) → Tone mapping → Serial @50 Hz
--------------------------------------------------------------------*/
#include <Arduino.h>
// ——— USER CONFIGURATION ———
// ADC channel definitions for ATmega32U4:
// A0→7, A1→6, A2→5, A3→4, A4→1, A5→0, A6→2
#define ADC_PIN A0
#define ADC_CHANNEL 7
// Number of ADC samples to accumulate per chunk index (must be a power of 2)
#define ACC_SAMPLES 128
// Compute log2(ACC_SAMPLES) for bitshifting
#define ACC_SAMPLES_LOG2 7
#if (ACC_SAMPLES & (ACC_SAMPLES - 1)) != 0
#error "ACC_SAMPLES must be a power of 2"
#endif
// EMA smoothing weights
#define EMA_ALPHA_NEW 0.2f
#define EMA_ALPHA_OLD (1.0f - EMA_ALPHA_NEW)
// Serial output rate (ms)
#define PRINT_INTERVAL_MS 20 // ~50 Hz
// Tone thresholds and frequency range
#define TONE_MIN_ABSOLUTE_THRESHOLD 1.0f // minimum EMA_High to enable tone
#define TONE_MIN_THRESHOLD 1.05f
#define TONE_MAX_THRESHOLD 20.0f
#define TONE_MIN_FREQ 200
#define TONE_MAX_FREQ 2000
// Buzzer output pin
#define BUZZER_PIN 9
// ——— SHARED STATE ———
// Persistent accumulators
volatile uint32_t chunkAcc[4] = {0};
volatile uint16_t accCnt = 0;
volatile uint16_t chunkMin = 0;
volatile uint16_t chunkMax = 0;
volatile bool chunkReady = false;
float emaHigh = 0.0f;
float emaLow = 0.0f;
unsigned long lastPrintTime = 0;
// ——— ADC ISR triggered at 40 kHz → accumulate per chunk index ———
ISR(ADC_vect)
{
// digitalWrite(8, !digitalRead(8));
uint16_t v = ADC; // read ADC result
uint8_t idx = accCnt & 0x03; // determine chunk index (0..3)
chunkAcc[idx] += v; // accumulate sample
accCnt++;
if (accCnt >= (ACC_SAMPLES * 4))
{
// Compute averaged value per chunk index and find min/max using bitshift
uint16_t mn = chunkAcc[0] >> ACC_SAMPLES_LOG2;
uint16_t mx = mn;
for (uint8_t i = 1; i < 4; i++)
{
uint16_t avg = chunkAcc[i] >> ACC_SAMPLES_LOG2;
mn = min(mn, avg);
mx = max(mx, avg);
}
chunkMin = mn;
chunkMax = mx;
chunkReady = true;
// Reset accumulators for next batch
for (uint8_t i = 0; i < 4; i++)
chunkAcc[i] = 0;
accCnt = 0;
}
}
// ISR for Timer1 Compare Match B
ISR(TIMER1_COMPB_vect)
{
// digitalWrite(7, !digitalRead(7));
}
void setup()
{
// pinMode(8, OUTPUT);
// pinMode(7, OUTPUT); // Compare-Match-B indicator
Serial.begin(115200);
pinMode(BUZZER_PIN, OUTPUT);
digitalWrite(BUZZER_PIN, LOW);
// Seed EMAs with an initial reading
uint16_t initVal = analogRead(ADC_PIN);
emaHigh = emaLow = initVal;
// Prepare ADC pin for analog only
pinMode(ADC_PIN, INPUT);
DIDR0 |= (1 << ADC7D);
// Configure Timer1 for CTC @40 kHz, no prescaling
TCCR1A = 0;
TCCR1B = (1 << WGM12) | (1 << CS10); // CTC, prescaler=1
OCR1A = F_CPU / 40000UL - 1; // TOP for 40 kHz
OCR1B = OCR1A; // Compare Match B = TOP
TCNT1 = 0; // reset counter to zero
// Enable Compare Match B interrupt
TIMSK1 |= (1 << OCIE1B);
// Configure ADC for auto-trigger on Timer1 Compare Match B
ADMUX = (1 << REFS0) | (ADC_CHANNEL & 0x07);
ADCSRB = (1 << ADTS2) | (1 << ADTS0); // ADTS[2:0] = 101 (Timer1 Compare Match B)
ADCSRA = (1 << ADEN) | (1 << ADIE) | (1 << ADATE) | (1 << ADPS2); // prescaler = 16 (1 MHz ADC clock)
ADCSRA |= (1 << ADSC); // start first conversion
sei();
lastPrintTime = millis();
}
void loop()
{
if (chunkReady)
{
noInterrupts();
uint16_t mn = chunkMin;
uint16_t mx = chunkMax;
chunkReady = false;
interrupts();
emaHigh = EMA_ALPHA_OLD * emaHigh + EMA_ALPHA_NEW * mx;
emaLow = EMA_ALPHA_OLD * emaLow + EMA_ALPHA_NEW * mn;
float ratio = emaHigh / emaLow;
unsigned int freq = 0;
if (ratio >= TONE_MIN_THRESHOLD && emaHigh >= TONE_MIN_ABSOLUTE_THRESHOLD)
{
float clamped = constrain(ratio, TONE_MIN_THRESHOLD, TONE_MAX_THRESHOLD);
float norm = (clamped - TONE_MIN_THRESHOLD) / (TONE_MAX_THRESHOLD - TONE_MIN_THRESHOLD);
freq = TONE_MIN_FREQ + unsigned(norm * (TONE_MAX_FREQ - TONE_MIN_FREQ) + 0.5f);
tone(BUZZER_PIN, freq);
}
else
{
noTone(BUZZER_PIN);
}
/*
unsigned long now = millis();
if (now - lastPrintTime >= PRINT_INTERVAL_MS)
{
lastPrintTime += PRINT_INTERVAL_MS;
Serial.print("EMA_High=");
Serial.print(emaHigh, 1);
Serial.print(" EMA_Low=");
Serial.print(emaLow, 1);
Serial.print(" Ratio=");
Serial.print(ratio, 2);
Serial.print(" Freq=");
Serial.println(freq);
}
*/
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment