DOCUMENTATION
La liaison UART (Universal Asynchronous Receiver Transceiver ) est une vieille liaison, peu performante, historiquement utilisée pour le dialogue entre un PC et un modem.
Cette liaison est malgré tout toujours présente dans les microprocesseurs en raison de sa simplicité de mise en oeuvre, dans un contexte
de Debug ou pour un dialogue avec un capteur.
Une trame UART permet d’envoyer un seul caractère, de longueur 7,8 ou 9 bits.
Pour chaque caractère, il faut ajouter :
Le microcontrôleur STM32F411 présente 3 registres de contrôle/configuration.
Dans ces 3 registres, les bits importants pour une utilisation minimale sont :
Le débit sur la ligne est plus lent que l’horloge du périphérique UART.
Il va donc falloir diviser la fréquence afin de définir le baud rate.
On a vu dans le chapitre ’liaison série’ qu’il fallait attendre T/2 pour faire l’acquisition
d’un bit ( T étant la durée d’un bit ), afin d’être sûr d’avoir un état stabilisé.
Pour garantir ce positionnement par rapport au débit sur la ligne, on divise le temps en quantums de temps
16 fois plus rapides que le débit.
Il faut donc régler dans le registre BRR un ratio tel que :
\( USARTDIV = \frac{F_{CK}}{16.baudrate} \)
Le registre BRR contient une valeur au format virgule fixe 12.4
soit \( F{CK} = 42MHz \)_
Je souhaite baudrate = 9600
J’ai donc :
\( USARTDIV= \frac{F_{CK}}{baudrate} \)
\( USARTDIV= \frac{42.10^6}{16*9600} \)
\( USARTDIV= 273.4375 \)
Comme on est au format 12.4, le codage de 273.4375 est le même que celui de \( 273.7375*2^4 = 4375 \)
Conclusion :
\( BRR = \frac{F_{CK}}{baudrate} \)
{: .encadrer_resultat}
Pour simplifier On fait l’hypothèse que les données font toujours 8 bits, qu’il n’y a pas de bit de parité, et qu’il y a un bit de stop.
RAPPEL : Les registres sont détaillés dans la documentation RefManual_STM32F411.pdf
Le STM32F411 comporte 3 périphériques UART ( USART1, USART2, USART6 ).
USART2 est relié au processeur gérant l’interface de debug de la carte ( STLINK ) , de telle sorte qu’il y ait conversion UART –> USB.
Ainsi la liaison UART2 est accessible directement par le cable USB.
Extrait de la documentation de la carte Nucleo F411 :
Les broches à configurer sont donc PA2 (TX) et PA3 (RX) ( Datasheet STM32F411 ) :
PA2 et PA3 doivent donc être configurées en Alternate Function 07
REMARQUE : PA3 est une entrée d’un périphérique, elle peut être également configurée en Input
void HAL_UART2_MspInit(void)
{
GPIO_InitTypeDef GPIO_InitStruct;
__GPIOA_CLK_ENABLE();
__USART2_CLK_ENABLE();
GPIO_InitStruct.Pin = GPIO_PIN_2 | GPIO_PIN_3;
GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_MEDIUM;
GPIO_InitStruct.Alternate = GPIO_AF7_USART2;
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
}
Nous allons considérer la structure UART_HandleTypeDef suivante :
typedef struct __UART_HandleTypeDef
{
USART_TypeDef *Instance; /*!< UART registers base address */
UART_InitTypeDef Init; /*!< UART communication parameters */
uint8_t *pTxBuffPtr; /*!< Pointer to UART Tx transfer Buffer */
uint16_t TxXferSize; /*!< UART Tx Transfer size */
__IO uint16_t TxXferCount; /*!< UART Tx Transfer Counter */
uint8_t *pRxBuffPtr; /*!< Pointer to UART Rx transfer Buffer */
uint16_t RxXferSize; /*!< UART Rx Transfer size */
__IO uint16_t RxXferCount; /*!< UART Rx Transfer Counter */
} UART_HandleTypeDef;
USART_TypeDef Permet d’accéder aux différents registres de UART :
typedef struct
{
__IO uint32_t SR; /*!< USART Status register, Address offset: 0x00 */
__IO uint32_t DR; /*!< USART Data register, Address offset: 0x04 */
__IO uint32_t BRR; /*!< USART Baud rate register, Address offset: 0x08 */
__IO uint32_t CR1; /*!< USART Control register 1, Address offset: 0x0C */
__IO uint32_t CR2; /*!< USART Control register 2, Address offset: 0x10 */
__IO uint32_t CR3; /*!< USART Control register 3, Address offset: 0x14 */
__IO uint32_t GTPR; /*!< USART Guard time and prescaler register, Address offset: 0x18 */
} USART_TypeDef;
UART_InitTypeDef Permet de définir les différents éléments de configuration :
typedef struct
{
uint32_t BaudRate;
uint32_t WordLength;
uint32_t StopBits;
uint32_t Parity;
uint32_t Mode;
uint32_t HwFlowCtl;
uint32_t OverSampling;
} UART_InitTypeDef;
La fonction HAL_UART_Init() permet de configurer les registres du périphérique à partir des éléments définis dans le champ Init de la structure UART_HandleTypeDef
Appel de HAL_UART_Init() en début du main() :
huart2.Instance = USART2;
huart2.Init.BaudRate = 115200;
huart2.Init.WordLength = UART_WORDLENGTH_8B;
huart2.Init.StopBits = UART_STOPBITS_1;
huart2.Init.Parity = UART_PARITY_NONE;
HAL_UART_Init(&huart2);
Dès lors que j’ai branché ma carte sur le PC, il se crée un fichier /dev/ttyACM0 auquel je ferai référence côté PC pour désigner l’émission et la réception de caractère sur l’UART.
Ouvrons sur le PC un terminal série ( gtkterm ou avec la commande minicom -D /dev/ttyACM0 -b 115200 )
Si j’écris, dans le programme de la carte :
huart2.Instance->DR = 'a';
J’écris le codage ASCII de ‘a’ dans le registre de transmission, ce caractère est sérialisé.
Si maintenant j’écris :
huart2.Instance->DR = 'a';
huart2.Instance->DR = 'b';
huart2.Instance->DR = 'c';
seul le caractère ‘c’ s’affiche, je n’ai pas attendu qu’un caractère soit sérialisé pour envoyer le suivant.
Il faut donc tester la bonne émission de chaque caractère avant d’écrire dans DR.
Le bit TXE ( registre USART_SR, bit 7 ) Transmit data register empty vaut 1 quand le registre TX est vide, autrement dit la donnée a
bien été transmise au registre sérialisateur, on peut écrire une nouvelle donnée dans DR.
Ainsi on peut écrire :
...
huart2.Instance->DR = 'a';
while ((huart2.Instance->SR & (1<<7)) == 0) {} ;
huart2.Instance->DR = 'b';
while ((huart2.Instance->SR & (1<<7)) == 0) {} ;
huart2.Instance->DR = 'c';
while ((huart2.Instance->SR & (1<<7)) == 0) {} ;
...
void HAL_UART2_MspInit(void)
{
...
NVIC_SetPriority(USART2_IRQn, USART2_IRQ_PRIO);
NVIC_EnableIRQ(USART2_IRQn);
}
int HAL_UART_Receive_IT(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size)
{
huart->Instance->CR1 = (huart->Instance->CR1) | (1<<5); // Receiver Not Empty Interrupt Enable
huart->pRxBuffPtr = pData;
huart->RxXferSize = Size;
huart->RxXferCount = Size;
return 0;
}
REMARQUE : La lecture du registre DR est dans tous les cas nécessaire afin d’acquiter la demande d’interruption.
void HAL_USART_IRQHandler(UART_HandleTypeDef *huart)
{
uint32_t sr = huart->Instance->SR;
if (sr & (1<<5)) // Read data register not empty interrupt
{
UART_Receive_IT(huart);
}
else if (sr & (1<<4)) // idle interrupt
{ huart->Instance->DR;}
else if (sr & (1<<3)) // overrun interrupt
{ huart->Instance->DR;}
else if (sr & (1<<0)) // parity error interrupt
{ huart->Instance->DR;}
}
void UART_Receive_IT(UART_HandleTypeDef *huart)
{
*huart->pRxBuffPtr++ = (uint8_t)(huart->Instance->DR);
huart->RxXferCount--;
if (huart->RxXferCount == 0U)
{
huart->Instance->CR1 = (huart->Instance->CR1) & ~(1<<5); // Disable Int
HAL_UART_RxCpltCallback(huart);
}
}
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
uint8_t c=0;
if(huart == &huart2)
{
c = huart2.Instance->DR;
HAL_UART_Receive_IT(&huart2, buf, 1);
}
}
PROJET SOURCE
WORKSPACE_F411_HAL_STM32CUBE.zip
Q1. Compléter le fichier stm32f4xx_hal_msp.c afin de configurer les broches PA2 et PA3 en tant que broches TX et RX du périphérique USART2.
La réception d’un caractère devant déclencher une interruption, le NVIC doit être configuré.
Q2. Véfifier à l’aide du debugger la valeurs des bits UE, M, PCE, TE, RE, STOP à l’issue de l’appel de la fonction HAL_UART_Init()
Q3. Compléter la fonction HAL_UART_Transmit(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size) permettant d’envoyer Size caractères situés à l’adresse pData avec le périphérique huart
Q4. Tester la fonction HAL_UART_Transmit() :
Q5. Compléter les fonctions uart_putc() et uart_puts permettant d’envoyer respectivement un caractère et une chaine de caractères se terminant par le caractère nul ( code ascii 0x00 )
Q6. Réaliser l’enregistrement d’une trame UART avec un analyseur logique.
Q7. Placer un point d’arrêt dans la routine d’interruption HAL_USART_IRQHandler() et vérfier qu’à chaque caractère reçu
on s’y arrête bien.
Vérifier que le caractère reçu est bien celui qui a été envoyé depuis le PC avec le terminal série.
Q8. On veut faire varier la vitesse de clignotement d’une led ( R, G ou B ) depuis le PC.
Pour cela nous devons envoyer via la liaison UART des ordres au format suivant :
La durée de clignotement est comprise entre 0 et 5000ms, cette donnée est envoyée sous forme de valeur ( et non en chaine de caractères ).
Le codage de cette valeur n’est donc pas possible sur 8 bits, il faut 2 octets.
Pour répartir notre valeur sur 2 octets, on fera donc :
A.N : valeur = 1000 = 0x03E8
Comme il faut recevoir et interpréter un message de plusieurs caractères, une méthode peut consister à :
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
uint8_t c=0;
if(huart == &huart2)
{
c = huart2.Instance->DR;
uart2_buffer[(p_wr++)%BUF_SIZE] = c; // % : operateur modulo : reste de la division
size++;
}
}
Pour envoyer les ordres depuis le PC, nous pouvons utiliser le programme python suivant :
#! /usr/bin/python3
import serial
import time
from tkinter import *
import struct
port='/dev/ttyACM0'
baudrate=115200
serBuffer = ""
########################################################################
# FUNCTIONS
########################################################################
def onPushSend():
var_led=led.get()
var_blink=blinkTime.get()
var_blink=int(var_blink)
var_blink_low=var_blink & 0xFF
var_blink_high=(var_blink >> 8) & 0xFF
ser.write(struct.pack('B',0xFF)) # Header
ser.write(var_led.encode())
ser.write(struct.pack('B',var_blink_high))
ser.write(struct.pack('B',var_blink_low))
########################################################################
# MAIN
########################################################################
if __name__ == "__main__" :
try:
ser = serial.Serial(port, baudrate, bytesize=8, parity='N', stopbits=1, timeout=None, rtscts=False, dsrdtr=False)
print("serial port " + ser.name + " opened")
except Exception:
print("error open serial port: " + port)
exit()
ui=Tk()
ui.title("SERIAL SEND ORDER")
ui.geometry("200x200")
labelToSendMes=Label(ui, text="LED (r/g/b)", font=("Arial", 10), fg="black")
labelToSendMes.pack()
led=StringVar()
toSendEntry=Entry(ui, textvariable=led)
toSendEntry.pack()
labelToSendMes=Label(ui, text="Blink time (ms)", font=("Arial", 10), fg="black")
labelToSendMes.pack()
blinkTime=StringVar()
toSendEntry=Entry(ui, textvariable=blinkTime)
toSendEntry.pack()
copyButton=Button(ui, text="SEND", font=("Arial",10, "bold"), bg="seagreen3", fg="black", bd=3, relief=RAISED, command=onPushSend)
copyButton.pack()
ui.mainloop() # MAIN LOOP
########################################################################
Pour lancer le programme, dans un terminal :
$ chmod +x sendOrder.py
$ ./sendOrder.py