void pwm_init(void) // Initialize the PWM module for controlling a DC motor. { // Initialize the pwm frequency divider value. pwm_div = registers_read_word(REG_PWM_FREQ_DIVIDER_HI, REG_PWM_FREQ_DIVIDER_LO); // Set EN_A (PD2) and EN_B (PD3) to low. PORTD &= ~((1<<PD2) | (1<<PD3)); // Set SMPLn_B (PD4) and SMPLn_A (PD7) to high. PORTD |= ((1<<PD4) | (1<<PD7)); // Enable PD2, PD3, PD4 and PD7 as outputs. DDRD |= ((1<<DDD2) | (1<<DDD3) | (1<<DDD4) | (1<<DDD7)); // Set PWM_A (PB1/OC1A) and PWM_B (PB2/OC1B) are low. PORTB &= ~((1<<PB1) | (1<<PB2)); // Enable PB1/OC1A and PB2/OC1B as outputs. DDRB |= ((1<<DDB1) | (1<<DDB2)); // Reset the timer1 configuration. TCNT1 = 0; // Timer/Counter1 1,2,...X TCCR1A = 0; // Timer/Counter1 Control Register A TCCR1B = 0; // Timer/Counter1 Control Register B TCCR1C = 0; // Timer/Counter1 Control Register C TIMSK1 = 0; // Timer/Counter1 Interrupt Mask // Set timer top value. ICR1 = PWM_TOP_VALUE(pwm_div); //Input Capture Register1 // Set the PWM duty cycle to zero. OCR1A = 0; //Output Compare Register1 A OCR1B = 0; //Output Compare Register1 B // Configure timer 1 for PWM, Phase and Frequency Correct operation, but leave outputs disabled. TCCR1A = (0<<COM1A1) | (0<<COM1A0) | // Disable OC1A output. (0<<COM1B1) | (0<<COM1B0) | // Disable OC1B output. (0<<WGM11) | (0<<WGM10); // PWM, Phase and Frequency Correct, TOP = ICR1 TCCR1B = (0<<ICNC1) | (0<<ICES1) | // Input on ICP1 disabled. (1<<WGM13) | (0<<WGM12) | // PWM, Phase and Frequency Correct, TOP = ICR1 (0<<CS12) | (0<<CS11) | (1<<CS10); // No prescaling. // Update the pwm values. registers_write_byte(REG_PWM_DIRA, 0); registers_write_byte(REG_PWM_DIRB, 0); }
void pwm_init(void) // Initialize the PWM module for controlling a DC motor. { // Initialize the pwm frequency divider value. pwm_div = registers_read_word(REG_PWM_FREQ_DIVIDER_HI, REG_PWM_FREQ_DIVIDER_LO); TCCR1A = 0; asm("nop"); asm("nop"); asm("nop"); // Set PB1/OC1A and PB2/OC1B to low. PORTB &= ~((1<<PB1) | (1<<PB2)); // Enable PB1/OC1A and PB2/OC1B as outputs. DDRB |= ((1<<DDB1) | (1<<DDB2)); // Reset the timer1 configuration. TCNT1 = 0; TCCR1A = 0; TCCR1B = 0; TCCR1C = 0; TIMSK1 = 0; // Set timer top value. ICR1 = PWM_TOP_VALUE(pwm_div); // Set the PWM duty cycle to zero. OCR1A = 0; OCR1B = 0; // Configure timer 1 for PWM, Phase and Frequency Correct operation, but leave outputs disabled. TCCR1A = (0<<COM1A1) | (0<<COM1A0) | // Disable OC1A output. (0<<COM1B1) | (0<<COM1B0) | // Disable OC1B output. (0<<WGM11) | (0<<WGM10); // PWM, Phase and Frequency Correct, TOP = ICR1 TCCR1B = (0<<ICNC1) | (0<<ICES1) | // Input on ICP1 disabled. (1<<WGM13) | (0<<WGM12) | // PWM, Phase and Frequency Correct, TOP = ICR1 (0<<CS12) | (0<<CS11) | (1<<CS10); // No prescaling. // Update the pwm values. registers_write_byte(REG_PWM_DIRA, 0); registers_write_byte(REG_PWM_DIRB, 0); }
void pwm_update(uint16_t position, int16_t pwm) // Update the PWM signal being sent to the motor. The PWM value should be // a signed integer in the range of -255 to -1 for clockwise movement, // 1 to 255 for counter-clockwise movement or zero to stop all movement. // This function provides a sanity check against the servo position and // will prevent the servo from being driven past a minimum and maximum // position. { uint8_t pwm_width; uint16_t min_position; uint16_t max_position; // Quick check to see if the frequency divider changed. If so we need to // configure a new top value for timer/counter1. This value should only // change infrequently so we aren't too elegant in how we handle updating // the value. However, we need to be careful that we don't configure the // top to a value lower than the counter and compare values. if (registers_read_word(REG_PWM_FREQ_DIVIDER_HI, REG_PWM_FREQ_DIVIDER_LO) != pwm_div) { // Hold EN_A (PD2) and EN_B (PD3) low. PORTD &= ~((1<<PD2) | (1<<PD3)); // Give the H-bridge time to respond to the above, failure to do so or to wait long // enough will result in brownouts as the power is "crowbarred" to varying extents. // The delay required is also dependant on factors which may affect the speed with // which the MOSFETs can respond, such as the impedance of the motor, the supply // voltage, etc. // // Experiments (with an "MG995") have shown that 5microseconds should be sufficient // for most purposes. // delay_loop(DELAYLOOP); // Disable OC1A and OC1B outputs. TCCR1A &= ~((1<<COM1A1) | (1<<COM1A0)); TCCR1A &= ~((1<<COM1B1) | (1<<COM1B0)); // Make sure that PWM_A (PB1/OC1A) and PWM_B (PB2/OC1B) are held low. PORTB &= ~((1<<PB1) | (1<<PB2)); // Reset the A and B direction flags. pwm_a = 0; pwm_b = 0; // Update the pwm frequency divider value. pwm_div = registers_read_word(REG_PWM_FREQ_DIVIDER_HI, REG_PWM_FREQ_DIVIDER_LO); // Update the timer top value. ICR1 = PWM_TOP_VALUE(pwm_div); // Reset the counter and compare values to prevent problems with the new top value. TCNT1 = 0; OCR1A = 0; OCR1B = 0; } // Are we reversing the seek sense? if (registers_read_byte(REG_REVERSE_SEEK) != 0) { // Yes. Swap the minimum and maximum position. // Get the minimum and maximum seek position. min_position = registers_read_word(REG_MAX_SEEK_HI, REG_MAX_SEEK_LO); max_position = registers_read_word(REG_MIN_SEEK_HI, REG_MIN_SEEK_LO); // Make sure these values are sane 10-bit values. if (min_position > MAX_POSITION) min_position = MAX_POSITION; if (max_position > MAX_POSITION) max_position = MAX_POSITION; // Adjust the values because of the reverse sense. min_position = MAX_POSITION - min_position; max_position = MAX_POSITION - max_position; } else { // No. Use the minimum and maximum position as is. // Get the minimum and maximum seek position. min_position = registers_read_word(REG_MIN_SEEK_HI, REG_MIN_SEEK_LO); max_position = registers_read_word(REG_MAX_SEEK_HI, REG_MAX_SEEK_LO); // Make sure these values are sane 10-bit values. if (min_position > MAX_POSITION) min_position = MAX_POSITION; if (max_position > MAX_POSITION) max_position = MAX_POSITION; } // Disable clockwise movements when position is below the minimum position. if ((position < min_position) && (pwm < 0)) pwm = 0; // Disable counter-clockwise movements when position is above the maximum position. if ((position > max_position) && (pwm > 0)) pwm = 0; // Determine if PWM is disabled in the registers. if (!(registers_read_byte(REG_FLAGS_LO) & (1<<FLAGS_LO_PWM_ENABLED))) pwm = 0; // Determine direction of servo movement or stop. if (pwm < 0) { // Less than zero. Turn clockwise. // Get the PWM width from the PWM value. pwm_width = (uint8_t) -pwm; // Turn clockwise. #if SWAP_PWM_DIRECTION_ENABLED pwm_dir_b(pwm_width); #else pwm_dir_a(pwm_width); #endif } else if (pwm > 0) { // More than zero. Turn counter-clockwise. // Get the PWM width from the PWM value. pwm_width = (uint8_t) pwm; // Turn counter-clockwise. #if SWAP_PWM_DIRECTION_ENABLED pwm_dir_a(pwm_width); #else pwm_dir_b(pwm_width); #endif } else { // Stop all PWM activity to the motor. pwm_stop(); } }
void pwm_update(uint16_t position, int16_t pwm) // Update the PWM signal being sent to the motor. The PWM value should be // a signed integer in the range of -255 to -1 for clockwise movement, // 1 to 255 for counter-clockwise movement or zero to stop all movement. // This function provides a sanity check against the servo position and // will prevent the servo from being driven past a minimum and maximum // position. { uint8_t pwm_width; uint16_t min_position; uint16_t max_position; // Quick check to see if the frequency divider changed. If so we need to // configure a new top value for timer/counter1. This value should only // change infrequently so we aren't too elegant in how we handle updating // the value. However, we need to be careful that we don't configure the // top to a value lower than the counter and compare values. if (registers_read_word(REG_PWM_FREQ_DIVIDER_HI, REG_PWM_FREQ_DIVIDER_LO) != pwm_div) { // Disable OC1A and OC1B outputs. TCCR1A &= ~((1<<COM1A1) | (1<<COM1A0)); TCCR1A &= ~((1<<COM1B1) | (1<<COM1B0)); // Clear PB1 and PB2. PORTB &= ~((1<<PB1) | (1<<PB2)); delay_loop(DELAYLOOP); // Reset the A and B direction flags. pwm_a = 0; pwm_b = 0; // Update the pwm frequency divider value. pwm_div = registers_read_word(REG_PWM_FREQ_DIVIDER_HI, REG_PWM_FREQ_DIVIDER_LO); // Update the timer top value. ICR1 = PWM_TOP_VALUE(pwm_div); // Reset the counter and compare values to prevent problems with the new top value. TCNT1 = 0; OCR1A = 0; OCR1B = 0; } // Are we reversing the seek sense? if (registers_read_byte(REG_REVERSE_SEEK) != 0) { // Yes. Swap the minimum and maximum position. // Get the minimum and maximum seek position. min_position = registers_read_word(REG_MAX_SEEK_HI, REG_MAX_SEEK_LO); max_position = registers_read_word(REG_MIN_SEEK_HI, REG_MIN_SEEK_LO); // Make sure these values are sane 10-bit values. if (min_position > 0x3ff) min_position = 0x3ff; if (max_position > 0x3ff) max_position = 0x3ff; // Adjust the values because of the reverse sense. min_position = 0x3ff - min_position; max_position = 0x3ff - max_position; } else { // No. Use the minimum and maximum position as is. // Get the minimum and maximum seek position. min_position = registers_read_word(REG_MIN_SEEK_HI, REG_MIN_SEEK_LO); max_position = registers_read_word(REG_MAX_SEEK_HI, REG_MAX_SEEK_LO); // Make sure these values are sane 10-bit values. if (min_position > 0x3ff) min_position = 0x3ff; if (max_position > 0x3ff) max_position = 0x3ff; } // Disable clockwise movements when position is below the minimum position. if ((position < min_position) && (pwm < 0)) pwm = 0; // Disable counter-clockwise movements when position is above the maximum position. if ((position > max_position) && (pwm > 0)) pwm = 0; // Determine if PWM is disabled in the registers. if (!(registers_read_byte(REG_FLAGS_LO) & (1<<FLAGS_LO_PWM_ENABLED))) pwm = 0; // Determine direction of servo movement or stop. if (pwm < 0) { // Less than zero. Turn clockwise. // Get the PWM width from the PWM value. pwm_width = (uint8_t) -pwm; // Turn clockwise. #if SWAP_PWM_DIRECTION_ENABLED pwm_dir_a(pwm_width); #else pwm_dir_b(pwm_width); #endif } else if (pwm > 0) { // More than zero. Turn counter-clockwise. // Get the PWM width from the PWM value. pwm_width = (uint8_t) pwm; // Turn counter-clockwise. #if SWAP_PWM_DIRECTION_ENABLED pwm_dir_b(pwm_width); #else pwm_dir_a(pwm_width); #endif } else { // Stop all PWM activity to the motor. pwm_stop(); } }