Skip to content

Instantly share code, notes, and snippets.

@jaggzh
Created March 18, 2024 12:33
Show Gist options
  • Save jaggzh/0080773fecedcd27339e0a0ec6e55fd0 to your computer and use it in GitHub Desktop.
Save jaggzh/0080773fecedcd27339e0a0ec6e55fd0 to your computer and use it in GitHub Desktop.
#include <stdint.h>
#include <cfloat> // FLT_MIN FLT_MAX
#include "driver/adc.h" // use esp-idf directly
#define OPT_PLOT_DATA
/* #define OPT_PLOT_COUNT */
#define US_PER_SAMP 45
#define US_PER_60HZ_CYCLE 8333
/* #define BS 120 */
#define BS 150
#define CYCLES 30
#define CHOP BS
#define TRIGGER_FRAC .90
// Define macros for simplified serial printing
#define sp(v) Serial.print(v)
#define spl(v) Serial.println(v)
#define spt(v) do { Serial.print(v); Serial.print('\t'); } while(0)
#define spnl() Serial.println("")
// Define pin for sensing
#define IN1PIN 32
#define __min(a,b) ((a)<(b)?(a):(b))
#define __max(a,b) ((a)>(b)?(a):(b))
// State machine states
enum State {
ST_START,
ST_HIGHWAIT,
/* ST_READ, */
ST_PROCESSING,
ST_LOWWAIT
};
// Timing and state management variables
unsigned long highwait_delay_us = 0;
unsigned long lowwait_delay_us = 5000;
unsigned long hightime_us = 0;
unsigned long lowtime_us = 0;
int16_t in1zeros = 0;
int16_t in1ones = 0;
int16_t in1ctr = 0; // Additional counter for tracking the number of processed events
// Calibration thresholds
#define MIN_RATIO_CTR 100
#define MAX_RATIO_CTR (INT16_MAX / 2 - 2)
#define RATIO_CTR_RESET_VAL (MAX_RATIO_CTR - MIN_RATIO_CTR)
State state = ST_START;
float maxv=FLT_MIN, minv=FLT_MAX;
float smoothv, slowv, basev;
float lastv;
/* OnePinCapSense opcs = OnePinCapSense(); */
int buf_v[BS+1];
float buf_mavg_div[BS+1];
void read_cycle(void) {
for (int i=0; i<BS; i++) {
buf_v[i] = adc1_get_raw(ADC1_CHANNEL_4); // twice as fast
}
}
void read_cycle_buf(int *b, int len) {
for (int i=0; i<len; i++) {
b[i] = adc1_get_raw(ADC1_CHANNEL_4); // twice as fast
}
}
#define copy_cycle(d,f,len) memcpy(d, f, len*sizeof(*f))
unsigned long us_rising_4isr_st=0;
unsigned long us_rising_4isr_en=0;
void fisr_rising() { us_rising_4isr_en=micros(); }
void setup() {
Serial.begin(230400);
delay(2000); // safety so easier to flash if crashing code
// adc currently not unused v v v v
adc1_config_width(ADC_WIDTH_BIT_11); // Set ADC resolution to 10 bits
adc1_config_channel_atten(ADC1_CHANNEL_4, ADC_ATTEN_DB_11); // Example configuration
pinMode(IN1PIN, INPUT);
smoothv = lastv = slowv = basev = adc1_get_raw(ADC1_CHANNEL_4);
pinMode(IN1PIN, OUTPUT);
digitalWrite(IN1PIN, LOW);
state = ST_START;
attachInterrupt(IN1PIN, fisr_rising, RISING);
delay(50);
/* read_cycle(); */
/* copy_cycle(buf_mavg_div, buf_v, BS); */
}
void merge_cycle_mavg(float *db, float *newb, int div, unsigned int len) {
// merge difference with divider
db[0] = newb[0];
for (int i=1; i<len; i++) {
db[i] += (newb[i] - db[i])/div;
}
}
void merge_cycle_mavg(float *db, int *newb, int div, unsigned int len) {
// merge difference with divider
db[0] = newb[0];
for (int i=1; i<len; i++) {
db[i] += (newb[i] - db[i])/div;
}
}
void get_minmax(float *mnstore, float *mxstore, float *b, int len) {
float mn = FLT_MAX;
float mx = FLT_MIN;
for (int i=0; i<len; i++) {
if (b[i] > mx) mx = b[i];
if (b[i] < mn) mn = b[i];
}
*mnstore = mn;
*mxstore = mx;
}
int find_cnt_at_thresh(float *b, int len, float frac, float mn, float mx) {
float thresh=mn + (mx-mn)*frac;
for (int i=0; i<len; i++) {
if (b[i] > thresh) return i;
}
return -1;
}
void smooth_buf(float *dest, int *src, int len, int window, bool repeat_boundary) {
// Check if the window size is odd, adjust if even to ensure symmetry for smoothing
if (window % 2 == 0) {
window++; // Make window size odd if it's even
}
int halfWindow = window / 2;
for (int i = 0; i < len; i++) {
float sum = 0;
int count = 0;
for (int j = -halfWindow; j <= halfWindow; j++) {
int idx = i + j;
// Handle boundary cases
if (repeat_boundary) {
// Repeat boundary values if outside the array
if (idx < 0) idx = 0;
else if (idx >= len) idx = len - 1;
} else {
// Skip the out-of-bounds indices
if (idx < 0 || idx >= len) continue;
}
sum += src[idx];
count++;
}
dest[i] = sum / count; // Compute the average and assign it to the dest array
}
}
// https://gist.github.com/jaggzh/552022494eff43cf2a3712931d315c4d
void loop() {
unsigned long cmicros = micros();
unsigned long cmillis = millis();
unsigned long endmicros = micros();
static unsigned long last_read = cmillis;
static unsigned long last_print = cmillis;
unsigned long buf_us[BS+1];
float curmin, curmax;
float buf_v_smooth[BS];
/* #define TEST_TIMING_60HZ */
#ifdef TEST_TIMING_60HZ
if (cmillis - last_read > 50) {
#define SS4 185*8
int bb[SS4];
pinMode(IN1PIN, INPUT);
read_cycle_buf(bb, SS4);
int minVal = bb[0];
int maxVal = bb[0];
for (int i = 1; i < SS4; i++) {
if (bb[i] < minVal) minVal = bb[i];
if (bb[i] > maxVal) maxVal = bb[i];
}
int midpoint = (minVal + maxVal) / 2;
int zeroup = -1; // Initialize with -1 to indicate "not found"
// Search for the first value crossing the midpoint upwards
for (int i = 0; i < SS4 - 1; i++) {
if (bb[i] <= midpoint && bb[i + 1] > midpoint) {
zeroup = i + 1; // +1 to mark the transition point
break; // Exit the loop once the first midpoint crossing is found
}
}
if (zeroup != -1) {
// Limit the output to a quarter of the buffer size from the zero crossing point
for (int i = zeroup; i < SS4 && i - zeroup < SS4 / 4; i+=4) {
sp("z:");
spl(bb[i]);
}
}
last_read = millis();
}
return;
#endif
#define TEST_TIMING_ISR
#ifdef TEST_TIMING_ISR
static float slow_rise_us=100; // just somewhere to start
unsigned long cur_rise_us;
if (state == ST_START) {
if (cmillis-last_read > 5) {
pinMode(IN1PIN, OUTPUT);
digitalWrite(IN1PIN, LOW);
delayMicroseconds(12);
state = ST_HIGHWAIT;
us_rising_4isr_en = 0;
us_rising_4isr_st = micros();
pinMode(IN1PIN, INPUT);
}
} else if (state == ST_HIGHWAIT) {
if (us_rising_4isr_en) { // isr must have triggered
pinMode(IN1PIN, OUTPUT);
digitalWrite(IN1PIN, LOW);
cur_rise_us = us_rising_4isr_en-us_rising_4isr_st;
slow_rise_us += (cur_rise_us-slow_rise_us)/12;
sp("Rise_US:"); spt(cur_rise_us);
sp("SlovAvg_/12:"); spl(slow_rise_us);
last_read = millis();
#warning "We're resetting last_read millis at the END of the rise, so our state timing is variable and dependent on the capacitance and whatnot"
state = ST_START;
}
}
return;
#endif // TEST_TIMING_ISR
/* #define TEST_TIMING_PULLUP */
#ifdef TEST_TIMING_PULLUP
if (cmillis-last_read > 50) {
for (int cyc=0; cyc<CYCLES; cyc++) {
pinMode(IN1PIN, OUTPUT);
digitalWrite(IN1PIN, LOW);
delayMicroseconds(12);
cmicros = micros();
pinMode(IN1PIN, INPUT);
read_cycle(); // fills global unsigned long buf_v[BS]
endmicros = micros();
pinMode(IN1PIN, OUTPUT);
digitalWrite(IN1PIN, LOW);
smooth_buf(buf_v_smooth, buf_v, BS, 15, true);
/* for (int i=0; i<BS; i++) // assign estimated timings */
/* buf_us[i] = (unsigned long)((i*((double)(endmicros-cmicros)))/BS); */
/* for (int i=0; i<BS; i+=15) { */
/* /1* sp("us:"); spt(buf_us[i]); *1/ */
/* sp("v:"); spt(buf_v[i]); */
/* sp("sv:"); spl(buf_v_smooth[i]); */
/* } */
/* delay(2); */
/* return; */
if (!cyc)
copy_cycle(buf_mavg_div, buf_v_smooth, BS);
else
merge_cycle_mavg(buf_mavg_div, buf_v_smooth, 4, BS); // merge difference
}
for (int i=0; i<BS; i++) // assign estimated timings
buf_us[i] = (unsigned long)((i*((double)(endmicros-cmicros)))/BS);
get_minmax(&curmin, &curmax, buf_mavg_div, BS);
int count;
count = find_cnt_at_thresh(buf_mavg_div, BS, TRIGGER_FRAC, curmin, curmax);
float slowcount=count;
slowcount += (count-slowcount)/3;
#ifdef OPT_PLOT_DATA
for (int i=0; i<CHOP; i+=2) {
/* sp("us:"); spt(buf_us[i]); */
sp("v:"); spt(buf_v[i]);
sp("smv:"); spt(buf_mavg_div[i]);
sp("scnt:"); spt(slowcount);
/* sp("min:"); spt(curmin); */
/* sp("max:"); spt(curmax); */
/* if (i>=count) { */
/* sp("trig:"); */
/* spt(curmin + (curmax-curmin)*TRIGGER_FRAC); */
/* } else { */
/* sp("trig:"); */
/* spt(curmin); */
/* } */
/* sp("count*100:"); */
/* spt(count*100); */
spl("");
}
#elif defined(OPT_PLOT_COUNT)
sp("c:"); spt(count);
sp("smc:"); sp(slowcount);
spl("");
#endif
/* lastv = buf_v[BS-1]; */
/* for (int i=0; i<3; i++) { */
/* lastv += analogRead(IN1PIN); */
/* delayMicroseconds(1000); */
/* } */
pinMode(IN1PIN, OUTPUT);
digitalWrite(IN1PIN, LOW);
last_read = millis();
return;
smoothv = (float)smoothv + (float)(lastv-smoothv)/3;
slowv = (float)slowv + (float)(lastv-slowv)/8;
basev = (float)basev + (float)(lastv-basev)/98;
last_read = cmillis;
if (cmillis-last_print > 20) {
/* sp("us:"); spt(cmillis-last_print); */
sp("v:"); spl(lastv-basev);
/* sp("smv:"); spt(smoothv-basev); */
/* sp("slv:"); spl(slowv-basev); */
/* spl("Hit 0"); */
/* pinMode(IN1PIN, OUTPUT); */
/* digitalWrite(IN1PIN, LOW); */
last_print = cmillis;
}
}
return;
#endif
/* #define TEST_TIMING_OPCS */
#ifdef TEST_TIMING_OPCS
Serial.print(opcs.readCapacitivePin(IN1PIN));
delay(250);
#endif
/* #define TEST_TIMING */
#ifdef TEST_TIMING
pinMode(IN1PIN, INPUT_PULLUP);
delayMicroseconds(10000);
pinMode(IN1PIN, INPUT);
int i;
int v;
i=0;
while ((v = analogRead(IN1PIN))) {
i++;
spt(i);
spl(v);
}
spl("Hit 0");
/* pinMode(IN1PIN, OUTPUT); */
/* digitalWrite(IN1PIN, LOW); */
delay(250);
return;
#endif
switch (state) {
case ST_START:
digitalWrite(IN1PIN, HIGH);
hightime_us = cmicros;
state = ST_HIGHWAIT;
break;
case ST_HIGHWAIT:
if (cmicros - hightime_us >= highwait_delay_us) {
digitalWrite(IN1PIN, LOW);
pinMode(IN1PIN, INPUT);
int val = digitalRead(IN1PIN);
lowtime_us = micros();
procval(1, val);
state = ST_PROCESSING;
}
break;
case ST_PROCESSING:
state = ST_LOWWAIT;
lowtime_us = micros();
break;
case ST_LOWWAIT:
if (cmicros - lowtime_us >= lowwait_delay_us) {
state = ST_START;
}
break;
}
}
void procval(int sensor, int val) {
if (!val) in1zeros++;
else in1ones++;
in1ctr++;
// Check for threshold and reset counters if needed
if (in1zeros >= MAX_RATIO_CTR || in1ones >= MAX_RATIO_CTR || in1ctr >= MAX_RATIO_CTR) {
in1zeros -= RATIO_CTR_RESET_VAL;
in1ones -= RATIO_CTR_RESET_VAL;
in1ctr -= RATIO_CTR_RESET_VAL;
}
// Prevent counters from going negative
in1zeros = __max(in1zeros, 0);
in1ones = __max(in1ones, 0);
in1ctr = __max(in1ctr, 0);
// Adjust highwait_delay_us based on the ratio, but only if we have enough samples
if (in1ctr > MIN_RATIO_CTR) {
float ratio = (float)in1ones / (in1zeros + in1ones);
// Protect against divide by zero
if (in1zeros + in1ones > 0) {
// Adjust delay based on observed ratio
if (ratio < 0.5) {
if (highwait_delay_us > 9)
highwait_delay_us -= 1000; // Increase delay if more zeros
} else if (ratio > 0.5) {
highwait_delay_us += 1000; // Decrease delay if more ones
}
// Print ratio and current high wait delay
sp("CTR:"); spt(in1ctr);
sp("HLRatio:"); spt(ratio);
sp("HighWait_us:"); spt(highwait_delay_us);
sp("1Zeros:"); spt(in1zeros);
sp("1Ones:"); spl(in1ones);
}
}
// Reset for the next cycle if needed
if (in1ctr >= MAX_RATIO_CTR) {
in1zeros = 0;
in1ones = 0;
in1ctr = 0;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment