|
| 1 | +/** |
| 2 | + * PWM Demo for Metro Mini Oscilloscope Testing |
| 3 | + * |
| 4 | + * Generates three different PWM frequencies using direct timer register manipulation: |
| 5 | + * - Timer2: ~245 Hz on pins 3 and 11 |
| 6 | + * - Timer1: 1000 Hz on pins 9 and 10 |
| 7 | + * - Timer0: 5000 Hz on pins 5 and 6 |
| 8 | + * |
| 9 | + * Each frequency pair has: |
| 10 | + * - One pin with fixed 50% duty cycle (square wave reference) |
| 11 | + * - One pin with sweeping duty cycle (0% → 100% → 0% over ~5 seconds) |
| 12 | + * |
| 13 | + * WARNING: Timer0 modification breaks millis()/delay() - uses _delay_ms() instead |
| 14 | + * |
| 15 | + * Hardware: Metro Mini (ATmega328P @ 16 MHz) |
| 16 | + */ |
| 17 | + |
| 18 | +#include <util/delay.h> |
| 19 | + |
| 20 | +// Pin assignments (fixed by ATmega328P hardware) |
| 21 | +#define PIN_245HZ_FIXED 11 // OC2A - 50% duty |
| 22 | +#define PIN_245HZ_SWEEP 3 // OC2B - sweeping duty |
| 23 | +#define PIN_1KHZ_FIXED 9 // OC1A - 50% duty |
| 24 | +#define PIN_1KHZ_SWEEP 10 // OC1B - sweeping duty |
| 25 | +#define PIN_5KHZ_FIXED 6 // OC0A - 50% duty (toggle mode) |
| 26 | +#define PIN_5KHZ_SWEEP 5 // OC0B - sweeping duty |
| 27 | + |
| 28 | +// Timer1 TOP value for 1000 Hz (16-bit timer allows exact frequency) |
| 29 | +// f = f_clk / (2 * N * TOP) for Phase Correct PWM |
| 30 | +// 1000 = 16000000 / (2 * 1 * 8000) |
| 31 | +#define TIMER1_TOP 8000 |
| 32 | + |
| 33 | +// Timer0 TOP value for 5000 Hz |
| 34 | +// f = f_clk / (2 * N * TOP) for Phase Correct PWM |
| 35 | +// 5000 = 16000000 / (2 * 8 * 200) |
| 36 | +#define TIMER0_TOP 200 |
| 37 | + |
| 38 | +// Sweep timing |
| 39 | +#define SWEEP_STEPS 100 // Steps in one direction |
| 40 | +#define SWEEP_DELAY_MS 25 // Delay per step (~5 sec full cycle) |
| 41 | + |
| 42 | +void setup() { |
| 43 | + Serial.begin(115200); |
| 44 | + while (!Serial) { ; } |
| 45 | + |
| 46 | + Serial.println(F("\n=== PWM Demo for Oscilloscope Testing ===")); |
| 47 | + Serial.println(F("Metro Mini (ATmega328P @ 16 MHz)\n")); |
| 48 | + |
| 49 | + // Configure all PWM pins as outputs |
| 50 | + pinMode(PIN_245HZ_FIXED, OUTPUT); |
| 51 | + pinMode(PIN_245HZ_SWEEP, OUTPUT); |
| 52 | + pinMode(PIN_1KHZ_FIXED, OUTPUT); |
| 53 | + pinMode(PIN_1KHZ_SWEEP, OUTPUT); |
| 54 | + pinMode(PIN_5KHZ_FIXED, OUTPUT); |
| 55 | + pinMode(PIN_5KHZ_SWEEP, OUTPUT); |
| 56 | + |
| 57 | + // ======================================== |
| 58 | + // Timer2: ~245 Hz (pins 3, 11) |
| 59 | + // Phase Correct PWM, prescaler 128 |
| 60 | + // f = 16MHz / (128 * 510) = 245.1 Hz |
| 61 | + // ======================================== |
| 62 | + TCCR2A = (1 << COM2A1) | // Clear OC2A on up-count match, set on down-count |
| 63 | + (1 << COM2B1) | // Clear OC2B on up-count match, set on down-count |
| 64 | + (1 << WGM20); // Phase Correct PWM, TOP = 0xFF |
| 65 | + TCCR2B = (1 << CS22) | // Prescaler = 128 (CS22:CS20 = 101) |
| 66 | + (0 << CS21) | |
| 67 | + (1 << CS20); |
| 68 | + OCR2A = 127; // Pin 11: 50% duty (127/255) |
| 69 | + OCR2B = 0; // Pin 3: start at 0% |
| 70 | + |
| 71 | + float freq2 = 16000000.0 / (128.0 * 510.0); |
| 72 | + Serial.println(F("Timer2 (~245 Hz):")); |
| 73 | + Serial.print(F(" Pin 11 (OC2A): 50% fixed - ")); |
| 74 | + Serial.print(freq2, 1); |
| 75 | + Serial.println(F(" Hz")); |
| 76 | + Serial.print(F(" Pin 3 (OC2B): sweeping - ")); |
| 77 | + Serial.print(freq2, 1); |
| 78 | + Serial.println(F(" Hz\n")); |
| 79 | + |
| 80 | + // ======================================== |
| 81 | + // Timer1: 1000 Hz exact (pins 9, 10) |
| 82 | + // Phase & Freq Correct PWM, ICR1 = TOP |
| 83 | + // f = 16MHz / (2 * 1 * 8000) = 1000 Hz |
| 84 | + // ======================================== |
| 85 | + TCCR1A = (1 << COM1A1) | // Clear OC1A on up-count match, set on down-count |
| 86 | + (1 << COM1B1); // Clear OC1B on up-count match, set on down-count |
| 87 | + TCCR1B = (1 << WGM13) | // Phase & Freq Correct PWM, TOP = ICR1 |
| 88 | + (1 << CS10); // Prescaler = 1 |
| 89 | + ICR1 = TIMER1_TOP; // TOP = 8000 for 1000 Hz |
| 90 | + OCR1A = TIMER1_TOP / 2; // Pin 9: 50% duty (4000/8000) |
| 91 | + OCR1B = 0; // Pin 10: start at 0% |
| 92 | + |
| 93 | + uint32_t freq1 = 16000000UL / (2UL * 1UL * TIMER1_TOP); |
| 94 | + Serial.println(F("Timer1 (1000 Hz):")); |
| 95 | + Serial.print(F(" Pin 9 (OC1A): 50% fixed - ")); |
| 96 | + Serial.print(freq1); |
| 97 | + Serial.println(F(" Hz")); |
| 98 | + Serial.print(F(" Pin 10 (OC1B): sweeping - ")); |
| 99 | + Serial.print(freq1); |
| 100 | + Serial.println(F(" Hz\n")); |
| 101 | + |
| 102 | + // ======================================== |
| 103 | + // Timer0: 5000 Hz (pins 5, 6) |
| 104 | + // Phase Correct PWM with OCRA as TOP |
| 105 | + // f = 16MHz / (2 * 8 * 200) = 5000 Hz |
| 106 | + // WARNING: This breaks millis()/delay()! |
| 107 | + // ======================================== |
| 108 | + TCCR0A = (1 << COM0A0) | // Toggle OC0A on compare match (50% square wave) |
| 109 | + (1 << COM0B1) | // Clear OC0B on up-count match, set on down-count |
| 110 | + (1 << WGM00); // Phase Correct PWM (WGM = 101 with WGM02) |
| 111 | + TCCR0B = (1 << WGM02) | // Phase Correct PWM, TOP = OCRA |
| 112 | + (1 << CS01); // Prescaler = 8 |
| 113 | + OCR0A = TIMER0_TOP; // TOP = 200, Pin 6 toggles = 50% square wave |
| 114 | + OCR0B = 0; // Pin 5: start at 0% |
| 115 | + |
| 116 | + uint32_t freq0 = 16000000UL / (2UL * 8UL * TIMER0_TOP); |
| 117 | + Serial.println(F("Timer0 (5000 Hz):")); |
| 118 | + Serial.print(F(" Pin 6 (OC0A): 50% fixed - ")); |
| 119 | + Serial.print(freq0); |
| 120 | + Serial.println(F(" Hz")); |
| 121 | + Serial.print(F(" Pin 5 (OC0B): sweeping - ")); |
| 122 | + Serial.print(freq0); |
| 123 | + Serial.println(F(" Hz\n")); |
| 124 | + |
| 125 | + Serial.println(F("WARNING: millis()/delay() broken (Timer0 modified)")); |
| 126 | + Serial.println(F("Using _delay_ms() for timing.\n")); |
| 127 | + Serial.println(F("Sweep cycle: ~5 seconds (0% -> 100% -> 0%)")); |
| 128 | + Serial.println(F("Probe pins with oscilloscope to see waveforms!")); |
| 129 | + Serial.println(F("==========================================\n")); |
| 130 | +} |
| 131 | + |
| 132 | +void loop() { |
| 133 | + // Sweep duty cycle up: 0% -> 100% |
| 134 | + for (uint8_t i = 0; i <= SWEEP_STEPS; i++) { |
| 135 | + // Timer2 sweep (8-bit, max 255) |
| 136 | + OCR2B = (uint8_t)(((uint32_t)i * 255) / SWEEP_STEPS); |
| 137 | + |
| 138 | + // Timer1 sweep (16-bit, max = TIMER1_TOP) |
| 139 | + OCR1B = (uint16_t)(((uint32_t)i * TIMER1_TOP) / SWEEP_STEPS); |
| 140 | + |
| 141 | + // Timer0 sweep (8-bit, max = TIMER0_TOP) |
| 142 | + OCR0B = (uint8_t)(((uint32_t)i * TIMER0_TOP) / SWEEP_STEPS); |
| 143 | + |
| 144 | + _delay_ms(SWEEP_DELAY_MS); |
| 145 | + } |
| 146 | + |
| 147 | + // Sweep duty cycle down: 100% -> 0% |
| 148 | + for (uint8_t i = SWEEP_STEPS; i > 0; i--) { |
| 149 | + // Timer2 sweep |
| 150 | + OCR2B = (uint8_t)(((uint32_t)i * 255) / SWEEP_STEPS); |
| 151 | + |
| 152 | + // Timer1 sweep |
| 153 | + OCR1B = (uint16_t)(((uint32_t)i * TIMER1_TOP) / SWEEP_STEPS); |
| 154 | + |
| 155 | + // Timer0 sweep |
| 156 | + OCR0B = (uint8_t)(((uint32_t)i * TIMER0_TOP) / SWEEP_STEPS); |
| 157 | + |
| 158 | + _delay_ms(SWEEP_DELAY_MS); |
| 159 | + } |
| 160 | +} |
0 commit comments