Étape 2: logiciel
Cette étape va passer par chaque partie du logiciel et expliquer ce qu’il fait. Vous pouvez aller si vous n’avez aucun intérêt à modifier le logiciel et je veux juste construire la chose, à l’étape suivante. Le fichier hex fourni fera le projet fonctionnent comme prévu.
Transactionnelles base de puce periferials
#pragma config OSC = INTIO2, WDT = OFF, LVP = OFF
#include < p18f1320.h >
Cela affecte l’oscillateur interne et charge les paramètres pour le 18F1320, si vous souhaitez utiliser une photo différente que c’est l’une des principales choses que vous devrez changer
goupilles d’installation pour l’entrée PWM
#define ReceiverPin PORTBbits.RB3
#define ReceiverTris TRISBbits.TRISB3
C’est juste donner un nom à la broche pour recevoir le signal n’est plus clair pour faire référence à.
Variables de capture PWM
unsigned int PWM1RiseTime = 0 ; capture de valeur de la minuterie à front montant
unsigned int PWM1FallTime = 0 ; valeur de la minuterie à la capture de bord tombant
unsigned int PWM1Width = 0 ; largeur calculée
unsigned int CH1_width = 0 ;
unsigned int CH2_width = 0 ;
unsigned int CH3_width = 0 ;
unsigned int PWMGap = 0 ; écart calculé entre deux impulsions
char PWM1Edge = 1 ; bord en train d’être surveillé 1 = rising, 0 = chute
variables de bouton
unsigned char button_1_debounce_wait = 0 ;
Car le signal PWM du récepteur communique par envoi d’impulsions de différentes variables de largeur sont déclarées à tenir ces largeurs une fois qu’elles sont calculées.
unsigned char calibration_mode = 0 ;
#define mode_operate 0
#define mode_CH1_high 1
#define mode_CH1_med 2
#define mode_CH1_low 3
#define mode_CH2_high 4
#define mode_CH2_med 5
#define mode_CH2_low 6
unsigned int limit_CH1_high = 2381 ;
unsigned int limit_CH1_med = 3307 ;
unsigned int limit_CH1_low = 4286 ;
unsigned int limit_CH2_high = 2022 ;
unsigned int limit_CH2_med = 2946 ;
unsigned int limit_CH2_low = 3983 ;
unsigned int CH_temp = 0 ;
Lorsque le mode de calibration est sur le système va être réinitialiser ces « limites » pour adapter le signal. Ce sont les valeurs par défaut, comment fonctionne l’étalonnage sera expliqué plus tard.
variables de contrôle moteur
#define Motor_PWM_Rez 16 //number si différentes vitesses possible avant et arrière
#define center_buffer 20 //this est la fraction de la plage avant le début du mouvement
Ce sont des constantes que vous pouvez ajuster si vous utilisez différentes parties. Le tampon Centre est vraiment la zone morte dans le centre où le contrôleur ne fera pas le moteur faire quoi que ce soit. La rezolution combien différentes vitesses, le système va diviser c’est gamme de contrôle en.
unsigned char Motor_Phase = 0; //as ce cycle sera fois les moteurs
unsigned int CH1_range = 2000 ;
unsigned char Motor_A_Speed = 0 ; Il s’agit de la vitesse du moteur A, % 100 il égalera la rezolution
unsigned char CH1_forward_increment = 10 ; //the largeur de gamme pour chaque sortie de vitesse
unsigned char CH1_reverse_increment = 10 ;
unsigned int CH2_range = 2000 ;
unsigned char Motor_B_Speed = 0 ; Il s’agit de la vitesse du moteur A, % 100 il égalera la rezolution
unsigned char CH2_forward_increment = 10 ; //the largeur de gamme pour chaque sortie de vitesse
unsigned char CH2_reverse_increment = 10 ;
typedef struct
{
unsigned motor_A_Direction: 1 ;
unsigned motor_B_Direction: 1 ;
unsigned button_1_last_state: 1 ;
} BITS ;
unsigned char motor_A_inverted = 1; //this associés à l’étalonnage
unsigned char motor_B_inverted = 1 ;
unsigned char motor_calibration_needed = 1 ;
volatiles BITS Bits ;
variables de chronométrage
unsigned char slow_count = 0 ; Ceci est utilisé pour créer la minuterie à l’échelle pour les événements plus lents
La variable ci-dessus est qu’un compteur afin qu’une sous-section de l’interruption timer peut partir tout seul hors de beaucoup d’horloge.
mettre en place l’interruption
void low_ISR (void); //prototype
#pragma code low_vector = 0 x 08 //0X08 est faible 0 x 18 IS HIGH
void low_interrupt (void) {}
_asm goto low_ISR _endasm
}
#pragma code
#pragma interruption low_ISR
Cette partie n’est pas l’interruption en soi, mais il vers le haut du jeu pour l’interruption de se produire. L’interruption est seulement un événement qui permet à quelque chose qui sera déclenché afin que le programme ne doit pas être une grande boucle.
void main (void)
{
OSCCON = 0 X 72 ; Horloge 8MHz
while (!. OSCCONbits.IOFS) ; Attendez que les OSC à devenir stable
configurer le timer1
PIR1bits.TMR1IF = 0 ; efface l’indicateur d’interrompre la minuterie 1
T1CONbits.TMR1ON = 1 ; mettre en marche la minuterie
T1CONbits.T1CKPS1 = 0 ; régler le diviseur
T1CONbits.T1CKPS0 = 0 ; régler le diviseur
le programme d’installation timer2
PIR1bits.TMR2IF = 0 ; efface l’indicateur d’interrompre le minuteur 2
PIE1bits.TMR2IE = 1 ; activez l’interruption
PR2 = 199 ;
T2CON = 0b00000100 ; (-) toujours 0 (-) postscale (-) marche/arrêt Prédiviseur (-)
configurer CCP1
CCP1CON = 0b0000101 ; configurer CCP1 pour la capture, s’élevant à bord
INTCONbits.PEIE=1 ; activer les interruptions périphériques
PIE1bits.CCP1IE=1 ; CPP1 interruption a permis
INTCONbits.GIE=1 ; activez la ramification interrompre
ReceiverTris = 1 ; la valeur de RB3 pour l’entrée pour la capture peut travailler.
TRISBbits.TRISB2 = 1 ; figurant rb2 pour donc il peut être utilisé pour différencier les canaux
Le module de capture fait tout les lourds travaillent ici. Ci-dessus il est initialisé pour attendre le signal de se lever, plus tard ce sera changé dynamiquement pour capturer la largeur d’impulsion.
configurer les ports
ADCON1 = 0XFF ; numérique pour tous
INTCON2bits.RBPU = 0 ; port b faibles tractions sur
ceux-ci seront sorties moteurs
TRISAbits.TRISA0 = 0 ;
#define Motor_Pin_A1 LATAbits.LATA0
TRISAbits.TRISA1 = 0 ;
#define Motor_Pin_A2 LATAbits.LATA1
TRISAbits.TRISA2 = 0 ;
#define Motor_Pin_B1 LATAbits.LATA2
TRISAbits.TRISA3 = 0 ;
#define Motor_Pin_B2 LATAbits.LATA3
Ces commandes définissent les axes nécessaires pour commander les moteurs pour servir de sorties. Puis les broches moteurs sont nommés pour facile d’accès.
ceux-ci seront sorties de l’indicateur
TRISAbits.TRISA6 = 0 ;
TRISAbits.TRISA7 = 0 ;
ce sera l’entrée de signal de servo
TRISBbits.TRISB0 = 1 ;
au départ de calibrer les gammes RC
motor_calibration_needed = 1 ;
while(1)
{
}
}
Ceci tout en boucles maintient le programme de se terminer. Ne soyez pas dupé par le fait qu’elle est vide. Interruptions déclenchera les événements et la minuterie est encore en cours d’exécution.
Voici le début de l’interruption de la minuterie. Il se déclenche périodiquement à la vitesse la plus élevée que n’importe quelle fonction nécessite, pour des opérations rapides telles que décider si il est temps de tourner le moteur allumé ou éteint, puis il est divisé avec compteurs à exploiter les fonctions qui ne nécessitent pas cette haute vitesse, telles que la surveillance de l’entrée et de décider si la vitesse a besoin de changer.
Sub low_ISR(void)
{
Indicateur de minuterie 2 (actuellement fixé à interrompre à 10Khz)
if(PIR1bits.TMR2IF == 1)
{
PIR1bits.TMR2IF = 0 ; efface l’indicateur d’interrompre la minuterie 1
Alors quant à pas perdre du temps à faire les choses plus vite que nécessaire (un bon moyen de regarder beaucoup de genres de travail) la partie ci-dessous utilise la variable "slow_count" pour seulement exécuter chaque 100 fois la boucle externe s’exécute.
remettre cette fonction s’exécute à 100 Hz ***
slow_count ++ ;
if(slow_count > 100)
{
slow_count = 1; //reset comte pour la prochaine fois
Poignée bouton de calibrage
if(button_1_debounce_wait > 0) {button_1_debounce_wait--;}
if(PORTBbits.RB0 == 0) {}
Si (Bits.button_1_last_state == 0 & & button_1_debounce_wait == 0) //button juste pressé
{
button_1_debounce_wait = 10 ; //set debounce comte
calibration_mode ++ ;
if(calibration_mode > 6) {calibration_mode = 0;}
}
Bits.button_1_last_state = 1 ;
}
d’autre
{
Bits.button_1_last_state = 0 ;
}
fin du bouton de calibrage
Ci-dessous, l’étalonnage est effectivement appliqué. Cela se fait en mode fonctionnement normal si les feux sont tous deux désactivés. Le programme vérifie si la gamme d’étalonnage est en arrière, haut est plus bas que bas et vice versa et dans l’affirmative, définit un indicateur afin que les directions des moteurs agira en conséquence.
Poignée Mode indicateurs de Led
if(calibration_mode == mode_operate)
{
LATAbits.LATA6 = 0 ;
LATAbits.LATA7 = 0 ;
if(motor_calibration_needed == 1)
{
motor_calibration_needed = 0 ; effacer l’indicateur
recalculer les variables de calibration pour CH1
if(limit_CH1_low < limit_CH1_high) //speed augmente avec numéro
{
motor_A_inverted = 0 ;
}
else / / vitesse diminue que nombre augmente
{
swap si élevé est la plus grande valeur
CH_temp = limit_CH1_low ;
limit_CH1_low = limit_CH1_high ;
limit_CH1_high = CH_temp ;
motor_A_inverted = 1 ;
}
CH1_range = limit_CH1_high-limit_CH1_low ;
CH1_forward_increment = (limit_CH1_high-limit_CH1_med-((limit_CH1_high-limit_CH1_med)/center_buffer)) / Motor_PWM_Rez ;
CH1_reverse_increment = (limit_CH1_med-limit_CH1_low-((limit_CH1_med-limit_CH1_low)/center_buffer)) / Motor_PWM_Rez ;
}
recalculer les variables de calibration pour CH2
if(limit_CH2_low < limit_CH2_high) //speed augmente avec numéro
{
motor_B_inverted = 0 ;
}
else / / vitesse diminue que nombre augmente
{
swap si élevé est la plus grande valeur
CH_temp = limit_CH2_low ;
limit_CH2_low = limit_CH2_high ;
limit_CH2_high = CH_temp ;
motor_B_inverted = 1 ;
}
CH2_range = limit_CH2_high-limit_CH2_low ;
CH2_forward_increment = (limit_CH2_high-limit_CH2_med-((limit_CH2_high-limit_CH2_med)/center_buffer)) / Motor_PWM_Rez ;
CH2_reverse_increment = (limit_CH2_med-limit_CH2_low-((limit_CH2_med-limit_CH2_low)/center_buffer)) / Motor_PWM_Rez ;
}
fin des indicateurs de led mode
Dessous de calibrage est géré. Chaque fois que la touche les changements de mode de calibration, indiquant qu’une nouvelle limite soit mises. Le modèle est CH1 complet avant, milieu au repos, complètement en arrière, alors les mêmes trois positions à nouveau sur le canal 2. L’indicateur lumineux de montrer pour pas en mode de calibration, un sur pour la marche avant, l’autre pour le retour et pour middle point de repos. Il n’est pas une interface robuste, mais il fait le travail.
calibration
if(calibration_mode == mode_CH1_high)
{
Tous ces trucs LATA est juste les lumières étant mis en marche pour indiquer le mode de l’utilisateur. Comme vous pouvez le voir il n’est pas réellement définir les limites lorsque vous appuyez sur le bouton. Il définit simplement eux à quelque endroit déclarent alors que dans ce mode, donc lorsque vous appuyez sur le bouton nouveau et que le mode se termine, qui reste le point de calibration.
LATAbits.LATA6 = 0 ;
LATAbits.LATA7 = 1 ;
limit_CH1_high = CH1_width ;
}
if(calibration_mode == mode_CH1_med)
{
LATAbits.LATA6 = 1 ;
LATAbits.LATA7 = 1 ;
limit_CH1_med = CH1_width ;
}
if(calibration_mode == mode_CH1_low)
{
LATAbits.LATA6 = 1 ;
LATAbits.LATA7 = 0 ;
limit_CH1_low = CH1_width ;
}
if(calibration_mode == mode_CH2_high)
{
LATAbits.LATA6 = 0 ;
LATAbits.LATA7 = 1 ;
limit_CH2_high = CH2_width ;
}
if(calibration_mode == mode_CH2_med)
{
LATAbits.LATA6 = 1 ;
LATAbits.LATA7 = 1 ;
limit_CH2_med = CH2_width ;
}
if(calibration_mode == mode_CH2_low)
{
LATAbits.LATA6 = 1 ;
LATAbits.LATA7 = 0 ;
limit_CH2_low = CH2_width ;
motor_calibration_needed = 1 ;
}
Maintenant les vitesses du moteur doivent être calculés. L’équation obtient la largeur de l’impulsion pour ce moteur, décide si elle est sur le point médian ou ne pas décider de la direction, puis estime qu’elle a rang à l’aide de la résolution de la commande de moteur au sein de la gamme totale des largeurs possibles.
calculer la vitesse du moteur A
Motor_A_Speed = 0 ;
if(CH1_width > limit_CH1_med+((limit_CH1_high-limit_CH1_med)/center_buffer)) gamme //upper
{
Motor_A_Speed = (CH1_width-limit_CH1_med-((limit_CH1_high-limit_CH1_med)/center_buffer)) / CH1_forward_increment ;
Bits.motor_A_Direction = motor_A_inverted ;
}
if(CH1_width < limit_CH1_med-((limit_CH1_med-limit_CH1_low)/center_buffer)) gamme //lower
{
Motor_A_Speed = (limit_CH1_med-CH1_width-((limit_CH1_med-limit_CH1_low)/center_buffer)) / CH1_reverse_increment ;
Bits.motor_A_Direction =! motor_A_inverted ;
}
calculer la vitesse du moteur B
Motor_B_Speed = 0 ;
if(CH2_width > limit_CH2_med+((limit_CH2_high-limit_CH2_med)/center_buffer)) gamme //upper
{
Motor_B_Speed = (CH2_width-limit_CH2_med-((limit_CH2_high-limit_CH2_med)/center_buffer)) / CH2_forward_increment ;
Bits.motor_B_Direction = motor_B_inverted ;
}
if(CH2_width < limit_CH2_med-((limit_CH2_med-limit_CH2_low)/center_buffer)) gamme //lower
{
Motor_B_Speed = (limit_CH2_med-CH2_width-((limit_CH2_med-limit_CH2_low)/center_buffer)) / CH2_reverse_increment ;
Bits.motor_B_Direction =! motor_B_inverted ;
}
fin du calcul de la vitesse du moteur
} //end de l’article 100hz
Ici la si instruction et compteur qui causent ce qui précède exécuter seulement à 100Hz sont terminés et nous sommes à la fréquence d’interruption complète minuterie. La partie inférieure des poignées génère le signal de commande du moteur de la vitesse calculée ci-dessus
Contol impulsions moteur
Motor_Phase ++ ;
if(Motor_Phase > Motor_PWM_Rez) {Motor_Phase = 1;}
A moteur
Si (Motor_A_Speed > = Motor_Phase & & Motor_A_Speed < 20) {}
if(bits.motor_A_Direction == 0) {}
Motor_Pin_A1 = 1 ;
Motor_Pin_A2 = 0 ;
}
if(bits.motor_A_Direction == 1) {}
Motor_Pin_A1 = 0 ;
Motor_Pin_A2 = 1 ;
}
}
else {}
Motor_Pin_A1 = 0 ;
Motor_Pin_A2 = 0 ;
}
Moteur B
Si (Motor_B_Speed > = Motor_Phase & & Motor_B_Speed < 20) {}
if(bits.motor_B_Direction == 0) {}
Motor_Pin_B1 = 1 ;
Motor_Pin_B2 = 0 ;
}
if(bits.motor_B_Direction == 1) {}
Motor_Pin_B1 = 0 ;
Motor_Pin_B2 = 1 ;
}
}
else {}
Motor_Pin_B1 = 0 ;
Motor_Pin_B2 = 0 ;
}
} //end des interruptions de minuterie
Voici le début de l’interruption de la CCP. C’est la partie qui gère la mesure de la largeur d’impulsion. Plus tôt, il a été défini à être déclenchée par un front montant. Lorsqu’il détecte le front, il enregistrera le temps à l’aide de CCPR1 puis elle passera pour regarder de chutes et de changer la variable PWM1Edge pour correspondre. Lorsqu’il détecte la chute, il bascule en arrière et enregistre le temps.
interruption de CCP
if(PIR1bits.CCP1IF == 1)
{
PIR1bits.CCP1IF = 0 ; Effacez l’indicateur
if(PWM1Edge == 1) //if levant Detection
{
CCP1CON = 0b0000100 ; //switch pour détecter le front descendant
PWM1Edge = 0; //switch pour indiquer le front descendant est prévu
PWMGap = CCPR1 - PWM1FallTime ; calculer l’écart entre les départs d’impulsion
PWM1RiseTime = CCPR1 ; //save la valeur faible minuterie pour le temps de montée
if(PWMGap < 10000) {CH2_width = PWMGap;}
}
d’autre //if détection de chute
{
CCP1CON = 0b0000101 ; //switch pour détecter les rising edge
PWM1Edge = 1; //switch pour indiquer le front est prévu
PWM1Width = CCPR1 - PWM1RiseTime ; (temps de montée de pwm est l’heure de la montée de pwm)
PWM1FallTime = CCPR1 ; //save la valeur faible minuterie pour le temps de chute
Vous aurez vraiment besoin de comprendre la logique derrière cette partie si vous devez modifier le code fonctionne sur d’autres récepteurs. Le récepteur de traxxas que j’ai utilisé toutes les impulsions met dos à dos. Cela fait en sorte que je ne pouvais pas lire juste une broche parce que l’ensemble des impulsions était une impulsion longue lorsqu’il est combiné. Donc j’ai conçu le programme si la puce est seulement reliée à chaque autre sortie, en l’occurrence servo sorties 1 et 3. De cette façon il y a un fossé. Le fossé court (moins de 10000 tel que détecté par la fi celle énoncé ci-dessous) est le seul intermédiaire et la longueur de l’impulsion moyenne, nombre d’impulsions 2. La première impulsion après la longue interruption est nombre d’impulsions 1 et celui après que l’écart court est numéro 3 de la pulsation.
if(PWMGap > 10000) {CH1_width = PWM1Width;}
if(PWMGap < 10000) {CH3_width = PWM1Width;}
}
}
}
S’il vous plaît n’hésitez pas à poser des questions. La va m’aider à rendre les choses plus claires alors j’ai vraiment apprécié leur.
Comme j’ai mentionné dans cette dernière note ce plan s’articule autour des impulsions survenant après l’autre. Certains récepteurs les espacer. Si tel était le cas vous n’aurais pas besoin de faire cette astuce à tous. Au lieu de cela vous savez juste qu’après que le long intervalle a impulsion un, puis après chaque intervalle court supplémentaire vous regardiez pulse 2, 3, 4 et ainsi de suite. Vous devez juste faire une variable pour garder une trace des impulsions combien vous aviez pris depuis la dernière lacune et réinitialiser lorsque vous avez eu l’un long, puis utilisez-le pour décider quel canal vous attribue une largeur d’impulsion capturés à.