mirror of
https://bitbucket.org/librepilot/librepilot.git
synced 2024-11-30 08:24:11 +01:00
f5e82b8242
No code changes, just file, variable and define names are changed. First, it better describes the serial protocol used by DSMx satellite receivers. Second, many people using Spektrum radio, assume Spektrum protocol. This is the attempt to address those inaccuracies.
408 lines
11 KiB
C
408 lines
11 KiB
C
/**
|
|
******************************************************************************
|
|
* @addtogroup PIOS PIOS Core hardware abstraction layer
|
|
* @{
|
|
* @addtogroup PIOS_DSM Spektrum/JR DSMx satellite receiver functions
|
|
* @brief Code to bind and read Spektrum/JR DSMx satellite receiver serial stream
|
|
* @{
|
|
*
|
|
* @file pios_dsm.c
|
|
* @author The OpenPilot Team, http://www.openpilot.org Copyright (C) 2011.
|
|
* @brief Code bind and read Spektrum/JR DSMx satellite receiver serial stream
|
|
* @see The GNU Public License (GPL) Version 3
|
|
*
|
|
*****************************************************************************/
|
|
/*
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation; either version 3 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful, but
|
|
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
|
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
|
* for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License along
|
|
* with this program; if not, write to the Free Software Foundation, Inc.,
|
|
* 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
|
*/
|
|
|
|
/* Project Includes */
|
|
#include "pios.h"
|
|
#include "pios_dsm_priv.h"
|
|
|
|
#if defined(PIOS_INCLUDE_DSM)
|
|
|
|
/* Forward Declarations */
|
|
static int32_t PIOS_DSM_Get(uint32_t rcvr_id, uint8_t channel);
|
|
static uint16_t PIOS_DSM_RxInCallback(uint32_t context,
|
|
uint8_t *buf,
|
|
uint16_t buf_len,
|
|
uint16_t *headroom,
|
|
bool *need_yield);
|
|
static void PIOS_DSM_Supervisor(uint32_t dsm_id);
|
|
|
|
/* Local Variables */
|
|
const struct pios_rcvr_driver pios_dsm_rcvr_driver = {
|
|
.read = PIOS_DSM_Get,
|
|
};
|
|
|
|
enum pios_dsm_dev_magic {
|
|
PIOS_DSM_DEV_MAGIC = 0x44534d78,
|
|
};
|
|
|
|
struct pios_dsm_state {
|
|
uint16_t channel_data[PIOS_DSM_NUM_INPUTS];
|
|
uint8_t received_data[DSM_FRAME_LENGTH];
|
|
uint8_t receive_timer;
|
|
uint8_t failsafe_timer;
|
|
uint8_t frame_found;
|
|
uint8_t byte_count;
|
|
#ifdef DSM_LOST_FRAME_COUNTER
|
|
uint8_t frames_lost_last;
|
|
uint16_t frames_lost;
|
|
#endif
|
|
};
|
|
|
|
struct pios_dsm_dev {
|
|
enum pios_dsm_dev_magic magic;
|
|
const struct pios_dsm_cfg *cfg;
|
|
enum pios_dsm_proto proto;
|
|
struct pios_dsm_state state;
|
|
};
|
|
|
|
/* Allocate DSM device descriptor */
|
|
#if defined(PIOS_INCLUDE_FREERTOS)
|
|
static struct pios_dsm_dev *PIOS_DSM_Alloc(void)
|
|
{
|
|
struct pios_dsm_dev *dsm_dev;
|
|
|
|
dsm_dev = (struct pios_dsm_dev *)pvPortMalloc(sizeof(*dsm_dev));
|
|
if (!dsm_dev)
|
|
return NULL;
|
|
|
|
dsm_dev->magic = PIOS_DSM_DEV_MAGIC;
|
|
return dsm_dev;
|
|
}
|
|
#else
|
|
static struct pios_dsm_dev pios_dsm_devs[PIOS_DSM_MAX_DEVS];
|
|
static uint8_t pios_dsm_num_devs;
|
|
static struct pios_dsm_dev *PIOS_DSM_Alloc(void)
|
|
{
|
|
struct pios_dsm_dev *dsm_dev;
|
|
|
|
if (pios_dsm_num_devs >= PIOS_DSM_MAX_DEVS)
|
|
return NULL;
|
|
|
|
dsm_dev = &pios_dsm_devs[pios_dsm_num_devs++];
|
|
dsm_dev->magic = PIOS_DSM_DEV_MAGIC;
|
|
|
|
return dsm_dev;
|
|
}
|
|
#endif
|
|
|
|
/* Validate DSM device descriptor */
|
|
static bool PIOS_DSM_Validate(struct pios_dsm_dev *dsm_dev)
|
|
{
|
|
return (dsm_dev->magic == PIOS_DSM_DEV_MAGIC);
|
|
}
|
|
|
|
/* Try to bind DSMx satellite using specified number of pulses */
|
|
static void PIOS_DSM_Bind(struct pios_dsm_dev *dsm_dev, uint8_t bind)
|
|
{
|
|
const struct pios_dsm_cfg *cfg = dsm_dev->cfg;
|
|
|
|
GPIO_InitTypeDef GPIO_InitStructure;
|
|
GPIO_InitStructure.GPIO_Pin = cfg->bind.init.GPIO_Pin;
|
|
GPIO_InitStructure.GPIO_Speed = cfg->bind.init.GPIO_Speed;
|
|
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
|
|
|
|
/* just to limit bind pulses */
|
|
if (bind > 10)
|
|
bind = 10;
|
|
|
|
GPIO_Init(cfg->bind.gpio, &cfg->bind.init);
|
|
|
|
/* RX line, set high */
|
|
GPIO_SetBits(cfg->bind.gpio, cfg->bind.init.GPIO_Pin);
|
|
|
|
/* on CC works up to 140ms, guess bind window is around 20-140ms after power up */
|
|
PIOS_DELAY_WaitmS(60);
|
|
|
|
for (int i = 0; i < bind ; i++) {
|
|
/* RX line, drive low for 120us */
|
|
GPIO_ResetBits(cfg->bind.gpio, cfg->bind.init.GPIO_Pin);
|
|
PIOS_DELAY_WaituS(120);
|
|
/* RX line, drive high for 120us */
|
|
GPIO_SetBits(cfg->bind.gpio, cfg->bind.init.GPIO_Pin);
|
|
PIOS_DELAY_WaituS(120);
|
|
}
|
|
/* RX line, set input and wait for data */
|
|
GPIO_Init(cfg->bind.gpio, &GPIO_InitStructure);
|
|
}
|
|
|
|
/* Reset channels in case of lost signal or explicit failsafe receiver flag */
|
|
static void PIOS_DSM_ResetChannels(struct pios_dsm_dev *dsm_dev)
|
|
{
|
|
struct pios_dsm_state *state = &(dsm_dev->state);
|
|
for (int i = 0; i < PIOS_DSM_NUM_INPUTS; i++) {
|
|
state->channel_data[i] = PIOS_RCVR_TIMEOUT;
|
|
}
|
|
}
|
|
|
|
/* Reset DSM receiver state */
|
|
static void PIOS_DSM_ResetState(struct pios_dsm_dev *dsm_dev)
|
|
{
|
|
struct pios_dsm_state *state = &(dsm_dev->state);
|
|
state->receive_timer = 0;
|
|
state->failsafe_timer = 0;
|
|
state->frame_found = 0;
|
|
#ifdef DSM_LOST_FRAME_COUNTER
|
|
state->frames_lost_last = 0;
|
|
state->frames_lost = 0;
|
|
#endif
|
|
PIOS_DSM_ResetChannels(dsm_dev);
|
|
}
|
|
|
|
/**
|
|
* Check and unroll complete frame data.
|
|
* \output 0 frame data accepted
|
|
* \output -1 frame error found
|
|
*/
|
|
static int PIOS_DSM_UnrollChannels(struct pios_dsm_dev *dsm_dev)
|
|
{
|
|
struct pios_dsm_state *state = &(dsm_dev->state);
|
|
uint8_t resolution;
|
|
|
|
#ifdef DSM_LOST_FRAME_COUNTER
|
|
/* increment the lost frame counter */
|
|
uint8_t frames_lost = state->received_data[0];
|
|
state->frames_lost += (frames_lost - state->frames_lost_last);
|
|
state->frames_lost_last = frames_lost;
|
|
#endif
|
|
|
|
/* check the frame type assuming master satellite stream */
|
|
uint8_t type = state->received_data[1];
|
|
switch (type) {
|
|
case 0x01:
|
|
case 0x02:
|
|
case 0x12:
|
|
/* DSM2, DSMJ stream */
|
|
if (dsm_dev->proto == PIOS_DSM_PROTO_DSM2) {
|
|
/* DSM2/DSMJ resolution is known from the header */
|
|
resolution = (type & DSM_DSM2_RES_MASK) ? 11 : 10;
|
|
} else {
|
|
/* DSMX resolution should explicitly be selected */
|
|
goto stream_error;
|
|
}
|
|
break;
|
|
case 0xA2:
|
|
case 0xB2:
|
|
/* DSMX stream */
|
|
if (dsm_dev->proto == PIOS_DSM_PROTO_DSMX10BIT) {
|
|
resolution = 10;
|
|
} else if (dsm_dev->proto == PIOS_DSM_PROTO_DSMX11BIT) {
|
|
resolution = 11;
|
|
} else {
|
|
/* DSMX resolution should explicitly be selected */
|
|
goto stream_error;
|
|
}
|
|
break;
|
|
default:
|
|
/* unknown yet data stream */
|
|
goto stream_error;
|
|
}
|
|
|
|
/* unroll channels */
|
|
uint8_t *s = &(state->received_data[2]);
|
|
uint16_t mask = (resolution == 10) ? 0x03ff : 0x07ff;
|
|
|
|
for (int i = 0; i < DSM_CHANNELS_PER_FRAME; i++) {
|
|
uint16_t word = ((uint16_t)s[0] << 8) | s[1];
|
|
s += 2;
|
|
|
|
/* skip empty channel slot */
|
|
if (word == 0xffff)
|
|
continue;
|
|
|
|
/* minimal data validation */
|
|
if ((i > 0) && (word & DSM_2ND_FRAME_MASK)) {
|
|
/* invalid frame data, ignore rest of the frame */
|
|
goto stream_error;
|
|
}
|
|
|
|
/* extract and save the channel value */
|
|
uint8_t channel_num = (word >> resolution) & 0x0f;
|
|
if (channel_num < PIOS_DSM_NUM_INPUTS)
|
|
state->channel_data[channel_num] = (word & mask);
|
|
}
|
|
|
|
#ifdef DSM_LOST_FRAME_COUNTER
|
|
/* put lost frames counter into the last channel for debugging */
|
|
state->channel_data[PIOS_DSM_NUM_INPUTS-1] = state->frames_lost;
|
|
#endif
|
|
|
|
/* all channels processed */
|
|
return 0;
|
|
|
|
stream_error:
|
|
/* either DSM2 selected with DSMX stream found, or vice-versa */
|
|
return -1;
|
|
}
|
|
|
|
/* Update decoder state processing input byte from the DSMx stream */
|
|
static void PIOS_DSM_UpdateState(struct pios_dsm_dev *dsm_dev, uint8_t byte)
|
|
{
|
|
struct pios_dsm_state *state = &(dsm_dev->state);
|
|
if (state->frame_found) {
|
|
/* receiving the data frame */
|
|
if (state->byte_count < DSM_FRAME_LENGTH) {
|
|
/* store next byte */
|
|
state->received_data[state->byte_count++] = byte;
|
|
if (state->byte_count == DSM_FRAME_LENGTH) {
|
|
/* full frame received - process and wait for new one */
|
|
if (!PIOS_DSM_UnrollChannels(dsm_dev))
|
|
/* data looking good */
|
|
state->failsafe_timer = 0;
|
|
|
|
/* prepare for the next frame */
|
|
state->frame_found = 0;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Initialise DSM receiver interface */
|
|
int32_t PIOS_DSM_Init(uint32_t *dsm_id,
|
|
const struct pios_dsm_cfg *cfg,
|
|
const struct pios_com_driver *driver,
|
|
uint32_t lower_id,
|
|
enum pios_dsm_proto proto,
|
|
uint8_t bind)
|
|
{
|
|
PIOS_DEBUG_Assert(dsm_id);
|
|
PIOS_DEBUG_Assert(cfg);
|
|
PIOS_DEBUG_Assert(driver);
|
|
|
|
struct pios_dsm_dev *dsm_dev;
|
|
|
|
dsm_dev = (struct pios_dsm_dev *)PIOS_DSM_Alloc();
|
|
if (!dsm_dev)
|
|
return -1;
|
|
|
|
/* Bind the configuration to the device instance */
|
|
dsm_dev->cfg = cfg;
|
|
dsm_dev->proto = proto;
|
|
|
|
/* Bind the receiver if requested */
|
|
if (bind)
|
|
PIOS_DSM_Bind(dsm_dev, bind);
|
|
|
|
PIOS_DSM_ResetState(dsm_dev);
|
|
|
|
*dsm_id = (uint32_t)dsm_dev;
|
|
|
|
/* Set comm driver callback */
|
|
(driver->bind_rx_cb)(lower_id, PIOS_DSM_RxInCallback, *dsm_id);
|
|
|
|
if (!PIOS_RTC_RegisterTickCallback(PIOS_DSM_Supervisor, *dsm_id)) {
|
|
PIOS_DEBUG_Assert(0);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* Comm byte received callback */
|
|
static uint16_t PIOS_DSM_RxInCallback(uint32_t context,
|
|
uint8_t *buf,
|
|
uint16_t buf_len,
|
|
uint16_t *headroom,
|
|
bool *need_yield)
|
|
{
|
|
struct pios_dsm_dev *dsm_dev = (struct pios_dsm_dev *)context;
|
|
|
|
bool valid = PIOS_DSM_Validate(dsm_dev);
|
|
PIOS_Assert(valid);
|
|
|
|
/* process byte(s) and clear receive timer */
|
|
for (uint8_t i = 0; i < buf_len; i++) {
|
|
PIOS_DSM_UpdateState(dsm_dev, buf[i]);
|
|
dsm_dev->state.receive_timer = 0;
|
|
}
|
|
|
|
/* Always signal that we can accept another byte */
|
|
if (headroom)
|
|
*headroom = DSM_FRAME_LENGTH;
|
|
|
|
/* We never need a yield */
|
|
*need_yield = false;
|
|
|
|
/* Always indicate that all bytes were consumed */
|
|
return buf_len;
|
|
}
|
|
|
|
/**
|
|
* Get the value of an input channel
|
|
* \param[in] channel Number of the channel desired (zero based)
|
|
* \output PIOS_RCVR_INVALID channel not available
|
|
* \output PIOS_RCVR_TIMEOUT failsafe condition or missing receiver
|
|
* \output >0 channel value
|
|
*/
|
|
static int32_t PIOS_DSM_Get(uint32_t rcvr_id, uint8_t channel)
|
|
{
|
|
struct pios_dsm_dev *dsm_dev = (struct pios_dsm_dev *)rcvr_id;
|
|
|
|
if (!PIOS_DSM_Validate(dsm_dev))
|
|
return PIOS_RCVR_INVALID;
|
|
|
|
/* return error if channel is not available */
|
|
if (channel >= PIOS_DSM_NUM_INPUTS)
|
|
return PIOS_RCVR_INVALID;
|
|
|
|
/* may also be PIOS_RCVR_TIMEOUT set by other function */
|
|
return dsm_dev->state.channel_data[channel];
|
|
}
|
|
|
|
/**
|
|
* Input data supervisor is called periodically and provides
|
|
* two functions: frame syncing and failsafe triggering.
|
|
*
|
|
* DSM frames come at 11ms or 22ms rate at 115200bps.
|
|
* RTC timer is running at 625Hz (1.6ms). So with divider 5 it gives
|
|
* 8ms pause between frames which is good for both DSM frame rates.
|
|
*
|
|
* Data receive function must clear the receive_timer to confirm new
|
|
* data reception. If no new data received in 100ms, we must call the
|
|
* failsafe function which clears all channels.
|
|
*/
|
|
static void PIOS_DSM_Supervisor(uint32_t dsm_id)
|
|
{
|
|
struct pios_dsm_dev *dsm_dev = (struct pios_dsm_dev *)dsm_id;
|
|
|
|
bool valid = PIOS_DSM_Validate(dsm_dev);
|
|
PIOS_Assert(valid);
|
|
|
|
struct pios_dsm_state *state = &(dsm_dev->state);
|
|
|
|
/* waiting for new frame if no bytes were received in 8ms */
|
|
if (++state->receive_timer > 4) {
|
|
state->frame_found = 1;
|
|
state->byte_count = 0;
|
|
state->receive_timer = 0;
|
|
}
|
|
|
|
/* activate failsafe if no frames have arrived in 102.4ms */
|
|
if (++state->failsafe_timer > 64) {
|
|
PIOS_DSM_ResetChannels(dsm_dev);
|
|
state->failsafe_timer = 0;
|
|
}
|
|
}
|
|
|
|
#endif /* PIOS_INCLUDE_DSM */
|
|
|
|
/**
|
|
* @}
|
|
* @}
|
|
*/
|