PrinceNai
Joined: 31 Oct 2016 Posts: 480 Location: Montenegro
|
Stepper motor pulse train |
Posted: Wed Apr 12, 2023 1:01 pm |
|
|
This is a code used to generate a pulse train to drive a stepper motor. It uses Tmr1 and CCP1 to do that. There are two possible ranges, 16-2040Hz or 16 - 4080Hz with a Timer1 resolution of 1us. It has an option to select a fixed number of equally spaced frequencies between 16Hz and Fmax. That was added because it is a part of a DIY motorized dolly attempt, where only a few speeds are needed from the stepper. On the oscilloscope it looks OK, still waiting for the controllers to see it in some real action
Code: |
// **************************************************************************
// This is a very basic code that generates a pulse train for driving
// a stepper motor. The idea is to vary the time between two
// successive Timer1 interrupts and by doing that control the speed
// of the stepper. In combination with CCP1 interrupt it is
// capable of generating a pulse train with frequencies between
// 16Hz and 2040Hz with basic steps of 8Hz or 16Hz - 4080Hz with 16. FREQUENCY_STEP
// determines that. 8bit variable Freq_Control controls the frequency. Pot is
// currently used to control it, but it might be anything else. There is also
// a possibility to decide in how many steps the frequency will go from min. to
// max. That is done via NUMBER_OF_FREQUENCIES define. The code assumes 1us
// resolution of Timer1.
// Pulse width is currently 36us, change CCPR1L value to make it wider or
// narrower.
// **************************************************************************
#include <18F46k22.h>
#FUSES NOWDT,NOPROTECT, NOPUT , NOLVP, BROWNOUT
#device ADC=8
#use delay(internal=32000000)
#byte CCPR1H = getenv("SFR:CCPR1H") // make visible the low and high bytes of the CCPR1 holding register
#byte CCPR1L = getenv("SFR:CCPR1L") // to allow them being easily used in the code
#define STEPPER_PIN PIN_C2 // pulses will be here
// Frequency is controlled by an 8 bit variable (ADC, whatever).
// Without scaling the frequency changes for 8 or 16Hz for every step
// of that variable. With define below we set how "coarse" we want
// this change of frequency to be. So with 10 we have ten steps
// of 200Hz, with 25 twenty five steps of 80Hz and so on.
#define NUMBER_OF_FREQUENCIES 10 // how many steps there will be between 0 and max. frequency. 255 gives you no scaling.
int8 Scaling_Factor = 255/NUMBER_OF_FREQUENCIES; // used for the math behind it
#define FREQUENCY_STEP 8 // this number determines the maximum frequency of pulses. 8 means 2040Hz, 16 means 4080Hz.
//#define FREQUENCY_STEP 16 // It can be any number between these two. It also determines the step of the frequency
// for each increment of Freq_Control variable
int8 Freq_Control; // variable that controls the frequency of pulses
int16 DesiredFrequency; // calculated from Freq_Control
int32 Needed_Tmr1_Period; // Timer1 period needed for the desired frequency
int16 Tmr1_Preload; // Timer1 preload to achieve the period needed
// ************************************************************
// ************************************************************
#INT_TIMER1
void TIMER1_isr(void)
{
delay_cycles(7); // fine tune when preloading (empirical value as measured in MPLAB SIM to get just the right frequencies)
set_timer1(Tmr1_Preload); // set Timer1 to repeatedly overflow with the desired frequency
output_low(STEPPER_PIN); // stop pulse on C2
}
// ------------------------------------------------------------
#INT_CCP1
void CCP1_isr(void)
{
output_high(STEPPER_PIN); // start pulse on C2
}
// ************************************************************
// ************************************************************
void main()
{
setup_timer_1(T1_INTERNAL | T1_DIV_BY_8); // 1us resolution at 32Mhz
setup_ccp1(CCP_COMPARE_INT); // setup CCP1 in compare mode, using Timer1
setup_adc_ports(sAN0, VSS_VDD); // setup ADC on channel 0
setup_adc(ADC_CLOCK_DIV_32 | ADC_TAD_MUL_20);
set_adc_channel(0);
enable_interrupts(INT_TIMER1);
enable_interrupts(GLOBAL);
CCPR1H = 0xFF; // set CCP1 register to the desired trip point where Tmr1 count will cause interrupt, here on 65500.
CCPR1L = 0XDC; // At that point PIN_C2 goes high and starts the pulse. Tmr1 ends it in overflow interrupt.
set_timer1(0);
// ............................................................
while(TRUE)
{
// read ADC, then scale or "trim" the reading as desired via NUMBER_OF_FREQUENCIES and Scaling_Factor.
// Looks stupid and useless to first divide and then multiply by the same number. But because it is
// an integer division, you lose everything behind theoretical "decimal point", making a division
// of 25/25 or 49/25 both equal to 1, leaving you with the same desired frequency for a bunch of different starting values of Freq_Control.
// Original code uses olympic averaging of ADC readings for stability.
Freq_Control = read_adc()/Scaling_Factor;;
Freq_Control = Freq_Control*Scaling_Factor;
if(Freq_Control == 0){ // shut down pulse generation if ADC reading is right in the bottom range of the pot to stop the stepper
disable_interrupts(INT_CCP1);
output_low(STEPPER_PIN);
delay_ms(50);
}
else{
enable_interrupts(INT_CCP1); // enable interrupt
DesiredFrequency = FREQUENCY_STEP*(int16)Freq_Control;// calculate the desired frequency and the period of Tmr1 from that.
Needed_Tmr1_Period = 1000000/DesiredFrequency; // 1.000.000/DesiredFrequency gives the result directly in us.
Tmr1_Preload = 65536 - (int16)Needed_Tmr1_Period + 7; // 7us are added because it takes cca. 7us for interrupt handler to arrive to the code
// in the interrupt itself. It corrects the error in frequencies that causes. It would be quite big at high frequencies.
// Tmr1_Preload determines the frequency of Tmr1 interrupts.
delay_ms(50);
}
} // while(TRUE)
} // main
|
|
|