Archives de catégorie : stm32
Ethernet sur nucleo-F429ZI
Mise en oeuvre sur un microcontrôleur STM32F429ZI (Nucleo-F429ZI) en UDP
Testé avec STM32CubeIDE 1.12.1
Objectif de ce tutoriel :
Etablir une communication ethernet entre un PC et une carte Nucleo-F429ZI en utilisant le protocole UDP.
Une fois la configuration hardware réalisée, nous aborderons deux cas d'utilisations :
- 1 La carte Nucleo-F429ZI dans le rôle de serveur UDP sur lequel vient se connecter le PC avec l'outil Netcat
- 2 La carte Nucleo-F429ZI dans le rôle de client UDP qui vient se connecter au serveur Netcat démarré sur le PC préalablement.
Step 0 : configuration de l'Ethernet histoire de communiquer avec le PC et Netcat
Step 1 : configuration des horloges du STM32
Step 1 : configuration du driver LWIP
Le schéma électronique de la nucleo-F429ZI nous renseigne sur le driver hardware (LAN8742), qu'on choisit donc pour notre projet.
Le checksum est calculé par le hardware :
On désactive le DHCP pour fixer une adresse IPV4 de notre choix. Ici 192.168.0.51
Gateway 192.168.0.50 (ce sera l'adresse IP du PC de test).
On alloue 10 KBytes pour la mémoire.
et on n'oublie pas d'activer les "interruptions" ethernet dans la section NVIC de System core dans le Pinout and configuration (IOC).
Serveur UDP
Dans cette partie nous allons traiter de la mise en oeuvre du code en C pour réaliser un serveur UDP sur la carte Nucleo-F429ZI. Il aura l'adresse IP 192.168.0.51 et le PC : 192.168.0.50
Serveur-Step 0 : que faire dans le main ?
On rajoute la bibliothèque udp.h ains
#include "lwip/udp.h"
void udp_receive_callback(void *arg, struct udp_pcb *upcb, struct pbuf *p, const ip_addr_t *addr, u16_t port);
void udpServer_init(void);
extern struct netif gnetif; // déjà défini ailleurs mais on en a besoin donc on le met en extern
struct udp_pcb *upcb;
int main(void)
{
HAL_Init();
SystemClock_Config();
MX_GPIO_Init();
MX_USART3_UART_Init();
MX_LWIP_Init();
udpServer_init();
while (1)
{
ethernetif_input(&gnetif);
sys_check_timeouts();
}
} //fin main
Serveur-Step 1 : que faire ensuite ?
On rajoute les fonctions udbServer_init et udp_receive_callback
void udpServer_init(void)
{
// UDP Control Block structure
struct udp_pcb *upcb;
err_t err;
/* 1. Create a new UDP control block */
upcb = udp_new();
/* 2. Bind the upcb to the local port */
ip_addr_t myIPADDR;
IP_ADDR4(&myIPADDR, 192, 168, 0, 51);
err = udp_bind(upcb, &myIPADDR, 7000); // 7 is the server UDP port
/* 3. Set a receive callback for the upcb */
if(err == ERR_OK)
{
udp_recv(upcb, udp_receive_callback, NULL);
}
else
{
udp_remove(upcb);
}
}
//cette fonction sera exécutée lorsqu'un paquet de données UDP arrivera sur la nucleo-F429ZI
//Elle renvoie le contenu du paquet et un petit Hello xxx From UDP SERVER
void udp_receive_callback(void *arg, struct udp_pcb *upcb, struct pbuf *p, const ip_addr_t *addr, u16_t port)
{
struct pbuf *txBuf;
/* Get the IP of the Client */
char *remoteIP = ipaddr_ntoa(addr);
char buf[100];
int len = sprintf (buf,"Hello %s From UDP SERVER\n", (char*)p->payload);
/* allocate pbuf from RAM*/
txBuf = pbuf_alloc(PBUF_TRANSPORT,len, PBUF_RAM);
/* copy the data into the buffer */
pbuf_take(txBuf, buf, len);
/* Connect to the remote client */
udp_connect(upcb, addr, port);
/* Send a Reply to the Client */
udp_send(upcb, txBuf);
/* free the UDP connection, so we can accept new clients */
udp_disconnect(upcb);
/* Free the p_tx buffer */
pbuf_free(txBuf);
/* Free the p buffer */
pbuf_free(p);
}
Serveur-Step 2 : c'est bien tout ça mais comment je teste ?
Pour les tests, la bonne manière est :
1 - de regarder si la carte répond à un ping
2 - de tester si le serveur répond
ping 192.168.0.51
Si la réponse est du genre :
➜ ozone ping 192.168.0.51
PING 192.168.0.51 (192.168.0.51) 56(84) bytes of data.
64 bytes from 192.168.0.51: icmp_seq=1 ttl=255 time=0.580 ms
64 bytes from 192.168.0.51: icmp_seq=2 ttl=255 time=0.686 ms
^C
--- 192.168.0.51 ping statistics ---
2 packets transmitted, 2 received, 0% packet loss, time 1006ms
rtt min/avg/max/mdev = 0.580/0.633/0.686/0.053 ms
C'est que ça fonctionne, on peut passer au test du serveur UDP.
Pour ce faire, on utilise l'outils Netcat (nc), cela donne :
➜ ozone nc -u 192.168.0.51 7000
-u : UDP
192.168.0.51 adresse du PC
7000 port utilisé dans notre cas.
Rien ne se passe, c'est normal, il n'y a pas de données échangées pour le moment entre le PC et la nucleo.
On tape Alice dans le terminal et si on voit cela :
➜ ozone nc -u 192.168.0.51 7000
Alice
Hello Alice
From UDP SERVER
C'est gagné !
Client UDP
Dans cette partie nous allons traiter de la mise en oeuvre du code en C pour réaliser un client UDP sur la carte Nucleo-F429ZI. Il aura l'adresse IP 192.168.0.51 et le PC qui sera un serveur aura l'adresse : 192.168.0.50
Client-Step 0 : que faire dans le main ?
On rajoute la bibliothèque udp.h ains
#include "lwip/udp.h"
void udp_receive_callback(void *arg, struct udp_pcb *upcb, struct pbuf *p, const ip_addr_t *addr, u16_t port);
void udpClient_connect(void);
static void udpClient_send(void);
extern struct netif gnetif; // déjà défini ailleurs mais on en a besoin donc on le met en extern
struct udp_pcb *upcb;
int main(void)
{
HAL_Init();
SystemClock_Config();
MX_GPIO_Init();
MX_USART3_UART_Init();
MX_LWIP_Init();
udpClient_connect();
while (1)
{
ethernetif_input(&gnetif);
sys_check_timeouts();
}
} //fin main
Client-Step 1 : que faire ensuite ?
On rajoute les fonctions udpClient_connect et udp_receive_callback
void udpClient_connect(void)
{
err_t err;
/* 1. Create a new UDP control block */
upcb = udp_new();
/* Bind the block to module's IP and port */
ip_addr_t myIPaddr;
IP_ADDR4(&myIPaddr, 192, 168, 0, 51);
udp_bind(upcb, &myIPaddr, 7000);
/* configure destination IP address and port */
ip_addr_t DestIPaddr;
IP_ADDR4(&DestIPaddr, 192, 168, 0, 50);
err= udp_connect(upcb, &DestIPaddr, 7000);
if (err == ERR_OK)
{
/* 2. Send message to server */
udpClient_send ();
/* 3. Set a receive callback for the upcb */
udp_recv(upcb, udp_receive_callback, NULL);
}
}
static void udpClient_send(void)
{
struct pbuf *txBuf;
char data[100];
int len = sprintf(data, "sending UDP client message\r\n");
/* allocate pbuf from pool*/
txBuf = pbuf_alloc(PBUF_TRANSPORT, len, PBUF_RAM);
if (txBuf != NULL)
{
/* copy data to pbuf */
pbuf_take(txBuf, data, len);
/* send udp data */
udp_send(upcb, txBuf);
/* free pbuf */
pbuf_free(txBuf);
}
}
//cette fonction sera exécutée lorsqu'un paquet de données UDP arrivera sur la nucleo-F429ZI
//Elle renvoie le contenu du paquet et un petit Hello xxx From UDP SERVER
void udp_receive_callback(void *arg, struct udp_pcb *upcb, struct pbuf *p, const ip_addr_t *addr, u16_t port)
{
struct pbuf *txBuf;
/* Get the IP of the Client */
char *remoteIP = ipaddr_ntoa(addr);
char buf[100];
int len = sprintf (buf,"Hello %s From Nucleo-F429ZI UDP client\n", (char*)p->payload);
/* allocate pbuf from RAM*/
txBuf = pbuf_alloc(PBUF_TRANSPORT,len, PBUF_RAM);
/* copy the data into the buffer */
pbuf_take(txBuf, buf, len);
/* Connect to the remote client */
udp_connect(upcb, addr, port);
/* Send a Reply to the Client */
udp_send(upcb, txBuf);
/* free the UDP connection, so we can accept new clients */
udp_disconnect(upcb);
/* Free the p_tx buffer */
pbuf_free(txBuf);
/* Free the p buffer */
pbuf_free(p);
}
Client-Step 2 : c'est bien tout ça mais comment je teste ?
Pour les tests, la bonne manière est :
1 - de regarder si la carte répond à un ping
2 - de tester si le serveur répond
ping 192.168.0.51
Si la réponse est du genre :
➜ ozone ping 192.168.0.51
PING 192.168.0.51 (192.168.0.51) 56(84) bytes of data.
64 bytes from 192.168.0.51: icmp_seq=1 ttl=255 time=0.580 ms
64 bytes from 192.168.0.51: icmp_seq=2 ttl=255 time=0.686 ms
^C
--- 192.168.0.51 ping statistics ---
2 packets transmitted, 2 received, 0% packet loss, time 1006ms
rtt min/avg/max/mdev = 0.580/0.633/0.686/0.053 ms
C'est que ça fonctionne, on peut passer au test de notre nucleo-F429ZI.
Ensuite on va lance le serveur UDP sur le PC
Pour ce faire, on utilise l'outils Netcat (nc), cela donne :
➜ ozone nc -ul 7000
-u : UDP
-l : listen (le PC est un serveur en écoute sur le port 7000)
7000 port utilisé dans notre cas.
On reset la nucleo
On tape Alice dans le terminal et si on voit cela :
➜ ozone nc -ul 7000
sending UDP client message
C'est gagné !
Et maintenant si c'est le PC qui envoie des données, cela donne :
➜ ozone nc -ul 7000
sending UDP client message
Bob
Hello Bob
From Nucleo-F429ZI UDP client
That's all folks!
Mise en œuvre de l’USART avec le DMA
Mise en oeuvre de l'USART avec le DMA
objectif de la manip :
Une chaine de caractère est envoyée automatiquement par l'USART du microcontrôleur. L'envoi est piloté par le contrôleur DMA sans intervention du coeur du microcontrôleur (il déclenche seulement l'envoi et c'est le DMA qui gère le reste).
Matériel requis
- un ordinateur avec un terminal (putty, termite,...)
- une carte nucleo-L073RZ ou F446RE ou autre...
- un câble USB pour programmer et émuler la liaison série.
Configuration de l'USART
Pas obligé d'activer les interruptions USART en mode circular buffer.
A noter que si on se met en Mode Circular et Increment Address coché pour Memory, il suffit de faire une seule fois l'appel à la fonction HAL_UART_Transmit_DMA(&huart2, testMessage, sizeof(testMessage)) pour que l'envoi se répète inlassablement.
// Appel de ces deux fonctions dans cet ordre la et pas l'inverse sinon cela ne fonctionne pas
MX_DMA_Init();
MX_USART2_UART_Init();
uint8_t testMessage[]="Message envoye automatiquement grace au controleur DMA\r\n";
HAL_UART_Transmit_DMA(&huart2, testMessage, sizeof(testMessage));
while(1)
{
//on ne fait rien
}
En mode normal, cela donne :
Attention, il faut activer les interruptions USART !
while (1)
{
HAL_UART_Transmit_DMA(&huart2, testMessage, sizeof(testMessage));
HAL_Delay(1000);
}
Tests
Ouvrir un terminal (putty) et le configurer en 115200 bauds, 8 bits, pas de parité. 1 bit de stop.
Programmer la carte.
Le message doit s'afficher toutes les secondes à l'écran du terminal.
Problèmes fréquents
La fonction d'initialisation du DMA MX_DMA_Init() est appelée avant celle de l'USART MX_USART_init();
Et c'est le drame. Rien ne fonctionne. Bienvenue en STM32zarbie.
MX_GPIO_Init();
MX_USART2_UART_Init();
MX_DMA_Init();
Mise en œuvre de l’ADC avec le DMA
Mise en oeuvre de l'ADC avec le DMA
Matériel requis
- un ordinateur avec un terminal (putty, termite,...)
- une carte nucleo-L073RZ ou STM32F446RE ou autre
- Un potentiomètre de 10kOhm par exemple relié à PA0 ou la carte IHM (mbed-application-shield)
Création du projet - Côté configuration
Configuration USART
On configure l'USART2 en 115200 baud.
Configuration ADC
Les choses importantes ici sont la résolution (12 bits), on aurait pu prendre moins.
Les paramètre Continous conversion Mode et Discontinuous Conversion Mode peuvent être indifféremment Enable ou Disable.
Le paramètre DMA Continuous Requests peut être aussi indifféremment Enable ou Disable.
Notons que les événements (interrupt) issues de l'ADC ne sont pas autorisées. Ce sera le contrôleur DMA qui se chargera d'informer d'une fin de transfert d'un échantillon vers l'emplacement mémoire où il sera stocké.
Configuration du DMA pour l'ADC
Comme dans cet exemple, on n'échantillonne qu'une seule entrée et on ne veut à l'instant t qu'une seule valeur, on peut être soit en mode Circular soit Normal, on peut cocher Increment Address ou non.
Dans notre cas, on aura une précision sur 12 bits donc on prend des word (16 bits).
Programmation - Côté code donc...
A rebrousse poil, cela donne :
Récupération des échantillons
Dans main.c et dans une section genre / USER CODE BEGIN 4 /
void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef* hadc){
ADC_FLAG=TRUE;
}
Dans la fonction main de main.c :
if (ADC_FLAG == TRUE)
{
ADC_FLAG = FALSE;
sprintf(message,"%ld\n\r", /*(uint16_t)*/pdata); //on peut caster mais pas obligé
HAL_UART_Transmit(&huart2, message , sizeof(message), HAL_MAX_DELAY);
HAL_ADC_Start_DMA(&hadc, &pdata, 1);
}
On aura évidemment fait auparavant :
uint8_t ADC_FLAG;
#define TRUE 1
#define FALSE 0
uint32_t pdata = 0; //very important de le mettre en uint32_t
uint8_t message[10]; //ça c'est pour envoyer une chaîne au PC
et in fine dans le main de main.c
uint8_t welcomeMessage[]="Test ADC & DMA sur PA0 8 bits 115200 bauds\r\n"; //un message d'accueil ne fait jamais de mal
HAL_UART_Transmit(&huart2, welcomeMessage , sizeof(welcomeMessage), HAL_MAX_DELAY); //et on le balance
HAL_ADC_Start_DMA(&hadc, &pdata, 1); //on lance les hostivités
while (1)
{
if (ADC_FLAG == TRUE) //sur fin de conversion (merci le callback AL_ADC_ConvCpltCallback)
{
ADC_FLAG = FALSE;
sprintf(message,"%ld\n\r", /*(uint16_t)*/pdata); //on peut caster mais pas obligé
HAL_UART_Transmit(&huart2, message , sizeof(message), HAL_MAX_DELAY);
HAL_ADC_Start_DMA(&hadc, &pdata, 1); // et rebelote
}
}
}
Attention !
Seule cet ordre fonctionne. Si on s'amuse à mettre MX_DMA_Init(); après MX_ADC_Init(); ça déraille complètement, on n'est plus sur 12 bits mais sur 8... Bienvenue en STM32zarbie.
MX_GPIO_Init();
MX_USART2_UART_Init();
MX_DMA_Init();
MX_ADC_Init();
void MX_ADC_Init(void) (extrait)
Si on ne veut numériser qu'un seul échantillon sous 8 bits ou plus, alors on peut se mettre en circular buffer ou non.
Avoir à l'esprit que les conversions seront lancées avec la fonction :
HAL_ADC_Start_DMA(&hadc, &pdata, 1);
Cette fonction signifie que le contrôleur DMA va lancer la conversion dont l'échantillon sera stockée dans pdata.
hadc.Init.ContinuousConvMode = DISABLE;
hadc.Init.DiscontinuousConvMode = DISABLE;
hadc.Init.DMAContinuousRequests = ENABLE;
Et quand cela fonctionne : exemple de sortie sur Putty
Mise en œuvre de l’ADC
Mise en oeuvre de l'ADC sans le DMA
Matériel requis
- un ordinateur avec un terminal (putty, termite,...)
- une carte nucleo-L073RZ ou STM32F446RE ou autre
- Un potentiomètre de 10kOhm par exemple relié à PA0 ou la carte IHM (mbed-application-shield)
Création du projet - Côté configuration
Configuration USART
On configure l'USART2 en 115200 baud.
Configuration ADC
Les choses importantes ici sont la résolution (12 bits), on aurait pu prendre moins.
Les paramètre Continous conversion Mode et Discontinuous Conversion Mode peuvent être indifféremment Enable ou Disable.
Si on met Enable à Conversion Continuous Mode plus besoin de faire à chaque fois un ADC_Start(), on le fait au début une fois pour toute.
Evidemment, si on fait un ADC_Stop(), va falloir remettre un ADC_Start() quand on veut un nouvel échantillon.
Fonction main de main.c :
MX_GPIO_Init();
MX_DMA_Init();
MX_USART2_UART_Init();
MX_ADC_Init();
HAL_UART_Transmit(&huart2, welcomeMessage , sizeof(welcomeMessage), HAL_MAX_DELAY);
uint8_t pdata = 0;
uint8_t message[5];
HAL_ADC_Start(&hadc);
while (1)
{
HAL_ADC_Start(&hadc);
HAL_ADC_PollForConversion(&hadc, 1);
pdata = HAL_ADC_GetValue(&hadc);
sprintf(message,"%d\r\n",HAL_ADC_GetValue(&hadc));
HAL_UART_Transmit(&huart2, message , sizeof(message), HAL_MAX_DELAY);
HAL_ADC_Stop(&hadc);
HAL_Delay(200);
}
}
Fonction MX_ADC_Init de main.c :
static void MX_ADC_Init(void)
{
ADC_ChannelConfTypeDef sConfig = {0};
hadc.Instance = ADC1;
hadc.Init.OversamplingMode = DISABLE;
hadc.Init.ClockPrescaler = ADC_CLOCK_SYNC_PCLK_DIV1;
hadc.Init.Resolution = ADC_RESOLUTION_8B;
hadc.Init.SamplingTime = ADC_SAMPLETIME_1CYCLE_5;
hadc.Init.ScanConvMode = ADC_SCAN_DIRECTION_FORWARD;
hadc.Init.DataAlign = ADC_DATAALIGN_RIGHT;
hadc.Init.ContinuousConvMode = ENABLE;
hadc.Init.DiscontinuousConvMode = DISABLE;
hadc.Init.ExternalTrigConvEdge = ADC_EXTERNALTRIGCONVEDGE_NONE;
hadc.Init.ExternalTrigConv = ADC_SOFTWARE_START;
hadc.Init.DMAContinuousRequests = DISABLE;
hadc.Init.EOCSelection = ADC_EOC_SINGLE_CONV;
hadc.Init.Overrun = ADC_OVR_DATA_PRESERVED;
hadc.Init.LowPowerAutoWait = DISABLE;
hadc.Init.LowPowerFrequencyMode = ENABLE;
hadc.Init.LowPowerAutoPowerOff = DISABLE;
if (HAL_ADC_Init(&hadc) != HAL_OK)
{
Error_Handler();
}
/** Configure for the selected ADC regular channel to be converted.
*/
sConfig.Channel = ADC_CHANNEL_0;
sConfig.Rank = ADC_RANK_CHANNEL_NUMBER;
if (HAL_ADC_ConfigChannel(&hadc, &sConfig) != HAL_OK)
{
Error_Handler();
}
}
Mettre en oeuvre d’une LED avec un microcontrôleur STM32L073 (ou autre de chez ST)
Mise en oeuvre d'une led branchée sur la sortie PA5 du STM32L073
Choisir un nom de projet explicite, par exemple :
GPIO_PA5_LD2_SET_RESET_TOGGLE_L073RZ
Et dans le main avant le while (1) :
Allumage de la led
HAL_GPIO_WritePin(LD2_GPIO_Port, LD2_Pin, GPIO_PIN_SET);
Extinction de la led
HAL_GPIO_WritePin(LD2_GPIO_Port, LD2_Pin, GPIO_PIN_RESET);
Clignotement de la led
HAL_GPIO_TogglePin(LD2_GPIO_Port, LD2_Pin);
code du de la boucle while du main complet
while (1)
{
//Décommenter la ligne suivante pour allumer la led 2 branchée sur PA5
// HAL_GPIO_WritePin(LD2_GPIO_Port, LD2_Pin, GPIO_PIN_SET);
// Décommenter les deux lignes suivantes pour faire clignoter la led branchée sur PA5
HAL_GPIO_TogglePin(LD2_GPIO_Port, LD2_Pin);
HAL_Delay(500);
}
Note :
Si on appelle la fonction HAL_GPIO_WritePin(LD2_GPIO_Port, LD2_Pin, GPIO_PIN_SET);
comme des bourrins, dans un while(1) sans mettre de délais (c'est pas malin de l'appeler en permanance...) la led ne s'allumera pas. Qu'on se le dise.
Objectif pour un étudiant : le faire en moins de 8 minutes
Utiliser un encodeur dans vos projets
Encodeur rotatif sous STM32
Mise en oeuvre sur un STM32-F429ZI (nucleo-F429ZI)
-
Mise en oeuvre d'un codeur rotatif avec bouton central.
-
Mise en garde : désactiver l'ethernet qui s'il n'est pas configuré fait planter au démarrage.
-
Au niveau du Timer à utiliser
utiliser un timer qui permet le mode Encoder
Attention : Sur la nucleo-F429ZI le timer2 ne semble pas bien connecté sur PA0 et PA1 (A vérifier) -
Timer3 : PC7 et PA6. Testé avec succès.
-
GPIO : s'il n'y a pas de résistances de pull-up, les rajouter en interne.
Step 0 : configuration de l'USART3 histoire de communiquer avec le PC et putty (115200 8N1)
Step 1 : configuration des horloges du STM32
Step 2 : configuration du Timer 3
Step 3 : configuration du Timer 3, suite
Step 4 : et dans le main
// ne pas oublier
#include "stdio.h"
int main(void)
{
uint16_t EncVal = 0;
uint8_t msg[]="Encoder test with default settings of project\r\n";
/* MCU Configuration--------------------------------------------------------*/
/* Reset of all peripherals, Initializes the Flash interface and the Systick. */
HAL_Init();
/* Configure the system clock */
SystemClock_Config();
/* Initialize all configured peripherals */
MX_GPIO_Init();
MX_USART3_UART_Init();
MX_TIM3_Init();
uint8_t Uart_Buf[70];
HAL_TIM_Encoder_Start(&htim3, TIM_CHANNEL_ALL);
HAL_UART_Transmit(&huart3, msg, sizeof(msg), 1000);
while (1)
{
EncVal = (TIM3->CNT)>>2; //selon les valeurs configurées, pas nécessaire de faire une division par 4
// EncVal = __HAL_TIM_GET_COUNTER(&htim2);
int len = sprintf(Uart_Buf, "%d\r\n",EncVal);
HAL_UART_Transmit(&huart3, Uart_Buf, len, 1000);
HAL_Delay(100);
}
}
Step 5 : Hardware
Bibliographie
https://stm32world.com/wiki/STM32_Rotary_Encoder
https://stm32f4-discovery.net/2014/08/library-26-rotary-encoder-stm32f4/
Nucleo-F429ZI Schematic and pinout
foo@bar:~$ wget https://www.st.com/content/ccc/resource/technical/layouts_and_diagrams/schematic_pack/63/95/5c/0c/d6/af/4b/e2/nucleo_144pins_sch.zip/files/nucleo_144pins_sch.zip/jcr:content/translations/en.nucleo_144pins_sch.zip
Nucleo-F446RE Schematic and pinout
foo@bar:~$ wget https://www.st.com/content/ccc/resource/technical/layouts_and_diagrams/schematic_pack/71/1e/2a/ac/b5/c1/4b/a9/nucleo_64pins_sch.zip/files/nucleo_64pins_sch.zip/jcr:content/translations/en.nucleo_64pins_sch.zip
STM32F429ZI Reference Manual
https://www.st.com/resource/en/reference_manual/rm0090-stm32f405415-stm32f407417-stm32f427437-and-stm32f429439-advanced-armbased-32bit-mcus-stmicroelectronics.pdf
USB
-
Côté PC - programmation
https://geekthis.net/post/usb-sniffing-and-programming/ -
Avec WireShark
https://wiki.wireshark.org/CaptureSetup/USB