Last active
May 16, 2025 23:35
-
-
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
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/*-------------------------------------------------------------------- | |
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