Leemos hojas de datos 2: SPI en STM32; PWM, temporizadores e interrupciones en STM8

Leemos hojas de datos 2: SPI en STM32; PWM, temporizadores e interrupciones en STM8

En parte anterior He intentado explicar a los entusiastas de la electrónica, que han crecido desde sus días con Arduino, cómo y por qué deberían leer las hojas de datos y otra documentación sobre microcontroladores. El texto resultó ser extenso, así que prometí mostrar ejemplos prácticos en un artículo separado. Bueno, como dice el dicho, prometí y ahora tengo que cumplir.

Hoy mostraré cómo resolver tareas bastante sencillas, pero necesarias para muchos proyectos en controladores STM32 (Blue Pill) y STM8 utilizando las hojas de datos. Todos los proyectos de demostración están dedicados a mis adorables LED, que encenderemos en grandes cantidades, por lo que tendremos que involucrar toda una variedad de periféricos interesantes.

El texto volvió a ser enorme, así que para conveniencia hago un índice:

STM32 Blue Pill: 16 LEDs con controlador DM634
STM8: Configurando seis pines PWM
STM8: 8 LEDs RGB en tres pines, interrupciones

Descargo de responsabilidad: no soy ingeniero, no pretendo tener un conocimiento profundo de la electrónica, este artículo está destinado a aficionados como yo. En realidad, consideré como audiencia objetivo a mí mismo de hace dos años. Si alguien me hubiese dicho entonces que no es aterrador leer hojas de datos de chips desconocidos, no habría perdido mucho tiempo buscando fragmentos de código en Internet ni inventando soluciones improvisadas con tijeras y cinta adhesiva.

En el centro de este artículo están las hojas de datos, no los proyectos, así que el código puede no estar muy pulido y a menudo es una solución improvisada. Los proyectos en sí son muy sencillos, aunque son adecuados para un primer encuentro con un nuevo chip.

Espero que mi artículo ayude a alguien en una etapa similar de inmersión en el hobby.

STM32

16 LEDs con DM634 y SPI

Un pequeño proyecto utilizando Blue Pill (STM32F103C8T6) y el controlador de LED DM634. Con la ayuda de las hojas de datos, entenderemos el controlador, los puertos IO de STM y configuraremos SPI.

DM634

Chip taiwanés con 16 salidas PWM de 16 bits, se pueden conectar en cadenas. El modelo de 12 bits más pequeño es conocido por un proyecto local. Lightpack. En su momento, al elegir entre el DM63x y el conocido TLC5940, me decidí por el DM por varias razones: 1) el TLC en AliExpress es definitivamente una falsificación, mientras que este no; 2) el DM tiene un PWM autónomo con su propio generador de frecuencia; 3) se podía comprar a buen precio en Moscú, en lugar de esperar un paquete de Ali. Y, por supuesto, me interesaba aprender a controlar el chip por mí mismo, en lugar de utilizar una biblioteca lista. Actualmente, los chips están principalmente disponibles en un paquete SSOP24, que no es difícil de soldar a un adaptador.

Dado que el fabricante es taiwanés, hoja de datos el chip está escrito en un inglés chino, lo que significa que será divertido. Primero, observamos el pinout (Conexión de pines), para entender a qué pin conectar qué, y la descripción de los pines (Descripción de pines). 16 salidas:

Leemos hojas de datos 2: SPI en STM32; PWM, temporizadores e interrupciones en STM8
Fuentes de corriente constante entrante (drenaje abierto)

Drenar / Salida de drenaje abierto – drenaje; fuente de corriente entrante; salida, en estado activo conectada a tierra, – los LEDs se conectan al controlador por el cátodo. Eléctricamente, esto no es ningún 'drenaje abierto' (drenaje abierto), pero en las hojas de datos esta designación para las salidas en modo de drenaje se encuentra con frecuencia.

Leemos hojas de datos 2: SPI en STM32; PWM, temporizadores e interrupciones en STM8
Resistencias externas entre REXT y GND para establecer el valor de la corriente de salida

Entre el pin REXT y tierra se coloca una resistencia de referencia que controla la resistencia interna de las salidas, ver gráfico en la página 9 de la hoja de datos. En el DM634, esta resistencia también se puede controlar por software, ajustando el brillo general (brillo global); en este artículo no entraré en detalles, solo pondré aquí una resistencia de 2.2 – 3 kΩ.

Para entender cómo controlar el chip, veamos la descripción de la interfaz del dispositivo:

Leemos hojas de datos 2: SPI en STM32; PWM, temporizadores e interrupciones en STM8

Ah, aquí está, el inglés chino en toda su gloria. Traducir esto es problemático, se puede entender si se desea, pero hay otra forma: echar un vistazo a cómo se describe la conexión en la hoja de datos del funcionalmente similar TLC5940:

Leemos hojas de datos 2: SPI en STM32; PWM, temporizadores e interrupciones en STM8
… Para ingresar datos al dispositivo solo se requieren tres pines. El flanco ascendente de la señal SCLK desplaza los datos desde el pin SIN al registro interno. Una vez que se han cargado todos los datos, una corta señal alta XLAT fija los datos transferidos secuencialmente en los registros internos. Los registros internos son bisagras activadas por el nivel de señal XLAT. Todos los datos se transmiten comenzando por el bit más significativo.

Latch – bisagra/cierre.
Flanco ascendente – flanco ascendente del pulso
MSB primero – con el bit más significativo (extremo izquierdo) hacia adelante.
para sincronizar datos – transferir datos secuencialmente (bit a bit).

La palabra latch se encuentra a menudo en la documentación de los chips y se traduce de diversas maneras, por lo que para su comprensión permítanme

ofrecer una pequeña aclaraciónUn controlador LED – en esencia un registro de desplazamiento. "Desplazamiento" (shift) en el nombre implica el movimiento de datos a nivel de bits dentro del dispositivo: cada nuevo bit introducido avanza toda la cadena hacia adelante. Dado que durante el desplazamiento nadie quiere observar parpadeos caóticos de los LEDs, el proceso ocurre en registros intermedios, separados de los registros de trabajo por una compuerta (latch) – es una especie de vestíbulo, donde los bits se alinean en la secuencia adecuada. Cuando todo está listo, la compuerta se abre y los bits comienzan a trabajar, reemplazando el lote anterior. La palabra latch en la documentación de los microchips casi siempre implica tal compuerta, independientemente de las combinaciones en las que se utilice.

Así, la transmisión de datos en el DM634 se realiza de la siguiente manera: configuramos la entrada DAI al valor del bit más significativo del LED más lejano, hacemos un pulso en DCK; configuramos la entrada DAI al valor del siguiente bit, hacemos un pulso en DCK; y así sucesivamente, hasta que todos los bits se transmiten (clocked in), después de lo cual hacemos un pulso en LAT. Esto se puede hacer manualmente (bit-bang), pero es mejor utilizar una interfaz diseñada específicamente para esto, ya que está disponible en nuestro STM32 en dos instancias.

La pastilla azul STM32F103

Introducción: los controladores STM32 son considerablemente más complejos que el Atmega328, lo que puede asustar. Sin embargo, por razones de ahorro de energía, al principio casi toda la periferia está desactivada y la frecuencia de reloj es de 8 MHz proveniente de la fuente interna. Afortunadamente, los programadores de STM escribieron un código que lleva el chip a los 72 MHz "teóricos", y los autores de todos los IDE que conozco lo incluyeron en el procedimiento de inicialización, por lo que no necesitamos ajustar la frecuencia (pero podemos hacerlo si realmente queremos). Sin embargo, tendremos que activar la periferia.

Documentación: en la Blue Pill está instalado el popular chip STM32F103C8T6, para el cual hay dos documentos útiles:

En la hoja de datos, podemos estar interesados en:

  • Mapas de pines – disposiciones de los chips – en caso de que decidamos hacer nuestras propias placas;
  • Mapa de memoria: un mapa de memoria para un chip específico. En el Manual de referencia hay un mapa para toda la línea, en el que se mencionan registros que no están presentes en el nuestro.
  • Tabla de definiciones de pines: enumeración de las funciones principales y alternativas de los pines; para la 'tableta azul' se pueden encontrar en Internet imágenes más convenientes con la lista de pines y sus funciones. Así que busquemos inmediatamente el pinout de la Blue Pill y tengamos a mano una imagen como esta:

Leemos hojas de datos 2: SPI en STM32; PWM, temporizadores e interrupciones en STM8
Nota: en la imagen de Internet había un error, señalado en los comentarios, por lo que agradecemos. La imagen ha sido reemplazada, pero esto es una lección: es mejor verificar la información que no se encuentra en las hojas de datos.

Eliminamos la hoja de datos, abrimos el Manual de referencia, a partir de ahora solo lo utilizaremos.
Orden de acciones: nos familiarizamos con la entrada/salida estándar, configuramos SPI, habilitamos los periféricos necesarios.

Entrada/salida

En el Atmega328, la entrada/salida se implementa de forma muy sencilla, lo que puede confundir por la abundancia de opciones del STM32. Ahora solo necesitamos salidas, pero incluso estas tienen cuatro variantes:

Leemos hojas de datos 2: SPI en STM32; PWM, temporizadores e interrupciones en STM8
salida de colector abierto, salida 'push-pull', alternativa 'push-pull', colector abierto alternativo

'Push-pull' (push-pull) es la salida habitual de Arduino, el pin puede aceptar valores ya sea HIGH o LOW. Sin embargo, surgen problemas con el 'colector abierto' dificultades, aunque en realidad todo es sencillo:

Leemos hojas de datos 2: SPI en STM32; PWM, temporizadores e interrupciones en STM8
Leemos hojas de datos 2: SPI en STM32; PWM, temporizadores e interrupciones en STM8
Configuración de salida / cuando el puerto está designado como salida: / buffer de salida habilitado: / – modo colector abierto: '0' en el registro de salida activa el N-MOS, '1' en el registro de salida deja el puerto en modo Hi-Z (el P-MOS no se activa) / – modo 'push-pull': '0' en el registro de salida activa el N-MOS, '1' en el registro de salida activa el P-MOS.

La única diferencia entre el colector abierto (drenaje abierto) y el 'push-pull' (push-pull) es que en el primero el pin no puede aceptar un estado HIGH: al escribir uno en el registro de salida, pasa al modo de alta impedancia (high impedance, Hi-Z). Al escribir un cero, el pin se comporta igual en ambos modos, tanto lógicamente como eléctricamente.

En el modo de salida normal, el pin simplemente transmite el contenido del registro de salida. En 'alternativo', está controlado por el periférico correspondiente (ver 9.1.4):

Leemos hojas de datos 2: SPI en STM32; PWM, temporizadores e interrupciones en STM8
Si el bit del puerto está configurado como salida de función alternativa, el registro de salida se desactiva y el pin se conecta a la señal de salida del periférico.

La funcionalidad alternativa de cada pin se describe en Definiciones de pines La hoja de datos está disponible en la imagen descargada. La nota en la hoja de datos responde a la pregunta de qué hacer si un pin tiene varias funciones alternativas.

Leemos hojas de datos 2: SPI en STM32; PWM, temporizadores e interrupciones en STM8
Si varios bloques periféricos utilizan el mismo pin, para evitar un conflicto entre las funciones alternativas, solo se debe utilizar un bloque periférico a la vez, alternando con el bit de activación del temporizador periférico (en el registro correspondiente de RCC).

Finalmente, los pines en modo de salida también tienen velocidad de reloj. Esta es otra característica de ahorro de energía; en nuestro caso, simplemente la ajustamos al máximo y nos olvidamos.

Entonces: estamos utilizando SPI, lo que significa que dos pines (uno para datos y otro para la señal de reloj) deben ser 'función alternativa pull-push', y otro (LAT) - 'pull-push normal'. Pero antes de asignarlos, entenderemos SPI.

SPI

Una pequeña lección adicional

SPI o Interfaz Periférica Serial (Serial Peripheral Interface) es una interfaz simple y muy efectiva para la comunicación de microcontroladores con otros microcontroladores y el mundo exterior. Su principio de funcionamiento ya se ha descrito anteriormente, donde se menciona el controlador LED chino (en el manual de referencia, ver sección 25). SPI puede operar en modo maestro ('host') y esclavo ('slave'). SPI tiene cuatro canales básicos, de los cuales no todos pueden estar activos:

  • MOSI, Master Output / Slave Input: este pin envía datos en modo maestro y recibe datos en modo esclavo.
  • MISO, Master Input / Slave Output: al contrario, en modo maestro recibe y en modo esclavo envía.
  • SCK, Reloj Serial: establece la frecuencia de transmisión de datos en maestro o recibe la señal de reloj en esclavo. En esencia, marca los bits.
  • SS, Selección de Esclavo: este canal permite que el esclavo sepa que se le está solicitando algo. En STM32 se llama NSS, donde N = negativo, es decir, el controlador se convierte en esclavo si la tierra está en este canal. Combina bien con el modo de salida de drenaje abierto, pero esa es otra historia.

Como todo lo demás, SPI en STM32 es rico en funcionalidades, lo que complica un poco su comprensión. Por ejemplo, no solo puede operar como SPI, sino también como interfaz I2S, y en la documentación, sus descripciones están mezcladas, por lo que es necesario filtrar lo innecesario a tiempo. Nuestra tarea es muy simple: solo necesitamos enviar datos, utilizando solo MOSI y SCK. Vamos a la sección 25.3.4 (comunicación half-duplex), donde encontramos 1 reloj y 1 línea de datos unidireccional (1 señal de reloj y 1 flujo de datos unidireccional):

Leemos hojas de datos 2: SPI en STM32; PWM, temporizadores e interrupciones en STM8
En este modo, la aplicación utiliza SPI ya sea en modo de solo transmisión o solo recepción. El modo de solo transmisión es similar al modo dúplex: los datos se transmiten a través del pin de transmisión (MOSI en modo maestro o MISO en modo esclavo), y el pin receptor (MISO o MOSI respectivamente) puede usarse como un pin de entrada/salida normal. En este caso, la aplicación solo necesita ignorar el búfer Rx (si se lee, no habrá datos transmitidos en él).

Perfecto, el pin MISO está libre, conectaremos la señal LAT. Vamos a solucionar el Slave Select, que en STM32 se puede gestionar por software, lo cual es sumamente conveniente. Leamos el párrafo homónimo de la sección 25.3.1 Descripción General de SPI:

Leemos hojas de datos 2: SPI en STM32; PWM, temporizadores e interrupciones en STM8
Control de NSS por software (SSM = 1) / La información sobre la selección del esclavo se encuentra en el bit SSI del registro SPI_CR1. El pin NSS externo permanece libre para otros usos de la aplicación.

Es hora de escribir en los registros. He decidido usar SPI2, busquemos en la hoja de datos su dirección base – en la sección 3.3 Mapa de Memoria:

Leemos hojas de datos 2: SPI en STM32; PWM, temporizadores e interrupciones en STM8

Y comenzamos:

#define _SPI2_(mem_offset) (*(volatile uint32_t *)(0x40003800 + (mem_offset)))

Abrimos la sección 25.3.3 con el elocuente título "Configuración del SPI en modo maestro":

Leemos hojas de datos 2: SPI en STM32; PWM, temporizadores e interrupciones en STM8

1. Establezca la frecuencia de reloj de la interfaz serial con los bits BR[2:0] en el registro SPI_CR1.

Los registros se agrupan en la sección homónima del manual de referencia. El desplazamiento de dirección (Address offset) en CR1 es 0x00, por defecto todos los bits están reiniciados (Reset value 0x0000):

Leemos hojas de datos 2: SPI en STM32; PWM, temporizadores e interrupciones en STM8

Los bits BR establecen el divisor de la frecuencia de reloj del controlador, determinando así la frecuencia a la que operará el SPI. La frecuencia de STM32 será de 72 MHz, el controlador LED, según su hoja de datos, opera a una frecuencia de hasta 25 MHz, por lo que hay que dividir entre cuatro (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/4

2. Establezca los bits CPOL y CPHA para definir las relaciones entre la transmisión de datos y el cronometraje de la interfaz serial (ver el esquema en la pág. 240).

Dado que aquí estamos leyendo la hoja de datos y no analizando esquemas, mejor estudiemos la descripción de texto sobre los bits CPOL y CPHA en la pág. 704 (Descripción General de SPI):

Leemos hojas de datos 2: SPI en STM32; PWM, temporizadores e interrupciones en STM8
Fase y polaridad de la señal de reloj.
A través de los bits CPOL y CPHA del registro SPI_CR1 se pueden programar cuatro variantes de relaciones de temporización. El bit CPOL (polaridad de la señal de reloj) controla el estado de la señal de reloj cuando no se transmiten datos. Este bit gestiona los modos maestro y esclavo. Si CPOL está reiniciado, el pin SCK en modo reposo está en bajo. Si el bit CPOL está establecido, el pin SCK en modo reposo está en alto.
Si se establece el bit CPHA (fase del ciclo de reloj), el flanco del segundo borde de la señal SCK actúa como el muestreador del bit más significativo (descendente si CPOL está en bajo, o ascendente si CPOL está en alto). Los datos se fijan en el segundo cambio del ciclo de reloj. Si el bit CPHA está en bajo, el flanco del primer borde de la señal SCK actúa como el muestreador del bit más significativo (descendente si CPOL está en alto, o ascendente si CPOL está en bajo). Los datos se fijan en el primer cambio del ciclo de reloj.

Al sumergirnos en este conocimiento, llegamos a la conclusión de que ambos bits deben permanecer en cero, ya que necesitamos que la señal SCK se mantenga baja cuando no está en uso, y los datos se transmitan en el flanco ascendente del pulso (vea Rising Edge en la hoja de datos DM634).

Por cierto, aquí nos encontramos con una peculiaridad del léxico en las hojas de datos de ST: en ellas la frase «resetear un bit» se escribe to reset a bit, y no to clear a bit, como, por ejemplo, en la Atmega.

3. Establezca el bit DFF para definir el formato de 8 bits o 16 bits del bloque de datos

Elegí específicamente el DM634 de 16 bits para no complicarme con la transmisión de datos PWM de 12 bits, como en el DM633. DFF tiene sentido establecerlo en uno:

#define DFF         0x0800

_SPI2_ (_SPI_CR1) |= DFF; // 16-bit mode

4. Configure el bit LSBFIRST en el registro SPI_CR1 para determinar el formato del bloque

LSBFIRST, como su nombre indica, configura la transmisión con el bit menos significativo primero. Pero el DM634 quiere recibir datos comenzando con el bit más significativo. Por lo tanto, lo dejamos en cero.

5. En modo hardware, si se requiere entrada desde el pin NSS, aplique una señal alta al pin NSS durante toda la secuencia de transmisión de bytes. En modo software, configure los bits SSM y SSI en el registro SPI_CR1. Si el pin NSS debe funcionar como salida, solo se debe establecer el bit SSOE.

Establecemos SSM y SSI para olvidarnos del modo hardware de NSS:

#define SSI         0x0100
#define SSM         0x0200

_SPI2_ (_SPI_CR1) |= SSM | SSI; //enable software control of SS, SS high

6. Los bits MSTR y SPE deben estar establecidos (siguen establecidos solo si se aplica una señal alta a NSS)

De hecho, con estos bits designamos nuestro SPI como maestro y lo activamos:

#define MSTR        0x0004
#define SPE         0x0040

_SPI2_ (_SPI_CR1) |= MSTR; //SPI master
//когда все готово, включаем SPI
_SPI2_ (_SPI_CR1) |= SPE;

SPI configurado, ahora escribamos funciones que envíen bytes al controlador. Continuemos leyendo 25.3.3 «Configuración de SPI en modo maestro»:

Leemos hojas de datos 2: SPI en STM32; PWM, temporizadores e interrupciones en STM8
Orden de transmisión de datos
La transmisión comienza cuando se escribe un byte en el buffer Tx.
El byte de datos se carga en el registro de desplazamiento en modo paralelo (desde el bus interno) durante la transmisión del primer bit, después se transmite en modo secuencial. secuencial en modo pin MOSI, con el primer o último bit adelante según la configuración del bit LSBFIRST en el registro CPI_CR1. El flag TXE se establece después de la transmisión de datos desde el búfer Tx al registro de desplazamiento, y también se genera una interrupción si se establece el bit TXEIE en el registro CPI_CR1.

He destacado algunas palabras en la traducción para señalar una característica de la implementación SPI en los controladores STM. En el Atmega, el flag TXE (Tx Empty, Tx está vacío y listo para recibir datos) solo se establece después de que todo el byte ha sido enviado hacia afuera. Pero aquí este flag se establece después de que el byte ha sido colocado en el registro de desplazamiento interno. Dado que se inserta allí con todos los bits al mismo tiempo (paralelamente), y luego los datos se transmiten secuencialmente, TXE se establece antes de que el byte se envíe completamente. Esto es importante, ya que en el caso de nuestro controlador de LED, necesitamos activar el pin LAT después de enviar todas los datos, es decir, solo el flag TXE no será suficiente.

Y eso significa que necesitamos algún otro flag. Consultemos en 25.3.7 – «Flags de estado»:

Leemos hojas de datos 2: SPI en STM32; PWM, temporizadores e interrupciones en STM8
<…>
Leemos hojas de datos 2: SPI en STM32; PWM, temporizadores e interrupciones en STM8
El flag BUSY
El flag BSY se establece y se restablece de forma hardware (escribir en él no tiene efecto). El flag BSY muestra el estado de la capa de comunicación SPI.
Se restablece:
cuando la transmisión se ha completado (excepto en modo maestro, si la transmisión es continua)
cuando SPI está desactivado
cuando ocurre un error en modo maestro (MODF=1)
Si la transmisión no es continua, el flag BSY se restablece entre cada transmisión de datos

Okay, esto será útil. Determinamos dónde está el búfer Tx. Para ello, leemos el «Registro de datos SPI»:

Leemos hojas de datos 2: SPI en STM32; PWM, temporizadores e interrupciones en STM8
Bits 15:0 DR[15:0] Registro de datos
Datos recibidos o datos para transmitir.
El registro de datos está dividido en dos búferes: uno para escribir (búfer de transmisión) y otro para leer (búfer de recepción). La escritura en el registro de datos escribe en el búfer Tx, y la lectura del registro de datos devolverá el valor contenido en el búfer Rx.

Y el registro de estados, donde se encuentran los flags TXE y BSY:

Leemos hojas de datos 2: SPI en STM32; PWM, temporizadores e interrupciones en STM8

Escribimos:

#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
}

Pero como necesitamos transmitir 16 veces dos bytes, por el número de salidas del controlador de LED, así es como será:

void sendLEDdata()
{
    LAT_low();
    uint8_t k = 16;
    do
    {   k--;
        dm_shift16(leds[k]);
    } while (k);

    while (_SPI2_(_SPI_SR) & BSY); // terminar transmisión

    LAT_pulse();
}

Pero aún no sabemos cómo activar el pin LAT, así que volvamos a I/O.

Asignamos los pines

En el STM32F1, los registros que corresponden al estado de los pines son bastante inusuales. Es obvio que hay más que en el Atmega, pero también difieren de otros chips STM. Sección 9.1 Descripción general de GPIO:

Leemos hojas de datos 2: SPI en STM32; PWM, temporizadores e interrupciones en STM8
Cada uno de los puertos de entrada/salida de propósito general (GPIO) tiene dos registros de configuración de 32 bits (GPIOx_CRL y GPIOx_CRH), dos registros de datos de 32 bits (GPIOx_IDR y GPIOx_ODR), un registro de establecimiento/restablecimiento de 32 bits (GPIOx_BSRR), un registro de restablecimiento de 16 bits (GPIOx_BRR) y un registro de bloqueo de 32 bits (GPIOx_LCKR).

Son inusuales y también bastante incómodos, aquí los primeros dos registros, porque 16 pines del puerto están distribuidos entre ellos en un formato de 'cuatro bits por hermano'. Es decir, los pines de cero a siete están en CRL, y los demás en CRH. Sin embargo, los demás registros acomodan exitosamente los bits de todos los pines del puerto, a menudo quedando a medio 'reservados'.

Para simplificar, comenzaremos desde el final de la lista.

No necesitaremos el registro de bloqueo.

Los registros de establecimiento y restablecimiento son bastante curiosos en el sentido de que se duplican parcialmente: se puede escribir todo solo en BSRR, donde los 16 bits superiores restablecerán el pin a cero, y los inferiores lo establecerán en 1, o también se puede usar BRR, cuyos 16 bits inferiores solo restablecen el pin. Prefiero la segunda opción. Estos registros son importantes porque proporcionan acceso atómico a los pines:

Leemos hojas de datos 2: SPI en STM32; PWM, temporizadores e interrupciones en STM8
Leemos hojas de datos 2: SPI en STM32; PWM, temporizadores e interrupciones en STM8
Establecimiento o restablecimiento atómico
No es necesario desactivar las interrupciones al programar GPIOx_ODR a nivel de bit: se pueden modificar uno o varios bits en una operación atómica de escritura APB2. Esto se logra escribiendo '1' en el registro de establecimiento/restablecimiento (GPIOx_BSRR o, solo para restablecimiento, en GPIOx_BRR) del bit que se desea cambiar. Los demás bits permanecerán sin cambios.

Los registros de datos tienen nombres bastante explícitos: IDR = Entrada Registro de Dirección, registro de entrada; ODR = Salida Registro de Dirección, registro de salida. En el proyecto actual no los necesitaremos.

Y, finalmente, los registros de control. Dado que nos interesan los pines del segundo SPI, específicamente PB13, PB14 y PB15, revisamos inmediatamente CRH:

Leemos hojas de datos 2: SPI en STM32; PWM, temporizadores e interrupciones en STM8

Y vemos que será necesario escribir algo en los bits del 20 al 31.

Ya hemos discutido anteriormente lo que queremos de los pines, así que aquí me abstendré de incluir una captura de pantalla, simplemente diré que el MODE establece la dirección (entrada, si ambos bits están en 0) y la velocidad del pin (necesitamos 50MHz, es decir, ambos pines en «1»), y CNF establece el modo: normal «tirar y empujar» – 00, «alternativo» – 10. Por defecto, como hemos visto arriba, todos los pines tienen un tercer bit de abajo hacia arriba (CNF0), que los establece en modo entrada flotante.

Dado que planeo hacer algo más con este chip, he definido por simplicidad todos los posibles valores de MODE y CNF para los registros de control inferior y superior.

Así es como va

#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 0x20000000

Nuestros pines están en el puerto B (dirección base – 0x40010C00), código:

#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;

Y, por lo tanto, se pueden escribir defines para LAT, que se controlará mediante los registros BRR y 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 simplemente por inercia, siempre ha sido así, que se quede)

Ahora todo está bien, solo que no funciona. Porque es STM32, aquí se ahorra energía, así que hay que habilitar la señalización de la periferia necesaria.

Habilitamos la señalización

La señalización está controlada por los relojes, también conocidos como Clock. Y ya hemos notado la abreviatura RCC. La buscamos en la documentación: es Control de Reinicio y Señalización (Reset and Clock Control).

Como se mencionó arriba, afortunadamente, la parte más complicada del tema de señalización ha sido resuelta por el equipo de STM, por lo que les agradezco mucho (de nuevo, aquí está el enlace a el sitio de Di Halt’, para entender cuán complicado es esto). Solo necesitamos los registros responsables de habilitar la señalización de la periferia (Peripheral Clock Enable Registers). Primero, encontraremos la dirección base de RCC, que está al principio del "Mapa de memoria":

Leemos hojas de datos 2: SPI en STM32; PWM, temporizadores e interrupciones en STM8

#define _RCC_(mem_offset) (*(volatile uint32_t *)(0x40021000 + (mem_offset)))

Y luego, o hacemos clic en el enlace para intentar encontrar algo en la tabla, o, mucho mejor, revisamos las descripciones de los registros de habilitación de la sección sobre registros de habilitación. Donde encontraremos RCC_APB1ENR y RCC_APB2ENR:

Leemos hojas de datos 2: SPI en STM32; PWM, temporizadores e interrupciones en STM8
Leemos hojas de datos 2: SPI en STM32; PWM, temporizadores e interrupciones en STM8

Y en ellos, respectivamente, los bits que habilitan la señalización SPI2, IOPB (Puerto I/O B) y funciones alternativas (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;

El código final se puede encontrar aquí.

Si hay oportunidad y deseo de probar, conectamos el DM634 así: DAI a PB15, DCK a PB13, LAT a PB14. Alimentamos el controlador con 5 voltios, no olvidemos unir las tierras.

Leemos hojas de datos 2: SPI en STM32; PWM, temporizadores e interrupciones en STM8

STM8 PWM

PWM en STM8

Cuando estaba planeando este artículo, decidí como ejemplo intentar dominar alguna funcionalidad de un chip que no conocía solo a través de la hoja de datos, para no ser un zapatero sin zapatos. El STM8 era perfecto para este propósito: en primer lugar, tenía un par de placas chinas con STM8S103, y en segundo lugar, no es muy popular, por lo que la tentación de buscar soluciones en Internet se ve limitada por la falta de dichas soluciones.

Al chip también se le conoce como hoja de datos y manual de referencia RM0016, en el primero está el pinout y las direcciones de los registros, y en el segundo – todo lo demás. El STM8 se programa en C en una IDE un poco fea. ST Visual Develop.

El clock y el I/O

Por defecto, el STM8 funciona a una frecuencia de 2 MHz, esto debe corregirse de inmediato.

Leemos hojas de datos 2: SPI en STM32; PWM, temporizadores e interrupciones en STM8
La señal de clock HSI (interior rápido)
La señal de clock HSI proviene de un generador RC interno de 16 MHz con un divisor programable (de 1 a 8). Se configura en el registro del divisor de clock (CLK_CKDIVR).
Nota: al arrancar, la fuente de clock principal elegida es el generador RC HSI con un divisor de 8.

Buscamos la dirección del registro en la hoja de datos, la descripción en el refman y vemos que el registro debe limpiarse:

#define CLK_CKDIVR *(volatile uint8_t *)0x0050C6

CLK_CKDIVR &= ~(0x18);

Ya que vamos a iniciar PWM y conectar diodos LED, revisamos el pinout:

Leemos hojas de datos 2: SPI en STM32; PWM, temporizadores e interrupciones en STM8

El chip es pequeño, muchas funciones están asignadas a los mismos pines. Lo que está entre corchetes cuadrados es "funcionalidad alternativa", que se alterna con "bytes de opción" (option bytes) – algo así como los fusibles de un Atmega. Se pueden cambiar sus valores de manera programática, pero no es necesario, ya que se activa la nueva funcionalidad solo después de un reinicio. Es más fácil usar el ST Visual Programmer (que se descarga junto con Visual Develop), que puede modificar estos bytes. En el pinout se puede ver que las salidas CH1 y CH2 del primer temporizador están ocultas entre corchetes cuadrados; debemos marcar los bits AFR1 y AFR0 en STVP, y el segundo también moverá la salida CH1 del segundo temporizador de PD4 a PC5.

Por lo tanto, se controlarán los LEDs con 6 pines: PC6, PC7 y PC3 para el primer temporizador, PC5, PD3 y PA3 para el segundo.

Configurar los propios pines de entrada y salida en el STM8 es más simple y lógico que en el STM32:

  • el registro de dirección de datos DDR familiar de Atmega (Data Direction Register): 1 = salida;
  • el primer registro de control CR1 al enviar configura el modo "push-pull" (1) o drenaje abierto (0); como estoy conectando los LEDs al chip por cátodo, dejo ceros aquí;
  • el segundo registro de control CR2 al enviar establece la velocidad de clock: 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)); //fast

Configuración de PWM

Para empezar, definamos algunos términos:

  • Frecuencia de PWM – la frecuencia a la que el temporizador hace tic;
  • Auto-reload, AR – valor de recarga automática hasta donde contará el temporizador (período de pulso);
  • Evento de actualización, UEV – evento que ocurre cuando el temporizador cuenta hasta AR;
  • Ciclo de trabajo de PWM – coeficiente de llenado de PWM, a menudo denominado 'ciclo de trabajo';
  • Valor de captura/comparación – valor para captura/comparación, al que el temporizador realiza una acción (en el caso de PWM, invierte la señal de salida);
  • Valor de precarga – valor pre-cargado. El valor de comparación no puede cambiar mientras el temporizador está corriendo, de lo contrario, el ciclo de PWM se romperá. Por lo tanto, los nuevos valores transmitidos se almacenan en un búfer y se extraen de allí cuando el temporizador llega al final del conteo y se reinicia;
  • Modos alineados por borde y Modos alineados por centro – alineación por borde y por centro, lo que corresponde a los modos atmel. PWM rápido y PWM de fase corregida.
  • OCiREF, Señal de referencia de comparación de salida – señal de salida de referencia, de hecho, lo que en modo PWM aparece en el pin correspondiente.

Como ya es evidente del esquema de pines, el PWM está disponible en dos temporizadores: el primero y el segundo. Ambos son de 16 bits, el primero tiene muchas características adicionales (en particular, puede contar hacia arriba y hacia abajo). Necesitamos que ambos funcionen igual, por lo que decidí comenzar con el segundo, que es deliberadamente más limitado, para no arriesgarme a usar algo que no está en él. Un problema es que la descripción de la funcionalidad de PWM de todos los temporizadores se encuentra en el manual de referencia en el capítulo sobre el primer temporizador (17.5.7 Modo PWM), por lo que tengo que saltar de un lado a otro en el documento.

El PWM en STM8 tiene una ventaja importante sobre el PWM de Atmega:

Leemos hojas de datos 2: SPI en STM32; PWM, temporizadores e interrupciones en STM8
PWM alineado por borde
Configuración de conteo ascendente
El conteo ascendente está activo si el bit DIR en el registro TIM_CR1 está en cero.
Ejemplo
El ejemplo utiliza el primer modo de PWM. La señal de referencia de PWM OCiREF se mantiene en nivel alto, mientras TIM1_CNT < TIM1_CCRi. De lo contrario, acepta nivel bajo. Si el valor de comparación en el registro TIM1_CCRi es mayor que el valor de carga automática (registro TIM1_ARR), la señal OCiREF se mantiene en 1. Si el valor de comparación es 0, OCiREF se mantiene en cero.…

El temporizador STM8 durante el evento de actualización primero verifica el valor de comparación, y solo luego emite la señal de referencia. En Atmega, el temporizador primero cuenta y luego compara, lo que resulta en que cuando el valor de comparación == 0 Al final, obtenemos una aguja con la que hay que lidiar de alguna manera (por ejemplo, invirtiendo la lógica por software).

Entonces, lo que queremos hacer es: PWM de 8 bits (AR == 255), contando de abajo hacia arriba, alineación por límite. Dado que las luces están conectadas al chip por los cátodos, el PWM debe emitir 0 (LED encendido) hasta el valor de comparación y 1 después.

Ya hemos leído sobre ciertos PWM mode, así que buscamos el registro necesario del segundo temporizador en el manual de referencia con esta frase (18.6.8 – TIMx_CCMR1):

Leemos hojas de datos 2: SPI en STM32; PWM, temporizadores e interrupciones en STM8
110: Primer modo PWM – al contar de abajo hacia arriba, el primer canal está activo mientras TIMx_CNT < TIMx_CCR1. De lo contrario, el primer canal no está activo. [más adelante en el documento hay un error de copia del temporizador 1]
111: Segundo modo PWM – al contar de abajo hacia arriba, el primer canal no está activo mientras TIMx_CNT < TIMx_CCR1. De lo contrario, el primer canal está activo.

Dado que los LEDs están conectados al microcontrolador por los cátodos, nos conviene el segundo modo (el primero también, pero aún no lo sabemos).

Leemos hojas de datos 2: SPI en STM32; PWM, temporizadores e interrupciones en STM8
Bit 3 OC1PE: Activar la pre-carga de la salida 1
0: El registro de pre-carga en TIMx_CCR1 está apagado. Se puede escribir en TIMx_CCR1 en cualquier momento. El nuevo valor funciona de inmediato.
1: El registro de pre-carga en TIMx_CCR1 está activado. Las operaciones de lectura/escritura acceden al registro de pre-carga. El valor pre-cargado de TIMx_CCR1 se carga en el registro sombra durante cada evento de actualización.
*Nota: Para el correcto funcionamiento del modo PWM, los registros de pre-carga deben estar activados. Esto no es necesario en el modo de señal única (el bit OPM está configurado en el registro TIMx_CR1).

Bien, activamos todo lo necesario para los tres canales del segundo temporizador:

#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 consiste en dos registros de ocho bits, aquí es simple:

#define TIM2_ARRH  *(volatile uint8_t *)0x00530F
#define TIM2_ARRL  *(volatile uint8_t *)0x005310

TIM2_ARRH = 0;
TIM2_ARRL = 255;

El segundo temporizador solo puede contar de abajo hacia arriba, alineación por límite, no hay que cambiar nada. Establezcamos el divisor de frecuencia, por ejemplo, en 256. El divisor del segundo temporizador se configura en el registro TIM2_PSCR y es una potencia de dos:

#define TIM2_PSCR  *(volatile uint8_t *)0x00530E

TIM2_PSCR = 8;

Solo queda activar las salidas y el propio segundo temporizador. La primera tarea se resuelve mediante los registros Capture/Compare Enable: hay dos, tres canales están distribuidos de manera asimétrica. Aquí también podemos averiguar que se puede cambiar la polaridad de la señal, es decir, en principio se podría utilizar también el PWM Mode 1. Escribimos:

#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;

Y, finalmente, iniciamos el temporizador en el registro TIMx_CR1:

Leemos hojas de datos 2: SPI en STM32; PWM, temporizadores e interrupciones en STM8

#define TIM2_CR1   *(volatile uint8_t *)0x005300

TIM2_CR1 |= 1;

Escribamos una simple analogía de AnalogWrite(), que transmitirá al temporizador los valores para comparación. Los registros se llaman predeciblemente Capture/Compare registers, dos por cada canal: los 8 bits inferiores en TIM2_CCRxL y los superiores en TIM2_CCRxH. Dado que hemos configurado un PWM de 8 bits, es suficiente escribir solo los bits inferiores:

#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;
}

El lector atento notará que hemos obtenido un PWM ligeramente defectuoso, incapaz de emitir un llenado del 100% (con un valor máximo de 255, la señal se invierte en un ciclo del temporizador). Para los LED esto no importa, y el lector atento ya puede deducir cómo corregirlo.

El PWM en el segundo temporizador funciona, pasemos al primero.

El primer temporizador tiene exactamente los mismos bits en los mismos registros (simplemente, los bits que quedaban "reservados" en el segundo temporizador, en el primero se utilizan activamente para diversas funciones avanzadas). Por lo tanto, es suficiente encontrar las direcciones de estos registros en la hoja de datos y copiar el código. Y cambiar el valor del divisor de frecuencia, ya que el primer temporizador quiere recibir un valor exacto de 16 bits en dos registros, no una potencia de dos. Prescaler High y Bajo. Hacemos todo y… el primer temporizador no funciona. ¿Cuál es el problema?

El problema solo se puede resolver revisando todo el apartado sobre los registros de control del temporizador 1, donde buscamos el que no tiene el segundo temporizador. Se encontrará 17.7.30 Registro de ruptura (TIM1_BKR), donde hay un bit como este:

Leemos hojas de datos 2: SPI en STM32; PWM, temporizadores e interrupciones en STM8
Activar la salida principal

#define TIM1_BKR   *(volatile uint8_t *)0x00526D

TIM1_BKR = (1<<7);

Ahora sí, todo, el código ahí mismo.

Leemos hojas de datos 2: SPI en STM32; PWM, temporizadores e interrupciones en STM8

STM8 Multiplex

Multiplexión en STM8

El tercer mini-proyecto consiste en conectar ocho LED RGB al segundo temporizador en modo PWM y hacer que muestren diferentes colores. Se basa en el concepto de multiplexión de LED, que consiste en que si encendemos y apagamos los LED muy, muy rápido, parecerá que están iluminados constantemente (persistencia de la visión, inercia de la percepción visual). En su momento hice algo así en Arduino.

El algoritmo de funcionamiento es el siguiente:

  • conectamos el ánodo del primer LED RGB;
  • lo encendemos, enviando las señales necesarias a los cátodos;
  • esperamos el final del ciclo PWM;
  • conectamos el ánodo del segundo LED RGB;
  • lo encendemos…

Y así sucesivamente. Por supuesto, para que funcione bien, es necesario que la conexión del ánodo y el "encendido" del LED ocurran simultáneamente. Bueno, o casi. En cualquier caso, necesitamos escribir un código que emita valores a través de los tres canales del segundo temporizador, cambiándolos al alcanzar UEV y, al mismo tiempo, cambiar el LED RGB activo en ese momento.

Dado que el cambio de LED se realiza automáticamente, es necesario crear una "memoria de video" de la que el controlador de interrupciones obtendrá los datos. Es un simple array:

uint8_t colors[8][3];

Para cambiar el color de un LED específico, basta con escribir en este array los valores deseados. La variable responsable del número del LED activo será

uint8_t cnt;

Demux

Para un correcto multiplexado, necesitaremos, sorprendentemente, un demultiplexor CD74HC238. El demultiplexor es un chip que implementa hardware el operador <<. A través de tres pines de entrada (bits 0, 1 y 2) le suministramos un número de tres bits X, y él activa el número de salida (1<<X). El resto de las entradas del chip se utilizan para escalar toda la construcción. Este chip no solo es necesario para reducir el número de pines ocupados en el microcontrolador, sino también por seguridad, para no encender accidentalmente más LED de los que se pueden y no quemar el MC. Su costo es mínimo, siempre es útil tenerlo en el botiquín doméstico.

El CD74HC238 se encargará de suministrar voltaje al ánodo del LED adecuado. En un multiplexado completo, suministraría voltaje a la columna a través de un P-MOSFET, pero en esta demo se puede hacer directamente, ya que este tira 20 mA, de acuerdo a valores máximos absolutos en la hoja de datos. En la hoja de datos del CD74HC238 necesitaremos el desglose del pinout y esta guía rápida:

Leemos hojas de datos 2: SPI en STM32; PWM, temporizadores e interrupciones en STM8
H = nivel alto de voltaje, L = nivel bajo de voltaje, X – indiferente

Conectamos E2 y E1 a tierra, E3, A0, A1 y A3 a los pines PD5, PC3, PC4 y PC5 del STM8. Dado que la tabla anterior contiene niveles bajos y altos, configuramos estos pines como salidas push-pull.

PWM

La PWM en el segundo temporizador se configura de la misma manera que en la historia anterior, con dos diferencias:

Primero, debemos habilitar la interrupción en Update Event (UEV), que llamará a la función que cambia el LED activo. Esto se hace modificando el bit Update Interrupt Enable en el registro con un nombre muy apropiado

Leemos hojas de datos 2: SPI en STM32; PWM, temporizadores e interrupciones en STM8
Registro de habilitación de interrupciones

#define TIM2_IER   *(volatile uint8_t *)0x005303

//enable interrupt
TIM2_IER = 1;

La segunda diferencia está relacionada con el fenómeno del multiplexado llamado ghosting – el brillo parásito de los diodos. En nuestro caso, puede aparecer debido a que el temporizador, al provocar una interrupción en UEV, sigue contando y el controlador de interrupciones no tiene tiempo de cambiar el LED antes de que el temporizador comience a escribir algo en las salidas. Para combatir esto, es necesario invertir la lógica (0 = brillo máximo, 255 = nada se ilumina) y evitar valores extremos de ciclo de trabajo. Es decir, lograr que después de UEV los diodos se apaguen por completo en un ciclo de PWM.

Cambiamos la polaridad:

//set polarity 
    TIM2_CCER1 |= (CC1P | CC2P);
    TIM2_CCER2 |= CC3P;

Evitamos establecer r, g y b en 255 y no olvidamos invertirlos al usarlos.

Interrupciones

La esencia de la interrupción es que, bajo ciertas circunstancias, el chip deja de ejecutar el programa principal y llama a alguna función externa. Las interrupciones ocurren debido a estímulos externos o internos, incluidos los del temporizador.

Cuando creamos el proyecto por primera vez en ST Visual Develop, además de main.c recibimos una ventana con un misterioso archivo stm8_interrupt_vector.c, automáticamente incluido en el proyecto. En este archivo, a cada interrupción se le asigna una función NonHandledInterrupt. Necesitamos vincular nuestra función a la interrupción adecuada.

En la hoja de datos hay una tabla de vectores de interrupción en la que encontramos lo necesario:

Leemos hojas de datos 2: SPI en STM32; PWM, temporizadores e interrupciones en STM8
13 Actualización/Desbordamiento TIM2
14 Captura/Comparación TIM2

Necesitamos cambiar el LED al UEV, así que necesitamos la interrupción nº 13.

En consecuencia, en primer lugar, en el archivo stm8_interrupt_vector.c cambiamos el nombre de la función que maneja la interrupción nº 13 (IRQ13) por defecto a la nuestra:

{0x82, TIM2_Overflow}, /* irq13 */

En segundo lugar, tendremos que crear un archivo main.h con el siguiente contenido:

#ifndef __MAIN_H
#define __MAIN_H

@far @interrupt void TIM2_Overflow (void);
#endif

Y, finalmente, definir esta función en main.c:

@far @interrupt void TIM2_Overflow (void)
{
    PD_ODR &= ~(1<<5); // desactivamos el desmultiplexor
    PC_ODR = (cnt<<3); // escribimos un nuevo valor en el desmultiplexor
    PD_ODR |= (1<<5); // activamos el desmultiplexor

    TIM2_SR1 = 0; // restablecemos la bandera de Interrupción Pendiente de Actualización

    cnt++; 
    cnt &= 7; // movemos el contador LED

    TIM2_CCR1L = ~colors[cnt][0]; // transferimos valores invertidos al búfer
    TIM2_CCR2L = ~colors[cnt][1]; // para el siguiente ciclo de PWM
    TIM2_CCR3L = ~colors[cnt][2]; // 

    return;
}

Ahora solo queda habilitar las interrupciones. Esto se hace con el comando ensamblador rim – habrá que buscarlo en Manual de Programación:

//enable interrupts
_asm("rim");

Otro comando ensamblador – sim – desactiva las interrupciones. Deben desactivarse al grabar nuevos valores en la "memoria de video", para que una interrupción activada en un momento inoportuno no estropee el arreglo.

Todo el código está en GitHub.

Leemos hojas de datos 2: SPI en STM32; PWM, temporizadores e interrupciones en STM8

Si a alguien le resulta útil este artículo, significa que no lo escribí en vano. Agradeceré comentarios y observaciones, y trataré de responder a todos.

Fuente: habr.com

Compra un hosting fiable para sitios web con protección contra DDoS, servidores VPS VDS 🔥 Compra un hosting fiable para sitios web con protección contra DDoS, servidores VPS VDS | ProHoster