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

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

В la primera parte Intenté decirles a los ingenieros electrónicos aficionados que crecieron con pantalones Arduino cómo y por qué deberían leer hojas de datos y otra documentación para microcontroladores. El texto resultó ser extenso, por lo que prometí mostrar ejemplos prácticos en un artículo aparte. Bueno, se hacía llamar hongo de leche...

Hoy le mostraré cómo usar hojas de datos para resolver tareas bastante simples, pero necesarias para muchos proyectos, en controladores STM32 (Blue Pill) y STM8. Todos los proyectos de demostración están dedicados a mis LED favoritos, los encenderemos en grandes cantidades, para lo cual tendremos que utilizar todo tipo de periféricos interesantes.

El texto nuevamente resultó ser enorme, así que por conveniencia estoy creando el contenido:

STM32 Blue Pill: 16 LED con controlador DM634
STM8: Configuración de seis pines PWM
STM8: 8 LED RGB en tres pines, interrupciones

Descargo de responsabilidad: no soy ingeniero, no pretendo tener conocimientos profundos en electrónica, el artículo está destinado a aficionados como yo. De hecho, hace dos años me consideré el público objetivo. Si alguien me hubiera dicho entonces que las hojas de datos de un chip desconocido no daban miedo de leer, no habría pasado mucho tiempo buscando fragmentos de código en Internet e inventando muletas con tijeras y cinta adhesiva.

El enfoque de este artículo está en las hojas de datos, no en los proyectos, por lo que es posible que el código no sea muy claro y, a menudo, estrecho. Los proyectos en sí son muy sencillos, aunque adecuados para un primer contacto con el nuevo chip.

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

STM32

16 LED con DM634 y SPI

Un pequeño proyecto que utiliza Blue Pill (STM32F103C8T6) y un controlador LED DM634. Usando hojas de datos, descubriremos el controlador, los puertos STM IO y configuraremos SPI.

DM634

Chip taiwanés con 16 salidas PWM de 16 bits, se puede conectar en cadena. El modelo de gama baja de 12 bits se conoce por un proyecto nacional. Paquete de luz. En un momento, al elegir entre el DM63x y el conocido TLC5940, elegí DM por varias razones: 1) TLC en Aliexpress es definitivamente falso, pero este no lo es; 2) DM tiene un PWM autónomo con su propio generador de frecuencia; 3) se podía comprar a bajo costo en Moscú, en lugar de esperar un paquete de Ali. Y, por supuesto, fue interesante aprender a controlar el chip usted mismo, en lugar de utilizar una biblioteca ya preparada. Los chips ahora se presentan principalmente en el paquete SSOP24; son fáciles de soldar a un adaptador.

Dado que el fabricante es taiwanés, ficha de datos El chip está escrito en chino inglés, lo que significa que será divertido. Primero miramos el pinout (Conexión de clavija) para entender qué pata conectar a qué y una descripción de los pines (Pin Descripción). 16 pines:

Lea las hojas de datos 2: SPI en STM32; PWM, temporizadores e interrupciones en STM8
Fuentes disipadoras de CC (drenaje abierto)

Lavadero / Salida de drenaje abierto - drenar; fuente de corriente entrante; la salida está conectada a tierra en el estado activo; los LED están conectados al controlador mediante cátodos. Desde el punto de vista eléctrico, esto no es, por supuesto, un "desagüe abierto" (drenaje abierto), pero en las hojas de datos se encuentra a menudo esta designación para pasadores en modo de drenaje.

Lea las 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

Se instala una resistencia de referencia entre el pin REXT y tierra, que controla la resistencia interna de las salidas, consulte el gráfico en la página 9 de la hoja de datos. En el DM634, esta resistencia también se puede controlar mediante software, configurando el brillo general (brillo global); No entraré en detalles en este artículo, solo pondré aquí una resistencia de 2.2 - 3 kOhm.

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

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

Sí, aquí está, el inglés chino en todo su esplendor. Traducir esto es problemático, puede entenderlo si lo desea, pero hay otra manera: observe cómo se describe la conexión al TLC5940 funcionalmente similar en la hoja de datos:

Lea las hojas de datos 2: SPI en STM32; PWM, temporizadores e interrupciones en STM8
... Solo se requieren tres pines para ingresar datos en el dispositivo. El flanco ascendente de la señal SCLK desplaza los datos del pin SIN al registro interno. Después de que se hayan cargado todos los datos, una señal XLAT corta y alta bloquea los datos transferidos secuencialmente en los registros internos. Los registros internos son puertas activadas por el nivel de la señal XLAT. Todos los datos se transmiten primero el bit más significativo.

Pestillo – pestillo/pestillo/bloqueo.
Flanco ascendente – flanco anterior del pulso
MSB primero – bit más significativo (más a la izquierda) hacia adelante.
para cronometrar datos – transmitir datos secuencialmente (bit a bit).

Palabra pestillo se encuentra a menudo en la documentación de chips y se traduce de varias maneras, por lo que para comprenderlo me permitiré

pequeño programa educativoEl controlador LED es esencialmente un registro de desplazamiento. "Cambio" (Turno) en el nombre: movimiento bit a bit de datos dentro del dispositivo: cada nuevo bit introducido en el interior empuja toda la cadena hacia adelante frente a él. Como nadie quiere observar un parpadeo caótico de los LED durante el turno, el proceso se lleva a cabo en registros de búfer separados de los registros de trabajo por un amortiguador (pestillo) es una especie de sala de espera donde los bits se disponen en la secuencia deseada. Cuando todo está listo, se abre la persiana y las brocas se ponen a trabajar, reemplazando el lote anterior. Palabra pestillo en la documentación de microcircuitos casi siempre implica un amortiguador de este tipo, sin importar en qué combinaciones se utilice.

Entonces, la transferencia de datos al DM634 se realiza así: configure la entrada DAI al valor del bit más significativo del LED lejano, tire del DCK hacia arriba y hacia abajo; configure la entrada DAI al valor del siguiente bit, extraiga DCK; y así sucesivamente hasta que se hayan transmitido todos los bits (registrado en), después de lo cual tiramos LAT. Esto se puede hacer manualmente (bit-bang), pero es mejor utilizar una interfaz SPI especialmente diseñada para esto, ya que se presenta en nuestro STM32 en dos copias.

Pastilla azul STM32F103

Introducción: los controladores STM32 son mucho más complejos que Atmega328 de lo que podrían parecer atemorizantes. Además, por razones de ahorro de energía, casi todos los periféricos se apagan al inicio y la frecuencia del reloj es de 8 MHz desde la fuente interna. Afortunadamente, los programadores de STM escribieron código que lleva el chip a los 72 MHz "calculados", y los autores de todos los IDE que conozco lo incluyeron en el procedimiento de inicialización, por lo que no necesitamos cronometrar (pero puedes si realmente quieres). Pero tendrás que encender los periféricos.

Documentación: Blue Pill está equipada con el popular chip STM32F103C8T6, hay dos documentos útiles para ello:

En la hoja de datos nos puede interesar:

  • Pinouts – pinouts de chip – en caso de que decidamos hacer las placas nosotros mismos;
  • Mapa de memoria: mapa de memoria para un chip específico. El Manual de Referencia tiene un mapa de toda la línea y menciona registros que el nuestro no tiene.
  • Tabla de definiciones de pines: enumera las funciones principales y alternativas de los pines; Para la "píldora azul", puede encontrar imágenes más prácticas en Internet con una lista de pines y sus funciones. Por lo tanto, inmediatamente buscamos en Google el pinout de Blue Pill y tenemos esta imagen a mano:

Lea las hojas de datos 2: SPI en STM32; PWM, temporizadores e interrupciones en STM8
NB: hubo un error en la imagen de Internet, que se anotó en los comentarios, gracias por eso. La imagen ha sido reemplazada, pero esto es una lección: es mejor verificar la información que no esté en las hojas de datos.

Quitamos la hoja de datos, abrimos el Manual de Referencia, y de ahora en adelante solo la usamos.
Procedimiento: nos ocupamos de la entrada/salida estándar, configuramos SPI, encendemos los periféricos necesarios.

De entrada y salida

En el Atmega328, la E/S se implementa de manera extremadamente simple, razón por la cual la abundancia de opciones STM32 puede resultar confusa. Ahora sólo nos faltan conclusiones, pero incluso éstas tienen cuatro opciones:

Lea las hojas de datos 2: SPI en STM32; PWM, temporizadores e interrupciones en STM8
drenaje abierto, push-pull, push-pull alternativo, drenaje abierto alternativo

"tirar-empujar" (empujar tirar) es la salida habitual del Arduino, el pin puede tomar el valor ALTO o BAJO. Pero con el “drenaje abierto” hay complejidad, aunque en realidad aquí todo es sencillo:

Lea las hojas de datos 2: SPI en STM32; PWM, temporizadores e interrupciones en STM8
Lea las hojas de datos 2: SPI en STM32; PWM, temporizadores e interrupciones en STM8
Configuración de salida / cuando el puerto está asignado a la salida: / búfer de salida habilitado: / – modo de drenaje abierto: “0” en el registro de salida habilita N-MOS, “1” en el registro de salida deja el puerto en modo Hi-Z ( P-MOS no está activado) / – modo push-pull: “0” en el registro de salida activa N-MOS, “1” en el registro de salida activa P-MOS.

Toda la diferencia entre drenaje abierto (drenaje abierto) de “empujar-tirar” (empujar tirar) es que en el primer pin no puede aceptar el estado ALTO: al escribir uno en el registro de salida, entra en modo de alta resistencia (alta impedancia, Hola-Z). Al escribir cero, el pin se comporta igual en ambos modos, tanto lógica como eléctricamente.

En el modo de salida normal, el pin simplemente transmite el contenido del registro de salida. En la "alternativa" se controla mediante los correspondientes periféricos (ver 9.1.4):

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

La funcionalidad alternativa de cada pin se describe en Definiciones de pines La hoja de datos está en la imagen descargada. A la pregunta de qué hacer si un pin tiene varias funciones alternativas, la respuesta se encuentra en una nota a pie de página en la hoja de datos:

Lea las hojas de datos 2: SPI en STM32; PWM, temporizadores e interrupciones en STM8
Si varios periféricos usan el mismo pin, para evitar conflictos entre funciones alternativas, solo se debe usar un periférico a la vez, conmutado usando el bit de habilitación del reloj periférico (en el registro RCC apropiado).

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

Entonces: estamos usando SPI, lo que significa que dos pines (con datos y con señal de reloj) deben ser "función push-pull alternativa", y otro (LAT) debe ser "push-pull normal". Pero antes de asignarlos, tratemos el SPI.

SPI

Otro pequeño programa educativo.

SPI o Serial Peripheral Interface (interfaz periférica serie) es una interfaz sencilla y muy eficaz para conectar un MK con otros MK y con el mundo exterior en general. El principio de su funcionamiento ya se ha descrito anteriormente, en relación con el controlador LED chino (en el manual de referencia, consulte la sección 25). SPI puede funcionar en modo maestro (“master”) y esclavo (“slave”). SPI tiene cuatro canales básicos, de los cuales no todos pueden utilizarse:

  • MOSI, Salida maestra/Entrada esclava: este pin transmite datos en modo maestro y recibe datos en modo esclavo;
  • MISO, Master Input / Slave Output: por el contrario recibe en el maestro y transmite en el esclavo;
  • SCK, Serial Clock: establece la frecuencia de transmisión de datos en el maestro o recibe una señal de reloj en el esclavo. Básicamente golpeando ritmos;
  • SS, Slave Select: con la ayuda de este canal, el esclavo sabe que se quiere algo de él. En STM32 se llama NSS, donde N = negativo, es decir el controlador se convierte en esclavo si hay tierra en este canal. Combina bien con el modo Open Drain Output, pero esa es otra historia.

Como todo lo demás, SPI en STM32 es rico en funcionalidades, lo que hace que sea algo difícil de entender. Por ejemplo, puede funcionar no solo con SPI, sino también con la interfaz I2S, y en la documentación sus descripciones se mezclan, es necesario cortar el exceso de manera oportuna. Nuestra tarea es extremadamente simple: solo necesitamos enviar datos usando únicamente MOSI y SCK. Pasamos al apartado 25.3.4 (comunicación semidúplex, comunicación semidúplex), donde encontramos 1 reloj y 1 cable de datos unidireccional (1 señal de reloj y 1 flujo de datos unidireccional):

Lea las hojas de datos 2: SPI en STM32; PWM, temporizadores e interrupciones en STM8
En este modo, la aplicación utiliza SPI en modo de solo transmisión o de solo recepción. / El modo de solo transmisión es similar al modo dúplex: los datos se transmiten en el pin de transmisión (MOSI en modo maestro o MISO en modo esclavo), y el pin de recepción (MISO o MOSI respectivamente) se puede usar como un pin de E/S normal. . En este caso, la aplicación sólo necesita ignorar el búfer Rx (si se lee, no se transferirán datos allí).

Genial, el pin MISO está libre, conectemosle la señal LAT. Veamos Slave Select, que en STM32 se puede controlar mediante programación, lo cual es extremadamente conveniente. Leemos el párrafo del mismo nombre en el apartado 25.3.1 Descripción General del SPI:

Lea las hojas de datos 2: SPI en STM32; PWM, temporizadores e interrupciones en STM8
Control de software NSS (SSM = 1) / La información de selección de esclavo está contenida en el bit SSI del registro SPI_CR1. El pin NSS externo permanece libre para otras necesidades de la aplicación.

Es hora de escribir en los registros. Decidí usar SPI2, busque su dirección base en la hoja de datos, en la sección 3.3 Mapa de memoria:

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

Bueno, comencemos:

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

Abra la sección 25.3.3 con el título que se explica por sí mismo “Configuración de SPI en modo maestro”:

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

1. Configure la frecuencia del reloj en serie con los bits BR[2:0] en el registro SPI_CR1.

Los registros se recogen en el apartado del manual de referencia del mismo nombre. Cambio de dirección (Desplazamiento de dirección) para CR1 – 0x00, de forma predeterminada se borran todos los bits (Restablecer valor 0x0000):

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

Los bits BR configuran el divisor de reloj del controlador, determinando así la frecuencia a la que funcionará el SPI. Nuestra frecuencia STM32 será de 72 MHz, el controlador LED, según su hoja de datos, funciona con una frecuencia de hasta 25 MHz, por lo que debemos dividirlo por 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. Configure los bits CPOL y CPHA para definir la relación entre la transferencia de datos y la temporización del reloj en serie (consulte el diagrama en la página 240).

Dado que aquí estamos leyendo una hoja de datos y no mirando esquemas, echemos un vistazo más de cerca a la descripción textual de los bits CPOL y CPHA en la página 704 (Descripción general de SPI):

Lea las hojas de datos 2: SPI en STM32; PWM, temporizadores e interrupciones en STM8
Fase de reloj y polaridad.
Utilizando los bits CPOL y CPHA del registro SPI_CR1, puede seleccionar mediante programación cuatro relaciones de temporización. El bit CPOL (polaridad del reloj) controla el estado de la señal del reloj cuando no se transmiten datos. Este bit controla los modos maestro y esclavo. Si se restablece CPOL, el pin SCK está bajo en modo de reposo. Si el bit CPOL está configurado, el pin SCK está alto durante el modo de reposo.
Cuando se establece el bit CPHA (fase de reloj), la luz estroboscópica de captura de bits altos es el segundo flanco de la señal SCK (que cae si CPOL está libre y aumenta si CPOL está establecido). Los datos son capturados por el segundo cambio en la señal del reloj. Si el bit CPHA está limpio, la luz estroboscópica de captura de bits altos es el flanco ascendente de la señal SCK (flanco descendente si se establece CPOL, flanco ascendente si se borra CPOL). Los datos se capturan en el primer cambio en la señal del reloj.

Habiendo absorbido este conocimiento, llegamos a la conclusión de que ambos bits deben permanecer ceros, porque Queremos que la señal SCK permanezca baja cuando no esté en uso y que los datos se transmitan en el flanco ascendente del pulso (ver Fig. Flanco ascendente en la hoja de datos DM634).

Por cierto, aquí encontramos por primera vez una característica del vocabulario en las hojas de datos de ST: en ellas está escrita la frase "restablecer el bit a cero". para restablecer un pocoY no aclarar un poco, como, por ejemplo, Atmega.

3. Configure el bit DFF para determinar si el bloque de datos tiene un formato de 8 o 16 bits.

Específicamente tomé un DM16 de 634 bits para no molestarme en transmitir datos PWM de 12 bits, como el DM633. Tiene sentido establecer DFF 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 DM634 quiere recibir datos empezando por el bit más significativo. Por tanto, lo dejamos reseteado.

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 transferencia de bytes. En el modo de software NSS, configure los bits SSM y SSI en el registro SPI_CR1. Si se va a utilizar el pin NSS como salida, solo es necesario configurar el bit SSOE.

Instale SSM y SSI para olvidarse del modo hardware 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 configurados (permanecen configurados solo si la señal NSS es alta)

En realidad, 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 está configurado, escribamos inmediatamente funciones que envíen bytes al controlador. Continúe leyendo 25.3.3 “Configuración de SPI en modo maestro”:

Lea las hojas de datos 2: SPI en STM32; PWM, temporizadores e interrupciones en STM8
Orden de transferencia de datos
La transmisión comienza cuando se escribe un byte en el búfer Tx.
El byte de datos se carga en el registro de desplazamiento en paralelo modo (desde el bus interno) durante la transmisión del primer bit, después del cual se transmite a secuencial Modo pin MOSI, primer o último bit hacia adelante dependiendo de la configuración del bit LSBFIRST en el registro CPI_CR1. El indicador TXE se establece después de la transmisión de datos. desde el búfer de Tx al registro de desplazamientoy también genera una interrupción si el bit TXEIE en el registro CPI_CR1 está establecido.

Destaqué algunas palabras en la traducción para llamar la atención sobre una característica de la implementación de SPI en los controladores STM. En Atmega la bandera TXE (Tx vacío, Tx está vacío y listo para recibir datos) se configura solo después de que se haya enviado el byte completo fuera. Y aquí esta bandera se establece después de que el byte se haya insertado en el registro de desplazamiento interno. Dado que se empuja allí con todos los bits al mismo tiempo (en paralelo) y luego los datos se transmiten secuencialmente, TXE se configura antes de que el byte se envíe por completo. Esto es importante porque en el caso de nuestro controlador LED, debemos tirar del pin LAT después de enviar todos datos, es decir La bandera TXE por sí sola no será suficiente para nosotros.

Esto significa que necesitamos otra bandera. Veamos 25.3.7 - “Indicadores de estado”:

Lea las hojas de datos 2: SPI en STM32; PWM, temporizadores e interrupciones en STM8
<…>
Lea las hojas de datos 2: SPI en STM32; PWM, temporizadores e interrupciones en STM8
bandera OCUPADO
El indicador BSY se establece y borra mediante hardware (escribir en él no tiene ningún efecto). La bandera BSY indica el estado de la capa de comunicación SPI.
Se reinicia:
cuando se completa la transferencia (excepto en modo maestro si la transferencia es continua)
cuando SPI está deshabilitado
cuando ocurre un error en el modo maestro (MODF=1)
Si la transferencia no es continua, el indicador BSY se borra entre cada transferencia de datos.

Bien, esto será útil. Averigüemos dónde se encuentra el búfer Tx. Para hacer esto, lea “Registro de datos SPI”:

Lea las 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 a transmitir.
El registro de datos se divide en dos buffers: uno para escritura (buffer de transmisión) y otro para lectura (buffer de recepción). Escribir en el registro de datos escribe en el búfer Tx y leer del registro de datos devolverá el valor contenido en el búfer Rx.

Bueno, y el registro de estado, donde se encuentran las banderas TXE y BSY:

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

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

Bueno, ya que necesitamos transmitir 16 veces dos bytes, según la cantidad de salidas del controlador LED, algo como esto:

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

    while (_SPI2_(_SPI_SR) & BSY); // finish transmission

    LAT_pulse();
}

Pero todavía no sabemos cómo tirar del pin LAT, así que volveremos a E/S.

Asignar pines

En el STM32F1, los registros responsables del estado de los pines son bastante inusuales. Está claro que hay más que Atmega, pero también se diferencian de otros chips STM. Sección 9.1 Descripción general de GPIO:

Lea las hojas de datos 2: SPI en STM32; PWM, temporizadores e interrupciones en STM8
Cada uno de los puertos de E/S de uso 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 configuración/reinicio de 32 bits (GPIOx_BSRR), un registro de reinicio de 16 bits (GPIOx_BRR) y un registro de reinicio de 32 bits (GPIOx_CRL y GPIOx_CRH). registro de bloqueo de bits (GPIOx_LCKR).

Los dos primeros registros son inusuales y también bastante inconvenientes, porque los 16 pines del puerto están dispersos entre ellos en un formato de “cuatro bits por hermano”. Aquellos. Los pines del cero al siete están en CRL y el resto en CRH. Al mismo tiempo, los registros restantes contienen con éxito los bits de todos los pines del puerto, quedando a menudo la mitad "reservados".

Para simplificar, comencemos desde el final de la lista.

No necesitamos un registro de bloqueo.

Los registros set y reset son bastante divertidos porque se duplican parcialmente entre sí: puedes escribir todo solo en BSRR, donde los 16 bits superiores restablecerán el pin a cero y los inferiores se establecerán en 1, o también puedes use BRR, cuyos 16 bits inferiores solo restablecen el pin. Me gusta la segunda opción. Estos registros son importantes porque proporcionan acceso atómico a los pines:

Lea las hojas de datos 2: SPI en STM32; PWM, temporizadores e interrupciones en STM8
Lea las hojas de datos 2: SPI en STM32; PWM, temporizadores e interrupciones en STM8
Conjunto atómico o reinicio
No es necesario desactivar las interrupciones al programar GPIOx_ODR a nivel de bits: se pueden cambiar uno o más bits con una única operación de escritura atómica APB2. Esto se logra escribiendo un "1" en el registro de configuración/reinicio (GPIOx_BSRR o, solo para reinicio, GPIOx_BRR) del bit que debe cambiarse. Otros bits permanecerán sin cambios.

Los registros de datos tienen nombres que se explican por sí solos - IDR = Entrada Registro de dirección, registro de entrada; ODR = Salida Registro de dirección, registro de salida. No los necesitaremos en el proyecto actual.

Y por último, los registros de control. Como estamos interesados ​​en los segundos pines SPI, es decir, PB13, PB14 y PB15, inmediatamente miramos CRH:

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

Y vemos que necesitaremos escribir algo en bits del 20 al 31.

Ya hemos descubierto arriba lo que queremos de los pines, así que aquí lo haré sin una captura de pantalla, solo diré que MODE especifica la dirección (entrada si ambos bits están configurados en 0) y la velocidad del pin (necesitamos 50 MHz, es decir. ambos pines en “1”) y CNF establece el modo: regular “push-pull” – 00, “alternativo” – 10. De forma predeterminada, como vemos arriba, todos los pines tienen el tercer bit desde abajo (CNF0), los pone en modo entrada flotante.

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

Bueno, algo asi

#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 ubicados 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, en consecuencia, puede escribir definiciones para LAT, que serán alteradas por 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 solo por inercia, siempre ha sido así, que se quede)

Ahora todo está genial, pero no funciona. Como se trata de STM32, ahorran electricidad, lo que significa que es necesario habilitar la sincronización de los periféricos necesarios.

Activar el fichaje

El reloj, también conocido como Reloj, es el encargado de marcar la hora. Y ya pudimos notar la abreviatura RCC. Lo buscamos en la documentación: esto es Reset y Control de Reloj.

Como se dijo anteriormente, afortunadamente, la parte más difícil del tema del fichaje la hizo gente de STM, por lo que les agradecemos mucho (una vez más les daré un enlace a Sitio web de Di Halt, para dejar claro lo confuso que es). Solo necesitamos registros responsables de habilitar el reloj periférico (Peripheral Clock Enable Registers). Primero, busquemos la dirección base del RCC, está al comienzo del "Mapa de memoria":

Lea las 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 haga clic en el enlace donde intenta encontrar algo en la placa o, mucho mejor, consulte las descripciones de los registros de habilitación de las secciones sobre habilitar registros. Donde encontraremos RCC_APB1ENR y RCC_APB2ENR:

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

Y, en consecuencia, contienen bits que incluyen la sincronización de SPI2, IOPB (puerto de E/S 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 tiene la oportunidad y desea realizar la prueba, conecte el DM634 de esta manera: DAI a PB15, DCK a PB13, LAT a PB14. Alimentamos el controlador con 5 voltios, no olvides conectar la masa.

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

STM8PWM

PWM en STM8

Cuando estaba planeando este artículo, decidí, como ejemplo, intentar dominar alguna funcionalidad de un chip desconocido usando solo una hoja de datos, para no terminar con un zapatero sin botas. STM8 era ideal para este rol: en primer lugar, tenía un par de placas base chinas con STM8S103 y, en segundo lugar, no es muy popular y, por lo tanto, la tentación de leer y encontrar una solución en Internet radica en la falta de estas mismas soluciones.

El chip también tiene ficha de datos и manual de referencia RM0016, en el primero hay direcciones de registro y asignación de pines, en el segundo, todo lo demás. STM8 está programado en C en un pésimo IDE Desarrollo visual ST.

Reloj y E/S

De forma predeterminada, STM8 opera a una frecuencia de 2 MHz, esto debe corregirse de inmediato.

Lea las hojas de datos 2: SPI en STM32; PWM, temporizadores e interrupciones en STM8
Reloj HSI (interno de alta velocidad)
La señal de reloj HSI se deriva de un oscilador RC interno de 16 MHz con un divisor programable (1 a 8). Se establece en el registro divisor de reloj (CLK_CKDIVR).
Nota: al principio, se selecciona un oscilador HSI RC con un divisor de 8 como fuente principal de la señal de reloj.

Encontramos la dirección del registro en la hoja de datos, la descripción en refman y vemos que es necesario borrar el registro:

#define CLK_CKDIVR *(volatile uint8_t *)0x0050C6

CLK_CKDIVR &= ~(0x18);

Como vamos a ejecutar PWM y conectar los LED, veamos el pinout:

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

El chip es pequeño, muchas funciones están suspendidas en los mismos pines. Lo que está entre corchetes es “funcionalidad alternativa”, se cambia por “bytes de opción” (bytes de opción) – algo así como fusibles Atmega. Puede cambiar sus valores mediante programación, pero no es necesario, porque La nueva funcionalidad se activa solo después de reiniciar. Es más fácil usar ST Visual Programmer (descargado con Visual Develop), que puede cambiar estos bytes. La distribución de pines muestra que los pines CH1 y CH2 del primer temporizador están ocultos entre corchetes; es necesario configurar los bits AFR1 y AFR0 en STVP, y el segundo también transferirá la salida CH1 del segundo temporizador de PD4 a PC5.

Así, 6 pines controlarán los LED: PC6, PC7 y PC3 para el primer temporizador, PC5, PD3 y PA3 para el segundo.

Configurar los pines de E/S en STM8 es más simple y lógico que en STM32:

  • familiar del registro de dirección de datos Atmega DDR (Registro de dirección de datos): 1 = salida;
  • el primer registro de control CR1, cuando se emite, establece el modo push-pull (1) o drenaje abierto (0); como conecto los LED al chip con cátodos, aquí dejo ceros;
  • el segundo registro de control CR2, cuando se emite, establece la velocidad del reloj: 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

Primero, definamos los términos:

  • Frecuencia de PWM – frecuencia con la que funciona el cronómetro;
  • Recarga automática, AR – valor autocargable hasta el cual contará el temporizador (período de pulso);
  • Evento de actualización, UEV – un evento que se produce cuando el temporizador ha contado hasta AR;
  • Ciclo de trabajo PWM – Ciclo de trabajo PWM, a menudo llamado “factor de trabajo”;
  • Capturar/comparar valor – valor para captura/comparación, con el que ha contado el temporizador hará algo (en el caso de PWM, invierte la señal de salida);
  • Valor de precarga – valor precargado. Comparar valor no puede cambiar mientras el temporizador está en marcha, de lo contrario el ciclo PWM se interrumpirá. Por lo tanto, los nuevos valores transmitidos se colocan en un búfer y se extraen cuando el temporizador llega al final de su cuenta regresiva y se reinicia;
  • Alineado con los bordes и Modos alineados al centro – alineación a lo largo del borde y en el centro, igual que la de Atmel PWM rápido и PWM con corrección de fase.
  • OCiREF, señal de referencia de comparación de salida – señal de salida de referencia, de hecho, la que aparece en el pin correspondiente en modo PWM.

Como ya se desprende claramente del pinout, dos temporizadores tienen capacidades PWM: el primero y el segundo. Ambos son de 16 bits, el primero tiene muchas funciones adicionales (en particular, puede contar tanto hacia arriba como hacia abajo). Necesitamos que ambos funcionen por igual, así que decidí comenzar con el segundo obviamente más pobre, para no usar accidentalmente algo que no está allí. Algún problema es que la descripción de la funcionalidad PWM de todos los temporizadores en el manual de referencia se encuentra en el capítulo sobre el primer temporizador (17.5.7 Modo PWM), por lo que debe saltar hacia adelante y hacia atrás a lo largo del documento todo el tiempo.

PWM en STM8 tiene una ventaja importante sobre PWM en Atmega:

Lea las hojas de datos 2: SPI en STM32; PWM, temporizadores e interrupciones en STM8
PWM alineado con límites
Configuración de la cuenta de abajo hacia arriba
El conteo ascendente está activo si se borra el bit DIR en el registro TIM_CR1
ejemplo
El ejemplo utiliza el primer modo PWM. La señal de referencia PWM OCiREF se mantiene alta mientras TIM1_CNT < TIM1_CCRi. De lo contrario se necesita un 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....

Temporizador STM8 durante evento de actualización comprueba primero comparar valor, y sólo entonces produce una señal de referencia. El temporizador de Atmega primero falla y luego compara, lo que resulta en compare value == 0 la salida es una aguja, que debe tratarse de alguna manera (por ejemplo, invirtiendo la lógica mediante programación).

Entonces, lo que queremos hacer: PWM de 8 bits (AR == 255), contando de abajo hacia arriba, alineación a lo largo del borde. Dado que las bombillas están conectadas al chip mediante cátodos, el PWM debería emitir 0 (LED encendido) hasta que comparar valor y 1 después.

Ya hemos leído sobre algunos Modo PWM, por lo que encontramos el registro requerido del segundo temporizador buscando en el manual de referencia esta frase (18.6.8 - TIMx_CCMR1):

Lea las hojas de datos 2: SPI en STM32; PWM, temporizadores e interrupciones en STM8
110: Primer modo PWM: cuando se cuenta de abajo hacia arriba, el primer canal está activo mientras TIMx_CNT < TIMx_CCR1. De lo contrario, el primer canal está inactivo. [más adelante en el documento hay un copiar y pegar erróneo del temporizador 1] 111: Segundo modo PWM: cuando se cuenta de abajo hacia arriba, el primer canal está inactivo mientras TIMx_CNT < TIMx_CCR1. De lo contrario, el primer canal está activo.

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

Lea las hojas de datos 2: SPI en STM32; PWM, temporizadores e interrupciones en STM8
Bit 3 OC1PE: Habilita la precarga del pin 1
0: El registro de precarga en TIMx_CCR1 está deshabilitado. Puedes escribir a TIMx_CCR1 en cualquier momento. El nuevo valor funciona inmediatamente.
1: El registro de precarga en TIMx_CCR1 está habilitado. Las operaciones de lectura/escritura acceden al registro de precarga. El valor precargado TIMx_CCR1 se carga en el registro oculto durante cada evento de actualización.
*Nota: Para que el modo PWM funcione correctamente, los registros de precarga deben estar habilitados. Esto no es necesario en el modo de señal única (el bit OPM se establece en el registro TIMx_CR1).

Bien, encendamos todo lo que necesitamos 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 consta de dos registros de ocho bits, todo es sencillo:

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

TIM2_ARRH = 0;
TIM2_ARRL = 255;

El segundo cronómetro solo puede contar de abajo hacia arriba, alineado a lo largo del borde, no es necesario cambiar nada. Configuremos el divisor de frecuencia, por ejemplo, en 256. Para el segundo temporizador, el divisor se establece en el registro TIM2_PSCR y es una potencia de dos:

#define TIM2_PSCR  *(volatile uint8_t *)0x00530E

TIM2_PSCR = 8;

Todo lo que queda es encender las conclusiones y el segundo cronómetro. El primer problema se resuelve con registros. Capturar/Comparar permitir: hay dos, tres canales repartidos asimétricamente. Aquí también podemos aprender que es posible cambiar la polaridad de la señal, es decir. en principio, era posible utilizar el Modo PWM 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 por último, iniciamos el cronómetro en el registro TIMx_CR1:

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

#define TIM2_CR1   *(volatile uint8_t *)0x005300

TIM2_CR1 |= 1;

Escribamos un análogo simple de AnalogWrite(), que transferirá los valores reales al temporizador para compararlos. Los registros se nombran de forma predecible. Capturar/Comparar registros, hay dos de ellos para cada canal: los 8 bits de orden inferior en TIM2_CCRxL y los de orden superior en TIM2_CCRxH. Como hemos creado un PWM de 8 bits, basta con escribir solo los bits menos significativos:

#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 tenemos un PWM ligeramente defectuoso, incapaz de producir un llenado del 100% (a un valor máximo de 255, la señal se invierte durante un ciclo del temporizador). En el caso de los LED, esto no importa y el lector atento ya puede adivinar cómo solucionarlo.

PWM en el segundo temporizador funciona, pasemos al primero.

El primer temporizador tiene exactamente los mismos bits en los mismos registros (solo que los bits que permanecieron "reservados" en el segundo temporizador se utilizan activamente en el primero para todo tipo de cosas avanzadas). Por tanto, basta con buscar las direcciones de los mismos registros en la hoja de datos y copiar el código. Bueno, cambia el valor del divisor de frecuencia, porque... el primer temporizador no quiere recibir una potencia de dos, sino un valor exacto de 16 bits en dos registros Preescalador alto и Baja. Hacemos de todo y... el primer cronómetro no funciona. ¿Qué pasa?

El problema solo se puede solucionar mirando todo el apartado sobre los registros de control del temporizador 1, donde buscamos el que no tiene el segundo temporizador. Habrá 17.7.30 Registro de ruptura (TIM1_BKR), donde está este bit:

Lea las hojas de datos 2: SPI en STM32; PWM, temporizadores e interrupciones en STM8
Habilitar salida principal

#define TIM1_BKR   *(volatile uint8_t *)0x00526D

TIM1_BKR = (1<<7);

Eso es todo seguro ahora, el código. ibid.

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

Múltiplex STM8

Multiplexación en STM8

El tercer miniproyecto consiste en conectar ocho LED RGB al segundo temporizador en modo PWM y hacer que muestren diferentes colores. Se basa en el concepto de multiplexación de LED, que consiste en que si enciendes y apagas los LED muy, muy rápidamente, nos parecerá que están constantemente encendidos (persistencia de la visión, inercia de la percepción visual). una vez lo hice algo como esto en Arduino.

El algoritmo de trabajo se ve así:

  • conectó el ánodo del primer LED RGB;
  • lo encendió, enviando las señales necesarias a los cátodos;
  • esperó hasta el final del ciclo PWM;
  • conectó el ánodo del segundo LED RGB;
  • Enciéndalo...

Bueno, etc Por supuesto, para un buen funcionamiento es necesario que el ánodo esté conectado y el LED esté "encendido" al mismo tiempo. Bueno, o casi. En cualquier caso, necesitamos escribir un código que generará valores en tres canales del segundo temporizador, los cambiará cuando se alcance UEV y al mismo tiempo cambiará el LED RGB actualmente activo.

Dado que el cambio de LED es automático, necesitamos crear una "memoria de video" desde la cual el controlador de interrupciones recibirá datos. Esta es una matriz simple:

uint8_t colors[8][3];

Para cambiar el color de un LED específico, bastará con escribir los valores requeridos en esta matriz. Y la variable será responsable del número del LED activo.

uint8_t cnt;

Demux

Para una multiplexación adecuada, necesitamos, curiosamente, un demultiplexor CD74HC238. Demultiplexor: un chip que implementa el operador en hardware <<. A través de tres pines de entrada (bits 0, 1 y 2) le introducimos un número X de tres bits y, en respuesta, activa el número de salida (1<<X). Las entradas restantes del chip se utilizan para escalar todo el diseño. Necesitamos este chip no solo para reducir la cantidad de pines ocupados del microcontrolador, sino también por seguridad, para no encender accidentalmente más LED de los posibles y no quemar el MK. El chip cuesta un centavo y siempre debe guardarse en el botiquín de su casa.

Nuestro CD74HC238 será el encargado de suministrar tensión al ánodo del LED deseado. En un multiplex completo, suministraría voltaje a la columna a través de un P-MOSFET, pero en esta demostración es posible directamente, porque consume 20 mA, según Índices absolutos máximos en la hoja de datos. De Hoja de datos CD74HC238 Necesitamos pines y esta hoja de trucos:

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

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

PWM

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

Primero, necesitamos habilitar la interrupción en Actualizar evento (UEV) que llamará a una función que alterna el LED activo. Esto se hace cambiando la broca. Activar interrupción de actualización en un registro con un nombre revelador

Lea las hojas de datos 2: SPI en STM32; PWM, temporizadores e interrupciones en STM8
registro de habilitación de interrupción

#define TIM2_IER   *(volatile uint8_t *)0x005303

//enable interrupt
TIM2_IER = 1;

La segunda diferencia está relacionada con el fenómeno de la multiplexación, como fantasma – resplandor parásito de diodos. En nuestro caso, esto puede aparecer debido al hecho de que el temporizador, después de haber provocado una interrupción en el UEV, continúa funcionando y el controlador de interrupciones no tiene tiempo de encender el LED antes de que el temporizador comience a escribir algo en los pines. Para combatir esto, tendrás que invertir la lógica (0 = brillo máximo, 255 = no hay nada encendido) y evitar valores extremos del ciclo de trabajo. Aquellos. asegúrese de que después de UEV los LED se apaguen por completo durante un ciclo PWM.

Cambio de polaridad:

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

Evite configurar r, gyb en 255 y recuerde invertirlos cuando los use.

Interrupciones

La esencia de una interrupción es que, bajo determinadas circunstancias, el chip deja de ejecutar el programa principal y llama a alguna función externa. Las interrupciones se producen debido a influencias externas o internas, incluido el temporizador.

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

La hoja de datos tiene una tabla de vectores de interrupción, donde encontramos los que necesitamos:

Lea las hojas de datos 2: SPI en STM32; PWM, temporizadores e interrupciones en STM8
13 Actualización/desbordamiento de TIM2
14 captura/comparación TIM2

Necesitamos cambiar el LED en UEV, por lo que necesitamos la interrupción n.° 13.

Así pues, en primer lugar, en el expediente stm8_interrupt_vector.c cambie el nombre predeterminado de la función responsable de la interrupción número 13 (IRQ13) por el suyo propio:

{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, escribe esta función en tu main.c:

@far @interrupt void TIM2_Overflow (void)
{
    PD_ODR &= ~(1<<5); // вырубаем демультиплексор
    PC_ODR = (cnt<<3); // записываем в демультиплексор новое значение
    PD_ODR |= (1<<5); // включаем демультиплексор

    TIM2_SR1 = 0; // сбрасываем флаг Update Interrupt Pending

    cnt++; 
    cnt &= 7; // двигаем счетчик LED

    TIM2_CCR1L = ~colors[cnt][0]; // передаем в буфер инвертированные значения
    TIM2_CCR2L = ~colors[cnt][1]; // для следующего цикла ШИМ
    TIM2_CCR3L = ~colors[cnt][2]; // 

    return;
}

Todo lo que queda es habilitar las interrupciones. Esto se hace usando el comando ensamblador. rim - tendrás que buscarlo en Manual de programación:

//enable interrupts
_asm("rim");

Otro comando del ensamblador es sim – desactiva las interrupciones. Deben estar apagados mientras se escriben nuevos valores en la “memoria de video”, para que una interrupción causada en el momento equivocado no estropee la matriz.

Todo el código - en GitHub.

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

Si al menos alguien encuentra útil este artículo, entonces no lo escribí en vano. Estaré encantado de recibir comentarios y observaciones, intentaré responder a todo.

Fuente: habr.com

Añadir un comentario