Une fois, avant moi, il y avait une tâche pour fournir un accès à Internet sur le STM32 n'ayant pour cela qu'un port COM. Pour résoudre ce problème, j'avais besoin de PPP ou, pour être précis, PPPoS (Point-to-Point Protocol over Serial - l'un des moyens de mettre en œuvre PPP, il est utilisé lors de la connexion via le port COM).
Dans le processus de résolution de la tâche qui m'était confiée, j'ai rencontré des difficultés, dont l'une, à mon avis, était une couverture insuffisante des problèmes liés aux PPPoS sur Internet. Avec ce poste, je vais essayer de combler l'écart désigné, autant que mes modestes connaissances le permettront.
Cet article explique comment créer un projet pour System Workbench pour STM32 à partir de zéro. Affiche un exemple de travail avec UART. Il existe des exemples de code pour implémenter PPP. Et bien sûr, un exemple d'envoi d'un message à un ordinateur voisin.
Présentation
PPP (Point-to-Point Protocol) est un protocole de liaison de données à deux points du modèle de réseau OSI. Il est généralement utilisé pour établir une communication directe entre deux nœuds de réseau et il peut fournir une authentification de connexion, un chiffrement et une compression de données. Utilisé sur de nombreux types de réseaux physiques: câble null modem, ligne téléphonique, cellulaire, etc.
Il existe souvent des sous-espèces du protocole PPP, telles que le protocole point à point sur Ethernet (PPPoE), utilisées pour se connecter via Ethernet, et parfois via DSL; et le protocole point à point sur ATM (PPPoA), qui est utilisé pour la connexion via ATM Adaptation Layer 5 (AAL5), qui est la principale alternative PPPoE pour DSL.
PPP est une famille de protocoles: Link Control Protocol (LCP), Network Control Protocol (NCP), Authentication Protocols (PAP, CHAP), Multichannel PPP (MLPPP).
De Wikipédia .
La préparation
Pour résoudre le problème dont nous avons besoin:
Fer:
- Carte de débogage stm32f4_discovery:
- Adaptateur USB vers miniUSB pour connecter la carte Ă un ordinateur.
- Deux adaptateurs USBtoUART FT232:
- Deux rallonges USB sont également utiles, pas nécessairement, mais simplement pratiques.
Doux:
- Machine virtuelle VirtualBox. Vous pouvez le télécharger ici . Nous téléchargeons et installons également le pack d'extension pour VirtualBox.
- Deux disques d'installation avec les systèmes d'exploitation Windows et Linux. Nous prenons Windows ici , Linux ici .
Après avoir installé le système d'exploitation, vous devrez installer des modules complémentaires pour le système d'exploitation invité. Pour la tâche que nous avons suffisamment de systèmes 32x, vous ne pouvez pas tromper avec l'inclusion de la virtualisation. - Pour Windows, nous avons besoin d'un programme qui peut accepter les demandes et y répondre via TCP / IP, et un programme terminal pour travailler avec un port COM. Téléchargez PacketSender ici (cliquez sur "Non merci, laissez-moi simplement télécharger."), Le terminal est ici . De plus, nous avons besoin de STM32CubeMX pour la configuration initiale du projet. Téléchargez depuis st.com (après l'enregistrement, le lien sera envoyé par e-mail).
- Nous avons mis System Workbench pour STM32 sur le système d'exploitation principal. Téléchargez ici (inscription requise).
Étape 1. Création d'un projet
Tout d'abord, ouvrez STM32CubeMX et créez un nouveau projet pour notre carte de découverte stm32f4. Activez RCC, Ethernet (ETH), SYS, USART2, USART3, puis activez FREERTOS et LWIP.


Pour les diagnostics, nous avons besoin de LED sur la carte. Pour cela, configurez les jambes de PD12-PD15 en tant que GPIO_Output.

Dans l'onglet Configuration de l'horloge, définissez la fréquence, comme dans l'image ci-dessous.

Ensuite, dans l'onglet Configuration, configurez les ports USART. Nous travaillerons avec eux en mode DMA. Nous avons deux ports USART, un que nous utiliserons pour transmettre et recevoir des données via PPP, le second pour la journalisation. Pour les faire fonctionner, nous devons configurer DMA sur RX et TX pour les deux ports. Pour toutes les jambes de réglage DMA, définissez Moyen sur priorité. Pour USART2 leg RX, réglez le mode sur "Circular". Les autres paramètres sont laissés par défaut.

Vous devrez également activer l'interruption globale pour les deux ports dans l'onglet «Paramètres NVIC».
Ceci termine la configuration initiale du projet dans STM32CubeMX. Nous enregistrons le fichier de projet et faisons la génération de code pour System Workbench pour STM32.

Implémentation
Vérifions maintenant que le code téléchargé compile et fonctionne. Pour ce faire, dans le fichier main.c de la fonction "StartDefaultTask", nous remplaçons le corps de la boucle infinie for (;;) par le code LED on et off.
Cela devrait ĂŞtre comme ceci:
void StartDefaultTask(void const * argument) { MX_LWIP_Init(); for(;;) { HAL_GPIO_WritePin(GPIOD, GPIO_PIN_12|GPIO_PIN_13|GPIO_PIN_14|GPIO_PIN_15, GPIO_PIN_SET); osDelay(1000); HAL_GPIO_WritePin(GPIOD, GPIO_PIN_12|GPIO_PIN_13|GPIO_PIN_14|GPIO_PIN_15, GPIO_PIN_RESET); osDelay(1000); } }
Nous compilons le firmware et regardons. Les quatre voyants doivent clignoter sur la carte.
Étape 2. Travailler avec USART
Notre prochaine tâche est de vérifier le bon fonctionnement de notre USART.
La première chose que nous devons faire est de connecter notre FT232 à la découverte. Pour ce faire, regardez quelles jambes les interfaces USART sont divorcées. Je l'ai PD6 et PD5 pour USART2_RX et USART2_TX respectivement.

Ainsi que PD9 et PD8 pour USART3_RX et USART3_TX, respectivement.

De plus, nous avons besoin d'un pied GND.
Nous trouvons ces broches sur la carte et les connectons aux broches FT232, tandis que la broche GND sur la carte peut être quelconque, la broche RX sur la carte doit être connectée à la broche TX sur le FT232, et la broche TX sur la carte doit être connectée à la broche RX sur le FT232. Les conclusions restantes ne sont pas utilisées.
Il reste à connecter notre FT232 aux ports USB de l'ordinateur, ainsi qu'à connecter la carte de découverte elle-même via le connecteur miniUSB à l'ordinateur (à ne pas confondre avec microUSB).
Après avoir connecté FT232, le système d'exploitation principal installera les pilotes pour eux, après quoi ces périphériques devront être transmis à l'invité Windows sur la machine virtuelle.
Nous ajoutons maintenant le code de programme nécessaire au fonctionnement de notre USART. Pour ce faire, nous ajouterons quatre fichiers: usart.h, usart.c, logger.h, logger.c.
Contenu du fichier:
fichier usart.h #ifndef _USART_ #define _USART_ #include "stm32f4xx_hal.h" void usart_Open(void); bool usart_Send(char* bArray, int size_bArray); uint16_t usart_Recv(char* bArray, uint16_t maxLength); #endif
fichier usart.c #include "usart.h" #include "logger.h" #include "cmsis_os.h" #define Q_USART2_SIZE 200 xQueueHandle g_qUsart; osThreadId g_usart_rxTaskHandle; extern UART_HandleTypeDef huart2; void usart_rxTask(void); uint8_t bGet[Q_USART2_SIZE] = {0}; uint16_t g_tail = 0; void usart_Open(void) { g_qUsart = xQueueCreate( Q_USART2_SIZE, sizeof( unsigned char ) ); osThreadDef(usart_rxTask_NAME, usart_rxTask, osPriorityNormal, 0, Q_USART2_SIZE/4+128); g_usart_rxTaskHandle = osThreadCreate(osThread(usart_rxTask_NAME), NULL); HAL_UART_Receive_DMA(&huart2, bGet, Q_USART2_SIZE); } void usart_rxTask(void) { for(;;) { uint16_t length = Q_USART2_SIZE - huart2.hdmarx->Instance->NDTR; while(length - g_tail) { uint8_t tmp = bGet[g_tail]; xQueueSendToBack( g_qUsart, &tmp, 100 ); g_tail++; if (g_tail == Q_USART2_SIZE) g_tail = 0; } } } bool usart_Send(char* bArray, int size_bArray) { HAL_StatusTypeDef status; status = HAL_UART_Transmit_DMA(&huart2, bArray, size_bArray); while (HAL_UART_GetState(&huart2) != HAL_UART_STATE_READY) { if (HAL_UART_GetState(&huart2) == HAL_UART_STATE_BUSY_RX) break; osDelay(1); } if (status == HAL_OK) return true; return false; } uint16_t usart_Recv(char* bArray, uint16_t maxLength) { uint8_t tmp = 0; uint16_t length = 0; while(uxQueueMessagesWaiting(g_qUsart)) { xQueueReceive( g_qUsart, &tmp, 100 ); bArray[length] = tmp; length++; if (length >= maxLength) break; } return length; }
fichier logger.h #ifndef _LOGGER_ #define _LOGGER_ void logger(const char *format, ...); #endif
fichier logger.c #include "logger.h" #include "stm32f4xx_hal.h" #include <stdarg.h> extern UART_HandleTypeDef huart3; #define MAX_STRING_SIZE 1024 HAL_StatusTypeDef logger_Send(char* bArray, uint32_t size_bArray) { HAL_StatusTypeDef status; for(int i=0;i<5;i++) { status = HAL_UART_Transmit_DMA(&huart3, bArray, size_bArray); if (status == HAL_OK) break; osDelay(2); } while (HAL_UART_GetState(&huart3) != HAL_UART_STATE_READY) { osDelay(1); } return status; } void logger(const char *format, ...) { char buffer[MAX_STRING_SIZE]; va_list args; va_start (args, format); vsprintf(buffer, format, args); va_end(args); buffer[MAX_STRING_SIZE-1]=0; logger_Send(buffer, strlen(buffer)); }
Nous avons besoin de usart pour transmettre et recevoir des données sur usart2. Ce sera notre interface principale pour communiquer avec un serveur PPP.
Nous avons besoin de Logger pour implémenter la journalisation en envoyant des messages au terminal. La fonction void usart_Open (void) forme une file d'attente et démarre la tâche de maintenance de cette file d'attente. Cette fonction doit être terminée avant d'utiliser USART. Alors tout est simple, la fonction bool usart_Send (char * bArray, int size_bArray) envoie des données au port, et
uint16_t usart_Recv (char * bArray, uint16_t maxLength) les extrait de la file d'attente dans laquelle la fonction void usart_rxTask (void) les a aimablement ajoutés.
Pour l'enregistreur, c'est encore plus simple; il n'est pas nécessaire d'obtenir des données, donc ni la file d'attente ni la tâche de maintenance de file d'attente ne sont nécessaires.
Au début du fichier
main.h ,
vous devez ajouter plusieurs définitions décrivant le type bool, qui n'est pas disponible en C.
typedef unsigned char bool; #define true 1 #define false 0
Il est maintenant temps de vérifier la fonctionnalité du code résultant. Pour ce faire, dans le fichier
main.c , changez le code de la tâche déjà connue "StartDefaultTask"
#include "usart.h" #include "logger.h" #define MAX_MESSAGE_LENGTH 100 void StartDefaultTask(void const * argument) { MX_LWIP_Init(); usart_Open(); uint8_t send[] = "Send message\r\n"; uint8_t recv[MAX_MESSAGE_LENGTH] = {0}; uint16_t recvLength = 0; for(;;) { HAL_GPIO_WritePin(GPIOD, GPIO_PIN_12|GPIO_PIN_13|GPIO_PIN_14|GPIO_PIN_15, GPIO_PIN_SET); osDelay(1000); HAL_GPIO_WritePin(GPIOD, GPIO_PIN_12|GPIO_PIN_13|GPIO_PIN_14|GPIO_PIN_15, GPIO_PIN_RESET); osDelay(1000); if (usart_Send(send, sizeof(send)-1)) logger("SEND - %s", send); recvLength = usart_Recv(recv, MAX_MESSAGE_LENGTH-1); if (recvLength) { recv[recvLength] = 0; logger("RECV - %s\r\n", recv); } } }
De plus, nous devons donner plus de mémoire à la pile de notre tâche. Pour ce faire, dans l'appel à la fonction osThreadDef (), le fichier main.c, vous devez corriger 128 par 128 * 10 pour obtenir ceci:
osThreadDef(defaultTask, StartDefaultTask, osPriorityNormal, 0, <b>128*10</b>)
Nous compilons et flashons. Les LED clignotent de la même manière que dans la tâche précédente.
Pour voir le résultat de notre travail, vous devez exécuter le programme Terminal dans notre machine virtuelle. Une instance du programme pour le port de journalisation, la seconde pour le port principal. Regardez dans le gestionnaire de périphériques les numéros de port attribués à votre FT232. Si plus de 10 numéros ont été attribués, réaffectez-les.
Lorsque vous démarrez la deuxième instance du programme, une erreur peut se produire, fermez la fenêtre avec l'erreur et continuez à travailler avec le programme.
Pour les deux ports, nous établissons une connexion à 115200 bauds, bits de données - 8, parité - aucun, bits d'arrêt - 1, établissement de liaison - aucun.
Si vous avez tout fait correctement, alors dans la fenêtre du terminal pour usart2 le message «Envoyer un message» sera transmis. Le même message sera dupliqué dans la fenêtre du terminal pour l'enregistreur uniquement avec le préfixe "SEND -"
Si dans la fenêtre du terminal pour usart2 vous tapez du texte dans le champ "Envoyer" et cliquez sur le bouton correspondant à droite de ce champ, alors dans la fenêtre de l'enregistreur vous verrez le même message avec le préfixe "RECV -"
Dans l'image ci-dessous: Ă gauche est l'enregistreur, Ă droite est usart2.

Étape 3. Premiers pas avec PPP
Dans le cadre de cette tâche, nous établirons une connexion PPP. Tout d'abord, activez l'utilisation de PPP, changez la valeur du PPP_SUPPORT define dans le fichier ppp_opts.h à 1. Ensuite, nous redéfinissons les définitions nécessaires dans le fichier lwipopts.h,
#define MEMP_NUM_SYS_TIMEOUT 8 #define CHECKSUM_GEN_IP 1 #define CHECKSUM_GEN_TCP 1
Dans le même temps, les anciennes définitions doivent être mises en commentaire.
Maintenant, nous modifions le fichier lwip.c, insérons le code suivant dans le bloc «/ * USER CODE BEGIN 0 * /»:
#include "usart.h" #include "pppos.h" #include "sio.h" #include "dns.h" #include "ppp.h" static ppp_pcb *ppp; struct netif pppos_netif; void PppGetTask(void const * argument) { uint8_t recv[2048]; uint16_t length = 0; for(;;) { length=usart_Recv(recv, 2048); if (length) { pppos_input(ppp, recv, length); logger("read - PppGetTask() len = %d\n", length); } osDelay(10); } } #include "ip4_addr.h" #include "dns.h" static void ppp_link_status_cb(ppp_pcb *pcb, int err_code, void *ctx) { struct netif *pppif = ppp_netif(pcb); LWIP_UNUSED_ARG(ctx); switch(err_code) { case PPPERR_NONE: { logger("ppp_link_status_cb: PPPERR_NONE\n\r"); logger(" our_ip4addr = %s\n\r", ip4addr_ntoa(netif_ip4_addr(pppif))); logger(" his_ipaddr = %s\n\r", ip4addr_ntoa(netif_ip4_gw(pppif))); logger(" netmask = %s\n\r", ip4addr_ntoa(netif_ip4_netmask(pppif))); } break; case PPPERR_PARAM: logger("ppp_link_status_cb: PPPERR_PARAM\n"); break; case PPPERR_OPEN: logger("ppp_link_status_cb: PPPERR_OPEN\n"); break; case PPPERR_DEVICE: logger("ppp_link_status_cb: PPPERR_DEVICE\n"); break; case PPPERR_ALLOC: logger("ppp_link_status_cb: PPPERR_ALLOC\n"); break; case PPPERR_USER: logger("ppp_link_status_cb: PPPERR_USER\n"); break; case PPPERR_CONNECT: logger("ppp_link_status_cb: PPPERR_CONNECT\n"); break; case PPPERR_AUTHFAIL: logger("ppp_link_status_cb: PPPERR_AUTHFAIL\n"); break; case PPPERR_PROTOCOL: logger("ppp_link_status_cb: PPPERR_PROTOCOL\n"); break; case PPPERR_PEERDEAD: logger("ppp_link_status_cb: PPPERR_PEERDEAD\n"); break; case PPPERR_IDLETIMEOUT: logger("ppp_link_status_cb: PPPERR_IDLETIMEOUT\n"); break; case PPPERR_CONNECTTIME: logger("ppp_link_status_cb: PPPERR_CONNECTTIME\n"); break; case PPPERR_LOOPBACK: logger("ppp_link_status_cb: PPPERR_LOOPBACK\n"); break; default: logger("ppp_link_status_cb: unknown errCode %d\n", err_code); break; } }
Ensuite, dans la fonction MX_LWIP_Init (), dans le bloc «/ * USER CODE BEGIN 3 * /» nous ajoutons un appel à la fonction pppConnect ().
De plus, vous devez augmenter la taille du segment de mémoire, pour cela, dans le fichier FreeRTOSConfig.h, vous devez mettre en commentaire la configuration configTOTAL_HEAP_SIZE et, à la fin du fichier, dans le bloc / * USER CODE BEGIN Defines * /, déclarez-le avec une nouvelle valeur.
#define configTOTAL_HEAP_SIZE ((size_t)1024*30)
En outre, dans le fichier usart.c, remplacez la valeur de la définition Q_USART2_SIZE par 2048.
La configuration de la connexion commence par la fonction MX_LWIP_Init (); elle a été créée automatiquement; nous venons d'y ajouter un appel à la fonction pppConnect (). Dans cette fonction, les tâches desservant la connexion PPPOS sont lancées. Les fonctions pppos_create () doivent recevoir les adresses des fonctions qui serviront à l'envoi de messages et à la sortie d'informations sur la modification de l'état de la connexion. Pour nous, ce sont les fonctions ppp_output_cb () et ppp_link_status_cb (), respectivement. De plus, la fonction pppConnect () démarre la tâche de maintenance des messages reçus. À la fin de son opération, la fonction pppConnect () attendra qu'une connexion au serveur soit établie, puis terminera son opération.
Le travail avec le réseau sera effectué à un niveau supérieur, dès que LWIP décidera qu'il est nécessaire d'envoyer un message au réseau, la fonction ppp_output_cb () sera appelée automatiquement. La réponse du réseau sera reçue par la fonction PppGetTask (), dans le cadre de la tâche de service des messages entrants, et transférée dans les entrailles de LWIP. Si l'état de la connexion change, la fonction ppp_link_status_cb () sera appelée automatiquement.
Enfin, nous allons modifier la tâche StartDefaultTask. Maintenant, cela devrait ressembler à ceci:
void StartDefaultTask(void const * argument) {
Terminé, vous pouvez compiler et flasher.
À ce stade, vous devez démarrer le serveur PPP. Pour ce faire, vous devez d'abord déployer une machine virtuelle avec Linux. J'ai utilisé Ubuntu 16.04 x32. Après avoir installé le système d'exploitation, vous devez configurer l'utilisation du port COM.
Dans cette partie, nous n'avons pas besoin d'une machine virtuelle avec Windows, nous pouvons la désactiver en toute sécurité. Nous connectons les deux FT232 sous Linux.
Sous Linux, avant de commencer à travailler avec un port COM, vous devez autoriser l'utilisateur à l'utiliser. Pour ce faire, exécutez la commande suivante:
sudo addgroup USERNAME dialout
oĂą USERNAME est le nom de l'utilisateur actuel.
Pour voir les ports disponibles dans le système COM, vous devez exécuter la commande:
dmesg | grep tty

Nous voyons qu'il y a deux ports ttyUSB dans le système. Nous ne pouvons pas dire immédiatement lequel est enregistreur et lequel est usart2. Il vous suffit de les vérifier à tour de rôle.
Tout d'abord, exécutez les commandes pour lire à partir d'un port:
stty -F /dev/ttyUSB0 115200 cat /dev/ttyUSB0
puis d'un autre:
stty -F /dev/ttyUSB1 115200 cat /dev/ttyUSB1
OĂą nous voyons une telle image, c'est l'enregistreur.

Vous pouvez quitter cette fenêtre, cela ne nous dérangera pas.
Ensuite, vous devez autoriser les paquets envoyés depuis notre carte à quitter les limites de leur sous-réseau. Pour ce faire, configurez iptables. Nous effectuons les actions suivantes:
1. Ouvrez une nouvelle fenĂŞtre de console
2. Vous devez trouver votre nom d'IP et d'interface réseau (exécutez la commande
ifconfig )

3. Exécutez les commandes de configuration nat
sudo echo 1 | sudo tee -a /proc/sys/net/ipv4/ip_forward > /dev/null sudo echo 1 | sudo tee -a /proc/sys/net/ipv4/ip_dynaddr > /dev/null sudo iptables -F FORWARD sudo iptables -F -t nat sudo iptables -t nat -A POSTROUTING -o enp0s3 -j SNAT --to-source 192.168.10.196 sudo iptables -t nat -L
où enp0s3 est le nom de l'interface réseau
192.168.10.196 - votre adresse IP
/ proc / sys / net / ipv4 / - chemin vers le fichier correspondant.
Ces commandes peuvent être réécrites dans un fichier de commandes et exécutées à chaque fois avant de démarrer le serveur PPP. Vous pouvez l'ajouter à l'exécution automatique, mais je ne l'ai pas fait.
Maintenant que nous sommes prêts à démarrer le serveur, il ne reste plus qu'à créer un fichier de paramètres. Je l'ai appelé "
pppd.conf ", je suggère d'utiliser les paramètres suivants:
nodetach noauth passive local debug lock 192.168.250.1:192.168.250.2 /dev/ttyUSB1 115200 lcp-echo-interval 10 lcp-echo-failure 1 cdtrcts
Nous réécrivons les paramètres dans un fichier et vous pouvez ensuite démarrer le serveur. Cela se fait avec la commande
sudo pppd file ./pppd.confLe serveur PPPD doit être démarré avant le début de la découverte, donc après le démarrage de PPPD, vous devez cliquer sur le bouton "Réinitialiser" situé sur la carte.
Si vous avez tout fait correctement, vous verrez l'image suivante:

Exécution de pppd à gauche, enregistreur à droite.
Étape 4. Nous envoyons un sac
À ce stade, nous avons besoin des deux machines virtuelles. Linux pour pppd et Windows pour recevoir le package. Pour simplifier la tâche, il est nécessaire que les deux machines soient sur le même sous-réseau, la solution idéale serait de spécifier une connexion de pont réseau pour les deux machines dans les paramètres réseau de VirtualBox et de désactiver le pare-feu dans Windows.
Nous démarrons les machines virtuelles et configurons la connexion ppp de la carte de découverte avec pppd. Sous Windows, nous découvrons l'adresse IP de la machine (commande ipconfig), je l'ai obtenue 192.168.10.97.
Lancez Packet Sender et configurez-le comme suit:

Maintenant, modifiez à nouveau la tâche StartDefaultTask dans le fichier main.c.
#include "logger.h" #include "sockets.h" typedef uint32_t SOCKET; void StartDefaultTask(void const * argument) {
Comme valeur de la variable addr, nous utilisons l'adresse de la machine Windows, le numéro de port 6565.
Message envoyé "Message de test TCP / IP.", Réponse "Le message est reçu."
Ici, vous pouvez voir que les fonctions PPP ne sont pas directement utilisées pour envoyer et recevoir des messages. Tous les travaux se déroulent à un niveau supérieur et nos fonctions sont appelées automatiquement.
Nous compilons et flashons.
Le résultat de la connexion à pppd est visible sur une machine Linux:

Les demandes reçues et les réponses envoyées sont visibles dans le programme Packet Sender sur une machine Windows:

Eh bien, c'est tout, le paquet que nous avons envoyé à partir de la carte de découverte est allé au port COM, est arrivé au serveur pppd, a été envoyé au port Windows 6565 de la machine, il a été reçu avec succès, en réponse à cela, un autre paquet a été envoyé qui a réussi cette dans la direction opposée et a été adopté avec succès au conseil. Vous pouvez également envoyer des messages à n'importe quelle machine sur Internet.
→ Le code complet du projet peut être téléchargé
ici