STM8s partie 2, configuration de l'UART
RetourIntro
Lorsqu’on développe du code sur microcontrôleur, il est essentiel de bien comprendre et de visualiser le comportement du matériel pour pouvoir debug facilement. Chaque ligne de programme interagit directement avec des registres, des broches et des périphériques, et sans une bonne lecture de ce qui se passe réellement, il devient vite difficile de progresser. Sur le STM8, les possibilités de débogage sont réduites par rapport à des plateformes plus modernes, ce qui impose de s’adapter. Le port série reste l’outil le plus simple et le plus accessible pour observer l’exécution du programme, suivre des variables ou valider un comportement, et devient de fait le canal privilégié pour instrumenter et tester son code.
Matos requis
Il vous faut :
- Une carte STM8s “blue pill”
- Un Stlink V2
- Un PC sous Ubuntu
- Un adaptateur série/usb
- Un Doliprane (au cas ou)
La datasheet
Voici les broches de l’UART selon la datasheet :
Fonction | Broche STM8S103F3 | Broche physique |
---|---|---|
TX (USART1_TX) | PD5 | pin 20 |
RX (USART1_RX) | PD6 | pin 19 |
TX doit être connecté sur le RX du module USB série et RX sur le TX du module USB série. Eh oui, quand vous parlez ce qui sort de votre bouche arrive dans l’oreille de votre interlocuteur il faut donc croiser
Ensuite il faut configurer les sorties GPIO comme nous l’avons vu dans le précédent article :
// TX - PD5
PD_DDR |= (1 << 5); // Data Direction Register: 1 = Output
PD_CR1 |= (1 << 5); // Control Register 1: 1 = Push-Pull
PD_CR2 |= (1 << 5); // Control Register 2: 1 = Fast Mode
// RX - PD6
PD_DDR &= ~(1 << 6); // 0 = Input
PD_CR1 |= (1 << 6); // Pull-up activé
PD_CR2 &= ~(1 << 6); // Pas d’interruption
Dans le cas standard nous allons travailler a une vitesse de 9600 bauds.
Sur STM8s la manière de configurer le baud rate est pour le moins spéciale, deux registres sont utilisés a des addresses bizarres ce qui rends la configuration difficile et pas claire. J’ai passé des heures a tester avant d’y parvenir.
Il faut déjà calculer la valeur requise pour subdiviser l’horloge principale (16 MHz) et obtenir une vitesse de transmission de 9600 bauds :
USARTDIV = f_master / (16 × BaudRate)
Il faut ensuite découper ce résultat en deux partie :
- Une mantisse (la partie avant la virgule)
- Une fraction (4 bit de poids faibles)
Si on applique le calcul précédent on obtiens ceci :
USARTDIV = 16,000,000 / (16 * 9600) = 104.166666...
Le Doliprane c’est maintenant..
- La mantisse vaut 104
- La fraction vaut 16 x 0,166666 soit 2.66 que l’on va arrondir a 3 soit 0011 en binaire
Les registres sont donc les suivants :
Registre | Bits utilisés | Contenu | Source |
---|---|---|---|
BRR1 |
[7:0] | Bits 11 à 4 de la mantisse | mantisse >> 4 |
BRR2 |
[7:4] | Bits 3 à 0 de la mantisse | mantisse & 0x0F |
BRR2 |
[3:0] | Bits 3 à 0 de la fraction | (fraction * 16) |
Donc :
USARTDIV = 104.166
- En binaire, 104 =
01101000
- bits 11–4 =
01101000
va dansBRR1
- bits 3–0 =
1000
va dansBRR2[7:4]
- fraction =
.1666 × 16 = 2.666 = 3
soit0011
va dansBRR2[3:0]
Oui, c’est illogique mais continuons…
Voici ce que cela donne concrètement :
UART1_BRR1 = 0x68; // 104 decimal
UART1_BRR2 = 0x83; // 0x80 | 0x03
- 0x80 =
10000000
→ BRR2[7:4] =1000
- 0x03 =
00000011
→ BRR2[3:0] =0011
Voici une fonction qui calcule ce charabia automatiquement selon le baudrate et la cadence CPU :
void uart_config() {
// Calcul du diviseur USARTDIV pour la vitesse de transmission
uint16_t usartdiv = (F_CPU + BAUDRATE / 2) / BAUDRATE;
// Extraction des bits pour BRR1 et BRR2 :
// - BRR1 prend les bits 11 à 4 (poids forts)
// - BRR2 combine les bits 3 à 0 (LSB) et la fraction sur 4 bits (MSB)
uint8_t brr1 = (usartdiv >> 4) & 0xFF; // Bits 11:4
uint8_t brr2 = ((usartdiv & 0x0F)) | ((usartdiv >> 8) & 0xF0); // Bits 3:0 + Bits 11:8
UART1_BRR1 = brr1;
UART1_BRR2 = brr2;
UART1_CR1 = 0x00; // 8 data bits, no parity
UART1_CR3 = 0x00; // 1 stop bit
UART1_CR2 = (1 << UART1_CR2_TEN) | (1 << UART1_CR2_REN); // active RX et TX
//Nettoyage des registres
(void)UART1_SR;
(void)UART1_DR;
}
Ensuite on code des fonctions pour envoyer et recevoir des octets :
void uart_write(uint8_t data) {
UART1_DR = data;
while (!(UART1_SR & (1 << UART1_SR_TXE)));
}
uint8_t uart_read() {
while (!(UART1_SR & (1 << UART1_SR_RXNE)));
return UART1_DR;
}
Il est aussi possible de rediriger stdout avec SDCC :
int putchar(int c) {
uart1_send_byte(c);
return 0;
}
Voici le code complet :
#include <stdint.h> // ne pas oublier pour printf
#include <stdio.h> // ne pas oublier pour printf
#include "../stm8s.h"
#define F_CPU 16000000UL //16MHz
#define BAUDRATE 9600 // vitesse du port série
void uart_config() {
// Calcul du diviseur USARTDIV pour la vitesse de transmission
uint16_t usartdiv = (F_CPU + BAUDRATE / 2) / BAUDRATE;
// Extraction des bits pour BRR1 et BRR2 :
// - BRR1 prend les bits 11 à 4 (poids forts)
// - BRR2 combine les bits 3 à 0 (LSB) et la fraction sur 4 bits (MSB)
uint8_t brr1 = (usartdiv >> 4) & 0xFF; // Bits 11:4
uint8_t brr2 = ((usartdiv & 0x0F)) | ((usartdiv >> 8) & 0xF0); // Bits 3:0 + Bits 11:8
UART1_BRR1 = brr1;
UART1_BRR2 = brr2;
UART1_CR1 = 0x00; // 8 data bits, no parity
UART1_CR3 = 0x00; // 1 stop bit
UART1_CR2 = (1 << UART1_CR2_TEN) | (1 << UART1_CR2_REN); // active RX et TX
//Nettoyage des registres
(void)UART1_SR;
(void)UART1_DR;
}
void uart_write(uint8_t data) {
UART1_DR = data;
PB_ODR &= ~(1 << 5); // LED OFF
while (!(UART1_SR & (1 << UART1_SR_TC)));
PB_ODR |= (1 << 5); // LED ON
}
uint8_t uart_read() {
while (!(UART1_SR & (1 << UART1_SR_RXNE)));
return UART1_DR;
}
int putchar(int c) {
uart_write(c);
return 0;
}
static inline void delay_ms(uint16_t ms) {
uint32_t i;
for (i = 0; i < ((F_CPU / 18000UL) * ms); i++)
__asm__("nop");
}
void main() {
CLK_CKDIVR = 0x00; // NE SURTOUT PAS OUBLIER CECI
uint8_t counter = 0;
uart_config();
// LED onboard
PB_CR1= (1 << 5);
PB_DDR = (1 << 5);
while (1) {
uint8_t c = uart_read(); // Attendre un caractère du terminal
printf("Echo : %c \r\n", c);
}
}
Ici, le code renvoie simplement les caractères que vous lui transmettez, pour tester l’envoie/réception.
Note : A partir d’ici il ne faut plus jamais oublier cette ligne dans le main :
CLK_CKDIVR = 0x00; // NE SURTOUT PAS OUBLIER CECI
Elle permets de forcer la cadence CPU a 16 MhZ est la majorité des protocoles(série,i2c ou encore SPI) sont TRES dépendant de ceci pour fonctionner
Conclusions
Dans cette partie nous avons mis en place la sortie série pour nous aider a debug notre code. Dans le prochain épisode nous utiliserons le bit banging pour discuter avec un capteur de température en bus one-wire.
Retour