# Fonctionnement et descriptions des implémentations pour le projet Voilier ## Résumé des fonctionnalitées réalisées - [x] Batterie affichée via l'UART Rx - [x] Temps en heure et minute de la RTC affiché via l'UART Rx - [x] Contrôle avec PWM du moteur via l'UART en Tx - [x] Mise à une position d'origine pour la voile à l'aide du servo moteur - [x] Si l'accéléromètre détecte un désalage, la voile est mise à 180° *NB : La pin ayant changé entre le servomoteur et la girouette (anciennement PA0), ce dernier n'est plus fonctionnel pour le moment.* - [ ] Changement de la position de la voile à l'aide de la position de la girouette - [x] Incrémentation/Décrémentation de la girouette - [ ] Changement de la position avec cette donnée ## Documents explicatifs divers - Explication du fonctionnement de l'UART avec un Raspberry Pi 4 [ici](implementation/remote.md) - Explication des drivers dans une [cheat sheet](driver/driver.md) ### Tableau de répartition des services | Service | Nom | Fonction | | --- | :-:| :-:| |Girouette|Simon|Mesure de l'angle girouette.| |Module Xbee|Yohan|Communication a distance.| |IMU|Guilhem|Mesure de roulis, système anti-chavirement.| |RTC|Alix|Horloge temp réel.| |Mesure analogique|Yohan|Mesure de la tension de batterie.| |Servo moteur|Alix|Controle de l'écoute des voiles.| |Motoréducteur|Alix|Rotation du plateau.| ### Tableau de répartition des périphériques | Service | Nom | Designation| Périphériques Timer| Channel Timer|Périphériques GPIOX| | --- | :-: | :-: | :-: | :-: | :-: | |Girouette|Simon|NC|Timer-4 1MHz| 0 |PB6 PB7 PA0| |Module Xbee|Yohan|UART|NC| NC |PA9 PA10| |IMU|Guilhem|SPI|NC| NC |PA4 PA5 PB10 PB11| |RTC|Alix|I2C|NC| NC |PB12 PB13 PB14 PB15| |Mesure analogique|Yohan|NC|Timer-5 1Hz| 10 |PC0| |Servo moteur|Alix|PWM|Timer-2 50Hz | 2 |PA1| |Motoréducteur|Alix|NC|Timer-3 50KHz| 3 |PB0| ___ ## Motoréducteur ![CDC MOTOR](assets/cdc_motor.png) Le motoréducteur se contrôle via 2 broches, une où est transmise une PWM de plus de 20kHz avec un rapport cyclique de 0% à 100% afin de contrôler la vitesse de rotation du plateau et une afin de changer la direction de rotation du plateau. *Cette implémentation utiliser les drivers Timer et GPIO faient durant le projet* ##### Calculs préliminaires Le driver timer demande d'instancier une strcture qui contient les valeurs des futurs registres ARR et PSC. L'équation qui lie la fréquence du timer et celle du microcontrôleur est la suivante : $$ f_{tim} = {f_µ \over ARR * PSC} $$ Sachant que la fréquence du microcontrôleur est de 72 MHz et que l'o souhaite une fréquence de 20 kHz, on peut facilement calculer les registres ARR et PSC. $$ ARR * PSC = {f_µ \over f_{tim}} = {72000000 \over 20000} = 3600 $$ Comme on peut le voir dans la documentation, les registres sont sur 16 bits et peuvent donc avoir une valeur maximale de 65536. ![ARR MOTOR](assets/arr_motor.png) ![PSC MOTOR](assets/psc_motor.png) Nous allons donc choisir de mettre ARR à 3599 (le registre compte à partir de 0) et PSC à 0, ce qui va à moindre échelle éviter au passage quelques calculs non nécessaire au microcontrôleur. *Il faudra donc ensuite prévoir de gérer le rapport cyclique seulement entre 0 et 3599* ___ Il y a d'abord deux defines qui vont être utilisés pour simplifier la fonction de changement du sens de rotation : ```c #define HORAIRE 0x1 #define ANTIHOR 0x0 ``` 3 fonctions ont été instanciées dans le fichier motoreducteur.h : ```c void MyMotor_Init(void); void MyMotor_ChangeSpeed(unsigned int DC); void MyMotor_ChangeDirection(uint8_t Sens); ``` **void MyMotor_Init(void)** ```c MyTimer_Struct_Typedef Timer; MyGPIO_Struct_TypeDef Pin_Direction; Timer.Timer = TIM3; Timer.ARR = 3599; Timer.PSC = 0; Pin_Direction.GPIO = GPIOB; Pin_Direction.GPIO_Pin = 1; Pin_Direction.GPIO_Conf = Out_PullUp; MyTimer_Base_Init(&Timer); MyGPIO_Init(&Pin_Direction); MyTimer_PWM(TIM3, 3); MyTimer_DutyCycle(TIM3, 3, 0); MyTimer_Base_Start(Timer); MyMotor_ChangeDirection(ANTIHOR); ``` La fonction MyMotor_Init initialise le timer qu'elle va utiliser pour produire une PWM de fréquence 50 kHz puis initiliaser la broche sur laquelle va être transmise le sens de rotation voulu du plateau. Elle va ensuite démarrer le timer. **void MyMotor_ChangeSpeed(unsigned int DC)** ```c MyTimer_DutyCycle(TIM3, 3, DC); ``` La fonction MyMotor_ChangeSpeed sert principalement a avoir un meilleure nom et respecter le nommage des fonctions de cette implémentation, elle change la valeur du rapport cyclique du timer concerné (la valeur doit être comprise entre 0 et 10000). **void MyMotor_ChangeDirection(uint8_t Sens)** ```c if (Sens == HORAIRE) MyGPIO_Set(GPIOB, 1); if (Sens == ANTIHOR) MyGpio_Reset(GPIOB, 1); ``` La fonction MyMotor_ChangeDirection met à un la broche qui gère la direction du plateau si le sens choisi est horaire et met à 0 si le sens choisi est antihoraire. #### Test de l'implémentation Le motoréducteur a été testé en simulation (vérification des registres, de l'avancement des timers, des signaux de PB1 et du channel 3 du timer 3) ansi que sur maquette en essayant des valeurs dans le code puis en connectant à l'implémentation de la télécommande. En testant, nous nous sommes rendu compte qu'entre 0 et 1000, le moteur ne tournait pas car il n'avait pas assez de puissance. ___ ## Servomoteur ![PWM SERVO](assets/pwm_servo.png) Le servomoteur se contrôle via 1 broches où est transmise une PWM de période de 20 ms avec un rapport cyclique de 5% à 10% afin de contrôler l'angle de l'axe du servomoteur. *Cette implémentation utiliser le driver Timer fait durant le projet* ##### Calculs préliminaires Le driver timer demande d'instancier une strcture qui contient les valeurs des futurs registres ARR et PSC. L'équation qui lie la fréquence du timer et celle du microcontrôleur est la suivante : $$ f_{tim} = {f_µ \over ARR * PSC} $$ Sachant que la fréquence du microcontrôleur est de 72 MHz et que l'o souhaite une fréquence de 20 kHz, on peut facilement calculer les registres ARR et PSC. $$ ARR * PSC = {f_µ \over f_{tim}} = {72000000 \over 50} = 1440000 $$ Comme on peut le voir dans la documentation, les registres sont sur 16 bits et peuvent donc avoir une valeur maximale de 65536. ![ARR MOTOR](assets/arr_motor.png) ![PSC MOTOR](assets/psc_motor.png) Nous allons donc choisir de mettre ARR à 3599 car il faut pouvoir gérer 180° possibles sur une seule des 20 ms de la période du timer (180 * 20 = 3600) et PSC à 399 pour avoir le division de 3600 * 499 = 1440000. *Il faudra donc ensuite prévoir de gérer le rapport cyclique seulement entre 0 et 3599* ___ 2 fonctions ont été instanciées dans le fichier servo.h : ```c void MyServo_Init(void); void MyServo_ChangeAngle(uint8_t Angle); ``` **void MyServo_Init(void)** ```c MyTimer_Struct_Typedef Timer; //Période de 20ms -> F de 50Hz Timer.Timer = TIM2; Timer.ARR = 3599; //20*180 (angle) = 3600 Timer.PSC = 399; //72Mhz / 50Hz = 1.44Mhz et 3600*400 = 1.44M MyTimer_Base_Init(&Timer); //Pin Timer 2 Channel 1 PA0 MyTimer_PWM(TIM2, 1); MyTimer_DutyCycle(TIM2, 1, 750); MyTimer_Base_Start(Timer); ``` La fonction MyServo_Init initialise le timer qu'elle va utiliser pour produire une PWM de fréquence 50 Hz puis met une valeur arbitraire qui sera testée sur maquette au rapport cyclique et démarre le timer. **void MyServo_ChangeAngle(uint8_t Angle)** ```c if (Angle > 180) Angle = 180; int DC = 500 + (Angle * 500 / 180); MyTimer_DutyCycle(TIM2, 1, DC); ``` La fonction MyServo_ChangeAngle reçoit en paramètre l'angle souhaité pour le servomoteur et la fonction teste si ce dernier dépasse 180° (angle maximum de rotation du servomoteur) avant de calculer la valeur correspondante entre 5% et 10% du rapport cyclique et l'appliquer au timer. #### Test de l'implémentation Le servomoteur a été testé en simulation (vérification des registres, de l'avancement des timers et du channel 1 du timer 2) ansi que sur maquette en essayant des valeurs entre 0 et 180°. Les tests ont permis de mettre en valeur le fait que la voile est complètement lache à 0° et tendu à 180°. ___ ## Télécommande La télécommande permet deux choses via l'IHM : - Transmettre la vitesse de rotation demandé et le sens pour le voilier - Afficher une transmission série d'informations importantes comme la tension de la batterie, si le voilier a chavirer et toutes les 3 secondes les données de l'accéléromètre *Cette implémentation utiliser le driver Uart fait durant le projet* ___ 3 fonctions ont été instanciées dans le fichier remote.h : ```c void remote(uint8_t data); void initRemote(void); void testRemote(void); ``` **Structure de donnée MyUART** ```c MyUART_Struct_Typedef uartCool = {USART1,9600,lengthBit8,parityNone,stopBit1}; ``` Cette structure contient les informations sur quel UART va être utilisé, son baudrate ainsi que des paramètres pour la transmission (longueur de la donnée, parité et nombre de bits de stop). **void remote(uint8_t data)** ```c MyUART_Send(&uartCool,data); int8_t signedData = (int8_t)data; if(signedData >= 0) { MyMotor_ChangeDirection(HORAIRE); MyMotor_ChangeSpeed(signedData*100); } else { MyMotor_ChangeDirection(ANTIHOR); MyMotor_ChangeSpeed((-signedData)*100); } ``` La fonction remote attend la réception d'une donnée de la télécommande (la valeur de la vitesse et le sens de rotation voulu du plateau) puis utilise l'implémentation motoreducteur afin de contrôler la rotation du voilier. **void initRemote(void)** ```c MyUART_InitGPIO(&uartCool); MyUART_Init(&uartCool); MyUART_Init_Periph(remote); ``` La fonction initRemote initialise l'UART qui va être utilisé afin de communiquer avec la télécommande du projet voilier. Elle initialiser donc les broches, puis les registres et enfin déclare la fonction remote comme la nouvelle *Interrupt request handler* de ce timer. **void testRemote(void)** ```c MyUART_Send(&uartCool,'s'); MyUART_Send(&uartCool,'a'); MyUART_Send(&uartCool,'l'); MyUART_Send(&uartCool,'u'); MyUART_Send(&uartCool,'t'); ``` La fonction testRemote teste basiquement si la télécommande fonctionne en lui envoyant une chaîne de caractère. #### Test de l'implémentation Le fonctionnement de la télécommande et la réception de la donnée de vitesse et rotation du plateau à d'abord été testée en déboguage afin de connapitre le format de cette donnée. Les tests suivant sur maquette ont permis de vérifier le bon envoi de données ainsi que la réception de la commande et son application au plateau. ___ ## Real Time Clock La real time clock est un composant sur la carte du voilier qui permet de garder en mémoire le temps qui s'écoule précisément. *Cette implémentation utiliser le driver I2C founit durant le projet* ___ 2 fonctions ont été instanciées dans le fichier rtc.h : ```c void MyRTC_Init(void); void MyRTC_GetTime(int* sec, int* min, int* hour, int* day, int* date, int* month, int* year); ``` **void MyRTC_Init(void)** ```c MyI2C_Init(I2C2, 15, IT_I2C_Err); ``` La fonction MyRTC_Init utilise la librarie founie I2C afin d'initialiser l'I2C 2 du microcontrôleur et demande une priorité minimale à son interruption (cela ne pose pas de problème de ne pas avoir l'heure). **void MyRTC_GetTime(int* sec, int* min, int* hour, int* day, int* date, int* month, int* year)** ```c MyI2C_RecSendData_Typedef data; char regCopy = 0; data.SlaveAdress7bits = 0x68; data.Ptr_Data = ®Copy; data.Nb_Data = 1; MyI2C_GetString(I2C2, 0x00, &data); *sec = ((regCopy >> 4) & 0x07) * 10 + (regCopy & 0x0F); MyI2C_GetString(I2C2, 0x01, &data); *min = ((regCopy >> 4) & 0x07) * 10 + (regCopy & 0x0F); MyI2C_GetString(I2C2, 0x02, &data); *hour = 0; MyI2C_GetString(I2C2, 0x03, &data); *day = (regCopy & 0x07); MyI2C_GetString(I2C2, 0x04, &data); *date = ((regCopy >> 4) & 0x03) * 10 + (regCopy & 0x0F); MyI2C_GetString(I2C2, 0x05, &data); *month = ((regCopy >> 4) & 0x01) * 10 + (regCopy & 0x0F); MyI2C_GetString(I2C2, 0x06, &data); *year = ((regCopy >> 4) & 0xF0) * 10 + (regCopy & 0x0F) + 2000; ``` La fonction MyRTC_GetTime instancie une structure de donnée I2C en fournissant l'adresse esclave du composant RTC, l'adresse de la variable qui va contenir les données reçu mais également le nombre d'octet à recevoir. ![ADD RTC](assets/add_rtc.png) Elle va ensuite utilisé le driver SPI pour lire les regsitres de l'adresse 0x00 à 0x06 (en 7 fois donc) et lire les secondes, minutes, heures, jour, date, mois et années puis les écrires dans les variables contenu par les pointeurs fournis en paramètres à la fonction. ![REG RTC](assets/reg_rtc.png) #### Test de l'implémentation Un simple affichage du contenu des variables après l'appel à cette fonction en mode déboguage à permis de vérifier que l'implémentation rtc fonctionne bien. ___ ## Accéléromètre L'accéléromètre est un composant qui est sur la carte du voilier et qui permet de connaître la position angulaire de ce dernier dans l'espace via de la lecture dans les regsitres à l'aide du SPI. ![ADC ACCEL](assets/adc_accel.png) *Cette implémentation utiliser le driver SPI founit durant le projet* ___ 2 fonctions ont été instanciées dans le fichier accelerometer.h : ```c void Init_accelerometre(void); void Lecture_accelerometre(float *GX, float* GY, float* GZ); ``` **void Init_accelerometre(void)** ```c MySPI_Init(SPI1); MySPI_Clear_NSS(); //CS LOW MySPI_Send(0x2D);//Registre Auto_sleep + Write + measure a 1 MySPI_Send(0x08);// d�sactive MySPI_Set_NSS();//CS HIGH /* MySPI_Clear_NSS(); //CS LOW MySPI_Send(0xAD);// regisstre 0x2D en lecture testReg = MySPI_Read(); // lecture de la valeur du registre MySPI_Set_NSS();//CS HIGH */ MySPI_Clear_NSS(); //CS LOW MySPI_Send(0X2C);//Registre power consuption + Write MySPI_Send(0X1A);//Param�trage hors low consumption + 100Hz output data rate MySPI_Set_NSS();//CS HIGH MySPI_Clear_NSS(); //CS LOW MySPI_Send(0x31);//registre Data format + write MySPI_Send(0x17);// MySPI_Set_NSS();//CS HIGH ``` La fonction Init_accelerometre utiliser d'abord la librarie SPI founit afin d'initialiser le SPI 1. Elle paramètre ensuite l'accéléromètre comme souhaité pour désactiver le mode auto sleep, utiliser le mode de faible consommation et formater les données qui vont être ensuite lues. ![REG ACCEL](assets/reg_accel.png) **void Lecture_accelerometre(float* GX, float* GY, float* GZ)** ```c int16_t dataX ; int16_t dataY ; int16_t dataZ ; MySPI_Clear_NSS(); //CS LOW MySPI_Send(0xF2);//lecture 1er registre acc�l�rom�tre + lecture multibytes dataX = MySPI_Read()<<8; // donn�es sur 13 (12 data + sign) bits passage 8 bit en PF dataX |= MySPI_Read(); //lecture SPI dataX &= 0x1FFF; //masquage au dessus du bit 13 if (dataX > 511) { dataX -= 1024; } *GX = dataX*0.004;//valeur du registre x pas accelerometre dataY = MySPI_Read()<<8; dataY |= MySPI_Read(); dataY &= 0x1FFF; if (dataY > 511) { dataY -= 1024; } *GY = dataY*0.004;//valeur du registre x pas accelerometre dataZ = ((MySPI_Read()<<8)|MySPI_Read()); dataZ &= 0x1FFF; /* if (dataZ > 511) { dataZ -= 1024; } */ *GZ = dataZ*0.004;//valeur du registre x pas accelerometre MySPI_Set_NSS();//CS HIGH ``` La fonction Lecture_accelerometre renvoie dans des variables pointé en paramètres les valeurs de la force en X, en Y et en Z. Le fonctionnement est comme suis : - Envoi de l'adresse du resgistre désiré - Lecture et mise dans une variable locale de la valeur du regsitre - Masque et calcul afin de formater la données à renvoyer Ces trois étapes sont faites pour fournir en X, Y et Z la gravité (donc ensuite l'angle) que chaque axe perçoit. #### Test de l'implémentation l'implémenation a été testée en mode débogage afion de savoir dans un premier temps si les données sont bien reçues puis dans un second temps des calculs ont été faits afin de vérifier si les données sont correctes. Des test sur maquette sont en cours... ___ ## Batterie L'implémentation batterie va permettre de savoir si la tension de la batterie intégrée au projet voilier à une tension suffisante au bon fonctionnement du système et d'envoyer ensuite cette donnée vers l'UART connecté à la télécommande. *Cette implémentation utiliser les drivers ADC et GPIO faient durant le projet* ___ Il y a d'abord un define qui permet de tester facilement la tension minimala acceptable pour la batterie intégré au voilier. ```c #define MAX_BAT 1145 ``` 4 fonctions ont été instanciées dans le fichier battery.h : ```c void battery(uint32_t data); void getGauge(char gauge[], float percent); void initBattery(void); char isClose(uint32_t data, uint32_t compare, int precision); ``` **void battery(uint32_t data)** ```c MyRTC_Struct_TypeDef rtcBattery; MyRTC_GetTime(&rtcBattery); if((actualMinutes == rtcBattery.minutes) && isClose(oldAdc,data,50)) //pas de precision/10 % { return; } oldAdc = data; actualMinutes = rtcBattery.minutes; float percentBattery = ((float)data)/MAX_BAT; char batteryBar[13]="[__________]"; char testChar[24]; getGauge(batteryBar, percentBattery); sprintf(testChar,"[%.2d:%.2d] %s %.2d%%",rtcBattery.hours,rtcBattery.minutes,batteryBar,(int)(percentBattery*100)); MyUART_SendArray(&uartCool, (uint8_t *)testChar, 24); MyUART_Send(&uartCool, '\n'); ``` ss **void getGauge(char gauge[], float percent)** ```c int i; percent=percent*10; if(percent>10) { percent = 10.0; } for(i=(10-percent)+1; i<11; i++) { gauge[i]='#'; } gauge[12]='\0'; ``` ss **void initBattery(void)** ```c MyADC_Init_Periph(battery); MyADC_Struct_TypeDef adcBattery = {ADC1,10,cycles41d5}; MyADC_Init(&adcBattery); MyGPIO_Struct_TypeDef gpioBattery = {GPIOC,0,In_Analog}; MyGPIO_Init(&gpioBattery); ``` ss **char isClose(uint32_t data, uint32_t compare, int precision)** ```c if(data < precision) { return !(data >= compare+precision); } return !((data >= compare+precision) || (data <= compare-precision)); ``` ss #### Test de l'implémentation Des test sur maquette ont été dans un premier temps faits pour vérifier que l'ADC renvoyé une valeur correcte (changeante en fonction de la tension d'une alimentation émulant la batterie). Des test sur maquette sont en cours... ___ ## Girouette *Cette implémentation utiliser les drivers Timer et GPIO faient durant le projet* ___ 3 fonctions ont été instanciées dans le fichier girouette.h : ```c int MyGirouette_Angle(TIM_TypeDef *TIMX); void MyGirouette_Init(TIM_TypeDef *TIMX); void MyGirouette_Init_IT_Z(uint8_t GPIO_Pin); ``` **void MyGirouette_Init(TIM_TypeDef* TIMX)** ```c //configuration gpiob6 et gpiob7 en entrées In_Floating imposer par le timer4 MyGPIO_Struct_TypeDef MyGPIO={GPIOB,6,In_Floating}; MyGPIO_Init(&MyGPIO); MyGPIO.GPIO_Pin=7; MyGPIO_Init(&MyGPIO); //config pa0 en entrées MyGPIO.GPIO_Pin=0; MyGPIO.GPIO=GPIOA; MyGPIO_Init(&MyGPIO); //configuration TIM4 reset a 360 MyTimer_Struct_Typedef MyTimerGirouette ={TIMX,1439,0}; MyTimer_Base_Init(&MyTimerGirouette); TIMX->SMCR &=~0x07; TIMX->SMCR |=TIM_SMCR_SMS_1; TIMX->CCMR1 &=~(0xF2F2); TIMX->CCMR1 |= TIM_CCMR1_CC1S_0; TIMX->CCMR1 |= TIM_CCMR1_CC2S_0; //CC2S dans CCMR1 et pas CCMR2 TIMX->CCER &=~TIM_CCER_CC1P; TIMX->CCER &=~TIM_CCER_CC2P; TIMX->CCMR1&=~(0x0F<<4); //IC1F TIMX->CCMR1&=~(0x0F<<12);//IC2F TIMX->CR1 |= 1; ``` La fonction MyGirouette_Angle initialise d'abord les broches dont elle a besoin puis instancie une structure de timer et le paramètre en codeur incrémental. **int MyGirouette_Angle(TIM_TypeDef* TIMX)** ```c return (TIMX->CNT)/4; ``` La fonction MyGirouette_Angle retourne la valeur de l'angle de la girouette en calculant à partir du resgistre CTN du timer en codeur incrémental. **void MyGirouette_Init_IT_Z(uint8_t GPIO_Pin)** ```c RCC->APB2ENR |=0x01; EXTI->IMR |= (0x01<RTSR|= (0x01<EXTICR[0] &= ~(0x0000000F);// L'interruption « EXTI0 » doit être provoquée par une modification PA0 NVIC->IP[EXTI0_IRQn] |= (1 << 0x4); //Prio de l'interruption (p.197 manuel reference RM0008 pour ADC1_IRQn) NVIC->ISER[0] |= (1 << (6 & 0x1F)); // Autorisation de l'interruption « EXTI0 » NUMERO 6, ``` La fonction MyGirouette_Init_IT_Z paramètre une interruption extérieure sur broche afin de faire fonctionner le codeur incrémental et pour récuperer les données de la girouette. #### Test de l'implémentation Le fonctionnement a été vérifié sur site par Simon.