Capitulo II. - FirtecOnline

Anuncio
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
Descargar