Bueno, hoy tengo ganas de avanzar un poco por este lado, así que veamos un poco la lectura de la señal del receptor.
El otro día estaba pensando y finalmente me decidí por tomar la señal PPM dentro del receptor de RC y luego decodificarla usando una sola entrada Input Capture.
Entonces me decidí a soldar los cables en el interior del receptor:

como ven he soldado un cable en línea negativo (verde), otro en el naranja (positivo) y otro en el pin 1 (clock) del registro de desplazamiento. Es ese registro el que se encarga de decodificar la señal PPM en cada canal PWM (cable amarillo). Entonces desde mi placa de desarrollo para la LPC1769 alimento el módulo y la señal PPM ingresará al pin P0.4 (CAP2.0).
Los ARM cortex M3 tienen 4 timer de 32 bits, cada uno con dos input capture. Acá podría usar una entrada común y su interrupcion on change, pero eso requerirá un poco más de software, en cambio si usamos el input capture, todo lo relacionado a almacenar el valor del timer en el registro CAPTURE, generar interrupcion, etc, lo hace el hardware dedicado.
Bueno, para saber como configurar el timer debemos saber con que señales esperamos encontrarnos. Dijimos que la señal PPM codifica el tiempo en ON del PWM de cada canal uno destras del otro. Esperamos encontrar entonces (como vimos en el post anterior) un tren de pulso VARIABLES, y el período de cada pulso (tiempo ON + Tiempo OFF) es el tiempo ON del PWM de cada canal. El tiempo Off del PWM de cada canal es 20mseg (la frecuencia de trabajo del PWM es 50Hz) menos el tiempo ON. Veamos entonces entre que valores varía el tiempo ON de cada canal:

Aquí vemos que con el TRIM en 0 y el Stick del TX en 0 el tiempo ON del PWM (en este caso el canal 3) es de prácticamente 1ms.

Aquí vemos que con el TRIM en al máximo y el Stick del TX en el máximo el tiempo ON del PWM (en este caso el canal 3) es de prácticamente 2ms.
Bien, el timer 2 que quiero usar con la Input Captura 2, canal 0 es de 32 bits. Si el micro corre a 100MHz (puedo llegar a 120, pero voy a 100

) y configuro como entrada del timer la señal CoreClock tendremo un Tick cada 10nseg. con 32 bits puedo contar hasta 4.294.967.296 o lo que es lo mismo, 42.9 segundos, con una presicion de 10nseg!!!!! Bue, no pongo prescaler ni nada y contaremos decenas de nsegundos. La idea es que cada flanco de subida de la señal PPM me dispare una Input Capture, voy almacenando el Tiempo actual y el previo, de modo tal de poder medir cuantas decenas de ns pasa entre cada Rising edge de la señal PPM.
Como hago para sincronizar el comienzo del canal 1? buscando el pulso final, el cual es mucho más largo. Cuando lo encuentro, inicializo un contador y a aprtir de alli, voy llevando la cuenta de los pulsos de cada canal.
Lo bueno de esto es que trabaja de fondo, con interrupcion, por lo que lo dejo midiendo los pulsos del Rx y cuando deseo saber el contenido actual, de forma asíncrona llamo a una funcion que me devuelve el array con los 4 valores de los 4 canales.
Este driver está tomado de una de las páginas que puse en el primer post y fue modificada para mi necesida.
Bien, mi Rx.h es el siguiente:
#ifndef RX_H_
#define RX_H_
#include "LPC17xx.h"
#define RX_CHANNELS 4
/* defines */
#define RX_SUCCESS (0x00)
#define RX_NO_PACKET (0x01)
/* typedefs */
/* functions */
void rx_init(uint8_t irq_priority);
uint8_t rx_get_packet(uint32_t packet[RX_CHANNELS]);
#endif /* RX_H_ */
Aquí, como punto importante es definir el número de canales de nuestro Rx.
Luego mi Rx.c es el siguiente:
/*
* RX.c
*
* Created on: 28/01/2014
* Author: elgarbe
*/
/*****************************************************************************
Copyright 2011
All Rights Reserved
Information:
File Name : rx.c
Author(s) : Nic McDonald
Project : Quadrotor
Hardware : LPCXpresso LPC1768
Purpose : RC Receiver Interface
******************************************************************************
Modification History:
Revision Date Author Description of Revision
1.00 05/19/2011 NGM initial
******************************************************************************
Theory of Operation:
This modules implements an RC receiver interface. It expects the input
signal to have all channels combined into one signal and each pulse must be
separated.
Ex:
____
__| |___________________________ channel 1
__
_________| |______________________ channel 2
___
______________| |________________ channel 3
____ __ ___
__| |_| |_| |________________ all channels
This implementation utilizes TIMER2 to measure PWM signals.
******************************************************************************
Esta explicación es para el caso de hacer una OR con cada salida del Rx. En mi caso
extraigo la señal PPM del Rx y por lo tanto tengo que leer solamente flancos de subida,
ya que cada pulso termina donde comienza el siguiente, no hay un tiempo muerto entre
pulso y pulso
*****************************************************************************/
#include "rx.h"
/****************************************************************************/
/* Local Defines */
/****************************************************************************/
#define TICKS_PER_US (100) //A 100MHz el Tick es de 10ns x 100 = 1useg
#define TICKS_PER_MS (TICKS_PER_US*1000)
/****************************************************************************/
/* Private Variables */
/****************************************************************************/
static uint32_t pulses[RX_CHANNELS]; //Matriz para guardarel tiempo de cada pulso de cada canal
static uint8_t packet_available; //Bandera para indicar que ya recibimos RX_CHANNELS pulsos
/****************************************************************************/
/* Private Function Declarations */
/****************************************************************************/
static inline void cap2_0_interrupts_on(void);
static inline void cap2_0_interrupts_off(void);
/****************************************************************************/
/* Public Functions */
/****************************************************************************/
// This functions initializes TIMER2 as the receiver interface.
// Assumes system clock is 100 MHz.
void rx_init(uint8_t irq_priority) {
uint32_t ch;
// clear the pulses array
for (ch=0; ch<RX_CHANNELS; ch++)
pulses[ch] = 0;
// start with no packets available
packet_available = 0;
// give power to TIMER2
LPC_SC->PCONP |= (1 << 22);
// set peripheral clock selection for timer 2
LPC_SC->PCLKSEL1 &= ~(3 << 12); //Borramos bits
LPC_SC->PCLKSEL1 |= (1 << 12); // set to "01" = Core Clock (100 MHz)
// set the pin functions to CAP2.0 for P0.4
LPC_PINCON->PINSEL0 &= ~(3 << 8); //Borramos bits. Seteamos a 11 en los bits 8 y 9
LPC_PINCON->PINSEL0 |= (3 << 8); //para elejir CAP2.0 como funcion del puerto P0.4
// set the clock source for TIMER2 to PCLK
LPC_TIM2->CTCR = 0; // this is the default
// set the prescaler
LPC_TIM2->PR = 0; // TC increments on every PCLK
// enable and reset the the counters
LPC_TIM2->TCR = 0x00000003;
LPC_TIM2->TCR = 0x00000001;
// set captures to occur on rising edges
LPC_TIM2->CCR = 0x00000001; //Capture only on Rising edge. Sin interrupciones
// Habilito la interrupcion en la Input Capture
cap2_0_interrupts_on();
// Registramos la interrupcion del Timer2
NVIC_EnableIRQ(TIMER2_IRQn);
// Fijamos la prioridad de la interrupcion
NVIC_SetPriority(TIMER2_IRQn, irq_priority); // '0' es la mayor
}
// If a packet has arrived since the last call, 'packet' is filled with
// the contents of the packet and RX_SUCCESS is returned. If no packet
// is available, RX_NO_PACKET is returned.
// The size of 'packet' must be at least RX_CHANNELS.
// This is NOT thread safe.
uint8_t rx_get_packet(uint32_t packet[RX_CHANNELS]) {
uint32_t avail, ch;
// turn off timer2 capture interrupts while accessing packet_available
cap2_0_interrupts_off();
avail = packet_available; // grab current value
packet_available = 0; // set to zero
cap2_0_interrupts_on(); // Vuelvo a encender la Interrupcion
// check if a packet is available
if (avail) {
// copy packet
for (ch=0; ch<RX_CHANNELS; ch++)
packet[ch] = pulses[ch];
// set as success
return RX_SUCCESS;
}
else {
// set as failure
return RX_NO_PACKET;
}
}
/****************************************************************************/
/* Private Functions */
/****************************************************************************/
static inline void cap2_0_interrupts_on(void) {
LPC_TIM2->CCR |= 0x00000004;
}
static inline void cap2_0_interrupts_off(void) {
LPC_TIM2->CCR &= ~0x00000004;
}
/****************************************************************************/
/* Interrupt Functions */
/****************************************************************************/
void TIMER2_IRQHandler(void) {
// persistent local variables
static uint32_t tmp_pulses[RX_CHANNELS];
static uint32_t prev_ticks = 0;
static uint32_t current_pulse = 0;
// local variables
uint32_t curr_ticks, nanos;
// uint32_t rising_edge;
uint32_t c;
// // check that this interrupt is from CAP2.0
if((LPC_TIM2->IR & 0x0000003F) == 0x00000010){
// compute delta time (each tick is 10ns)
curr_ticks = LPC_TIM2->CR0;
nanos = (curr_ticks - prev_ticks) * 10;
//Si el tiempo del pulso supera los 3mseg es que estamos en el pulso de sincronismo
if (nanos > 3000000){
current_pulse = 0;
}
else {
// store this sample
tmp_pulses[current_pulse] = nanos;
// advance to next pulse
current_pulse++;
// check for end of packet
if (current_pulse == RX_CHANNELS) {
// wrap pointer
current_pulse = 0;
// copy out packet
for (c=0; c<RX_CHANNELS; c++)
pulses[c] = tmp_pulses[c];
// signal available packet
packet_available = 1;
}
}
// clear the interrupt
LPC_TIM2->IR |= 0x00000010;
// save current as previous for next event
prev_ticks = curr_ticks;
}
}
He dejado parte de la explicación del código original para referencia.
Mi main.c es este:
/*
===============================================================================
Name : main.c
Author : elgarbe
Description : Esta práctica es para mostrar un conteo binario con led externos (P2.0..7)
a una frecuencia establecida por el timer. Hace uso de la librería CMSIS
===============================================================================
*/
#ifdef __USE_CMSIS
#include "LPC17xx.h"
#endif
#include <cr_section_macros.h>
#include <NXP/crp.h>
#include "RX.h"
#include "uart2.h"
__CRP const unsigned int CRP_WORD = CRP_NO_CRP ;
volatile uint32_t msTicks;
__INLINE static void delay_ms (uint32_t delayTicks) {
uint32_t currentTicks;
currentTicks = msTicks; // read current tick counter
while ((msTicks - currentTicks) < delayTicks);
}
void SysTick_Handler(void) {
msTicks++; /* increment counter necessary in Delay() */
}
int main(void) {
uint32_t rx_values[RX_CHANNELS];
uint8_t hay_datos = 0;
uint8_t canal=0;
//Configuro el SysTick para que interrumpa cada 1mseg
if (SysTick_Config(SystemCoreClock / 1000)) {
while (1);
}
rx_init(0); //Inicializo el Timer2 para que lea la señal PPM
UART2_Init(115200); // Inicializamos el UART a 115200
UART2_PrintString ("\r\nLeyendo Rx\r\n");
UART2_PrintString ("===== elgarbe =====\r\n");
while(1) {
hay_datos = rx_get_packet(rx_values); //Verifico si hay datos nuevo provenientes del Tx
if(hay_datos == RX_SUCCESS){ //esto es asincrónico, verifico cuando quiero saber
for (canal=0; canal<RX_CHANNELS; canal++){ //Pero la int por input capture
uart2_printUint32(rx_values[canal], 10); //esta constantemente leyendo los datos
UART2_Sendchar(','); //de la señal PPM del Rx
}
UART2_PrintString("\r\n");
}
delay_ms(250);
}
return 0 ;
}
Bien, veamos una captura del hyperterminal cuando muevo el stick del canal 1:

Como ven el canal 1 varía desde 1.5mseg en reposo hasta 1.93 con el stick en un extremo y 1.03 con el stick en otro extremo.
Bueno, por ahora un tema menos, ya podemos recibir comandos con el RC!!!
Saludos!