Contactos

Temporizadores básicos en STM32. Trabajar con temporizadores simples Descubrimiento STM32 F4 Interrupciones del temporizador Stm32

El artículo describe los temporizadores de microcontroladores ARM de 32 bits de la serie STM32 de STMicroelectronics. Se consideran la arquitectura y composición de los registros del temporizador básicos y se dan ejemplos prácticos de programas.

Para cualquier microcontrolador, el temporizador es uno de los componentes más importantes, que le permite contar intervalos de tiempo con mucha precisión, contar los pulsos que llegan a las entradas, generar interrupciones internas, generar señales con modulación de ancho de pulso (PWM) y admitir acceso directo a la memoria ( DMA) procesos.

El microcontrolador STM32 contiene varios tipos de temporizadores que se diferencian entre sí en su funcionalidad. El primer tipo de temporizadores es el más simple y son los temporizadores básicos. A este tipo pertenecen los temporizadores TIM6 y TIM7. Estos temporizadores son muy fáciles de configurar y controlar utilizando un mínimo de registros. Son capaces de contar intervalos de tiempo y generar interrupciones cuando el temporizador alcanza un valor específico.
El segundo tipo son los temporizadores de uso general. Esto incluye los temporizadores TIM2 a TIM5 y los temporizadores TIM12 a TIM17. Pueden generar PWM, contar pulsos que llegan a ciertos pines del microcontrolador, procesar señales del codificador, etc.

El tercer tipo define temporizadores con control avanzado (Advanced-Control Timer). Este tipo incluye el temporizador TIM1, que es capaz de realizar todas las operaciones anteriores. Además, a partir de este temporizador se puede construir un dispositivo capaz de controlar un accionamiento eléctrico trifásico.

Dispositivo temporizador básico

Consideremos el diseño y funcionamiento de un temporizador básico, cuyo diagrama de bloques se muestra en la figura. El temporizador básico se basa en registros de 16 bits. Su base es el registro de contaje TIMx_CNT. (En adelante, el símbolo "x" reemplaza el número 6 o 7 para los temporizadores básicos TIM6 y TIM7, respectivamente). El preescalador TIMx_PSC le permite ajustar la frecuencia del reloj para el registro del contador, y el registro de carga automática TIMx_ARR permite configurar el rango de conteo del temporizador. El controlador de disparo y sincronización, junto con los registros de control y estado, sirven para organizar el modo de funcionamiento del temporizador y permiten controlar su funcionamiento.

Gracias a su organización, el contador del cronómetro puede contar hacia adelante y hacia atrás, así como hasta la mitad de un rango determinado en dirección hacia adelante y luego hacia atrás. La entrada del temporizador base se puede suministrar desde varias fuentes, incluida la señal de reloj del bus APB1, una señal externa o la salida de otros temporizadores aplicados a los pines de captura y comparación. Los temporizadores TIM6 y TIM7 se controlan desde el bus APB1. Si utiliza un cristal de 8 MHz y la configuración de reloj predeterminada de fábrica, la frecuencia de reloj del bus de reloj APB1 será de 24 MHz.

Registros de temporizador básicos

La tabla muestra el mapa de registros para los temporizadores básicos TIM6 y TIM7. Los temporizadores básicos incluyen los siguientes 8 registros:

●● TIMx_CNT – Contador (registro de conteo);
●● TIMx_PSC – Preescalador (preescalador);
●● TIMx_ARR – Registro de recarga automática;
●● TIMx_CR1 – Registro de control 1 (registro de control 1);
●● TIMx_CR2 – Registro de control 2 (registro de control 2);
●● TIMx_DIER – Registro de habilitación de interrupciones DMA (DAP y registro de habilitación de interrupciones);
●● TIMx_SR – Registro de estado;
●● TIMx_EGR – Registro de Generación de Eventos.

Los registros TIMx_CNT, TIMx_PSC y TIMx_ARR utilizan 16 bits de información y le permiten escribir valores de 0 a 65535. La frecuencia de los pulsos de reloj para el registro contador TIMx_CNT, que pasa a través del divisor TIMx_PSC, se calcula mediante la fórmula: Fcnt = Fin /(PSC + 1), donde Fcnt es la frecuencia de pulso del registro del contador del temporizador; Fin – frecuencia de reloj; PSC: contenido del registro del temporizador TIMx_PSC, que determina el coeficiente de división. Si escribe el valor 23999 en el registro TIMx_PSC, entonces el registro del contador TIMx_CNT a una frecuencia de reloj de 24 MHz cambiará su valor 1000 veces por segundo. El registro de carga automática almacena el valor para cargar el registro del contador TIMx_CNT. El contenido del registro TIMx_CNT se actualiza después de que se desborda o se reinicia, dependiendo de la dirección de conteo especificada para él. El registro de control TIMх_CR1 tiene varios bits de control. El bit ARPE habilita y deshabilita el almacenamiento en búfer de escrituras en el registro de carga automática TIMx_ARR. Si este bit es cero, cuando se escriba un nuevo valor en TIMx_ARR, se cargará en él inmediatamente. Si el bit ARPE es igual a uno, la carga en el registro se producirá después de que el registro de conteo alcance el valor límite. La descarga OPM habilita el modo “pulso único”. Si está configurado, después de que el registro del contador se desborde, el conteo se detiene y el bit CEN se restablece. El bit UDIS habilita o deshabilita la generación de un evento de temporizador. Si se borra, el evento se generará cuando se dé la condición para generar el evento, es decir, cuando el temporizador se desborde o cuando se programe el bit UG en el registro TIMx_EGR. El bit CEN enciende y apaga el temporizador. Si restablece este bit, el conteo se detendrá y, cuando se establezca, el conteo continuará. El divisor de entrada comenzará a contar desde cero. El registro de control TIMx_CR2 tiene tres bits de control MMS2... MMS0, que determinan el modo maestro del temporizador. El registro TIMx_DIER utiliza dos bits. El bit UDE permite o deshabilita la emisión de una solicitud DMA cuando ocurre un evento. El bit UIE habilita y deshabilita las interrupciones del temporizador. El registro TIMx_SR utiliza solo un bit UIF como indicador de interrupción. Se instala mediante hardware cuando ocurre un evento del temporizador. Debe restablecerlo mediante programación. El registro TIMx_EGR contiene un bit UG, que le permite generar mediante programación el evento "desbordamiento del registro de conteo". Cuando se establece este bit, se genera un evento y se restablecen el registro de conteo y el preescalador. Este bit se restablece mediante hardware. Gracias a este bit, puede generar mediante programación un evento desde un temporizador y, por lo tanto, llamar con fuerza a la función del controlador de interrupciones del temporizador.

Veamos el propósito de los registros de control y el estado del temporizador usando ejemplos de programas específicos.

Programas de ejemplo

Para iniciar un temporizador, se deben realizar varias operaciones, como cronometrar el temporizador e inicializar sus registros. Veamos estas operaciones basándonos en programas de ejemplo para trabajar con temporizadores. Muy a menudo en el proceso de programación surge la tarea de implementar retrasos en el tiempo. Para resolver este problema, se requiere una función de generación de retraso. En el Listado 1 se muestra un ejemplo de una función de este tipo basada en el temporizador TIM7 básico para STM32.

Listado 1

#define FAPB1 24000000 // Frecuencia de reloj del bus APB1 // Función de retardo en milisegundos y microsegundos void delay(unsigned chart, unsigned int n)( // Carga el registro del preescalador PSC If(t = = 0) TIM7->PSC = FAPB1 /1000000-1; // para contar microsegundos If(t = = 1) TIM7->PSC = FAPB1/1000-1; // para contar milisegundos TIM7->ARR = n // Carga el número de muestras en la carga automática. registrar ARR TIM7 ->EGR |= TIM_EGR_UG; // Generar un evento de actualización // para escribir datos en los registros PSC y ARR TIM7->CR1 |= TIM_CR1_CEN|TIM_CR1_OPM // Iniciar el temporizador // escribiendo el bit de habilitación de conteo; CEN // y el bit de modo pasan OPM al registro de control CR1 mientras (TIM7->CR1&TIM_CR1_CEN != 0 // Esperando a que finalice el conteo)

Esta función puede generar retrasos en microsegundos o milisegundos dependiendo del parámetro "t". La duración del retraso se establece mediante el parámetro “n”. Este programa utiliza el modo de paso único del temporizador TIM7, en el que el registro del contador CNT cuenta hasta el valor de desbordamiento registrado en el registro ARR. Cuando estos valores sean iguales, el cronómetro se detendrá. El hecho de que el temporizador se haya detenido se espera en el bucle while comprobando el bit CEN del registro de estado CR1. La habilitación del cronometraje de los temporizadores se realiza una vez en el módulo principal del programa durante su inicialización. Los temporizadores básicos están conectados al bus APB1, por lo que el suministro del reloj tiene el siguiente aspecto:

RCC->APB1ENR |= RCC_APB1ENR_TIM6EN; // Habilitar el fichaje en TIM6 RCC->APB1ENR |= RCC_APB1ENR_ TIM7EN; // Habilitar el fichaje en TIM7

El método de software para generar un retraso descrito anteriormente tiene un inconveniente importante debido al hecho de que el procesador se ve obligado a sondear la bandera durante todo el tiempo de retraso y, por lo tanto, no puede realizar otras tareas durante este tiempo. Este inconveniente se puede eliminar utilizando el modo de interrupción del temporizador. Las funciones de manejo de interrupciones para temporizadores básicos suelen verse así:

Void TIM7_IRQHandler())( TIM7->SR = ~TIM_SR_UIF; // Borrar la bandera //Realizar operaciones ) void TIM6_DAC_IRQHandler())( //Si el evento es de TIM6 if(TIM6->SR & TIM_SR_UIF)( TIM6- >SR =~ TIM_SR_UIF ; // Borrar la bandera // Realizar operaciones ) ;

Consideremos un ejemplo de un programa para organizar un retraso en un temporizador TIM6 básico, que utiliza interrupciones de un temporizador. Para controlar la ejecución del programa, utilizamos uno de los pines del microcontrolador para controlar los indicadores LED, que deberán conmutarse en intervalos determinados por el retraso del programa organizado en el temporizador TIM6. Un ejemplo de dicho programa se muestra en el Listado 2.

Listado 2

// Incluyendo bibliotecas #include #incluir #incluir #incluir #incluir // Asignaciones de pines para enumeración de indicadores LED (LED1 = GPIO_Pin_8, LED2 = GPIO_Pin_9); // Función para inicializar los puertos de control de LED void init_leds() ( RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC, ENABLE); GPIO_InitTypeDef gpio; GPIO_StructInit(&gpio); gpio.GPIO_Mode = GPIO_Mode_Out_PP; gpio.GPIO_Pin = LED1 | GPIO_; Init(GPIOC, &gpio); / /Función de inicialización del temporizador TIM6 void init_timer_TIM6() ( // Habilitar el reloj del temporizador RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM6, ENABLE); TIM_TimeBaseInitTypeDef base_timer; TIM_TimeBaseStructInit(&base_timer); // Establece el divisor en 23999 base_timer.TIM_Prescale r = 24000 - 1; // Establecer el período a 500 ms base_timer.TIM_Period = 500; TIM_TimeBaseInit(TIM6, &base_timer); // Habilitar el procesamiento de interrupción de desbordamiento del temporizador TIM_ITConfig(TIM6, TIM_IT_Update, ENABLE); // Habilitar el procesamiento de interrupción de desbordamiento del temporizador. Función de manejo de interrupciones del temporizador void TIM6_DAC_IRQHandler())( // Si se produce una interrupción debido a un desbordamiento del contador del temporizador TIM6 if (TIM_GetITStatus(TIM6, TIM_IT_Update) != RESET) ( //Restablece el bit de la interrupción procesada TIM_ClearITPendingBit( TIM6 , TIM_IT_Update); //Invierte el estado de los indicadores LED GPIO_Write(GPIOC, GPIO_ReadOutputData(GPIOC) ^ (LED1 | LED2)); ) ) // Módulo principal del programa int main() ( init_leds(); GPIO_SetBits(GPIOC, LED1); GPIO_ResetBits(GPIOC, LED2); init_timer_TIM6(); while (1) ( // Lugar para otros comandos ) )

En este programa, la función de retardo se llama una vez, después de lo cual el procesador puede realizar otras operaciones y el temporizador generará interrupciones periódicamente en el intervalo de retardo especificado. Se puede escribir un programa similar para el temporizador TIM7. La diferencia entre dicho programa estará en los nombres de los registros y el nombre del controlador de interrupciones. El controlador de interrupciones del temporizador TIM6 tiene una característica relacionada con el hecho de que el vector de procesamiento de interrupciones para este temporizador se combina con una interrupción de un convertidor digital a analógico (DAC). Por lo tanto, la función del controlador de interrupciones verifica el origen de la interrupción. Puede obtener más información sobre los temporizadores del microcontrolador STM32 en el sitio web de St.com. Hay muchas otras tareas para el temporizador, descritas anteriormente, que puede resolver con éxito. Por tanto, su uso en un programa aligera significativamente la carga del procesador y hace que el programa sea más eficiente.

Cualquier controlador moderno tiene temporizadores. Este artículo hablará sobre temporizadores simples (básicos) descubrimiento stm32f4.
Estos son cronómetros regulares. Son de 16 bits con reinicio automático. Además, hay un programable de 16 bits. divisor de frecuencia. Es posible generar interrupciones de desbordamiento del contador y/o solicitud de DMA.

Empecemos. Como antes uso Eclipse + st-util en ubuntu linux

En primer lugar, conectamos los encabezados:

#incluir #incluir #incluir #incluir #incluir

No hay nada nuevo en esto. Si no está claro de dónde vienen, lea los artículos anteriores o abra el archivo y léalo.

Definamos dos constantes. Uno para indicar diodos, el otro una serie de los mismos diodos:

Const uint16_t LEDS = GPIO_Pin_12 | GPIO_Pin_13 | GPIO_Pin_14 | GPIO_Pin_15; // todos los diodos son constantes uint16_t LED = (GPIO_Pin_12, GPIO_Pin_13, GPIO_Pin_14, GPIO_Pin_15); // matriz con diodos

Lo más probable es que ya esté familiarizado con la función de inicializar periféricos (es decir, diodos):

Void init_leds())( RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOD, ENABLE); // habilitar el reloj GPIO_InitTypeDef gpio; // estructura GPIO_StructInit(&gpio); // rellenar con valores estándar gpio.GPIO_OType = GPIO_OType_PP; // pull-up con resistencias gpio .GPIO_Mode = GPIO_Mode_OUT; // funciona como salida gpio.GPIO_Pin = LEDS; // todos los pines del diodo GPIO_Init(GPIOD, &gpio);

Función de inicializador del temporizador:

Void init_timer())( RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM6, ENABLE); // habilitar el reloj del temporizador /* Otros parámetros de la estructura TIM_TimeBaseInitTypeDef * no tienen sentido para los temporizadores base. */ TIM_TimeBaseInitTypeDef base_timer; TIM_TimeBaseStructInit(&base_timer); /* El divisor es se tiene en cuenta como TIM_Pre scaler + 1, por lo que se resta 1 */ base_timer.TIM_Prescaler = 24000 - 1; // divisor 24000 base_timer.TIM_Period = 1000 //período de 1000 pulsos TIM_TimeBaseInit(TIM6, &base_timer); actualizar (en este caso - * para desbordamiento) Contador del temporizador TIM6 */ TIM_ITConfig(TIM6, TIM_IT_Update, ENABLE); TIM_Cmd(TIM6, ENABLE); // Habilita el temporizador /* Habilita el procesamiento de la interrupción de desbordamiento del contador TIM6. La interrupción también es responsable de vaciar el DAC.

Comenté el código, así que creo que todo está claro.
Los parámetros clave aquí son el divisor (TIM_Prescaler) y el período (TIM_Period) del temporizador. Estos son los parámetros que realmente configuran el funcionamiento del temporizador.

Por ejemplo, si tiene una frecuencia de reloj configurada en 48 MHz en el STM32F4 DISCOVERY, entonces la frecuencia en los temporizadores de uso general es de 24 MHz. Si configura el divisor (TIM_Prescaler) en 24000 (frecuencia de conteo = 24MHz/24000 = 1KHz) y el período (TIM_Period) en 1000, entonces el temporizador contará el intervalo en 1s.

Tenga en cuenta que todo depende de la velocidad del reloj. Debes averiguarlo exactamente.

También observo que a altas frecuencias, la conmutación del LED por interrupción distorsiona significativamente el valor de la frecuencia. Con un valor de 1 MHz en la salida recibí aproximadamente 250 KHz, es decir. la diferencia es inaceptable. Este resultado aparentemente se obtiene debido al tiempo empleado en ejecutar la interrupción.

Variable global - bandera de diodo encendida:

Bandera U16 = 0;

Manejador de interrupciones que genera el temporizador. Porque La misma interrupción se genera cuando el DAC está funcionando, primero verificamos que haya sido activada por el temporizador:

Void TIM6_DAC_IRQHandler())( /* Dado que este controlador también se llama para el DAC, es necesario verificar * si se produjo la interrupción de desbordamiento del contador del temporizador TIM6. */ if (TIM_GetITStatus(TIM6, TIM_IT_Update) != RESET) ( flag++; if (flag> 3) flag = 0; /* Borra el bit de la interrupción que se está procesando */ TIM_ClearITPendingBit(TIM6, TIM_IT_Update);

función principal:

Int main())( init_leds(); init_timer(); hacer ( ) mientras(1); )

Dejamos el ciclo vacío. El contador realiza su trabajo de forma asincrónica y la interrupción es una interrupción, para no depender de la operación que se está realizando actualmente.

Buenas tardes. Hoy escribiré el primer artículo sobre temporizadores en STM32. En general, los temporizadores en STM32 son tan geniales que incluso Schwarzenegger fuma nerviosamente debido a la frialdad))) Y tendrás que estudiarlos en más de uno, dos o tres artículos. Pero primero, no nos molestemos demasiado, simplemente estudiemos los primeros temporizadores simples y trabajemos con ellos.

En STM32 generalmente hay tres tipos de temporizadores
1) temporizadores básicos
2) temporizadores de uso general
3) avanzado (temporizadores de control avanzado)

Los temporizadores avanzados son los más chulos y combinan las capacidades de los dos grupos anteriores, además de muchas funciones adicionales como trabajar con motores trifásicos, etc. etcétera. Todavía estamos muy lejos de ellos, por lo que en esta parte consideraremos trabajar con temporizadores básicos.
Primero, veamos qué temporizadores hay en nuestro procesador STM32F407VG (mira los procesadores con los que trabajas)). Mi procesador tiene 14 temporizadores: 12 de 16 bits y 2 de 32 bits.

Como vemos en las imágenes, los temporizadores TIM2, TIM3, TIM4, TIM5, TIM6, TIM7, TIM12 están conectados al bus ARV1.
Y al autobús ARV2: TIM1, TIM8, TIM9, TIM10, TIM11
Ahora veamos la imagen de cómo configurar nuestro cronometraje en el programa CubeMX. También describiré el sistema de cronometraje por separado, ya que no puedo vivir sin él, pero por ahora solo mostraré cómo podemos cronometrar nuestros temporizadores usando la fuente de reloj interna HSI.
Aquí está nuestra configuración de reloj estándar sin multiplicadores de frecuencia, etc. Esto es lo que usaremos.

Y aquí hay una opción para acelerar el trabajo)) Pero te aconsejo que no subas demasiado con tus manitas juguetonas, de lo contrario puede poner el procesador sobre tus omóplatos)) Estudiaremos y consideraremos todo esto más adelante.

Entonces, abrimos el Manual de referencia de la serie de microcontroladores F4 y comenzamos a fumar el manual. SÍ, en STM32 no todo es tan sencillo, así que camaradas, aprendan inglés y lean los manuales, porque sin esto pasarán mucho tiempo buscando qué es qué. Solía ​​​​tener dificultades para leer la documentación (aparentemente porque las tareas eran simples y tenía suficientes ejemplos habituales de Internet). Bueno, ahora leemos... leemos... leemos...
Continuemos...
Entonces los temporizadores 6 y 7 son temporizadores básicos. Se sientan en el autobús ARV1 como vemos en la imagen del manual de referencia.

Los temporizadores básicos 6 y 7 son de 16 bits, tienen un preescalador ajustable de 0 a 65535. Para estos temporizadores existen estos registros disponibles para lectura/escritura.
Registro de contador (TIMx_CNT) - contador
Registro de preescalador (TIMx_PSC) - preescalador
Registro de recarga automática (TIMx_ARR) - registro de recarga

No profundizaremos demasiado en los detalles del trabajo, ya que tenemos a nuestra disposición 10 páginas de descripción de los registros, etc., las tres escritas arriba nos bastarán
Entonces, ¿qué son estos registros y por qué los necesitamos? Sí, por eso. Decidimos hacer parpadear urgentemente un LED, para sorprender a nuestros compañeros AVR, por ejemplo, y decimos: quien pueda configurar rápidamente el parpadeo de un LED con un período de medio segundo y el segundo con un período de un segundo, gana. (por cierto, puedes hacer un experimento similar))))
Para implementar esto solo necesitamos 5 pasos: 1
1) Inicie CubeMX y cree un proyecto para nuestro controlador.
2) en CubeMX configurar el funcionamiento de los temporizadores
3) generar un proyecto y abrirlo en Keil uVision
4) inicializar temporizadores (una línea por temporizador)
5) escriba en la interrupción de cada temporizador el código para cambiar constantemente el estado de la pata a la que está conectado el LED.
Así que veamos esto con más detalle. Primero que nada, lancemos nuestro programa CubeMX.
y configuramos nuestros 2 pines PD12 y PD13 a salida (patas donde se conectan los LED). Configuramos el modo GPIO_Output para ellos y el modo Output Push_Pull.
A continuación a la izquierda activamos nuestros temporizadores básicos 6 y 7.

Ahora ve a la pestaña de configuración. Como recordamos, no cambiamos nada en la configuración de frecuencia de nuestro procesador, por lo que tenemos todos los buses sincronizados a -16 MHz. Ahora, en base a esto, y en función de lo que necesitamos obtener, configuremos nuestros valores para los preescaladores y el registro de recarga automática.

Como recordamos, necesitamos que un LED parpadee con una frecuencia de 1 Hz (período de 1000 ms) y el segundo con una frecuencia de 2 Hz (período de 500 ms). ¿Cómo conseguimos esto? Es muy sencillo. Dado que se puede instalar cualquier preescalador en el STM32, simplemente calcularemos su valor
Entonces nuestra frecuencia es de 16.000.000 de tics por segundo, pero necesitamos 1.000 tics por segundo. Esto significa 16.000.000 \ 1.000 = 16.000 Ingresamos este número menos 1 en el valor del preescalador. Es decir, el número que obtenemos es 15999.
Ahora nuestro cronómetro avanza 1000 veces por segundo. A continuación, debemos indicar cuándo necesitamos una interrupción por desbordamiento. Para ello escribimos el número que necesitamos en Counter Period (registro de autorrecarga).
Es decir, necesitamos recibir una interrupción por segundo y, como recordamos, nuestro temporizador marca 1 vez por milisegundo. Hay 1000 ms en un segundo, por lo que ingresamos este valor en el registro de reinicio automático.
Para obtener una interrupción cada medio segundo, escribimos en consecuencia: 500.

Entonces, lo hemos configurado y ahora podemos generar nuestro proyecto de manera segura. Generado, bueno. Queda poco tiempo hasta que los LED parpadeen.
Abrimos nuestro proyecto. En principio ya está todo configurado y listo para nosotros, solo falta poner en marcha nuestros cronómetros, ya que aunque CubeMX hace todo por nosotros, ya no hace esto. Entonces, inicialicemos
Nuestros temporizadores están con estas líneas.

HAL_TIM_Base_Start_IT(&htim6);
HAL_TIM_Base_Start_IT(&htim7);

Aquí es donde se encuentran nuestros controladores de interrupciones para nuestros temporizadores.
Aquí está el controlador de interrupciones para el temporizador 7.

vacío TIM7_IRQHandler (vacío)
{
/* CÓDIGO DE USUARIO COMIENZO TIM7_IRQn 0 */

/* CÓDIGO DE USUARIO FINAL TIM7_IRQn 0 */
HAL_TIM_IRQHandler(&htim7);
/* CÓDIGO DE USUARIO COMIENZA TIM7_IRQn 1 */

/* CÓDIGO DE USUARIO FINAL TIM7_IRQn 1 */
}

Ingresamos en el controlador de interrupciones lo que queremos hacer y con cada interrupción queremos cambiar el estado de nuestras piernas a las que están conectados los LED.
Usamos esta construcción - HAL_GPIO_TogglePin(GPIOD, GPIO_PIN_13);

Eso es todo. Presionamos F7, nos aseguramos de que no haya errores y podremos cargar todo esto en nuestro procesador experimental.
Pues ya podemos disfrutar del interesante parpadeo de los LED.
Añadiré un vídeo un poco más tarde, pero por ahora, como siempre, la imagen es correcta. Bueno, no te olvides de la gratitud))

Los temporizadores en STM32, como todos los periféricos en principio, son muy sofisticados. La gran cantidad de funciones diferentes que pueden realizar los temporizadores puede incluso hacer que te dé vueltas la cabeza. Aunque, parecería que el cronómetro es solo un cronómetro, solo para contar. Pero en realidad todo es mucho más genial)

Los temporizadores no sólo tienen capacidades tan amplias, sino que cada controlador también tiene varias de ellas. ¡Y ni siquiera dos o tres, sino más! En general, todo esto se puede elogiar sin cesar. Averigüemos qué y cómo funciona. Entonces, el microcontrolador STM32F103CB tiene:

  • 3 temporizadores de uso general (TIM2, TIM3, TIM4)
  • 1 temporizador más avanzado con capacidades avanzadas (TIM1)
  • 2 WDT (temporizador de vigilancia)
  • 1 temporizador SysTick

En realidad, los temporizadores de uso general y el temporizador TIM1 no son muy diferentes entre sí, por lo que nos limitaremos a considerar un solo temporizador. Por cierto, elegí TIM4. No hay ningún motivo en particular, simplemente me apetecía =). Los temporizadores tienen 4 canales independientes que se pueden utilizar para:

  • Captura de señal
  • Comparaciones
  • generación pwm
  • Generación de pulso único
  • Desbordamiento
  • Captura de señal
  • Comparación
  • Acontecimiento desencadenante

Cuando ocurre cualquiera de estos eventos, los temporizadores pueden generar una solicitud a DMA (DMA es acceso directo a la memoria, nos ocuparemos de eso pronto =)). Ahora un poco más sobre cada uno de los modos de funcionamiento del temporizador.

Modo de captura de señal. Es muy conveniente medir el período de repetición del pulso cuando se opera el temporizador en este modo. Compruébelo usted mismo: llega un impulso, el temporizador introduce el valor actual del contador en el registro TIM_CCR. Rápidamente tomamos este valor y lo ocultamos en alguna variable. Nos sentamos y esperamos el siguiente impulso. ¡Ups! El impulso ha llegado, el cronómetro vuelve a empujar el valor del contador a TIM_CCR, y a este valor solo nos queda restar el que guardamos anteriormente. Este es probablemente el uso más sencillo de este modo de temporizador, pero muy útil. Puedes captar tanto el borde de ataque como el borde de salida de un pulso, por lo que las posibilidades son bastante grandes.

Modo de comparación. Aquí simplemente conectamos algún canal del temporizador a la salida correspondiente, y tan pronto como el temporizador cuente hasta un cierto valor (está en TIM_CCR) el estado de salida cambiará según la configuración del modo (se establecerá en uno, cero o cambiará a lo contrario).

Modo de generación PWM. Bueno, todo está oculto en el nombre) ¡En este modo, el temporizador genera PWM! Probablemente no tenga sentido escribir nada aquí ahora. Pronto habrá una muestra solo para PWM y la analizaremos con más detalle.

Modo de tiempo muerto. La esencia del modo es que aparece un cierto retraso entre las señales en los pines principal y complementario del temporizador. Hay bastante información en Internet sobre dónde se puede y se debe aplicar esto.

Bueno, en principio, hablaremos muy brevemente sobre los principales modos de funcionamiento del temporizador. Si tienes dudas sobre otros modos, más específicos, escribe en los Comentarios 😉

Necesitamos escribir lentamente un programa para trabajar con temporizadores. Pero primero, veamos qué hay en la Biblioteca de periféricos estándar. Entonces, los archivos son responsables de los temporizadores. stm32f10x_tim.h Y stm32f10x_tim.c. Abrimos el primero y vemos que la estructura del archivo repite la estructura del archivo para trabajar con GPIO, que comentamos en el artículo anterior. Esto describe las estructuras y campos de las estructuras que se necesitan para configurar los temporizadores. Es cierto que no hay una sola, sino varias estructuras (los temporizadores tienen más modos y, por lo tanto, configuraciones, que los puertos de E/S). Todos los campos de la estructura cuentan con comentarios, por lo que no debería haber ningún problema aquí. Bueno, por ejemplo:

uint16_t TIM_OCMode; // Especifica el modo TIM.

Aquí configuraremos el modo de funcionamiento del temporizador. Y aquí hay otro:

uint16_t TIM_Channel; // Especifica el canal TIM.

Aquí seleccionamos el canal del temporizador, nada inesperado) En general, todo es bastante transparente, si preguntas algo =) El primer archivo es claro. y en el archivo stm32f10x_tim.c– funciones listas para usar para trabajar con temporizadores. En general, también todo está claro. Ya hemos usado la biblioteca para trabajar con GPIO, ahora estamos trabajando con temporizadores y es obvio que para diferentes periféricos todo es muy similar. Entonces, creemos un proyecto y escribamos un programa.

Entonces, creemos un nuevo proyecto, agreguemos todos los archivos necesarios:

Escribimos el código:

Cabe destacar que en el campo TIM_Prescaler debemos escribir un valor uno menos que el que queremos obtener.

/******************************temporizadores.c****************** *************/#incluye "stm32f10x.h" #incluye "stm32f10x_rcc.h" #incluye "stm32f10x_gpio.h" #incluye "stm32f10x_tim.h" //Con este preescalador obtengo un tic del temporizador cada 10 µs#definir TIMER_PRESCALER 720 /*******************************************************************/ //Variable para almacenar el estado anterior del pin PB0 uint16_t estado anterior; Puerto GPIO_InitTypeDef; Temporizador TIM_TimeBaseInitTypeDef; /*******************************************************************/ void inicioTodo() ( //Habilitar el reloj del puerto GPIOB y el temporizador TIM4 //El temporizador 4 se bloquea en el bus APB1 RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, HABILITAR); RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM4, HABILITAR); //Aquí configuramos el puerto PB0 para salida //Más sobre esto en el artículo sobre GPIO GPIO_StructInit(& puerto); puerto.GPIO_Mode = GPIO_Mode_Out_PP; puerto.GPIO_Pin = GPIO_Pin_0; puerto.GPIO_Speed ​​​​= GPIO_Speed_2MHz; GPIO_Init(GPIOB, y puerto); //Y aquí está la configuración del temporizador. //Rellena los campos de la estructura con valores predeterminados TIM_TimeBaseStructInit(& temporizador); //Establece el preescalador timer.TIM_Prescaler = TIMER_PRESCALER - 1; //Aquí está el valor al cual el temporizador generará una interrupción //Por cierto, cambiaremos este valor en la propia interrupción temporizador.TIM_Period = 50; //Inicializa TIM4 con nuestros valores TIM_TimeBaseInit(TIM4, y temporizador); ) /*******************************************************************/ int principal() ( __enable_irq() ; initAll() ; //Configurar un temporizador para generar una interrupción de actualización (desbordamiento) TIM_ITConfig(TIM4, TIM_IT_Update, HABILITAR); //Inicia el cronómetro TIM_Cmd(TIM4, HABILITAR); //Habilita la interrupción correspondiente NVIC_EnableIRQ(TIM4_IRQn); mientras (1) ( //Somos infinitamente estúpidos) Todo trabajo útil está en la interrupción __NOP() ; ) ) /*******************************************************************/ //Si la salida fuera 0.. temporizador.TIM_Period = 50; TIM_TimeBaseInit(TIM4, y temporizador); //borrar el bit de interrupción TIM_ClearITPendingBit(TIM4, TIM_IT_Update); ) demás ( //Establece cero en la salida temporizador.TIM_Period = 250; TIM_TimeBaseInit(TIM4, y temporizador); TIM_ClearITPendingBit(TIM4, TIM_IT_Update); ) )

En este programa, observamos lo que había en la salida antes de que se generara la interrupción; si es cero, lo configuramos en uno durante 0,5 ms. Si hubiera uno, establezca cero en 2,5 ms. Compile y comience a depurar =)

Una pequeña pero muy importante digresión... Nuestro ejemplo, por supuesto, funcionará y será bastante adecuado para realizar pruebas, pero aún así, en los programas de "combate" es necesario controlar la optimización del código tanto en términos de volumen como en términos de rendimiento y consumo de memoria. En este caso, no tiene sentido utilizar la estructura del temporizador y también llamar a la función TIM_TimeBaseInit() cada vez que cambia el período. Es más correcto cambiar solo un valor en un registro, concretamente en el registro TIMx->ARR (donde x es el número del temporizador). En este ejemplo, el código se transforma de la siguiente manera:

/*******************************************************************/ anular TIM4_IRQHandler() ( //Si la salida fuera 0.. if (estado anterior == 0 ) ( //Establece uno en la salida Estado anterior = 1; GPIO_SetBits(GPIOB, GPIO_Pin_0); //El período es de 50 tics del temporizador, es decir, 0,5 ms. TIEMPO4->ARR = 50 ; ) demás ( //Establece cero en la salida Estado anterior = 0; GPIO_ResetBits(GPIOB, GPIO_Pin_0); //Y el período ahora será de 250 ticks – 2,5 ms TIM4->ARR = 250; ) TIM_ClearITPendingBit(TIM4, TIM_IT_Update); ) /****************************Fin del documento****************** **********/

Entonces, continuemos, tenemos otro rastrillo en camino) Es decir, el error:

..\..\..\SPL\src\stm32f10x_tim.c(2870): error: #20: el identificador “TIM_CCER_CC4NP” no está definido

No es tan aterrador como podría parecer, vaya al archivo stm32f10x.h, busque las líneas

Ahora todo está ensamblado, puedes depurarlo. Encienda el analizador lógico. En la línea de comando escribimos: el puertob&0x01 y ver el resultado:

Obtuvimos lo que queríamos) En otras palabras, todo funciona correctamente. En el próximo artículo profundizaremos en el modo de generación PWM, manténgase en contacto 😉

No te pierdas un buen artículo sobre temporizadores en general -.

El modo de captura es un modo de funcionamiento especial del temporizador, cuya esencia es la siguiente: cuando el nivel lógico cambia en un determinado pin del microcontrolador, el valor del registro de conteo se escribe en otro registro, que se llama registro de captura. .

¿Para qué es esto?
Con este modo, puede medir la duración del pulso o el período de la señal.

El modo de captura STM32 tiene algunas características:

  • capacidad de elegir qué frente estará activo
  • capacidad de cambiar la frecuencia de la señal de entrada usando un preescalador (1,2,4,8)
  • Cada canal de captura está equipado con un filtro de entrada incorporado.
  • la fuente de la señal de captura puede ser otro temporizador
  • Para cada canal hay dos indicadores, el primero se establece si se ha producido una captura, el segundo si se ha producido una captura mientras se establece el primer indicador

Los registros se utilizan para configurar el modo de captura. CCMR1(para 1.º y 2.º canal) y CCMR2(para 3 y 4), así como registros CCER, DIER.

Echemos un vistazo más de cerca a los campos de bits de registro. CCMR2, responsable de configurar el 4º canal del temporizador, lo configuraremos en el ejemplo. También me gustaría señalar que el mismo registro contiene campos de bits que se utilizan al configurar el temporizador en modo de comparación.

CC4S- determina la dirección de funcionamiento del cuarto canal (entrada o salida). Al configurar un canal como entrada, se le asigna una señal de captura

  • 00 - el canal funciona como salida
  • 01 - canal funciona como entrada, captura señal - TI4
  • 10 - canal funciona como entrada, captura de señal - TI3
  • 11 - el canal funciona como entrada, captura de señal - TRC
IC4PSC– determinar el coeficiente de división para la señal de captura
  • 00 - el divisor no se utiliza, la señal de captura IC1PS se genera para cada evento
  • 01: se genera una señal de captura por cada segundo evento
  • 10 - se genera una señal de captura por cada cuarto evento
  • 11 - se genera una señal de captura por cada octavo evento
IC4F- está destinado a configurar el filtro de entrada, además de la cantidad de muestras durante las cuales el microcontrolador no responderá a las señales de entrada, también puede configurar la frecuencia de muestreo. Básicamente, estamos ajustando el tiempo de retraso desde el momento en que llega el borde hasta la muestra de "confirmación".

Ahora veamos el registro. CCER.

CC4E- activa/desactiva el modo de captura.
CC4P- determina el frente por el que se realizará la captura, 0 - frente, 1 - atrás.

Y regístrate DIER.

CC4DE- le permite generar una solicitud a DMA.
CC4IE- permite la interrupción de la captura.

Después de que se ha producido una captura, se genera un evento de captura que establece el indicador correspondiente. Esto puede resultar en una interrupción que se genera y una solicitud DMA, si están permitidos en el registro DIER. Además, un evento de captura se puede generar mediante programación configurando un campo de bits en el registro de generación de eventos. EGR:

campos de bits CC1G, CC2G, CC3G y CC4G le permiten generar un evento en el canal de captura/comparación correspondiente.

Por cierto, CCR1, CCR2, CCR3 y CCR4- registros de captura, en los que se almacena el valor del temporizador en función de la señal de captura.

Para controlar la generación de la señal de captura, en el registro S.R. Se asignan dos banderas para cada canal.

CC4IF- se configuran cuando se genera una señal de captura, estos indicadores se restablecen mediante software o leyendo el registro de captura/comparación correspondiente.
CC4OF- configurar si la bandera CC4IF no se ha borrado, pero ha llegado otra señal de captura. Este indicador se borra mediante programación escribiendo cero.

Ahora pongamos en práctica este conocimiento; desde el generador de señales suministraremos una sinusoide con una frecuencia de 50 Hz desde el generador de señales a la entrada de TIM5_CH4 e intentaremos medir su período. Para acelerar el proceso, sugiero utilizar DMA. Qué pin MK corresponde al canal 4 TIM5 se puede encontrar en la hoja de datos del MK en la sección Pines y descripción de pines.

Para DMA dirección de registro requerida CCR4, aquí se explica cómo encontrarlo. Apertura RM0008 y en la mesa Registrar direcciones de límites Encuentre la dirección inicial de TIM5.


compensación para registro CCR4 se puede encontrar en el mismo documento en la sección mapa de registro.

#include "stm32f10x.h" #define TIM5_CCR4_Address ((u32)0x40000C00+0x40) #define DMA_BUFF_SIZE 2 uint16_t buff;//Buffer uint16_t volatile T; void DMA2_Channel1_IRQHandler (void) ( T = (buff > buff)? (buff - buff): (65535+ buff - buff); DMA2->IFCR |= DMA_IFCR_CGIF1; ) void Init_DMA(void) ( RCC->AHBENR |= RCC_AHBENR_DMA2EN ; //Habilitar el reloj del primer módulo DMA DMA2_Channel1->CPAR = TIM5_CCR4_Address; //Especifica la dirección del periférico - el registro de resultados de la conversión ADC para canales regulares DMA2_Channel1->CMAR = (uint32_t)buff //Establece la dirección de memoria - el dirección base de la matriz en RAM DMA2_Channel1 ->CCR &= ~DMA_CCR1_DIR //Indica la dirección de transferencia de datos, desde el periférico a la memoria DMA2_Channel1->CNDTR = DMA_BUFF_SIZE //El número de valores transferidos DMA2_Channel1->; CCR &= ~DMA_CCR1_PINC; //La dirección del periférico no se incrementa después de cada transferencia DMA2_Channel1 -> CCR | = DMCR1_MINC; //Prioridad: muy alta DMA2_Channel1->CCR |= DMA_CCR1_CIRC; //Habilitar operación DMA en modo cíclico DMA2_Channel1->CCR |= DMA_CCR1_TCIE;//Habilitar interrupción al final de la transmisión DMA2_Channel1->CCR |= DMA_CCR1_EN; //Habilita la operación del 1er canal DMA) int main(void) ( Init_DMA(); //habilita el cronometrado del puerto A, funciones alternativas y temporizador RCC->APB2ENR |= RCC_APB2ENR_IOPAEN | RCC_APB2ENR_AFIOEN; RCC->APB1ENR |= RCC_APB1ENR_TIM5EN ; TIM5 ->PSC = 56000-1;//nueva frecuencia 1Khz TIM5->CCMR2 |= TIM_CCMR2_CC4S_0;//seleccione TI4 para TIM5_CH4 TIM5->CCMR2 &= ~(TIM_CCMR2_IC4F | TIM_CCMR2_IC4PSC);//no filtrar y no usar un divisor TIM5- >CCER &= ~TIM_CCER_CC4P;//seleccionar captura en el borde de ataque TIM5->CCER |= TIM_CCER_CC4E;//activar el modo de captura para el cuarto canal TIM5->DIER |= TIM_DIER_CC4DE;//permitir para generar una solicitud a DMA //TIM5 ->DIER |= TIM_DIER_CC4IE; //habilitar interrupción de captura TIM5->CR1 |= TIM_CR1_CEN //habilitar el contador //NVIC->ISER |= NVIC_ISER_SETENA_18; //TIM5 Interrumpir NVIC-; >ISER |= NVIC_ISER_SETENA_24; Interrumpir mientras(1) ( ) )



¿Te gustó el artículo? Compártelo