
Dans J'ai essayé d'expliquer aux hobbyistes électronicien, issus des petites culottes Arduino, pourquoi et comment ils devraient lire les fiches techniques et la documentation des microcontrôleurs. Le texte est devenu long, j'ai donc promis de montrer des exemples pratiques dans un article séparé. Eh bien, on m'a appelé champignon…
Aujourd'hui, je vais montrer comment, à l'aide des fiches techniques, résoudre des tâches assez simples mais nécessaires pour de nombreux projets sur les contrôleurs STM32 (Blue Pill) et STM8. Tous les projets de démonstration sont consacrés à mes LED préférées, que nous allons allumer en grande quantité, pour cela, nous devrons utiliser différents périphériques intéressants.
Le texte est encore une fois énorme, donc pour plus de commodité, je fais un sommaire :
Avertissement : je ne suis pas ingénieur, je ne prétends pas avoir de profondes connaissances en électronique, cet article est destiné à ceux qui, comme moi, sont des amateurs. En fait, comme public cible, j'ai envisagé moi-même d'il y a deux ans. Si quelqu'un m'avait dit à l'époque que lire les fiches techniques d'une puce inconnue n'était pas effrayant, je n'aurais pas perdu beaucoup de temps à chercher des morceaux de code sur Internet et à inventer des solutions de fortune avec des ciseaux et du sparadrap.
Le centre de cet article est consacré aux fiches techniques, et non aux projets, donc le code peut ne pas être trop propre et souvent improvisé. Les projets eux-mêmes sont très simples, bien qu'ils soient valables pour une première approche d'une nouvelle puce.
J'espère que mon article pourra aider quelqu'un à un stade similaire d'immersion dans ce hobby.
STM32
16 LED avec DM634 et SPI
Un petit projet utilisant Blue Pill (STM32F103C8T6) et le driver de LED DM634. À l'aide des fiches techniques, nous allons examiner le driver, les ports IO de STM et configurer le SPI.
DM634
Puissance taïwanaise avec 16 sorties PWM 16 bits, peut être connecté en chaîne. Le modèle 12 bits inférieur est connu grâce à un projet national. . À l'époque, en choisissant entre le DM63x et le bien connu TLC5940, j'ai opté pour le DM pour plusieurs raisons : 1) le TLC sur AliExpress est sûrement une contrefaçon, tandis que celui-ci ne l'est pas ; 2) le DM possède un PWM autonome avec son propre générateur de fréquence ; 3) je pouvais l'acheter à bas prix à Moscou, sans avoir à attendre un colis d'Ali. Bien sûr, j'étais également curieux d'apprendre à contrôler la puce moi-même, au lieu d'utiliser une bibliothèque toute faite. Les puces sont principalement disponibles dans un boîtier SSOP24, facile à souder sur un adaptateur.
Étant donné que le fabricant est taïwanais, la documentation du chip est en anglais chinois, ce qui signifie que ce sera amusant. Commençons par regarder le brochage (Pin Connection), pour comprendre à quelle broche connecter quoi, et la description des broches (Pin Description). 16 broches :

Sources de courant continu entrant (open drain)
Sink / Open-drain output – drain ; source de courant entrant ; sortie, en état actif connectée à la terre, – les LED sont connectées aux drivers par les cathodes. Électriquement, ce n'est bien sûr pas un 'open drain' ( ), mais dans les fiches techniques, cette désignation pour les broches en mode drain apparaît souvent.drain ouvertDes résistances externes entre REXT et GND pour régler la valeur du courant de sortie
![]()
Un résistor de référence est connecté entre la broche REXT et la terre, contrôlant la résistance interne des sorties, voir le graphique à la page 9 de la fiche technique. Avec le DM634, cette résistance peut également être contrôlée par logiciel, permettant de définir la luminosité générale (
global brightness); dans cet article, je n'entrerai pas dans les détails, je vais simplement mettre ici un résistor de 2.2 à 3 kΩ.Pour comprendre comment contrôler la puce, examinons la description de l'interface de l'appareil :
Aha, voilà le chinois anglais dans toute sa splendeur. Le traduire est problématique, on peut comprendre si on le souhaite, mais il existe une autre méthode – jeter un œil à la description de la connexion dans la fiche technique d'un TLC5940 fonctionnellement proche :

… Pour entrer des données dans l'appareil, seules trois broches sont nécessaires. Le front montant du signal SCLK déplace les données de la broche SIN vers le registre interne. Une fois que toutes les données sont chargées, un court signal haut XLAT fixe les données transmises successivement dans les registres internes. Les registres internes sont des bascules déclenchées par le niveau du signal XLAT. Toutes les données sont transmises en commençant par le bit le plus significatif.

Latch
– bascule / verrou / fixateur. Rising edge
– front montant de l'impulsion MSB first
MSB d'abord – avec le bit le plus significatif à l'avant.
pour synchroniser les données – transmettre les données séquentiellement (bit par bit).
Le terme verrou est souvent rencontré dans la documentation des puces et est traduit de différentes manières, donc pour la compréhension, j'aimerais
offrir un petit coursLe driver LED est en fait un registre à décalage. "Décalage" (décalage) dans le nom signifie un déplacement bit à bit des données à l'intérieur de l'appareil : chaque nouveau bit inséré pousse la chaîne précédente vers l'avant. Étant donné qu'au moment du déplacement, personne ne veut voir clignoter les LED de manière désordonnée, le processus se déroule dans des registres tampon, séparés du travail par un clapet (verrou) – c'est une sorte d'antichambre, où les bits s'alignent dans l'ordre requis. Une fois que tout est prêt, le clapet s'ouvre et les bits partent au travail, remplaçant le précédent ensemble. Le terme verrou dans la documentation des microcontrôleurs implique presque toujours un tel clapet, quelle que soit la combinaison dans laquelle il est utilisé.
Ainsi, la transmission de données dans le DM634 se fait comme suit : nous fixons l'entrée DAI à la valeur du bit le plus significatif de la LED la plus lointaine, faisons oscillater DCK vers le haut et vers le bas ; nous fixons l'entrée DAI à la valeur du bit suivant, faisons osciller DCK ; et ainsi de suite, jusqu'à ce que tous les bits soient transmis (synchronisés), après quoi nous faisons osciller LAT. Cela peut être fait manuellement (bit-bang), mais il est préférable d'utiliser une interface SPI spécialement conçue à cet effet, qui est heureusement présente sur notre STM32 en deux exemplaires.
La Petite Pilule STM32F103
Introduction : les contrôleurs STM32 sont beaucoup plus complexes que l'Atmega328, malgré ce qui pourrait sembler effrayant. Par conséquent, par souci d'économie d'énergie, presque tous les périphériques sont désactivés au démarrage, et la fréquence d'horloge est de 8 MHz à partir de la source interne. Heureusement, les programmeurs STM ont écrit un code pour amener la puce à 72 MHz « nominal », et les auteurs de tous les IDE que je connais l'ont inclus dans la procédure d'initialisation, il n'est donc pas nécessaire de l'horloger (mais ). Cependant, il faudra activer les périphériques.
Documentation : la Blue Pill est équipée de la puce populaire STM32F103C8T6, pour laquelle deux documents utiles existent :
- pour les microcontrôleurs STM32F103x8 et STM32F103xB ;
- pour toute la série STM32F103 et au-delà.
Dans la fiche technique, nos points d'intérêt peuvent être :
- Les pinouts – les schémas de brochage des puces – au cas où nous déciderions de fabriquer nos propres cartes ;
- Carte mémoire – carte mémoire pour une puce spécifique. Le manuel de référence contient une carte pour toute la gamme, mentionnant des registres qui ne figurent pas dans le nôtre.
- Table des définitions de broches – énumération des fonctions principales et alternatives des broches ; pour la « pilule bleue », on peut trouver des images plus pratiques en ligne avec une liste des broches et de leurs fonctions. Donc, googlons immédiatement le pinout de la Blue Pill et tenons une telle image à portée de main :

Remarque : l'image trouvée en ligne contenait une erreur, signalée dans les commentaires, merci pour cela. L'image a été remplacée, mais c'est une leçon — il vaut mieux vérifier les informations en dehors des datasheets.
Nous éliminons la datasheet, ouvrons le manuel de référence, et dorénavant, nous ne l'utilisons que.
Ordre des opérations : nous nous occupons de l'entrée/sortie standard, configurons SPI, et activons le périphérique nécessaire.
Entrée-sortie
Sur l'Atmega328, l'entrée-sortie est réalisée de manière très simple, ce qui peut perturber en raison de l'abondance d'options sur le STM32. Pour l'instant, nous avons seulement besoin des sorties, mais même elles ont quatre variantes :

sortie à collecteur ouvert, sortie push-pull, push-pull alternatif, collecteur ouvert alternatif
Push-pull (push-pull) – sortie classique de l'Arduino, la broche peut prendre la valeur soit HIGH, soit LOW. En revanche, avec le « collecteur ouvert », il y a , bien qu'en réalité, c'est très simple :


Configuration de la sortie / quand le port est assigné à la sortie : / buffer de sortie activé : / – mode collecteur ouvert : « 0 » dans le registre de sortie active le N-MOS, « 1 » dans le registre de sortie laisse le port en mode Hi-Z (le P-MOS n'est pas activé) / – mode push-pull : « 0 » dans le registre de sortie active le N-MOS, « 1 » dans le registre de sortie active le P-MOS.
La seule différence entre le collecteur ouvert (drain ouvert) et le push-pull (push-pull) est que dans le premier, la broche ne peut pas prendre l'état HIGH : en écrivant une unité dans le registre de sortie, elle passe en mode haute impédance (high impedance, Hi-Z). En écrivant un zéro, la broche se comporte de la même manière dans les deux modes, tant logiquement qu'électriquement.
En mode de sortie normal, la broche retransmet simplement le contenu du registre de sortie. En mode « alternatif », elle est contrôlée par le périphérique correspondant (voir 9.1.4) :
![]()
Si le bit du port est configuré comme sortie de fonction alternative, le registre de sortie est désactivé, et la broche est connectée au signal de sortie du périphérique.
La fonctionnalité alternative de chaque broche est décrite dans les définitions des broches La fiche technique est sur l'image téléchargée. En réponse à la question de savoir que faire si une broche a plusieurs fonctions alternatives, la note de bas de page dans la fiche technique donne la réponse :
![]()
Si plusieurs blocs périphériques utilisent la même broche, pour éviter les conflits entre les fonctions alternatives, il faut utiliser uniquement un bloc périphérique à la fois, en basculant à l'aide de la bit d'activation de l'horloge des périphériques (dans le registre RCC approprié).
Enfin, les broches en mode sortie ont également une vitesse d'horloge. C'est une autre caractéristique d'économie d'énergie ; dans notre cas, nous la mettons simplement au maximum et on oublie.
Ainsi : nous utilisons SPI, ce qui signifie que deux broches (pour les données et pour l'horloge) doivent être 'fonction alternative de tirage-poussée', et une autre (LAT) – 'tirage-poussée classique'. Mais avant de les assigner, clarifions le SPI.
SPI
Encore un petit exposé
SPI ou Serial Peripheral Interface (interface périphérique série) – est une interface simple et très efficace pour la communication entre les microcontrôleurs et avec le monde extérieur en général. Son principe de fonctionnement est déjà décrit ci-dessus, là où l'on parle du pilote LED chinois (dans le manuel de référence, voir section 25). SPI peut fonctionner en mode maître et esclave. SPI dispose de quatre canaux de base, dont tous ne peuvent pas être utilisés :
- MOSI, Master Output / Slave Input : cette broche en mode maître envoie des données, tandis qu'en mode esclave, elle les reçoit ;
- MISO, Master Input / Slave Output : inversement, en mode maître elle reçoit, en mode esclave elle envoie ;
- SCK, Serial Clock : définit la fréquence de transmission des données en mode maître ou reçoit le signal d'horloge en mode esclave. En fait, il envoie des bits ;
- SS, Slave Select : grâce à ce canal, l'esclave sait qu'il y a une demande à son encontre. Sur le STM32, il s'appelle NSS, où N = négatif, c'est-à-dire que le contrôleur devient esclave si ce canal est à la terre. Cela fonctionne bien avec le mode Open Drain Output, mais c'est une autre histoire.
Comme tout le reste, SPI sur STM32 est riche en fonctionnalités, ce qui complique quelque peu sa compréhension. Par exemple, il peut fonctionner non seulement en SPI mais aussi en I2S, et dans la documentation, les descriptions sont mélangées, il est nécessaire de filtrer ce qui est superflu à temps. Notre tâche est extrêmement simple : il suffit de transmettre des données en utilisant uniquement MOSI et SCK. Allons à la section 25.3.4 (communication half-duplex), où nous trouvons 1 horloge et 1 fil de données unidirectionnel (1 signal d'horloge et 1 flux de données unidirectionnel) :

Dans ce mode, l'application utilise le SPI soit en mode d'envoi seul, soit en mode de réception seul. / Le mode d'envoi seul est similaire au mode duplex : les données sont transmises par la broche d'envoi (MOSI en mode maître ou MISO en mode esclave), et la broche de réception (MISO ou MOSI selon le cas) peut être utilisée comme une broche d'entrée-sortie classique. Dans ce cas, l'application peut ignorer le tampon Rx (s'il est lu, aucune donnée transmise ne sera présente).
Parfait, la broche MISO est maintenant libre, nous allons y connecter le signal LAT. Nous allons examiner le Slave Select, qui peut être contrôlé par programme sur STM32, ce qui est extrêmement pratique. Lisez le paragraphe correspondant dans la section 25.3.1 Description générale du SPI :

Contrôle logiciel de NSS (SSM = 1) / L'information concernant la sélection de l'esclave est contenue dans le bit SSI du registre SPI_CR1. La broche NSS externe reste libre pour d'autres besoins de l'application.
Il est temps d'écrire dans les registres. J'ai décidé d'utiliser SPI2, cherchons dans la fiche technique son adresse de base – dans la section 3.3 Carte mémoire :
![]()
Alors, commençons :
#define _SPI2_(mem_offset) (*(volatile uint32_t *)(0x40003800 + (mem_offset)))Ouvrons la section 25.3.3 avec son titre significatif « Configuration du SPI en mode maître » :

1. Réglez la fréquence d'horloge de l'interface série avec les bits BR[2:0] dans le registre SPI_CR1.
Les registres sont rassemblés dans la section homonyme du manuel de référence. Le décalage d'adresse (Address offset) au CR1 – 0x00, par défaut tous les bits sont réinitialisés (Reset value 0x0000):

Les bits BR définissent le diviseur de la fréquence d'horloge du contrôleur, déterminant ainsi la fréquence à laquelle le SPI fonctionnera. La fréquence de notre STM32 sera de 72 MHz, le pilote LED, selon sa fiche technique, fonctionne à une fréquence allant jusqu'à 25 MHz, il faut donc diviser par quatre (BR[2:0] = 001).
#define _SPI_CR1 0x00
#define BR_0 0x0008
#define BR_1 0x0010
#define BR_2 0x0020
_SPI2_ (_SPI_CR1) |= BR_0;// pclk/42. Réglez les bits CPOL et CPHA pour définir les relations entre la transmission de données et l'horloge de l'interface série (voir le schéma à la page 240)
Puisque nous lisons la fiche technique ici et non que nous examinons des schémas, étudions plutôt la description textuelle des bits CPOL et CPHA à la page 704 (Description générale du SPI) :

Phase et polarité du signal d'horloge
Avec les bits CPOL et CPHA du registre SPI_CR1, on peut choisir programmatiquement quatre options de relations de timing. Le bit CPOL (polarité du signal d'horloge) contrôle l'état du signal d'horloge lorsque les données ne sont pas transmises. Ce bit régule les modes maître et esclave. Si CPOL est réinitialisé, la broche SCK au repos est à un niveau bas. Si le bit CPOL est activé, la broche SCK au repos est à un niveau haut.
Si le bit CPHA (phase d'horloge) est activé, le deuxième front du signal SCK sert d'horloge pour le bit de poids fort (descendant si CPOL est désactivé, ou montant si CPOL est activé). Les données sont enregistrées au second changement d'horloge. Si le bit CPHA est désactivé, le front montant du signal SCK sert d'horloge pour le bit de poids fort (descendant si CPOL est activé, ou montant si CPOL est désactivé). Les données sont enregistrées au premier changement d'horloge.
En considérant ces connaissances, nous concluons que les deux bits doivent rester à zéro, car nous voulons que le signal SCK reste bas lorsqu'il n'est pas utilisé, et que les données soient transmises au front montant de l'impulsion (voir Rising Edge dans la datasheet DM634).
Au fait, c'est ici que nous rencontrons pour la première fois une particularité de la terminologie dans les datasheets ST : la phrase « réinitialiser un bit à zéro » s'écrit to reset a bit, pas to clear a bit, comme par exemple chez Atmega.
3. Activez le bit DFF pour déterminer le format de bloc de données 8 bits ou 16 bits
J'ai spécifiquement pris le DM634 16 bits, pour ne pas me compliquer avec la transmission de données PWM 12 bits, comme avec le DM633. Il est judicieux de mettre le DFF à un :
#define DFF 0x0800
_SPI2_ (_SPI_CR1) |= DFF; // 16-bit mode4. Configurez le bit LSBFIRST dans le registre SPI_CR1 pour déterminer le format du bloc.
LSBFIRST, comme son nom l'indique, configure la transmission en commençant par le bit de poids faible. Mais le DM634 souhaite recevoir des données en commençant par le bit de poids fort. Donc, laissons-le désactivé.
5. En mode matériel, si une entrée depuis la broche NSS est requise, appliquez un signal haut sur la broche NSS pendant toute la séquence de transmission des octets. En mode logiciel, définissez les bits SSM et SSI dans le registre SPI_CR1. Si la broche NSS doit fonctionner en sortie, seuls le bit SSOE doit être activé.
Nous activons SSM et SSI pour ne plus avoir à nous soucier du mode matériel NSS :
#define SSI 0x0100
#define SSM 0x0200
_SPI2_ (_SPI_CR1) |= SSM | SSI; //enable software control of SS, SS high6. Les bits MSTR et SPE doivent être activés (ils restent activés uniquement si un signal haut est appliqué sur NSS)
En fait, ces bits définissent notre SPI comme maître et l'activent :
#define MSTR 0x0004
#define SPE 0x0040
_SPI2_ (_SPI_CR1) |= MSTR; //SPI master
//когда все готово, включаем SPI
_SPI2_ (_SPI_CR1) |= SPE;Le SPI est configuré, écrivons immédiatement les fonctions qui envoient des octets au pilote. Continuons à lire le 25.3.3 « Configuration du SPI en mode maître » :

Ordre de transmission des données
La transmission commence lorsque le byte est écrit dans le tampon Tx.
Le byte de données est chargé dans le registre à décalage en mode parallèle (depuis le bus interne) lors de la transmission du premier bit, après quoi il est transmis en mode séquentiel. consécutif en mode MOSI, la première ou la dernière bit en avant selon la configuration du bit LSBFIRST dans le registre CPI_CR1. Le drapeau TXE est activé après la transmission des données du buffer Tx au registre de décalage, et une interruption est également générée si le bit TXEIE est activé dans le registre CPI_CR1.
J'ai mis en évidence quelques mots dans la traduction pour souligner une caractéristique de l'implémentation de SPI dans les contrôleurs STM. Sur l'ATMega, le drapeau TXE (Tx Vide, Tx est vide et prêt à recevoir des données) n'est activé qu'après l'envoi de tout le byte vers l'extérieur. Ici, ce drapeau est activé après que le byte ait été placé dans le registre de décalage interne. Comme il y est placé avec tous les bits en même temps (en parallèle), les données sont ensuite transmises séquentiellement, TXE est activé avant que le byte ne soit complètement transmis. C'est important, car dans le cas de notre pilote LED, nous devons activer le pin LAT après l'envoi tous des données, c'est-à-dire que le drapeau TXE seul ne sera pas suffisant.
Cela signifie que nous avons besoin d'un autre drapeau. Regardons dans 25.3.7 – « Drapeaux d'état » :

<…>

Drapeau BUSY
Le drapeau BSY est activé et désactivé de manière matérielle (l'écriture ne l'affecte pas). Le drapeau BSY indique l'état de la couche de communication SPI.
Il est réinitialisé :
lorsque la transmission est terminée (à l'exception du mode maître si la transmission est continue)
lorsque le SPI est désactivé
lorsqu'une erreur de mode maître se produit (MODF=1)
Si la transmission n'est pas continue, le drapeau BSY est réinitialisé entre chaque transmission de données
D'accord, cela sera utile. Déterminons où se trouve le buffer Tx. Pour cela, nous lisons « Le registre de données SPI » :

Bits 15:0 DR[15:0] Registre de données
Données reçues ou données à transmettre.
Le registre de données est divisé en deux buffers – un pour l'écriture (buffer de transmission) et un pour la lecture (buffer de réception). L'écriture dans le registre de données écrit dans le buffer Tx, tandis que la lecture du registre de données renverra la valeur contenue dans le buffer Rx.
Et le registre d'état, où l'on trouve les drapeaux TXE et BSY :

Nous écrivons :
#define _SPI_DR 0x0C
#define _SPI_SR 0x08
#define BSY 0x0080
#define TXE 0x0002
void dm_shift16(uint16_t value)
{
_SPI2_(_SPI_DR) = value; //send 2 bytes
while (!(_SPI2_(_SPI_SR) & TXE)); //wait until they're sent
}Puisque nous devons transmettre 16 fois deux bytes, correspondant au nombre de sorties du pilote LED, ce sera quelque chose comme ça :
void sendLEDdata()
{
LAT_low();
uint8_t k = 16;
do
{ k--;
dm_shift16(leds[k]);
} while (k);
while (_SPI2_(_SPI_SR) & BSY); // finir la transmission
LAT_pulse();
}Mais pour l'instant, nous ne savons pas encore activer le pin LAT, donc revenons à I/O.
Assignons les pins
Les registres de l'STM32F1, qui gèrent l'état des broches, sont plutôt atypiques. Il est évident qu'ils sont plus nombreux que ceux de l'Atmega, mais ils diffèrent également des autres puces STM. Section 9.1 Description générale des GPIO :

Chacun des ports d'entrée/sortie généralistes (GPIO) possède deux registres de configuration 32 bits (GPIOx_CRL et GPIOx_CRH), deux registres de données 32 bits (GPIOx_IDR et GPIOx_ODR), un registre de mise à jour/réinitialisation 32 bits (GPIOx_BSRR), un registre de réinitialisation 16 bits (GPIOx_BRR) et un registre de verrouillage 32 bits (GPIOx_LCKR).
Ils sont atypiques, et également assez encombrants, en ce sens que les deux premiers registres sont remplis de manière particulière : les 16 broches du port sont dispersées entre eux selon un format "quatre bits par registre". Autrement dit, les broches de 0 à 7 se trouvent dans le CRL, tandis que les autres se trouvent dans le CRH. Les autres registres, en revanche, parviennent à contenir les bits de toutes les broches du port, souvent en restant à moitié "réservés".
Pour simplifier, commençons par la fin de la liste.
Nous n'aurons pas besoin du registre de verrouillage.
Les registres de mise à jour et de réinitialisation sont assez intéressants car ils se chevauchent partiellement : on peut écrire exclusivement dans le BSRR, où les 16 bits supérieurs réinitialiseront la broche à zéro, tandis que les bits inférieurs la mettront à 1, ou utiliser également le BRR, dont les bits inférieurs ne réinitialisent que la broche. Je préfère la deuxième option. Ces registres sont importants car ils assurent un accès atomique aux broches :

![]()
Mise à jour ou réinitialisation atomique
Il n'est pas nécessaire de désactiver les interruptions lors de la programmation du GPIOx_ODR au niveau des bits : il est possible de modifier un ou plusieurs bits par une seule opération d'écriture atomique sur l'APB2. Cela se fait en écrivant "1" dans le registre de mise à jour/réinitialisation (GPIOx_BSRR ou, uniquement pour la réinitialisation, dans GPIOx_BRR) du bit que l'on souhaite modifier. Les autres bits resteront inchangés.
Les registres de données portent des noms explicites – IDR = Input Registre de direction, registre d'entrée ; ODR = Sortie Registre de direction, registre de sortie. Dans le projet actuel, nous n'en aurons pas besoin.
Enfin, les registres de contrôle. Puisque nous nous intéressons aux broches du second SPI, à savoir PB13, PB14 et PB15, nous regardons directement le CRH :

Et nous voyons qu'il faudra écrire quelque chose dans les bits de 20 à 31.
Nous avons déjà clarifié ce que nous attendons des broches, donc ici je n'utiliserai pas de captures d'écran, je dirai simplement que le MODE définit la direction (entrée, si les deux bits sont à 0) et la vitesse de la broche (nous avons besoin de 50MHz, c'est-à-dire que les deux broches doivent être à « 1 »), et que le CNF définit le mode : « tirage » classique – 00, « alternatif » – 10. Par défaut, comme nous l'avons vu plus haut, toutes les broches ont le troisième bit en bas (CNF0), qui les place en mode. entrée flottante.
Comme je prévois de faire encore quelque chose avec cette puce, j'ai défini par simplicité toutes les valeurs possibles de MODE et CNF pour les registres de contrôle supérieur et inférieur.
Voilà comment cela se présente.
#define CNF0_0 0x00000004
#define CNF0_1 0x00000008
#define CNF1_0 0x00000040
#define CNF1_1 0x00000080
#define CNF2_0 0x00000400
#define CNF2_1 0x00000800
#define CNF3_0 0x00004000
#define CNF3_1 0x00008000
#define CNF4_0 0x00040000
#define CNF4_1 0x00080000
#define CNF5_0 0x00400000
#define CNF5_1 0x00800000
#define CNF6_0 0x04000000
#define CNF6_1 0x08000000
#define CNF7_0 0x40000000
#define CNF7_1 0x80000000
#define CNF8_0 0x00000004
#define CNF8_1 0x00000008
#define CNF9_0 0x00000040
#define CNF9_1 0x00000080
#define CNF10_0 0x00000400
#define CNF10_1 0x00000800
#define CNF11_0 0x00004000
#define CNF11_1 0x00008000
#define CNF12_0 0x00040000
#define CNF12_1 0x00080000
#define CNF13_0 0x00400000
#define CNF13_1 0x00800000
#define CNF14_0 0x04000000
#define CNF14_1 0x08000000
#define CNF15_0 0x40000000
#define CNF15_1 0x80000000
#define MODE0_0 0x00000001
#define MODE0_1 0x00000002
#define MODE1_0 0x00000010
#define MODE1_1 0x00000020
#define MODE2_0 0x00000100
#define MODE2_1 0x00000200
#define MODE3_0 0x00001000
#define MODE3_1 0x00002000
#define MODE4_0 0x00010000
#define MODE4_1 0x00020000
#define MODE5_0 0x00100000
#define MODE5_1 0x00200000
#define MODE6_0 0x01000000
#define MODE6_1 0x02000000
#define MODE7_0 0x10000000
#define MODE7_1 0x20000000
#define MODE8_0 0x00000001
#define MODE8_1 0x00000002
#define MODE9_0 0x00000010
#define MODE9_1 0x00000020
#define MODE10_0 0x00000100
#define MODE10_1 0x00000200
#define MODE11_0 0x00001000
#define MODE11_1 0x00002000
#define MODE12_0 0x00010000
#define MODE12_1 0x00020000
#define MODE13_0 0x00100000
#define MODE13_1 0x00200000
#define MODE14_0 0x01000000
#define MODE14_1 0x02000000
#define MODE15_0 0x10000000
#define MODE15_1 0x20000000Nos broches se trouvent sur le port B (adresse de base – 0x40010C00), code :
#define _PORTB_(mem_offset) (*(volatile uint32_t *)(0x40010C00 + (mem_offset)))
#define _BRR 0x14
#define _BSRR 0x10
#define _CRL 0x00
#define _CRH 0x04
//используем стандартный SPI2: MOSI на B15, CLK на B13
//LAT пусть будет на неиспользуемом MISO – B14
//очищаем дефолтный бит, он нам точно не нужен
_PORTB_ (_CRH) &= ~(CNF15_0 | CNF14_0 | CNF13_0 | CNF12_0);
//альтернативные функции для MOSI и SCK
_PORTB_ (_CRH) |= CNF15_1 | CNF13_1;
//50 МГц, MODE = 11
_PORTB_ (_CRH) |= MODE15_1 | MODE15_0 | MODE14_1 | MODE14_0 | MODE13_1 | MODE13_0;Et, par conséquent, nous pouvons écrire des définitions pour LAT, qui seront manipulées par les registres BRR et BSRR :
/*** LAT pulse – high, then low */
#define LAT_pulse() _PORTB_(_BSRR) = (1<<14); _PORTB_(_BRR) = (1<<14)
#define LAT_low() _PORTB_(_BRR) = (1<<14)(LAT_low est simplement par habitude, ça a toujours été comme ça, laissons-le tel quel.)
Maintenant, tout va bien, mais ça ne fonctionne pas. Parce que c'est un STM32, ici on économise de l'électricité, et donc il faut activer l'horloge de la périphérie nécessaire.
Activation de l'horloge
L'horloge est responsable de la synchronisation, et nous avons déjà vu l'acronyme RCC. Cherchons-le dans la documentation : c'est Reset and Clock Control (Contrôle de réinitialisation et de l'horloge).
Comme mentionné précédemment, heureusement, les gens de STM ont fait le travail le plus difficile en matière de synchronisation pour nous, pour quoi nous les remercions (je donnerai encore une fois le lien vers , pour comprendre à quel point cela peut être complexe). Nous n'avons besoin que des registres qui contrôlent l'activation de l'horloge de la périphérie (Registres d'activation de l'horloge de la périphérie). D'abord, trouvons l'adresse de base de RCC, qui se trouve au tout début de la « Carte mémoire » :
![]()
#define _RCC_(mem_offset) (*(volatile uint32_t *)(0x40021000 + (mem_offset)))Ensuite, soit cliquer sur le lien pour essayer de trouver quelque chose dans le tableau, soit, ce qui est bien mieux, passer en revue les descriptions des registres d'activation dans les sections sur les registres d'activation.. Où nous trouverons RCC_APB1ENR et RCC_APB2ENR :


Et dans ceux-ci, respectivement, les bits qui activent l'horloge pour SPI2, IOPB (Port I/O B) et les fonctions alternatives (AFIO).
#define _APB2ENR 0x18
#define _APB1ENR 0x1C
#define IOPBEN 0x0008
#define SPI2EN 0x4000
#define AFIOEN 0x0001
//включаем тактирование порта B и альт. функций
_RCC_(_APB2ENR) |= IOPBEN | AFIOEN;
//включаем тактирование SPI2
_RCC_(_APB1ENR) |= SPI2EN;Le code final peut être trouvé .
S'il est possible et souhaité de tester, connectons le DM634 comme suit : DAI à PB15, DCK à PB13, LAT à PB14. Alimentons le driver avec 5 volts, n'oublions pas de relier les masses.

STM8 PWM
PWM sur STM8
Lorsque j'ai planifié cet article, j'ai décidé de tester un peu de fonctionnalité d'une puce qui m'était inconnue uniquement à partir de la documentation technique, afin de ne pas être un cordonnier mal chaussé. Le STM8 convenait parfaitement à ce rôle : d'une part, j'avais quelques cartes chinoises avec STM8S103, et d'autre part, il n'est pas très populaire, donc la tentation de fouiller sur Internet pour trouver une solution se heurte à l'absence de ces mêmes solutions.
Il y a aussi une et , le premier décrit le brochage et les adresses des registres, le second – tout le reste. Le STM8 se programme en C dans un environnement de développement peu engageant, .
Horloge et entrée-sortie
Par défaut, le STM8 fonctionne à une fréquence de 2 MHz, ce qui doit être corrigé immédiatement.

Le signal d'horloge HSI (interne haute vitesse)
Le signal d'horloge HSI provient d'un générateur RC interne de 16 MHz avec un diviseur programmable (de 1 à 8). Il est défini dans le registre de division d'horloge (CLK_CKDIVR).
Remarque : au démarrage, la source d'horloge principale choisie est le générateur HSI RC avec un diviseur de 8.
Nous localisons l'adresse du registre dans la documentation technique, la description dans le manuel de référence et voyons que le registre doit être nettoyé :
#define CLK_CKDIVR *(volatile uint8_t *)0x0050C6
CLK_CKDIVR &= ~(0x18);Puisque nous allons lancer le PWM et connecter des LED, nous vérifions le brochage :

La puce est petite, de nombreuses fonctions sont assignées aux mêmes broches. Ce qui est entre crochets – « fonctionnalité alternative », il peut être changé par « octets d'options » (option bytes) – quelque chose comme des fusibles d'Atmega. Il est possible de changer leurs valeurs par programmation, mais ce n'est pas nécessaire, car la nouvelle fonctionnalité n'est activée qu'après redémarrage. Il est plus simple d'utiliser ST Visual Programmer (téléchargeable avec Visual Develop), qui peut changer ces octets. Dans le brochage, on voit que les sorties CH1 et CH2 du premier timer sont cachées entre crochets ; il faut dans STVP définir les bits AFR1 et AFR0, le second déplaçant également la sortie CH1 du deuxième timer de PD4 à PC5.
Ainsi, les LED seront contrôlées par 6 broches : PC6, PC7 et PC3 pour le premier timer, PC5, PD3 et PA3 pour le deuxième.
La configuration des broches d'entrée-sortie sur le STM8 est plus simple et logique que sur le STM32 :
- le registre de direction des données DDR (Data Direction Register) : 1 = sortie ;
- le premier registre de contrôle CR1 en mode sortie définit le mode « push-pull » (1) ou open drain (0) ; comme je connecte les LED aux cathodes de la puce, je laisse des zéros ici ;
- le deuxième registre de contrôle CR2 en mode sortie définit la vitesse d'horloge : 1 = 10 MHz
#define PA_DDR *(volatile uint8_t *)0x005002
#define PA_CR2 *(volatile uint8_t *)0x005004
#define PD_DDR *(volatile uint8_t *)0x005011
#define PD_CR2 *(volatile uint8_t *)0x005013
#define PC_DDR *(volatile uint8_t *)0x00500C
#define PC_CR2 *(volatile uint8_t *)0x00500E
PA_DDR = (1<<3); //output
PA_CR2 |= (1<<3); //fast
PD_DDR = (1<<3); //output
PD_CR2 |= (1<<3); //fast
PC_DDR = ((1<<3) | (1<<5) | (1<<6) | (1<<7)); //output
PC_CR2 |= ((1<<3) | (1<<5) | (1<<6) | (1<<7)); //fastConfiguration PWM
Commençons par définir les termes :
- Fréquence PWM – la fréquence à laquelle le minuteur fonctionne ;
- Auto-reload, AR – valeur auto-rechargeable jusqu'à laquelle le minuteur va compter (période de l'impulsion) ;
- Update Event, UEV – événement se produisant lorsque le minuteur a atteint AR ;
- Cycle de service PWM – rapport cyclique du PWM, souvent appelé « largeur d'impulsion » ;
- Capture/Compare Value – valeur pour capture/comparaison, après laquelle le minuteur va faire quelque chose (dans le cas du PWM – inverse le signal de sortie) ;
- Valeur de préchargement – valeur préchargée. La valeur de comparaison ne peut pas changer tant que le minuteur est en cours, sinon le cycle PWM est rompu. Donc, les nouvelles valeurs transmises sont placées dans un tampon et en sont extraites lorsque le minuteur atteint la fin du décompte et se réinitialise ;
- Modes alignés sur les bords et Modes alignés au centre – alignement sur le bord et au centre, comme les modes ATMEL PWM rapide et PWM à phase correcte.
- OCiREF, Signal de référence de comparaison de sortie – signal de sortie de référence, ce qui est en fait ce qui apparaît sur la broche correspondante en mode PWM.
Comme il est clair dans le schéma de brochage, le PWM est disponible sur deux minuteurs – le premier et le second. Les deux sont 16 bits, le premier a beaucoup de fonctionnalités supplémentaires (notamment, il peut compter à la fois vers le haut et vers le bas). Nous devons que les deux fonctionnent de manière identique, donc j'ai décidé de commencer par le second, de manière à ne pas utiliser accidentellement quelque chose qui n'est pas présent. Un problème est que la description de la fonctionnalité PWM de tous les minuteurs se trouve dans le manuel de référence au chapitre sur le premier minuteur (17.5.7 Mode PWM), donc je dois constamment naviguer dans le document.
Le PWM sur STM8 a un avantage important sur le PWM d'Atmega :

PWM avec alignement sur les bords
Configuration de comptage vers le bas activée
Le comptage vers le bas est actif si le bit DIR dans le registre TIM_CR1 est à zéro
Exemple
L'exemple utilise le premier mode PWM. Le signal de référence PWM OCiREF est maintenu à un niveau élevé tant que TIM1_CNT < TIM1_CCRi. Sinon, il prend un niveau bas. Si la valeur de comparaison dans le registre TIM1_CCRi est supérieure à la valeur d'auto-rechargement (registre TIM1_ARR), le signal OCiREF est maintenu à 1. Si la valeur de comparaison est égale à 0, OCiREF est maintenu à zéro.…
Le minuteur STM8 pendant l' événement de mise à jour vérifie d'abord la valeur de comparaison, et ne génère ensuite le signal de référence. Sur Atmega, le minuteur génère d'abord et compare ensuite, ce qui signifie que lorsque la valeur de comparaison == 0 À la sortie, on obtient une aiguille qu'il faut combattre d'une manière ou d'une autre (par exemple, en inversant logiquement le programme).
Donc, ce que nous voulons faire : un PWM 8 bits (AR == 255), en comptant de bas en haut, avec un alignement sur les frontières. Étant donné que les lampes sont connectées au chip par les cathodes, le PWM doit émettre 0 (LED allumée) jusqu'à la valeur de comparaison et 1 après.
Nous avons déjà lu sur certains mode PWM, donc nous trouvons le registre nécessaire du deuxième timer en cherchant dans le manuel de référence cette phrase (18.6.8 – TIMx_CCMR1) :

110 : Premier mode PWM – lors du comptage de bas en haut, le premier canal est actif tant que TIMx_CNT < TIMx_CCR1. Sinon, le premier canal n'est pas actif. [suite dans le document avec un copier-coller erroné du timer 1]
111 : Deuxième mode PWM – lors du comptage de bas en haut, le premier canal n'est pas actif tant que TIMx_CNT < TIMx_CCR1. Sinon, le premier canal est actif.
Étant donné que les LEDs sont connectées au MC par les cathodes, le deuxième mode nous convient (le premier aussi, mais nous ne le savons pas encore).

Bit 3 OC1PE : Activer la précharge de la sortie 1
0 : Le registre de précharge sur TIMx_CCR1 est désactivé. On peut écrire dans TIMx_CCR1 à tout moment. La nouvelle valeur prend effet immédiatement.
1 : Le registre de précharge sur TIMx_CCR1 est activé. Les opérations de lecture/écriture se réfèrent au registre de précharge. La valeur préchargée de TIMx_CCR1 est chargée dans le registre de shadow à chaque événement de mise à jour.
*Remarque : pour un fonctionnement correct du mode PWM, les registres de précharge doivent être activés. Cela n'est pas nécessaire en mode signal unique (le bit OPM est réglé dans le registre TIMx_CR1).
D'accord, activons tout ce qu'il faut pour les trois canaux du deuxième timer :
#define TIM2_CCMR1 *(volatile uint8_t *)0x005307
#define TIM2_CCMR2 *(volatile uint8_t *)0x005308
#define TIM2_CCMR3 *(volatile uint8_t *)0x005309
#define PWM_MODE2 0x70 //PWM mode 2, 0b01110000
#define OCxPE 0x08 //preload enable
TIM2_CCMR1 = (PWM_MODE2 | OCxPE);
TIM2_CCMR2 = (PWM_MODE2 | OCxPE);
TIM2_CCMR3 = (PWM_MODE2 | OCxPE);AR se compose de deux registres de huit bits, c'est assez simple :
#define TIM2_ARRH *(volatile uint8_t *)0x00530F
#define TIM2_ARRL *(volatile uint8_t *)0x005310
TIM2_ARRH = 0;
TIM2_ARRL = 255;Le deuxième timer ne peut compter que de bas en haut, avec un alignement sur les frontières, il n'y a rien à changer. Réglons le diviseur de fréquence, par exemple, à 256. Pour le deuxième timer, le diviseur est réglé dans le registre TIM2_PSCR et est une puissance de deux :
#define TIM2_PSCR *(volatile uint8_t *)0x00530E
TIM2_PSCR = 8;Il ne reste plus qu'à activer les sorties et le deuxième timer lui-même. Le premier problème se résout avec les registres Capture/Compare Activer: il y en a deux, trois canaux sont dispersés de manière non symétrique. Ici, nous pouvons aussi apprendre qu'il est possible de changer la polarité du signal, c'est-à-dire qu'on aurait en principe pu utiliser le mode PWM 1. Écrivons :
#define TIM2_CCER1 *(volatile uint8_t *)0x00530A
#define TIM2_CCER2 *(volatile uint8_t *)0x00530B
#define CC1E (1<<0) // CCER1
#define CC2E (1<<4) // CCER1
#define CC3E (1<<0) // CCER2
TIM2_CCER1 = (CC1E | CC2E);
TIM2_CCER2 = CC3E;Eh bien, enfin, lançons le timer dans le registre TIMx_CR1 :

#define TIM2_CR1 *(volatile uint8_t *)0x005300
TIM2_CR1 |= 1;Écrivons une simple analogue d'AnalogWrite(), qui transmettra au timer les valeurs réelles à comparer. Les registres s'appellent prévisiblement registres Capture/Compare, deux pour chaque canal : les 8 bits inférieurs dans TIM2_CCRxL et les bits supérieurs dans TIM2_CCRxH. Puisque nous avons configuré un PWM 8 bits, il suffit d'écrire uniquement les bits inférieurs :
#define TIM2_CCR1L *(volatile uint8_t *)0x005312
#define TIM2_CCR2L *(volatile uint8_t *)0x005314
#define TIM2_CCR3L *(volatile uint8_t *)0x005316
void setRGBled(uint8_t r, uint8_t g, uint8_t b)
{
TIM2_CCR1L = r;
TIM2_CCR2L = g;
TIM2_CCR3L = b;
}L'observateur attentif remarquera que nous avons un PWM légèrement défectueux, incapable de délivrer un remplissage de 100 % (à la valeur maximale de 255, le signal s'inverse pendant un cycle de timer). Pour les LED, cela n'a pas d'importance, et le lecteur observateur devinera déjà comment corriger cela.
Le PWM sur le deuxième timer fonctionne, passons au premier.
Le premier timer possède exactement les mêmes bits dans les mêmes registres (simplement, les bits 'réservés' du second timer sont ici utilisés activement pour différentes fonctionnalités avancées). Il suffit donc de trouver les adresses de ces mêmes registres dans le datasheet et de copier le code. Et de changer la valeur du diviseur de fréquence, car le premier timer veut obtenir une valeur exacte de 16 bits dans deux registres. Prescaler High et Faible. Nous faisons tout cela et… le premier timer ne fonctionne pas. D'où vient le problème ?
Nous pouvons résoudre le problème uniquement en examinant toute la section sur les registres de contrôle du timer 1, où nous chercherons celui qui n'est pas présent dans le timer 2. On le trouvera 17.7.30 Break register (TIM1_BKR), où il y a ce bit :
![]()
Activer la sortie principale
#define TIM1_BKR *(volatile uint8_t *)0x00526D
TIM1_BKR = (1<<7);Voilà, tout est réglé, le code .

STM8 Multiplex
Multiplexage sur STM8
Le troisième mini-projet consiste à connecter huit LED RGB au deuxième timer en mode PWM et à les faire afficher différentes couleurs. À la base se trouve le concept de multiplexage LED, qui consiste à allumer et éteindre les LEDs très rapidement, donnant l'impression qu'elles sont allumées en permanence (persistence of vision, l'inertie de la perception visuelle). J'ai déjà réalisé .
L'algorithme fonctionne comme suit :
- nous avons connecté l'anode de la première LED RGB ;
- nous l'avons allumée en envoyant les signaux nécessaires aux cathodes ;
- nous avons attendu la fin du cycle PWM ;
- nous avons connecté l'anode de la seconde LED RGB ;
- nous l'avons allumée…
Et ainsi de suite. Bien sûr, pour un fonctionnement harmonieux, il est nécessaire que la connexion de l'anode et l'allumage de la LED se fassent simultanément. Ou presque. Dans tous les cas, nous devons écrire un code qui fournira des valeurs sur trois canaux du deuxième timer, en les changeant lors de l'atteinte de l'UEV et en changeant simultanément la LED RGB active à ce moment.
Étant donné que la commutation LED s'effectue automatiquement, il est nécessaire de créer une « mémoire vidéo » à partir de laquelle le gestionnaire d'interruptions obtiendra les données. C'est un simple tableau :
uint8_t colors[8][3];Pour changer la couleur d'une LED particulière, il suffira d'écrire les valeurs nécessaires dans ce tableau. La variable chargée de l'index de la LED active sera
uint8_t cnt;Démux
Pour un multiplexage correct, nous aurons besoin, étrangement, d'un démultiplexeur CD74HC238. Un démultiplexeur est une puce qui réalise matériellement l'opérateur <<. À travers trois broches d'entrée (bits 0, 1 et 2), nous lui fournissons un nombre binaire de trois bits X, et en réponse, il active la sortie numéro (1<<X). Les autres entrées de la puce sont utilisées pour le dimensionnement de l'ensemble de la construction. Nous avons besoin de cette puce non seulement pour réduire le nombre de broches occupées sur le microcontrôleur, mais aussi pour la sécurité – afin de ne pas allumer accidentellement plus de LED que possible et de ne pas brûler le microcontrôleur. La puce coûte presque rien, il vaut donc la peine de la garder dans votre trousse à outils.
Le CD74HC238 sera responsable de l'alimentation en tension de l'anode de la LED appropriée. Dans un multiplexage complet, il fournirait la tension sur la colonne via un P-MOSFET, mais dans cette démo, on peut le faire directement, car il tire 20 mA, selon absolute maximum ratings la fiche technique. nous aurons besoin du schéma de câblage et de cette aide-mémoire :

H = niveau de tension élevé, L = niveau de tension bas, X – peu importe.
Nous connectons E2 et E1 à la terre, E3, A0, A1 et A3 aux broches PD5, PC3, PC4 et PC5 du STM8. Étant donné que le tableau ci-dessus contient à la fois des niveaux bas et élevés, nous configurons ces broches comme des sorties push-pull.
PWM
Le PWM sur le second minuteur est configuré de la même manière que dans l'histoire précédente, avec deux différences :
Tout d'abord, nous devons activer l'interruption sur Update Event (UEV), qui appellera la fonction basculant la LED active. Cela se fait en modifiant le bit Update Interrupt Enable dans le registre avec un nom évocateur.

Registre d'activation des interruptions
#define TIM2_IER *(volatile uint8_t *)0x005303
//enable interrupt
TIM2_IER = 1;La deuxième différence concerne un phénomène de multiplexage appelé ghosting – la lueur parasite des diodes. Dans notre cas, cela peut se produire parce que le timer, ayant déclenché une interruption sur UEV, continue de tic-tac, et le gestionnaire d'interruption n'a pas le temps de basculer le LED avant que le timer commence déjà à écrire quelque chose dans les sorties. Pour y remédier, il faudra inverser la logique (0 = luminosité maximale, 255 = rien ne s’allume) et éviter les valeurs extrêmes du rapport cyclique. C'est-à-dire s'assurer qu'après UEV, les LED s'éteignent entièrement pendant un cycle PWM.
Changer la polarité :
//set polarity
TIM2_CCER1 |= (CC1P | CC2P);
TIM2_CCER2 |= CC3P;Évitons de régler r, g et b à 255 et n'oublions pas de les inverser lors de leur utilisation.
Interruptions
Le principe de l'interruption est que dans certaines circonstances, la puce cesse d'exécuter le programme principal et appelle une fonction externe. Les interruptions surviennent à cause d'événements externes ou internes, y compris celles du timer.
Lors de notre première création de projet dans ST Visual Develop, en plus de main.c , nous avons reçu une fenêtre avec un mystérieux fichier stm8_interrupt_vector.c, automatiquement inclus dans le projet. Dans ce fichier, une fonction est liée à chaque interruption. NonHandledInterrupt. Nous devons lier notre propre fonction à l'interruption souhaitée.
Dans le datasheet, il y a un tableau des vecteurs d'interruption, où nous trouvons les suivants :

13 TIM2 mise à jour/dépassement
14 TIM2 capture/comparaison
Nous devons changer le LED lors de l'UEV, donc nous avons besoin de l'interruption n°13.
Ainsi, premièrement, dans le fichier stm8_interrupt_vector.c nous renommerons la fonction par défaut répondant à l'interruption n°13 (IRQ13) par la nôtre :
{0x82, TIM2_Overflow}, /* irq13 */Deuxièmement, nous devrons créer un fichier main.h de ce contenu :
#ifndef __MAIN_H
#define __MAIN_H
@far @interrupt void TIM2_Overflow (void);
#endifEt enfin, écrire cette fonction dans notre main.c:
@far @interrupt void TIM2_Overflow (void)
{
PD_ODR &= ~(1<<5); // désactiver le démultiplexeur
PC_ODR = (cnt<<3); // écrire la nouvelle valeur dans le démultiplexeur
PD_ODR |= (1<<5); // activer le démultiplexeur
TIM2_SR1 = 0; // réinitialiser le drapeau d'interruption de mise à jour
cnt++;
cnt &= 7; // déplacer le compteur LED
TIM2_CCR1L = ~colors[cnt][0]; // transmettre les valeurs inversées dans le tampon
TIM2_CCR2L = ~colors[cnt][1]; // pour le prochain cycle PWM
TIM2_CCR3L = ~colors[cnt][2]; //
return;
}Il ne reste plus qu'à activer les interruptions. Cela se fait avec la commande d'assembleur rim – il faudra la chercher dans :
//enable interrupts
_asm("rim");Une autre commande d'assembleur – sim – désactive les interruptions. Elles doivent être désactivées le temps d'écrire de nouvelles valeurs dans la "mémoire vidéo" pour que l'interruption déclenchée à un moment inopportun ne corrompe pas le tableau.
Tout le code est .

Si cet article peut être utile à quelqu'un, cela signifie que je ne l'ai pas écrit en vain. Je serais ravi de lire vos commentaires et vos remarques, et je ferai de mon mieux pour répondre à toutes vos questions.
Source : habr.com
