
En la primera parte, traté de decirles a los aficionados a la electrónica que surgieron de los pantalones Arduino cómo y por qué deberían leer hojas de datos y otra documentación para microcontroladores. El texto resultó ser extenso, así que prometí mostrar ejemplos prácticos en un artículo separado. Bueno, se llamó a sí mismo un cargamento ...
Hoy mostraré cómo usar hojas de datos para resolver tareas bastante simples, pero necesarias para muchos proyectos, en los controladores STM32 (Blue Pill) y STM8. Todos los proyectos de demostración están dedicados a mis LED favoritos, los iluminaremos en grandes cantidades, para lo cual tendremos que usar todo tipo de periféricos interesantes.
El texto resultó ser enorme nuevamente, así que por conveniencia estoy haciendo el contenido:
Píldora azul STM32: 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 un conocimiento profundo en electrónica, el artículo está destinado a amantes como yo. De hecho, como público objetivo, me consideré hace dos años. Si alguien me dijera que no da miedo leer hojas de datos en un chip desconocido, no habría pasado mucho tiempo buscando algunos códigos en Internet e inventando muletas con tijeras y una curita.
En el centro de este artículo hay hojas de datos, no proyectos, por lo que es posible que el código no se peine demasiado y, a menudo, se forje. Los proyectos en sí mismos son muy simples, aunque adecuados para un primer contacto con el nuevo chip.
Espero que mi artículo ayude a alguien en una etapa similar en una inmersión de hobby.
STM32
16 LEDs con DM634 y SPI
Un pequeño proyecto con la píldora azul (STM32F103C8T6) y el controlador LED DM634. Con la ayuda de hojas de datos, trataremos con el controlador, IO-ports STM y configuraremos SPI.
DM634
Chip taiwanés con 16 salidas PWM de 16 bits, se puede conectar en cadenas. El modelo más joven de 12 bits es conocido por el proyecto doméstico Lightpack . En un 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 falso, pero este no lo es; 2) el DM tiene un PWM autónomo con su propio generador de frecuencia; 3) podría comprarse a bajo precio en Moscú, y no esperar un paquete con Ali. Bueno, por supuesto, fue interesante aprender a administrar el chip usted mismo y no usar una biblioteca preparada. Los chips ahora se presentan principalmente en el paquete SSOP24, son fáciles de soldar al adaptador.
Como el fabricante es taiwanés, la hoja de datos para el chip está escrita en inglés chino, lo que significa que será divertido. Primero, mire la conexión del pin ( Conexión del pin ) para comprender a qué pata conectarse y la descripción de los pines ( Descripción del pin ). 16 conclusiones:

Fuentes de entrada de corriente continua (drenaje abierto)
Salida de sumidero / drenaje abierto - drenaje; fuente de corriente entrante; salida activa conectada a tierra: los LED están conectados al controlador por cátodos. Eléctricamente, esto, por supuesto, no es un "drenaje abierto", pero en las hojas de datos esta designación para salidas en modo de drenaje es común.

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 la tierra, que controla la resistencia interna de las salidas, consulte el gráfico en la página 9 de la hoja de datos. En DM634, esta resistencia también se puede controlar mediante programación configurando el brillo global ; No entraré en detalles en este artículo, solo puse una resistencia de 2.2 - 3 kOhm aquí.
Para comprender cómo controlar el chip, mire la descripción de la interfaz del dispositivo:

Sí, aquí está, inglés chino en todo su esplendor. Es problemático traducir, puede comprenderlo si lo desea, pero hay otra forma: ver cómo se describe la conexión en la hoja de datos del TLC5940 funcionalmente cercano:

... Solo se requieren tres pines para ingresar datos en el dispositivo. El borde de ataque de la señal SCLK desplaza los datos del pin SIN al registro interno. Después de que se hayan descargado todos los datos, una breve señal XLAT alta captura los datos en serie en registros internos. Registros internos: válvulas de compuerta activadas por XLAT. Todos los datos se transmiten en el bit hacia adelante más significativo.
Pestillo : un pestillo / pestillo / abrazadera.
Borde ascendente - borde de ataque del impulso
MSB primero : el bit más significativo (más a la izquierda) hacia adelante.
para registrar datos : transmita datos secuencialmente (bit a bit).
La palabra pestillo a menudo se encuentra en la documentación para chips y se traduce de varias maneras, por lo que me permitiré entender
pequeño programa educativoEl controlador LED es esencialmente un registro de desplazamiento. "Shift" en el nombre es un movimiento de datos dentro del dispositivo: cada nuevo bit empujado hacia adentro empuja toda la cadena frente a sí mismo. Dado que nadie quiere ver el destello caótico de los LED durante el turno, el proceso se lleva a cabo en registros de búfer separados de los que funcionan por un pestillo ; este es un tipo de sala de espera, donde los bits están dispuestos en la secuencia correcta. Cuando todo está listo, el obturador se abre y los bits se envían a trabajar, reemplazando el lote anterior. La palabra pestillo en la documentación para microcircuitos casi siempre implica tal amortiguador, sin importar en qué combinación se use.
Entonces, la transferencia de datos al DM634 es la siguiente: establezca la entrada DAI en el bit de orden superior del LED lejano, jale el DCK hacia arriba y hacia abajo; establezca la entrada DAI al siguiente bit, extraiga DCK; y así sucesivamente, hasta que se transmitan todos los bits ( sincronizados ), luego de lo cual extraemos el LAT. Esto se puede hacer manualmente ( bit-bang ), pero es mejor usar la interfaz SPI afilada especialmente para esto, ya que se presenta en nuestro STM32 en dos copias.
Tableta azul STM32F103
Introductorio: los controladores STM32 son mucho más complicados que Atmega328 de lo que pueden asustar. Al mismo tiempo, por razones de ahorro de energía, casi todos los periféricos están desactivados al inicio, y la frecuencia del reloj es de 8 MHz desde una fuente interna. Afortunadamente, los programadores de STM escribieron un código que llevó el chip a los 72 MHz "calculados", y los autores de todos los IDE que conocía lo incluyeron en el procedimiento de inicialización, por lo que no es necesario que lo registremos (pero puede hacerlo , si realmente lo desea ). Pero tienes que encender los periféricos.
Documentación: El popular chip STM32F103C8T6 está instalado en la píldora azul, hay dos documentos útiles para ello:
En una hoja de datos podemos estar interesados en:
- Pinouts - pinouts de chips - en caso de que decidamos hacer tablas nosotros mismos;
- Mapa de memoria: tarjeta de memoria para un chip específico. En el Manual de referencia hay una tarjeta para toda la línea, enumera los registros que no están en el nuestro.
- Definiciones de pines de tabla: enumera las funciones principales y alternativas de los pines; para la "píldora azul" en Internet, puede encontrar imágenes más convenientes con una lista de pines y sus funciones. Por lo tanto, busque en Google el pinout Blue Pill y tenga a mano esta imagen:

NB: en la imagen de Internet se notó un error en los comentarios, por lo que gracias. La imagen ha sido reemplazada, pero esta es una lección: es mejor verificar la información de las hojas que no son de datos.
Eliminamos la hoja de datos, abrimos el Manual de referencia, ahora solo la usamos.
Procedimiento: nos ocupamos de la entrada / salida estándar, configuramos SPI, activamos los periféricos deseados.
Entrada-salida
Atmega328 I / O es extremadamente simple, por lo que la abundancia de opciones de STM32 puede ser confusa. Ahora solo necesitamos conclusiones, pero incluso hay cuatro opciones:

salida de drenaje abierto, salida de contrafase, salida alternativa de contrafase, drenaje abierto alternativo
" Push-pull " ( push-pull ): la conclusión habitual de Arduina, el pin puede ser ALTO o BAJO. Pero con el "drenaje abierto" hay dificultades , aunque en realidad todo es simple aquí:


Configuración de salida / cuando el puerto está asignado a la salida: / el búfer de salida está activado: / - modo de drenaje abierto: "0" activa N-MOS en el registro de salida, "1" deja el puerto en modo Hi-Z en el registro de salida (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.
La diferencia completa entre drenaje abierto y push-pull es que en el primer pin no puede aceptar el estado ALTO: al escribir una unidad en el registro de salida, cambia a alta impedancia , hola -Z ). Al grabar cero, el pin en ambos modos se comporta igual, tanto lógica como eléctricamente.
En el modo de salida normal, el pin simplemente traduce el contenido del registro de salida. En la "alternativa" está controlada por la periferia correspondiente (ver 9.1.4):

Si el bit de puerto se configura como una salida de función alternativa, el registro de salida se deshabilita y el pin se conecta a la señal de salida periférica
Una funcionalidad alternativa para cada pin se describe en la hoja de datos de Definiciones de Pin y se encuentra en la imagen descargada. Cuando se le pregunta qué hacer si el pin tiene varias funciones alternativas, la respuesta da una nota al pie en la hoja de datos:

Si varias unidades periféricas usan el mismo pin, para evitar un conflicto entre funciones alternativas, solo se debe usar una unidad periférica a la vez, cambiando usando el bit de activación del reloj periférico (en el registro RCC correspondiente).
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 lo configuramos al máximo y lo olvidamos.
Entonces: utilizamos SPI, por lo que dos pines (con datos y una señal de reloj) deberían ser una "función push-push alternativa", y otra (LAT) debería ser "pull-push normal". Pero antes de asignarlos, trataremos con SPI.
SPI
Otro pequeño programa educativoSPI o interfaz periférica en serie (interfaz periférica en serie): una interfaz simple y muy efectiva para la comunicación MK con otras MK y el mundo exterior en general. El principio de su funcionamiento ya se ha descrito anteriormente, donde se trata del controlador LED chino (en el manual de referencia, consulte la sección 25). SPI puede funcionar en modo maestro ("maestro") y esclavo ("esclavo"). SPI tiene cuatro canales básicos, de los cuales no todos pueden estar involucrados:
- MOSI, Salida maestra / Entrada esclava: este pin en el modo maestro envía, pero en el modo esclavo recibe datos;
- MISO, entrada maestra / salida esclava: por el contrario, en el maestro acepta, en el esclavo da;
- SCK, Serial Clock: establece la frecuencia de transferencia de datos en el maestro o recibe una señal de reloj en el esclavo. Esencialmente late bits;
- SS, Slave Select: a través de este canal, el esclavo aprende que 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 una historia diferente.
Como todo lo demás, el SPI en el STM32 es rico en funcionalidad, lo que hace que sea algo difícil de entender. Por ejemplo, puede funcionar no solo con el SPI, sino también con la interfaz I2S, y en la documentación sus descripciones están mezcladas, debe cortar el exceso de manera oportuna. Nuestra tarea es extremadamente simple: solo necesita enviar datos utilizando solo MOSI y SCK. Vamos a la sección 25.3.4 (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):

En este modo, la aplicación usa SPI en modo de solo transmisión o solo de 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 entrada-salida regular. En este caso, es suficiente que la aplicación ignore el búfer Rx (si lo lee, no habrá datos transmitidos).
Bueno, el pin MISO está libre de nosotros, conectemos la señal LAT a él. Nos ocuparemos de Slave Select, que puede controlarse mediante programación en el STM32, lo cual es extremadamente conveniente. Leemos el párrafo del mismo nombre en la sección 25.3.1 de la Descripción general de SPI:

Control de programa NSS (SSM = 1) / La información sobre la selección del esclavo está contenida en el bit SSI del registro SPI_CR1. El pin NSS externo permanece libre para otras necesidades de aplicación.
Es hora de escribir en los registros. Decidí usar SPI2, estamos buscando la dirección base en la hoja de datos, en la sección 3.3 Mapa de memoria:

Bueno, comenzamos:
#define _SPI2_(mem_offset) (*(volatile uint32_t *)(0x40003800 + (mem_offset)))
Abrimos la sección 25.3.3 con el dicho "Configuración de SPI en modo maestro":

1. Configure la velocidad del reloj serial con los bits BR [2: 0] en el registro SPI_CR1.
Los registros se recopilan en la sección del manual de referencia del mismo nombre. CR1 tiene un desplazamiento de dirección ( desplazamiento de dirección ) de 0x00, por defecto todos los bits se restablecen ( valor de restablecimiento 0x0000):

Los bits BR establecen el divisor de frecuencia del reloj del controlador, determinando así la frecuencia a la que operará el SPI. Tendremos una frecuencia STM32 de 72 MHz, el controlador LED, según su hoja de datos, funciona con una frecuencia de hasta 25 MHz, por lo que debe dividirlo en 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;
2. Configure los bits CPOL y CPHA para determinar la relación entre la transferencia de datos y la sincronización de la interfaz en serie (consulte el diagrama en la página 240)
Como estamos leyendo hojas de datos aquí, y sin considerar los circuitos, estudiemos mejor la descripción textual de los bits de CPOL y CPHA en la página 704 (Descripción general de SPI):

Fase de reloj y polaridad
Usando los bits CPOL y CPHA del registro SPI_CR1, se pueden seleccionar cuatro opciones para las relaciones de temporización mediante programación. El bit CPOL (polaridad del reloj) controla el estado del reloj cuando no se transmiten datos. Este bit controla los modos maestro y esclavo. Si se restablece el CPOL, el pin SCK está bajo en modo inactivo. Si se establece el bit CPOL, el pin SCK está en un nivel alto en modo inactivo.
Si se establece el bit CPHA (fase de reloj), el segundo borde de la señal SCK actúa como una puerta trampa del bit alto (descendente si se borra CPOL o ascendente si se establece CPOL). Los datos son capturados por el segundo cambio en la señal del reloj. Si se borra el bit CPHA, el borde de ataque de la señal SCK actúa como una puerta trampa del bit alto (hacia abajo si se establece CPOL, o hacia arriba si se borra CPOL). Los datos son capturados por el primer cambio en la señal del reloj.
Habiendo fumado este conocimiento, concluimos que ambos bits deben permanecer ceros, porque necesitamos que la señal SCK permanezca baja cuando no esté en uso, y que los datos se transmitan a lo largo del borde delantero del pulso (consulte el borde ascendente en la hoja de datos del DM634).
Por cierto, aquí encontramos por primera vez una característica del vocabulario en las hojas de datos ST: en ellas, la frase "restablecer bit a cero" se escribe para restablecer un bit y no para borrar un bit , como, por ejemplo, en Atmega.
3. Configure el bit DFF para definir un formato de bloque de datos de 8 o 16 bits.
Tomé específicamente el DM634 de 16 bits, para no molestarme con la transmisión de datos PWM de 12 bits, como el DM633. DFF tiene sentido poner en una unidad:
#define DFF 0x0800 _SPI2_ (_SPI_CR1) |= DFF;
4. Configure el bit LSBFIRST en el registro SPI_CR1 para determinar el formato de bloque
LSBFIRST, como su nombre lo indica, establece el bit de orden inferior hacia adelante. Pero el DM634 quiere recibir datos comenzando con el bit alto. Por lo tanto, lo dejamos descartado.
5. En modo hardware, si se requiere la entrada del pin NSS, envíe una señal alta al pin NSS durante toda la secuencia de transferencia de bytes. En el modo de programa NSS, configure los bits SSM y SSI en el registro SPI_CR1. Si el pin NSS debería funcionar en la salida, solo se debe establecer el bit SSOE.
Instale SSM y SSI para olvidarse del modo de hardware NSS:
#define SSI 0x0100 #define SSM 0x0200 _SPI2_ (_SPI_CR1) |= SSM | SSI;
6. Los bits MSTR y SPE deben establecerse (permanecen establecidos solo si se aplica una señal alta al NSS)
En realidad, con estos bits asignamos nuestro SPI como maestro y lo activamos:
#define MSTR 0x0004 #define SPE 0x0040 _SPI2_ (_SPI_CR1) |= MSTR;
SPI está configurado, escribamos funciones que envíen bytes al controlador de inmediato. Continuamos leyendo 25.3.3 "Configuración de SPI en modo maestro":

Procedimiento 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 modo paralelo (desde el bus interno) durante la transmisión del primer bit, después de lo cual se transmite en modo serie al pin MOSI, el primer o el ú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 que los datos se transfieren del búfer Tx al registro de desplazamiento , y también se crea una interrupción si el bit TXEIE se establece en el registro CPI_CR1.
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, el indicador TXE ( Tx vacío , Tx está vacío y listo para recibir datos) se establece solo después de que se haya apagado todo el byte. Y aquí esta bandera se establece después de que el byte se inserta en el registro de desplazamiento interno. Dado que todos los bits empujan allí simultáneamente (en paralelo), y luego los datos se transmiten secuencialmente, TXE se establece antes de que el byte se envíe completamente. Esto es importante porque en el caso de nuestro controlador LED, necesitamos extraer el pin LAT después de enviar todos los datos, es decir Solo la bandera TXE no será suficiente para nosotros.
Y esto significa que necesitamos otra bandera. Veamos en 25.3.7 - "Indicadores de estado":

<...>

Bandera ocupada
El indicador BSY se establece y restablece por hardware (escribir en él no afecta nada). El indicador BSY indica el estado de la capa de comunicación SPI.
Se reinicia:
cuando se completa la transferencia (excepto para el modo maestro, si la transferencia es continua)
cuando SPI está deshabilitado
cuando se produce un error en el modo asistente (MODF = 1)
Si la transmisión no es continua, el indicador BSY se borra entre cada transmisión de datos.
De acuerdo, ven a la mano. Descubrimos dónde se encuentra el búfer Tx. Para hacer esto, lea el "Registro de datos SPI":

Bits 15: 0 DR [15: 0] Registro de datos
Datos recibidos o datos para la transmisión.
El registro de datos se divide en dos memorias intermedias: una para escribir (memoria intermedia de transmisión) y la segunda para leer (memoria intermedia de recepción). Escribir en el registro de datos escribe en el búfer Tx, y leer desde el registro de datos devolverá el valor contenido en el búfer Rx.
Bueno, el registro de estado, donde hay banderas TXE y BSY:

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;
Bueno, dado que necesitamos transmitir 16 bytes dos veces, de acuerdo con el número 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);
Pero todavía no sabemos cómo extraer el pin LAT, por lo que volveremos a E / S.
Asignar pasadores
En STM32F1, los registros responsables del estado de los pines son bastante inusuales. Está claro que hay más de ellos que Atmega, pero también difieren de otros chips STM. Sección 9.1 Descripción general de GPIO:

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 restablecimiento / restablecimiento de 32 bits (GPIOx_BSRR), 16 bits un registro de reinicio (GPIOx_BRR) y un registro de bloque de 32 bits (GPIOx_LCKR).
Inusuales, además de bastante inconvenientes, los primeros dos registros están aquí, porque 16 pines de puerto están dispersos sobre ellos en el formato "cuatro bits por hermano". Es decir los pines del cero al séptimo están en la CRL, y el resto en el CRH. Al mismo tiempo, el resto de los registros caben con éxito en los bits de todos los pines del puerto, a menudo quedando medio "reservado".
Para simplificar, comience al final de la lista.
No necesitamos un registro de bloque.
Los registros de configuración y restablecimiento son bastante divertidos, ya que se duplican parcialmente entre sí: puede escribir todo solo en BSRR, donde los 16 bits superiores restablecerán el pin a cero, y los inferiores, configurados en 1, o usar también BRR, los 16 bits inferiores de los cuales solo restablecen el pin . Me gusta la segunda opción. Estos registros son importantes porque proporcionan acceso atómico a los pines:


Instalación atómica o reinicio
No necesita deshabilitar las interrupciones al programar GPIOx_ODR a nivel de bit: puede cambiar uno o más bits con una operación de escritura atómica APB2. Esto se logra escribiendo "1" en el registro de set / reset (GPIOx_BSRR o, solo para reset, en GPIOx_BRR) el bit que desea cambiar. Otros bits permanecerán sin cambios.
Los registros de datos tienen nombres bastante comunes: IDR = Registro de dirección de entrada, registro de entrada; ODR = Registro de dirección de salida, registro de salida. En el proyecto actual no los necesitaremos.
Y finalmente, los registros de control. Dado que estamos interesados en los pines del segundo SPI, a saber, PB13, PB14 y PB15, miramos inmediatamente a CRH:

Y vemos que será necesario escribir algo en bits del 20 al 31.
Ya hemos descubierto lo que queremos de los pines, así que aquí puedo prescindir de una captura de pantalla, solo digo que MODE establece la dirección (entrada, si ambos bits están configurados en 0) y la velocidad del pin (necesitamos 50MHz, es decir, ambos pin en "1"), y CNF establece el modo: normal "push-push" - 00, "alternativa" - 10. Por defecto, como vemos arriba, todos los pines tienen el tercer bit desde abajo (CNF0), los establece en modo de 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 inferior y superior.
Bueno algo como esto #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 se encuentran en el puerto B (la dirección base es 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
Y, en consecuencia, puede escribir definiciones para el LAT, que moverá los registros BRR y BSRR:
#define LAT_pulse() _PORTB_(_BSRR) = (1<<14); _PORTB_(_BRR) = (1<<14) #define LAT_low() _PORTB_(_BRR) = (1<<14)
(LAT_low solo por inercia, de alguna manera siempre fue así, déjate quedar)
Ahora todo ya está genial, simplemente no funciona. Debido a que es STM32, ahorra electricidad, lo que significa que debe habilitar el reloj para los periféricos necesarios.
Activar el tiempo
El reloj es el responsable del reloj, también son Clock. Y ya pudimos ver la abreviatura RCC. Lo estamos buscando en la documentación: esto es Restablecer y Control de reloj.
Como se dijo anteriormente, afortunadamente, la gente de STM hizo la parte más difícil del tema del tiempo para nosotros, por lo que agradecemos mucho (una vez más, le daré un enlace al sitio web de Di Halt para aclarar cuán confundido está). Solo necesitamos los registros responsables de habilitar los relojes periféricos (Peripheral Clock Enable Registers). Primero, encuentre la dirección base de RCC, está al comienzo de la "Tarjeta de memoria":

#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, repase las descripciones de los registros de inclusión de las secciones sobre habilitar registros . Donde encontramos RCC_APB1ENR y RCC_APB2ENR:


Y en ellos, respectivamente, bits, incluido el marcado SPI2, IOPB (I / O Port B) y funciones alternativas (AFIO).
#define _APB2ENR 0x18 #define _APB1ENR 0x1C #define IOPBEN 0x0008 #define SPI2EN 0x4000 #define AFIOEN 0x0001
El código final se puede encontrar aquí .
Si hay una oportunidad y deseo de probar, entonces conectamos DM634 de esta manera: DAI a PB15, DCK a PB13, LAT a PB14. Alimentamos al conductor desde 5 voltios, no olvides combinar la tierra.

STM8 PWM
PWM en STM8
Cuando estaba planeando este artículo, por ejemplo, decidí intentar aprender algunas funcionalidades de un chip desconocido con solo una hoja de datos, para no tener un zapatero sin botas. STM8 se adaptaba perfectamente a 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 reside en la ausencia de estas soluciones.
El chip también tiene una hoja de datos y un manual de referencia RM0016 , en el primer pinout y direcciones de registro, en el segundo, todo lo demás. STM8 está programado en C en el feo IDE ST Visual Develop .
Reloj y E / S
Por defecto, el STM8 funciona a una frecuencia de 2 MHz, esto debe ser reparado de inmediato.

Reloj HSI (velocidad interna)
El reloj HSI se obtiene de un oscilador RC interno de 16 MHz con un divisor programable (1 a 8). Se establece en el registro del divisor de reloj (CLK_CKDIVR).
Nota: al comienzo, el oscilador HSI RC con divisor 8 se selecciona como la fuente de reloj principal.
Encontramos la dirección del registro en la hoja de datos, la descripción en refman y vemos que el registro debe borrarse:
#define CLK_CKDIVR *(volatile uint8_t *)0x0050C6 CLK_CKDIVR &= ~(0x18);
Como vamos a iniciar PWM y conectar los LED, miramos el pinout:

El chip es pequeño, muchas funciones están suspendidas en los mismos pines. El hecho de que entre corchetes es "funcionalidad alternativa", se cambia por " bytes de opción ", algo así como los fusibles de Atmega. Puede cambiar sus valores mediante programación, pero no es necesario, porque La nueva funcionalidad se activa solo después de un reinicio. Es más fácil usar ST Visual Programmer (descargas junto con Visual Develop) que puede cambiar estos bytes. El pinout muestra que las conclusiones de CH1 y CH2 del primer temporizador están ocultas entre corchetes; es necesario poner los bits AFR1 y AFR0 en STVP, y el segundo también transferirá la salida de CH1 del segundo temporizador de PD4 a PC5.
Por lo tanto, 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 el STM8 es más simple y lógico que en el STM32:
- familiarizado con el registro de dirección de datos de Atmega: 1 = salida;
- el primer registro de control CR1 en la salida establece el modo push-pull (1) o drenaje abierto (0); Como conecto los LED al chip con cátodos, dejo aquí ceros;
- el segundo registro de control CR2 en la salida 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 PWM
Primero, definamos los términos:
- Frecuencia PWM : frecuencia con la que el temporizador está marcando;
- Recarga automática, AR : valor de carga automática con el que contará el temporizador (período de pulso);
- Actualizar evento, UEV : un evento que ocurre cuando el temporizador ha contado a AR;
- Ciclo de trabajo PWM - Ciclo de trabajo PWM, a menudo denominado "ciclo de trabajo";
- Capture / Compare Value (Valor de captura / comparación): valor de captura / comparación, habiendo contado hasta qué momento el temporizador hará algo (en el caso de PWM, invierte la señal de salida);
- Valor de precarga: valor precargado. El valor de comparación no puede cambiar mientras el temporizador está funcionando; de lo contrario, el ciclo PWM se interrumpirá. Por lo tanto, los nuevos valores transmitidos se colocan en el búfer y se extraen de allí cuando el temporizador llega al final del conteo y se reinicia;
- Modos alineados al borde y alineados al centro : alineación en el borde y en el centro, lo mismo que Atmelovskie Fast PWM y Phase-correct PWM .
- OCiREF, Señal de referencia de comparación de salida : la señal de salida de referencia, de hecho, que en el modo PWM está en el pin correspondiente.
Como ya se desprende del pinout, dos temporizadores tienen las capacidades PWM: el primero y el segundo. Ambos son de 16 bits, el primero tiene muchas características adicionales (en particular, puede contar tanto hacia arriba como hacia abajo). Necesitamos que ambos trabajen de la misma manera, así que decidí comenzar con el segundo obviamente más pobre, para no usar accidentalmente algo que no está en él. 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 de un lado a otro del documento.
PWM en STM8 tiene una ventaja importante sobre PWM Atmega:

PWM con alineación de bordes
Configuración de cuenta de abajo hacia arriba
El recuento ascendente está activo si el bit DIR en el registro TIM_CR1 se borra
Ejemplo
El ejemplo usa el primer modo PWM. La señal de referencia OCiREF PWM se mantiene alta mientras TIM1_CNT <TIM1_CCRi. De lo contrario, se necesita un nivel bajo. Si el valor de comparación en TIM1_CCRi es mayor que el valor de inicio (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 una señal de referencia. El temporizador de Atmega se baraja primero y luego se compara, como resultado de lo cual, cuando se compare value == 0
la salida da como resultado una aguja que necesita ser combatida de alguna manera (por ejemplo, mediante la lógica de inversión programática).
Entonces, lo que queremos hacer: PWM de 8 bits ( AR == 255
), consideramos de abajo hacia arriba, la alineación a lo largo del borde. Como las bombillas están conectadas al chip por cátodos, el PWM debe emitir 0 (el LED está encendido) antes de comparar el valor y 1 después.
Ya leímos acerca de algún modo PWM , por lo que encontramos el registro deseado del segundo temporizador buscando en el manual de referencia esta frase (18.6.8 - TIMx_CCMR1):

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 copiar y pegar erróneamente desde el 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 a los cátodos MK, el segundo modo es adecuado para nosotros (el primero también, pero aún no lo sabemos).

Bit 3 OC1PE: Habilitar precarga de salida 1
0: registro de precarga en TIMx_CCR1 apagado. Puede escribir a TIMx_CCR1 en cualquier momento. El nuevo valor funciona de inmediato.
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 sombra durante cada evento de actualización.
* Nota: para que el modo PWM funcione correctamente, los registros de precarga deben estar activados. ( TIMx_CR1 OPM).
, , , :
#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 , :
#define TIM2_ARRH *(volatile uint8_t *)0x00530F #define TIM2_ARRL *(volatile uint8_t *)0x005310 TIM2_ARRH = 0; TIM2_ARRL = 255;
-, , . , , 256. TIM2_PSCR :
#define TIM2_PSCR *(volatile uint8_t *)0x00530E TIM2_PSCR = 8;
. Capture/Compare Enable : , . , , .. PWM Mode 1. :
#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;
, , TIMx_CR1:

#define TIM2_CR1 *(volatile uint8_t *)0x005300 TIM2_CR1 |= 1;
AnalogWrite(), . Capture/Compare registers , : 8 TIM2_CCRxL TIM2_CCRxH. 8- , :
#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; }
, , 100% ( 255 ). , , .
, .
( , «» , ). . , .. , 16- Prescaler High Low . … . ?
1, , . 17.7.30 Break register (TIM1_BKR) , :

#define TIM1_BKR *(volatile uint8_t *)0x00526D TIM1_BKR = (1<<7);
, .

STM8 Multiplex
STM8
- , RGB- . – LED-, , - , , ( persistence of vision , ). - - .
:
.. , , «» . . , , , UEV RGB-.
LED , «», . :
uint8_t colors[8][3];
, , .
uint8_t cnt;
, , CD74HC238. – , <<
. ( 0, 1 2) X, ( 1<<X
). . , – , , . , .
CD74HC238 , . P-MOSFET, , .. 20 , absolute maximum ratings . CD74HC238 :

H = , L = , X –
E2 E1 , E3, A0, A1 A3 PD5, PC3, PC4 PC5 STM8. , , push-pull .
, , :
-, Update Event (UEV), , LED. Update Interrupt Enable

#define TIM2_IER *(volatile uint8_t *)0x005303 //enable interrupt TIM2_IER = 1;
, ghosting – . - , , UEV, , LED - . (0 = , 255 = ) . Es decir , UEV .
:
//set polarity TIM2_CCER1 |= (CC1P | CC2P); TIM2_CCER2 |= CC3P;
r, g b 255 .
, - . - , .
ST Visual Develop, main.c
stm8_interrupt_vector.c
, . NonHandledInterrupt
. .
, :

13 TIM2 /
14 TIM2 /
LED UEV, №13.
, -, stm8_interrupt_vector.c
, №13 (IRQ13) :
{0x82, TIM2_Overflow}, /* irq13 */
-, main.h
:
#ifndef __MAIN_H #define __MAIN_H @far @interrupt void TIM2_Overflow (void); #endif
, , 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; }
. rim
– Programming Manual :
//enable interrupts _asm("rim");
– sim
– . «», .
– .

- , , . , .