Skip to content

Instantly share code, notes, and snippets.

@LiSongMWO
Last active July 11, 2017 20:17
Show Gist options
  • Save LiSongMWO/b1e51cb9e80e1914b868a8bfb22a16fb to your computer and use it in GitHub Desktop.
Save LiSongMWO/b1e51cb9e80e1914b868a8bfb22a16fb to your computer and use it in GitHub Desktop.
Program for the target that is being calibrated
#include <avr/eeprom.h>
#include <avr/interrupt.h>
#include <avr/io.h>
#include <stdint.h>
#if F_CPU < 1000
#error "F_CPU must be defined to the MCU frequency in Hz"
#endif
#if MASTER_PWM_PERIOD < 1000
#error "MASTER_PWM_PERIOD must be PWM period of the master in target clocks"
#endif
#define MAX_CLOCK_TOLERANCE 3
#define MAX_CLOCK_VARIANCE 2
#define ICP 0
// Remember, the MCU must not have the CKOUT fuse programmed!
static_assert(F_CPU % MASTER_PWM_PERIOD == 0,
"The MASTER_PWM_PERIOD must evenly divide F_CPU!");
static_assert(
2 * F_CPU / MASTER_PWM_PERIOD < 0xFFFFULL,
"The MASTER_PWM_PERIOD is too high! There must be at least double "
"headroom in the 16bit counter to the target frequency.");
constexpr uint8_t num_samples_log2 = 3;
constexpr uint8_t num_samples = 1 << num_samples_log2;
constexpr uint8_t num_samples_mask = num_samples - 1;
constexpr uint8_t min_samples = num_samples - 2;
constexpr uint16_t max_sample_variance = 2;
volatile uint8_t sample_index = 0;
volatile uint16_t sample_buffer[num_samples];
// Computes the midpoint of two numbers without overflow, result is truncated to
// closest integer.
template <typename T> T middle(T low, T high) { return low + (high - low) / 2; }
ISR(TIMER1_CAPT_vect) {
TCNT1 = 0;
sample_buffer[sample_index] = ICR1;
sample_index = (sample_index + 1) & num_samples_mask;
}
/*******************************************************************************
* Will wait until all the samples in the sample buffer have been refreshed.
******************************************************************************/
void refresh_all_samples() {
auto start_index = sample_index;
bool measured_first = false;
while (!measured_first || start_index != sample_index) {
if (!measured_first && start_index != sample_index) {
measured_first = true;
}
}
}
/*******************************************************************************
* Will get a reliable measurement of the PWM period in host cycles
******************************************************************************/
uint16_t measure_pwm_period() {
uint16_t max = 0xFFFF;
uint16_t min = 0x0000;
while (max - min > MAX_CLOCK_VARIANCE) {
refresh_all_samples();
cli();
auto data = sample_buffer;
sei();
max = 0;
min = 0xFFFF;
for (uint8_t i = 0; i < num_samples; ++i) {
if (data[i] > max) {
max = data[i];
}
if (data[i] < min) {
min = data[i];
}
}
}
return middle(min, max);
}
int main() {
cli();
// Remove prescaler setting loaded from CKDIV8 fuse (if any) so we run
// at the native speed which is what we will calibrate.
CLKPR = _BV(CLKPCE);
CLKPR = 0;
// Pull up the Input Capture 1 Pin (PORTB0)
PORTB |= _BV(ICP);
DDRB &= ~_BV(ICP);
// Wait until the master pulls ICP line low.
while (PINB & _BV(ICP))
;
// Tristate the ICP pin
PORTB &= ~_BV(ICP);
// Enable input capture on ICP1 pin for 16bit timer/counter 1
TCCR1B = _BV(ICES1) | // Trigger Input Capture on positive edge.
_BV(CS10); // Enable timer with no pre-scaling.
TIMSK1 = _BV(ICIE1); // Enable IRQ on Input Capture
TCNT1 = 0; // Clear counter
// Give that the master PWM clock is 'K' Hz and our nominal clock rate is 'C'
// Hz. Then we should have 'C/K' of our clock cycles between each rising edge
// of the PWM signal. If the actual measured number of cycles 'X' is less than
// 'C/K' then our clock is too slow. Otoh if 'X > C/K' then our clock is too
// fast.
// The OSCCAL has two calibration ranges: 0x00 - 0x7F and 0x80 - 0xFF. The
// ranges overlap meaning that the setting OSCCAL to 0x7F gives higher
// frequency than 0x80.
// Static assert at top makes sure this wont overflow
constexpr uint16_t expected_pwm_period = F_CPU / MASTER_PWM_PERIOD; // 'C/K'
// We start by determining which range to search in.
uint8_t min = 0x00; // Try the low range first
uint8_t max = 0x7F;
// Try the upper bound of the lower range
OSCCAL = max;
sei();
auto sensed_pwm_period = measure_pwm_period();
if (sensed_pwm_period < expected_pwm_period) {
// The upper bound of the lower range is too slow, use the higher range.
min = 0x80;
max = 0xFF;
}
// Now binary search for a suitable value
while (min < max) {
auto mid = middle(min, max);
OSCCAL = mid;
sensed_pwm_period = measure_pwm_period();
if (sensed_pwm_period < expected_pwm_period) {
// mid is too (s)low
min = mid + 1;
} else if (sensed_pwm_period == expected_pwm_period) {
min = mid;
max = mid;
} else {
// mid is too hi (fast)
max = mid;
}
}
auto error = expected_pwm_period < sensed_pwm_period
? sensed_pwm_period - expected_pwm_period
: expected_pwm_period - sensed_pwm_period;
if (error < MAX_CLOCK_TOLERANCE) {
eeprom_update_byte(0, min);
} else {
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment