Capitulo II. Interrupciones. El M4 soporta 240 fuentes de interrupción con 256 niveles de prioridad. Cada interrupción tiene un vector (Interrupciones Vectorizadas) donde se aloja el código (ISR) que trata la interrupción. Hay un controlador para las interrupciones llamado NVIC (Nested Vector Interrupt Controller). Hay dos formas de tratar las prioridades: preemption priorities y sub priorities. El que tiene mayor prioridad (preemption priorities) se ejecuta en primer lugar, cuando dos interrupciones tienen la misma prioridad el que tiene mayor prioridad secundaria (sub priorities) se ejecutará primero. Si ambas tienen igual prioridad y sub-prioridad la interrupción que ocurra primero se ejecutará primero (orden de llegada). Los procesadores Cortex M3 y M4 utilizan 8 bits para almacenar las prioridades y sub-prioridades separados en cinco grupos para elegir la forma en que procesamos la prioridad de la interrupción. Hay cinco grupos diferentes que podemos establecer. • • • • • Grupo0 - 0 bits para el sobreseimiento, 4 bits para sub prioridad . Grupo1 - 1 bits para el sobreseimiento, 3 bits para sub prioridad . Group2 - 2 bits para el sobreseimiento, 2 bits para sub prioridad . Grupo3 - 3 bits para el sobreseimiento, 1 bits para sub prioridad . Grupo4 - 4 bits para el sobreseimiento, 0 bits para sub prioridad . La función NVIC_PriorityGroupConfig configura los grupos de prioridades. Si elijo grupo 4 la sintaxis seria NVIC_PriorityGroupConfig(NVIC_PriorityGroup_4) lo que me dejará 4 bits para NVIC_IRQChannelPreemptionPriority y 0 bits para NVIC_IRQChannelSubPriority. Pagina 39 Veamos un ejemplo para configurar el la interrupción del Timer3. NVIC_PriorityGroupConfig(NVIC_PriorityGroup_4); NVIC_InitStructure.NVIC_IRQChannel = TIM3_IRQn; NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1; NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0; NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; NVIC_Init(&NVIC_InitStructure); Importante: Cuando no se especifica NVIC_PriorityGroup por defecto en la biblioteca ST se establece el Grupo 2, que es de 2 bits para prioridad y 2 bits para sub prioridad. El archivo misc.c es el encargado de la configuración del NVIC y en el se puede encontrar información de lo comentado. Veamos un ejemplo de esto. Vamos a suponer que tenemos un interrupción EXT1 configurada como PriorityGroup0 y IRQChannelSubPriority15, también tengo TIM2 configurado en PriorityGroup0 IRQChannelSubPriority0 y finalmente ADC3 es configurado en PriorityGroup1. EXTI1 y TIM2 tienen NVIC_IRQChannelPreemptionPriority0 por defecto. Si ha ocurrido la interrupción EXTI1 y un instante después ocurre la interrupción de TIM2 este es puesto en la cola. Esto se debe a que EXTI1 y TIM2 tienen el mismo IRQChannelPreemptionPriority pero EXTI1 ha ocurrido primero. Si EXTI1 y TIM2 en el mismo momento, EXTI1 será puesto en la cola y TIM2 será atendido primero porque TIM2 tiene un NVIC_IRQChannelSubPriority de nivel superior. (Menor número mayor prioridad). Por el momento nos interesan las interrupciones por hardware generada por fuentes externas, EXTI0, 1, 2,3, …. estas interrupciones se pueden generar tanto por nivel como por flaco en el pin correspondiente. Las unidades Cortex M4 tenen características muy avanzadas para reducir la latencia en el llamado a la ISR. Las interrupciones se enumeran desde el cero y con números negativos para las excepciones, este micro tiene interrupciones y excepciones. El archivo stm32f4xx.h proporciona el número de interrupción y de excepción. typedef enum IRQn { /****** Cortex-M4 Processor Exceptions Numbers*******************************************/ NonMaskableInt_IRQn = -14, /*!< 2 Non Maskable Interrupt*/ MemoryManagement_IRQn = -12, /*!< 4 Cortex-M4 Memory Management Interrupt*/ BusFault_IRQn = -11, /*!< 5 Cortex-M4 Bus Fault Interrupt */ UsageFault_IRQn = -10, /*!< 6 Cortex-M4 Usage Fault Interrupt */ SVCall_IRQn = -5, /*!< 11 Cortex-M4 SV Call Interrupt */ DebugMonitor_IRQn = -4, /*!< 12 Cortex-M4 Debug Monitor Interrupt */ PendSV_IRQn = -2, /*!< 14 Cortex-M4 Pend SV Interrupt */ SysTick_IRQn = -1, /*!< 15 Cortex-M4 System Tick Interrupt */ /****** STM32 specific Interrupt Numbers*************************************************/ WWDG_IRQn = 0, /*!< Window WatchDog Interrupt */ PVD_IRQn = 1, /*!< PVD through EXTI Line detection Interrupt*/ TAMP_STAMP_IRQn = 2, /*!< Tamper and TimeStamp */ Pagina 40 RTC_WKUP_IRQn FLASH_IRQn RCC_IRQn EXTI0_IRQn ........ etc.... = = = = 3, 4, 5, 6, /*!< /*!< /*!< /*!< RTC Wakeup interrupt through the EXTI line */ FLASH global Interrupt */ RCC global Interrupt */ EXTI Line0 Interrupt */ En el caso del KEIL, ARM proporciona dentro del archivo startup_stm32f4xx.s una plantilla para el manejo de interrupciones y excepciones. ; External Interrupts DCD WWDG_IRQHandler ; DCD PVD_IRQHandler ; DCD TAMP_STAMP_IRQHandler ; DCD RTC_WKUP_IRQHandler ; DCD FLASH_IRQHandler ; DCD RCC_IRQHandler ; DCD EXTI0_IRQHandler ; DCD EXTI1_IRQHandler ; DCD EXTI2_IRQHandler ; DCD EXTI3_IRQHandler ; DCD EXTI4_IRQHandler ; DCD DMA1_Stream0_IRQHandler .... etc.. Window WatchDog PVD through EXTI Line detection Tamper and TimeStamps through the EXTI line RTC Wakeup through the EXTI line FLASH RCC EXTI Line0 EXTI Line1 EXTI Line2 EXTI Line3 EXTI Line4 ; DMA1 Stream 0 Normalmente encontrará que los servicios de interrupciones se escriben en el archivo stm32f4xx_it.c sin embargo las ISR son funciones del tipo weak las rutinas se pueden escribir directamente en la aplicación como puede observar en el código siguiente que se agrega como una función mas en el archivo principal. void EXTI0_IRQHandler(void){ Mismo nombre declarado en startup_stm32f4xx.s if(EXTI_GetITStatus(EXTI_Line0) != RESET){ GPIO_ToggleBits(GPIOD, GPIO_Pin_13); // Cambio el estado del pin. EXTI_ClearITPendingBit(EXTI_Line0); // Borra la bandera de la interrupción. } } Como se mencionaba anteriormente los Cortex M tiene una tecnología muy eficiente para reducir la latencia entre interrupciones. El siguiente ejemplo pone en practica lo visto con la interrupción en PA0. La idea es lograr encender el LED anaranjado cada vez que se oprime el botón de usuario en la placa entrenadora. Pagina 41 Hay 16 líneas de interrupción externa EXTI0, EXTI1, EXTI2......EXTI15 con una gran cantidad de pines GPIO que se pueden configurar para interrupciones externas. En este caso usaremos la línea 0 y el pin PA0. (Es de notar que la Discovery no tiene ningún condensador aplicado al pulsador por lo tanto es de esperar que el funcionamiento no será optimo a no se que agregue como indico anteriormente). En el siguiente ejemplo no se ha especificado grupo de prioridades por lo tanto por defecto se asignan el Grupo 2 para el tratamiento de las prioridades. Pagina 42 /********************************************************************************** * Nombre : INT_EXT.c * Descripción : Funcionamiento de las interrupción ext. pin23 (PA0). * Target : STM32F407VG * ToolChain : MDK-ARM * IDE : uVision 4 * www.firtec.com.ar **********************************************************************************/ #include "stm32f4xx_rcc.h" #include "stm32f4xx_gpio.h" void Config_Int(void); void Configurar_LED(void); unsigned char bandera = 0; Programa principal int main(void){ Configurar_LED(); // Configura pin del LED Config_Int(); //Configura EXTI Line0 (Pin PA0) en modo interrupcion while (1){ // Espera la interrupción. } } Función que configura puerto y pin para el LED void Configurar_LED() { GPIO_InitTypeDef GPIO_InitStructure;// Estructura para la configuración del // puerto GPIOD RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOD, ENABLE); // Habilita el reloj GPIO_InitStructure.GPIO_Pin = GPIO_Pin_13; // Configura el pin 13 GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT; // El pin será salida GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP; // El pin será push/pull GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; // Velocidad del puerto GPIO_Init(GPIOD, &GPIO_InitStructure); // Pasa la config. a la estructura. } Función que configura la interrupción por PA0 void Config_Int(void){ GPIO_InitTypeDef GPIO_InitStructure; NVIC_InitTypeDef NVIC_InitStructure; EXTI_InitTypeDef EXTI_InitStructure; // Estructura para los pines GPIO // Estructura para el NVIC // Estructura para la interrupción. //Habilita el reloj para el puerto GPIOA RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA, ENABLE); // Habilita SYSCFG clock RCC_APB2PeriphClockCmd(RCC_APB2Periph_SYSCFG, ENABLE); // Configura pin PA0 como entrada y flotante GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN; GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL; GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0; GPIO_Init(GPIOA, &GPIO_InitStructure); // Conecta la interrupción al pin PA0 SYSCFG_EXTILineConfig(EXTI_PortSourceGPIOA, EXTI_PinSource0); //Configura la interrupción EXTI Pagina 43 EXTI_InitStructure.EXTI_Line = EXTI_Line0; EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt; EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Rising; EXTI_InitStructure.EXTI_LineCmd = ENABLE; EXTI_Init(&EXTI_InitStructure); // Habilita y setea la inte como baja prioridad NVIC_InitStructure.NVIC_IRQChannel = EXTI0_IRQn; NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1; NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0; NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; NVIC_Init(&NVIC_InitStructure); } Servicio de la Interrupción void EXTI0_IRQHandler(void) Vector de interrupción. { if(EXTI_GetITStatus(EXTI_Line0) != RESET) // Verifica el estado del pin. { GPIO_ToggleBits(GPIOD, GPIO_Pin_13); EXTI_ClearITPendingBit(EXTI_Line0); // Borra la bandera de Interrupción } } El archivo main.c contiene nuestro código. System_stm32f4xx.c y satartup_stm32f4xx.s contienen la información para configurar el hardware del controlador. El archivo stm32f4xx_rcc.c gestiona los buses y relojes de perifericos, interrupciones y sus banderas. El archivo stm32f4xx_exti.c trata el evento de las interrupciones por hardware en los respectivos pines. El archivo stm32f4xx_syscfg.c proporciona funciones para: Este controlador proporciona funciones para: Reasignación de la memoria accesible en el área de código usando SYSCFG_MemoryRemapConfig (). Gestionar la líneas EXTI conexión con los GPIOs utilizando SYSCFG_EXTILineConfig () También seleccione la interfaz de comunicación Ethernet (RMII / RII) usando SYSCFG_ETH_MediaInterfaceConfig () si este fuera el caso. El archivo misc.c gestiona las interrupciones. En general podemos decir que todo está vinculado a un archivo . c (y su correspondiente archivo .h) que contiene las funciones necesarias para el control y como veremos cada vez que incorporemos un módulo de hardware será necesario cargar el correspondiente archivo que servirá como driver. Pagina 44 Trabajos practicos 1- De acuerdo a lo visto se pide que cambies la interrupción a PA1 y encienda el LED en PD12. 2- Diseña un contador que cuente de 0 a 9 cada vez que se oprima el pulsador de usuario y muestra el estado de cuenta en un display de 7 segmentos colocado en cualquier puerto GPIO. Pagina 45 Temporizador del sistema (SysTick). Es un temporizador presente en el núcleo del controlador, es independiente del fabricante e integrado al núcleo ARM esta disponible en todos los fabricantes. El funcionamiento es bastante simple existiendo la función SysTick_Config() donde n puede ser cualquier valor no mayor a 0xFFFFFF . Importante: Tel valor puesto como argumento en la función SysTick_Config() no puede ser mayor a un valor de 24 bits 0xFFFFFF (16777215). Como calcular el tiempo del Systick. Suponiendo que estamos trabajando con un reloj de 168Mhz el período de este reloj será de: 1/168Mhz = 0.005952 nS Ahora bien, si configurarmos el SysTick de la siguiente forma: SysTick_Config(SystemCoreClock / 1000) Tenemos un reloj de 168000, lo que llamaremos módulo del SysTick. Si multiplicamos el módulo por el tiempo de CPU: 0.005952 * 168000 = 999.9 mS El SysTick se interrumpe cada 999.9 milisegundos generando un señal de 500Hz. Recordar: Básicamente se multiplica el tiempo de CPU por el módulo establecido en SysTick_Config(). Veamos como funciona el siguiente programa que enciende el LED naranja de la entrenadora Discovery al ritmo que fija la rutina de conteo: void retardo(uint32_t nCount){ while(nCount--){} } Es decir que cambiando la variable nCount cambia la velocidad el bucle y también el tiempo de encendido de este LED. También se ha configurado el SysTick() para controlar el LED verde de la entrenadora. SysTick_Config(SystemCoreClock / 1000); En PD12 (LED Verde) deberíamos tener un señal de 500Hz con un periodo total de 2 milisegundos. Importante: Cuando una variable se declara de la siguiente forma: __IO uint32_t. es una variable volatile. Ej: __I volatile const , __O volatile , __IO volatile. Pagina 46 /********************************************************************************** * Nombre : SysTick.c * Descripción : Funcionamiento del timer del núcleo cortex. * Target : STM32F407VG * ToolChain : MDK-ARM * IDE : uVision 4 * www.firtec.com.ar *************************************************************/ #include "stm32f4xx_rcc.h" #include "stm32f4xx_gpio.h" void Configurar_LED(void); static __IO uint32_t TimingDelay; void retardo(uint32_t cuenta); void Retardo_Sys_Tick(void); Función principal int main(void) { En esta etapa el ajuste del reloj del microcontrolador ya está configurado,esto se hace a través de la función SystemInit() que se llama desde el arranque archivo (startup_stm32f4xx.s) antes de pasar a la aplicación principal. Para volver a la configuración predeterminada de SystemInit(),consulte archivo system_stm32f4xx.c Configurar_LED(); // Configura el puerto para los LED´s if (SysTick_Config(SystemCoreClock / 1000)) { /* Captura el error */ while (1); } while (1){ // Bucle principal infinito // PD13 oscila al rito de la función retardo sin importar el SysTick GPIO_ToggleBits(GPIOD, GPIO_Pin_13); retardo(8000000); } } Función de Temporización void retardo(uint32_t nCount){ while(nCount--){ // Bucle del temporizador} } Servicio de Interrupción para el SysTick void SysTick_Handler(void) { GPIO_ToggleBits(GPIOD, GPIO_Pin_12); // Enciende/Apaga LED Verde. } Función para configurar el puerto de los LED´s void Configurar_LED(){ GPIO_InitTypeDef GPIO_InitStructure; RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOD, ENABLE); // Habilitar reloj GPIO_InitStructure.GPIO_Pin = GPIO_Pin_12 | GPIO_Pin_13; // Pines usados GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT; GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; Pagina 47 GPIO_Init(GPIOD, &GPIO_InitStructure); } Como reformaría el programa para generar una señal de 300Hz por el Pin12? Siguiendo con los temporizadores veamos un ejemplo de uso de uno de los tantos temporizadores del STM32F407VG (17 en total), estos a diferencia del SysTick pueden variar en cantidad y funcionalidad dependiendo del fabricante del controlador. El trabajo propuesto enciende y apaga un LED (PD12) a un determinado ritmo, para eso usaremos el Timer3, en general todos son mas o menos iguales a excepción del Tim1 y Tim8 que tienes algunas características especiales, en la configuración al igual que los módulos anteriores usaremos estructuras. En la estructura de configuración hay dos puntos de relevancia: TIM_TimeBaseStructure.TIM_Period (16 bits) TIM_TimeBaseStructure.TIM_Prescaler (16 bits) Estos dos valores son los que en definitiva determinaran la frecuencia de la señal de salida aplicando la siguiente formula: TimerClock/(P-1 * Q-1) Donde TimerClock es la velocidad del bus del Timer que estemos usando, P es TIM_Period y Q es TIM_Prescaler. Veamos un ejemplo para el Timer3 con un reloj de 84Mhz en su reloj, queremos generar una señal con un período total equivalente a 25Hz para esto hacemos: 84000000/(10000-1 * 336-1) = 25,0 Con lo que nos quedaría una configuración del Timer de la siguiente forma: TIM_TimeBaseStructure.TIM_Period = 10000 -1; TIM_TimeBaseStructure.TIM_Prescaler = 336-1; TIM_TimeBaseStructure.TIM_ClockDivision = 0; TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up; // Contador ascendente. El punto TIM_TimeBaseStructure.TIM_ClockDivision es usado para entradas de filtros digitales y se puede dejar en cero. De acuerdo a lo estudiado configuremos el Timer3 para obtener un pulso de 2 segundos. /********************************************************************************** * Nombre : Timer3.c * Descripción : Funcionamiento del Timer3. * Target : STM32F407VG * ToolChain : MDK-ARM * IDE : uVision 4 * www.firtec.com.ar ***********************************************************/ #include "stm32f4xx_rcc.h" #include "stm32f4xx_gpio.h" void TIM_NVIC_Config(void); void Configurar_LED(void); TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure; Pagina 48 Programa principal int main(void){ Configurar_LED(); TIM_NVIC_Config(); // Configura el puerto para el LED. // Configura inte del Timer3 Configuración del Timer3: SYSCLK = 168Mhz igual que HCLK = 168Mhz. APB1 = HCLK / 4 >>> 168Mhz / 4 = 42Mhz. El timer clock es igual a 2 x APB1 = 84Mhz. La fórmula para el calculo de tiempo es: Timer Clock/(Periodo -1) * (Prescalador -1) 84000000/(32768-1)* (1282-1) = 2.00 El LED conmuta a la mitad de este tiempo 2/2= 1Hz. SystemCoreClock es definido en system_stm32f4xx.c Si se cambia el reloj del núcleo se debe llamar a SystemCoreClockUpdate() para validar el nuevo reloj. (Normalmente eso se hace automáticamente). // Configuración del Timer TIM_TimeBaseStructure.TIM_Period = 32768-1; TIM_TimeBaseStructure.TIM_Prescaler = 1282-1; TIM_TimeBaseStructure.TIM_ClockDivision = 0; TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up; TIM_TimeBaseInit(TIM3, &TIM_TimeBaseStructure); // Habilita la interrupción TIM_ITConfig(TIM3, TIM_IT_CC1, ENABLE); // Habilita el TIMER3 TIM_Cmd(TIM3, ENABLE); while (1) { // Espera la interrupción } } Configura la Interrupción del TIMER3 void TIM_NVIC_Config(void) { NVIC_InitTypeDef NVIC_InitStructure; // Habilita el reloj del TIMER3 RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE); // Habilita la interrupción global del TIM3 NVIC_InitStructure.NVIC_IRQChannel = TIM3_IRQn; NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0; NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1; NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; NVIC_Init(&NVIC_InitStructure); GPIO_SetBits(GPIOD, GPIO_Pin_12); // LED inicia encendido } Configura el puerto del LED void Configurar_LED() { GPIO_InitTypeDef GPIO_InitStructure; Pagina 49 RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOD, ENABLE); GPIO_InitStructure.GPIO_Pin = GPIO_Pin_12; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT; GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(GPIOD, &GPIO_InitStructure); } ISR de la interrupción del TIMER3 void TIM3_IRQHandler (void) { if (TIM_GetITStatus(TIM3, TIM_IT_CC1) != RESET) // Conforma la interrupción { GPIO_ToggleBits(GPIOD, GPIO_Pin_12); // Cambia el estado del pin TIM_ClearITPendingBit(TIM3, TIM_IT_CC1); // Borra la bandera de Int. } } Como se puede observar la configuración es muy elástica, muy diferente otras arquitecturas, aquí prácticamente se puede colocar cualquier número para ajustar los temporizadores. Como reformaría el programa para generar una señal de 50Hz por el Pin12? Recordar: La cantidad de Timer´s disponibles en el chip sumados al núcleo ARM depende del fabricante. Seguidamente vemos una simple forma de obtener un PWM a partir del Timer 1. (El TIM1 y TIM8 son especiales vea el diagrama interno del Cortex M4). Se pretende generar una señal de 20Khz con un duty del 50% por el pin PA8. En la línea: periodo = (SystemCoreClock / 20000 ) - 1; Donde 20000 es la frecuencia que deseamos generar y en la siguiente línea podemos configurar el duty de esta frecuencia (50% en este caso pero podría ser cualquiera). TIM_OCInitStructure.TIM_Pulse = periodo /2; /******************************************************************************* * Nombre : PWM.c * Descripción : TIM1 Chanel_1 pin 67 (PA8) * Target : STM32F407VG * ToolChain : MDK-ARM * IDE : uVision 4 * www.firtec.com.ar **********************************************************/ #include "stm32f4xx_rcc.h" #include "stm32f4xx_gpio.h" int main(void) { TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure; TIM_OCInitTypeDef TIM_OCInitStructure; GPIO_InitTypeDef GPIO_InitStructure; Pagina 50 uint16_t periodo; // Calcula el valor para obtener el periodo de 20 Khz. // periodo = (168000000/20000)-1 = 8399 Valor para obtener 20Khz periodo = (SystemCoreClock / 20000 ) - 1; // Habilita el clock del puerto A. RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA, ENABLE); // Inicializa PA8, Modo alternativo, 100Mhz, Modo Salida, Push-pull. GPIO_InitStructure.GPIO_Pin = GPIO_Pin_8; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz; GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP ; GPIO_Init(GPIOA, &GPIO_InitStructure); GPIO_PinAFConfig(GPIOA, GPIO_PinSource8, GPIO_AF_TIM1); // Clock del TIM1 está activo. RCC_APB2PeriphClockCmd(RCC_APB2Periph_TIM1 , ENABLE); // Estructura para configurar el TIMER1 TIM_TimeBaseStructInit(&TIM_TimeBaseStructure); TIM_TimeBaseStructure.TIM_Prescaler = 0; TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up; TIM_TimeBaseStructure.TIM_Period = periodo; TIM_TimeBaseInit(TIM1, &TIM_TimeBaseStructure); // Configura la salida por el Canal 1 del Timer1 TIM_OCStructInit(&TIM_OCInitStructure); TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM2; TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable; TIM_OCInitStructure.TIM_Pulse = periodo /2; TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_Low; TIM_OCInitStructure.TIM_OCIdleState = TIM_OCIdleState_Set; TIM_OC1Init(TIM1, &TIM_OCInitStructure); TIM_Cmd(TIM1, ENABLE); // TIM1 Habilitado // La salida del TIM1 esta activa y funcionando. TIM_CtrlPWMOutputs(TIM1, ENABLE); while (1) { __asm("nop"); // Bucle infinito espera el desborde el Timer. } } Si observa en la configuración del puerto por donde sale la señal (PA8) encontrará la línea: GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF; El pin se ha configura en modo alterno lo que indica que el pin se conectará con algún otro modulo. Observe esta línea de código: GPIO_PinAFConfig(GPIOA, GPIO_PinSource8, GPIO_AF_TIM1); Aquí se vincula el pin PA8 al Timer1. La generación de señales PWM es bastante simple permitiendo de esta forma un control muy fluido de sistemas de LED RGB, control de motores PAP, etc. En el siguiente ejemplo provisto por STMicroelectronics se generan cuatro señales de 42Khz con el TIM3 y cuatro duty's diferentes, para verificar el funcionamiento del este ejemplo necesitará conectar un osciloscopio en los pines: Pagina 51 • • • • PC6. (duty 50%) PC7. (duty 37.5%) PB0. (duty 25%) PB1. (duty 12.5%) Frecuencia de 42Khz. /********************************************************************************* * Nombre : PWM_4Can.c * Descripción : Genera cuatro frecuencias iguales con duty's diferentes. * Target : STM32F407VG * ToolChain : MDK-ARM * STMicroelectronics *********************************************************************************/ #include "stm32f4_discovery.h" #include <stdio.h> TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure; TIM_OCInitTypeDef TIM_OCInitStructure; uint16_t CCR1_Val = 333; uint16_t CCR2_Val = 249; uint16_t CCR3_Val = 166; uint16_t CCR4_Val = 83; uint16_t PrescalerValue = 0; void TIM_Config(void); FUNCIÓN PRINCIPAL int main(void) { TIM_Config(); // Configura el Temporizador // // // // // // // // // // TIM3CLK = HCLK / 2 = SystemCoreClock /2 To get TIM3 counter clock at 28 MHz, the prescaler is computed as follows: Prescaler = (TIM3CLK / TIM3 counter clock) - 1 Prescaler = ((SystemCoreClock /2) /28 MHz) - 1 To get TIM3 output clock at 30 KHz, the period (ARR)) is computed: ARR = (TIM3 counter clock / TIM3 output clock) – 1 = 665 TIM3 Channel1 duty cycle = (TIM3_CCR1/ TIM3_ARR)* 100 = 50% TIM3 Channel2 duty cycle = (TIM3_CCR2/ TIM3_ARR)* 100 = 37.5% TIM3 Channel3 duty cycle = (TIM3_CCR3/ TIM3_ARR)* 100 = 25% TIM3 Channel4 duty cycle = (TIM3_CCR4/ TIM3_ARR)* 100 = 12.5% /* Compute the prescaler value */ PrescalerValue = (uint16_t) ((SystemCoreClock /2) / 28000000) - 1; /* Time base configuration */ TIM_TimeBaseStructure.TIM_Period = 665; TIM_TimeBaseStructure.TIM_Prescaler = PrescalerValue; TIM_TimeBaseStructure.TIM_ClockDivision = 0; TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up; TIM_TimeBaseInit(TIM3, &TIM_TimeBaseStructure); /* PWM1 Mode configuration: Channel1 */ TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1; TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable; TIM_OCInitStructure.TIM_Pulse = CCR1_Val; Pagina 52 TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High; TIM_OC1Init(TIM3, &TIM_OCInitStructure); TIM_OC1PreloadConfig(TIM3, TIM_OCPreload_Enable); /* PWM1 Mode configuration: Channel2 */ TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable; TIM_OCInitStructure.TIM_Pulse = CCR2_Val; TIM_OC2Init(TIM3, &TIM_OCInitStructure); TIM_OC2PreloadConfig(TIM3, TIM_OCPreload_Enable); /* PWM1 Mode configuration: Channel3 */ TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable; TIM_OCInitStructure.TIM_Pulse = CCR3_Val; TIM_OC3Init(TIM3, &TIM_OCInitStructure); TIM_OC3PreloadConfig(TIM3, TIM_OCPreload_Enable); /* PWM1 Mode configuration: Channel4 */ TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable; TIM_OCInitStructure.TIM_Pulse = CCR4_Val; TIM_OC4Init(TIM3, &TIM_OCInitStructure); TIM_OC4PreloadConfig(TIM3, TIM_OCPreload_Enable); TIM_ARRPreloadConfig(TIM3, ENABLE); /* TIM3 enable counter */ TIM_Cmd(TIM3, ENABLE); while (1) {} } Función para configurar los pines de salida void TIM_Config(void) { GPIO_InitTypeDef GPIO_InitStructure; /* TIM3 clock enable */ RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE); /* GPIOC and GPIOB clock enable */ RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOC | RCC_AHB1Periph_GPIOB, ENABLE); /* GPIOC Configuration: TIM3 CH1 (PC6) and TIM3 CH2 (PC7) */ GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6 | GPIO_Pin_7 ; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz; GPIO_InitStructure.GPIO_OType = GPIO_OType_PP; GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP ; GPIO_Init(GPIOC, &GPIO_InitStructure); /* GPIOB Configuration: TIM3 CH3 (PB0) and TIM3 CH4 (PB1) */ GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0 | GPIO_Pin_1; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF; Pagina 53 GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz; GPIO_InitStructure.GPIO_OType = GPIO_OType_PP; GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP ; GPIO_Init(GPIOB, &GPIO_InitStructure); /* Connect TIM3 pins to GPIO_PinAFConfig(GPIOC, GPIO_PinAFConfig(GPIOC, GPIO_PinAFConfig(GPIOB, GPIO_PinAFConfig(GPIOB, AF2 */ GPIO_PinSource6, GPIO_PinSource7, GPIO_PinSource0, GPIO_PinSource1, GPIO_AF_TIM3); GPIO_AF_TIM3); GPIO_AF_TIM3); GPIO_AF_TIM3); } /******************* (C) COPYRIGHT STMicroelectronics *****END OF FILE***********/ Trabajo practico Analice el programa anterior para tratar de entender plenamente su funcionamiento y cambiar las señales de salida a voluntad. Pagina 54 Funcionamiento de la USART En la actualidad y ante la ausencia de puertos RS232 en las computadoras, acostumbramos a trabajar con puentes USB-RS232 que solucionan muchos o todos los problemas a la hora de vincularnos con computadoras a través del viejo protocolo 232. El STM32F407VG tiene un par de UART y varias USART que podemos usar incluso en distintos pines según nuestra conveniencia. El controlador no resuelve la capa física, es decir que para implementar las comunicaciones deberemos utilizar ya sea el clásico MAX232 o los modernos puentes USB-232. Esto es particularmente interesante porque con esta tecnología podemos reutilizar los viejos programas en puertos COM que por cuestiones de licencias o complejidad son mas simples de implementar que las aplicaciones USB nativas. El ejemplo que vamos a ver recibe los datos enviados desde el teclado de una PC y los re-transmite como un eco por el mismo puerto serial. Configuramos la USART 2 y le asignamos los pines GPIOA 2 para recibir y el GPIOA 3 para transmitir. Observe que se declaran las estructuras correspondientes al puerto, al controlador de interrupción y la estructura de la USART. GPIO_InitTypeDef GPIO_InitStructure; USART_InitTypeDef USART_InitStructure; NVIC_InitTypeDef NVIC_InitStructure; También se declaran las estructuras correspondientes a los relojes tanto del USART como del puerto involucrado. RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART2, ENABLE); RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA, ENABLE); Preste atención a que se hace referencia al bus APB1 dado que la USART que vamos a usar se encuentra en ese bus. Y desde luego AHB1 que es el bus para los puertos GPIO. Si observa la distribución de pines en el chip STM32F407VG notará que es posible asignar los pines GPIO a voluntad a distintos módulos, por ejemplo la USART2 podría también estar conectada a otros pines, lo cual lleva a pensar en que momento y como se asignan los pines a los módulos. Podemos ver aquí lo siguiente: GPIO_PinAFConfig(GPIOA, GPIO_PinSource2 ,GPIO_AF_USART2); GPIO_PinAFConfig(GPIOA, GPIO_PinSource3 ,GPIO_AF_USART2); Aquí se conectan los pines al módulo, la denominación como AF lo define como alterno a otro módulo. Pagina 55 En la configuración de pines se especifica: GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF; Lo que indica que los pines han sido configurados como alternativos a un módulo de la unidad. Se detalla a continuación el listado del programa ejemplo. /********************************************************* * Nombre : USART.c * Descripción : Funcionamiento de la USART2 en Cortex M4. * Target : STM32F407VG * ToolChain : MDK-ARM * IDE : uVision 4 * www.firtec.com.ar **********************************************************/ #include "stm32f4xx_rcc.h" #include "stm32f4xx_gpio.h" #define USARTx USART2 // Puerto que se usará /* Esta función se utiliza para transmitir una cadena de caracteres a través * El USART especificado en USARTx. * La cadena tiene que ser pasado a la función como un puntero porque * El compilador no conoce el tipo de datos string. En C una cadena es sólo * un conjunto de caracteres. **/ void Enviar_String(const char *s) { while(*s) { while(USART_GetFlagStatus(USART2, USART_FLAG_TXE) == RESET); USART_SendData(USART2, *s++); } } void Config_USARTx(void){ // Esta función configura el periférico USART2 TX=GPIOA_Pin_2 (RX) RX=GPIOA_Pin_3 (TX) RTS=GPIOA_Pin_1 (CTS) CTS=GPIOD_Pin_3 (RTS) GPIO_InitTypeDef GPIO_InitStructure; USART_InitTypeDef USART_InitStructure; NVIC_InitTypeDef NVIC_InitStructure; // Estructura para configurar las // interrupciones NVIC Activa APB1 reloj periférico para USART2 RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART2, ENABLE); Activa el reloj periférico para los pines utilizadas por la USART2, PA3 para TX y RX para PA2 RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA, ENABLE); Pagina 56 // Conecta los Pines a la UART // GPIO_PinAFConfig(GPIOA, GPIO_PinSource1 ,GPIO_AF_USART2); GPIO_PinAFConfig(GPIOA, GPIO_PinSource2 ,GPIO_AF_USART2); GPIO_PinAFConfig(GPIOA, GPIO_PinSource3 ,GPIO_AF_USART2); // GPIO_PinAFConfig(GPIOD, GPIO_PinSource3 ,GPIO_AF_USART2); //RTS (no usado) //TX //RX //CTS (no usado) // Configura los pines GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz; GPIO_InitStructure.GPIO_OType = GPIO_OType_PP; GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP; //GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1 | GPIO_Pin_2 | GPIO_Pin_3; GPIO_InitStructure.GPIO_Pin = GPIO_Pin_2 | GPIO_Pin_3; GPIO_Init(GPIOA, &GPIO_InitStructure); // Configura la USART USART_InitStructure.USART_BaudRate = 115200; // Velocidad en Baudios USART_InitStructure.USART_WordLength = USART_WordLength_8b;// 8 Bits USART_InitStructure.USART_StopBits = USART_StopBits_1; // 1 Bit de STOP USART_InitStructure.USART_Parity = USART_Parity_No; // Sin paridad USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None; USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx; USART_Init(USARTx, &USART_InitStructure); // Hace efectiva la configuración USART_ITConfig(USART2, USART_IT_RXNE, ENABLE); // Inte del receptor de activa NVIC_InitStructure.NVIC_IRQChannel = USART2_IRQn; // Configura la Interrupción. NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1; // Prioridad de USART NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0; // Subprioridad NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; // Habilitador global de inte. NVIC_Init(&NVIC_InitStructure); // La configuración se pasa a NVIC_Init(). USART_Cmd(USARTx, ENABLE); // Finalmente se habilita la USART2 } FUNCIÓN PRINCIPAL int main(void) { Config_USARTx(); Enviar_String("Esperando datos..."); // Mensaje inicial. while (1){ // Espera por la interrupción. } } Esta es la ISR para todas las interrupciones de USART2. void USART2_IRQHandler(void){ // Verifica la bandera del receptor. if( USART_GetITStatus(USART2, USART_IT_RXNE) ){ char caracter = USART2->DR; // El dato recibido es salvado en caracter USART_SendData(USART2, caracter); // Re-envía el dato recibido } } Pagina 57 Para visualizar los datos recibidos vamos a usar la aplicación terminal.exe que se encuentra entre las herramientas del curso. Se recibe el mismo dato enviado por la USART. Pagina 58 En el trabajo anterior vimos el funcionamiento de una de las USART conectada a un puerto RS232 sin embargo podemos hacer lo mismo con un VCP o Puerto Com Virtual. Para esto será necesario cargar el driver VCP_Vx.x.x_Setup_x64 o en 32 bits según el sistema operativo que tengamos. (Ambos están entre las herramientas del curso). Al conectar nuestra entrenadora al puerto USB de nuestra computadora automáticamente se creará un puerto virtual tal como se ve en la imagen siguiente. Nos conectamos con el micro USB de la entrenadora Discovery (El típico cable que viene en muchos celulares modernos) este es el puesto USB para usuario. Este tipo de aplicaciones son las clásicas aplicaciones USB_CDC. En otras arquitecturas el driver del lado de la computadora esta contenido en un archivo driver.inf pero STM lo encapsula en un ejecutable que realiza todo el trabajo para Windows 32/64 o un paquete instalador para Linux. El ejercicio que proponemos crea un puerto virtual, usted configurará el número de puerto que le resulte mas cómodo. Usando nuevamente el programa Terminal.exe nos conectamos al VCP creado y la siguiente aplicación debería tener un comportamiento igual al del ejemplo anterior. En el proyecto hay tres archivos que son vitales y a través de ellos podemos configurar toda la aplicación. • • • usbd_cdc_vcp.c usbd_desc.c usbd_usr.c En el primer archivo usbd_cdc_vcp.c vamos a encontrar dos funciones que nos van a permitir enviar y recibir datos por el puerto virtual, la función static uint16_t VCP_DataTx (uint8_t* Buf, uint32_t Pagina 59 Len) es la encargada de transmitir donde Buf es un buffer donde se guardan los datos que serán transmitidos. Len es la cantidad de datos a transmitir y la función retorna USBD_OK si todo a salido bien, en caso contrario retorna VCP_FAIL. Para enviar datos la forma de implementar la función sería: VCP_DataTx (0,dato); Donde el 0 indica el EndPoint por donde se envía la información al USB, dato es el Byte enviado. Un detalle es que esta función solo envía Bytes no se pueden enviar cadenas. La función static int16_t VCP_DataRx (uint8_t* Buf, uint32_t Len) es la encargada de recibir los datos que son almacenados en Buf siendo Len es la cantidad de datos recibidos. Notará que las funciones están declaradas como static lo que significa que las funciones solo se pueden acceder desde el módulo donde están declaradas. Si quiere tener acceso a estas funciones por ejemplo desde el main() debe quitar el static en la función y desde donde las llame agregar el modificador extern. extern uint16_t VCP_DataTx (uint8_t* Buf, uint32_t Len); Al final de este archivo encontrará la siguiente función: void DISCOVERY_EXTI_IRQHandler(void) { VCP_DataTx (0,'F'); VCP_DataTx (0,'i'); VCP_DataTx (0,'r'); VCP_DataTx (0,'t'); VCP_DataTx (0,'e'); VCP_DataTx (0,'c'); } Cada vez que se oprime el botón de usuario en la placa Discovery se envía por EndPoint 0 la palabra Firtec carácter por carácter. En el archivo usbd_desc.c encontramos todo lo referente al descriptor USB de la aplicación, si observa el contenido de este archivo verá que desde el se definen varias cosas como el PID-VID y en general la forma como la aplicación se identifica en el sistema, funciones clásicas de un descriptor USB. En usbd_usr.c encontrará cosas como estas: ////////////////////////////////////////////////////////////////////////////////// // Función que se ejecuta cuando el dispositivo se desconecta del puerto USB. ////////////////////////////////////////////////////////////////////////////////// void USBD_USR_DeviceSuspended(void) { STM32F4_Discovery_LEDOff(LED5); // Apaga LED rojo cuando desconecta // Aquí se puede poner el código para hacer algo cuando se desconecta del USB } ////////////////////////////////////////////////////////////////////////////////// // Función que se ejecuta cuando el dispositivo se conecta al puerto USB. ////////////////////////////////////////////////////////////////////////////////// void USBD_USR_DeviceResumed(void) { STM32F4_Discovery_LEDOn(LED5); // Enciende LED rojo cuando se conecta al USB // Aquí se puede poner el código para hacer algo cuando se conecta al puerto USB } Pagina 60 En este archivo se definen los eventos que el usuario ha dispuesto dependiendo de lo que este pasando en el USB, encendido de LED´s, crear funciones específicas para determinadas tareas, etc. Notará que solo hemos mencionado tres archivos de los muchos que conforman la aplicación esto porque en realidad trabajando en estos tres archivos tenemos control y configuración de lo que hacemos siendo el resto de los archivos los controladores propios del USB. Trabajo practico Como reformarías el programa anterior para que se envíe el estado de cuenta de un contador de 0 a 200. Pagina 61 Conversor Analógico con STM32F407VG. En muchos proyectos tenemos que tratar con las señales directamente de la naturaleza, como la temperatura, presión, corriente, etc . Estas señales son analógicas de forma predeterminada y en la mayoría de los casos se utilizan sensores que convierten estas señales a analógico de tensión eléctrica que será presentada en un pin del microcontrolador para hacer algún trabajo. Por desgracia, los microcontroladores son digitales y no pueden hacer frente a las señales analógicas por lo que estas señales deben ser convertidas en señales digitales que sean comprensibles por el microcontrolador. Para este propósito, los fabricantes de microcontroladores suelen incorporar uno o mas módulos ADC en el microcontrolador. Este módulo para la conversión analógica digital es omnipresente en la mayoría de los microcontroladores. De acuerdo con la hoja de datos de STM, el reloj del ADC de los núcleos Cortex debe estar en el rango de 600 kHz a 14 Mhz. Para mantener el reloj dentro de los parámetros seguros se debe usar ADC_Prescaler_Div 2, 4, 6 y 8. El controlador que estamos usando tiene tres módulos ADC de 12 bits con una serie de canales externos que pueden funcionar en un único disparo para iniciar la conversión o en modo de exploración. En el modo exploración el conversor mide de forma automática en un grupo seleccionado de entradas analógicas. El ADC puede se puede vincular al controlador DMA, interrupciones para el aviso de fin de conversión o poder sincronizar el conversor con los temporizadores TIM1,TIM2, TIM3, TIM4, TIM5 o TIM8. Los módulos ADC en STM32x utiliza el principio SAR (successive approximation register), por el que la conversión es obtenida en varios pasos. El número de pasos de conversión es igual al número de bits del ADC, 12 bits en nuestro caso. (Se recomienda la lectura de los documentos pdf CD00211314 y DM00035129) El número máximo de muestras en 1 canal es de 2,4 millones de muestras / segundo. En el modo triple ADC podemos llegar a 7,2 millones de muestras / segundo, por ejemplo triple ADC en el canal 1, 2, 3, 11, o 12. Vamos a analizar el funcionamiento básico en un canal para luego pasar a ejemplos mas complejos con los canales DMA y múltiples canales. Seleccionamos el pin PA6 configurado como analógico que medirá un voltaje proporcionado por un Pagina 62 potenciómetro, los datos se leen en una pantalla 16x2. Observe que en estos controladores los módulos pueden vincularse a distintos pines, suponiendo que usamos en ADC1 el siguiente paso seria ver si el pin PA6 se lo puede conectar al ADC1 y como se puede ver en el documento DM00037051.pdf (página 48) El pin PA6 se puede vincular a los módulos 1 y 2 publicando el canal analógico 6. Pasamos entonces a configura el pin PA6 . GPIO_InitTypeDef GPIO_InitStructure; // Estructura RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA, ENABLE);// GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6; // GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AN; // GPIO_InitStructure.GPIO_OType = GPIO_OType_PP; // GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL ; // GPIO_Init(GPIOA, &GPIO_InitStructure); // para configurar el PIN AHB1, reloj para el puerto Se trata del pin 6 En modo analógico Pin push/pull No hay NOPULL Valida la configuración Esto ha dejado el pin listo para ser usado por el ADC (Observe que no hemos hecho referencia a cual ADC), el siguiente paso es hacer una configuración global del modulo ADC. Pero antes repasemos un poco de teoría sobre los conversores AD. El valor analógico puede ser cualquiera pero los números que se puede tratar en el controlador tienen valores finitos. Está operación se suele llamar digitalización, y nos devuelve una secuencia de números entre 0 y 4095 para nuestro módulo conversor (12 bits). x1,x2,x3, . . . son los datos que entrega el conversor que luego escalaremos. En la mayoría de los conversores analógico-digitales hay dos Pagina 63 operaciones se cumplen de forma separada. Sample y Hold convierte la señal en una señal que sigue siendo analógica, pero tiene valores sólo en instantes de tiempo discretos (muestreo y retención ). ADC este elemento convierte la serie de muestras en números (conversor analógico a digital ). Anotemos algunas definiciones que no serán de utilidad: • • • ADC_Prescaler_Div: Determina el reloj del ADC siendo 14Mhz el máximo admisible. ADC_TwoSamplingDelay: Es un retardo adicional entre dos muestras. ADC_SampleTime: Es el tiempo (número de ciclos) en que se detecta la tensión de entrada al conversor. ADC_CommonInitTypeDef ADC_CommonInitStructure; ADC_CommonInitStructure.ADC_Mode = ADC_Mode_Independent; // Lee desde el canal // directamente. ADC_CommonInitStructure.ADC_Prescaler = ADC_Prescaler_Div6; // APB2 84Mhz/6 = 14hz // clock max. del adc ADC_CommonInitStructure.ADC_TwoSamplingDelay = ADC_TwoSamplingDelay_5Cycles; ADC_CommonInit(&ADC_CommonInitStructure) // Hace efectiva la configuración ADC_InitStructure.ADC_Resolution = ADC_Resolution_12b; // 12 bits (de 0-4095) ADC_InitStructure.ADC_ScanConvMode = DISABLE; // No scan de canales ADC_InitStructure.ADC_ContinuousConvMode = ENABLE; // Conversión continua ADC_InitStructure.ADC_ExternalTrigConvEdge = ADC_ExternalTrigConvEdge_None; ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_T1_CC1; ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right; ADC_InitStructure.ADC_NbrOfConversion = 1; ADC_RegularChannelConfig(ADC1, ADC_Channel_6,1,ADC_SampleTime_144Cycles); ADC_Cmd(ADC1, ENABLE); // Hace efectiva la configuración La línea: ADC_RegularChannelConfig(ADC1, ADC_Channel_6,1,ADC_SampleTime_144Cycles); Configura el modulo ADC1 conectando el canal 6 al módulo, una conversión (un canal) y se tomará una muestra cada 144 ciclos. Pagina 64