Last active
July 11, 2017 20:17
-
-
Save LiSongMWO/b1e51cb9e80e1914b868a8bfb22a16fb to your computer and use it in GitHub Desktop.
Program for the target that is being calibrated
This file contains 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
#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