diff --git a/demos/modules/gadc/main.c b/demos/modules/gadc/main.c index 8e5ecaa4..67eed456 100644 --- a/demos/modules/gadc/main.c +++ b/demos/modules/gadc/main.c @@ -167,10 +167,19 @@ int main(void) { gtimerStart(&lsTimer, LowSpeedTimer, ghText, TRUE, MY_LS_DELAY); #endif - // Allocate buffers for the high speed GADC device - 4 x 128 byte buffers. - // You may need to increase this for slower cpu's. - // You may be able to decrease this for low latency operating systems. - gfxBufferAlloc(4, 128); + /** + * Allocate buffers for the high speed GADC device - eg. 4 x 128 byte buffers. + * You may need to increase this for slower cpu's. + * You may be able to decrease this for low latency operating systems. + * 10 x 128 seems to work on the really slow Olimex SAM7EX256 board (display speed limitation) + * If your oscilloscope display stops but the low speed reading keep going then it is likely that + * your high speed timer has stalled due to running out of free buffers. Increase the number + * of buffers.. + * If you make the buffers too large with a slow sample rate you may not allow enough time for all + * the low speed items to occur in which case your memory will fill up with low speed requests until + * you run out of memory. + */ + gfxBufferAlloc(10, 128); /* Set up the scope window in the top right on the screen */ { diff --git a/drivers/gadc/AT91SAM7/gadc_lld.c b/drivers/gadc/AT91SAM7/gadc_lld.c index 52b06539..b01fdd6c 100644 --- a/drivers/gadc/AT91SAM7/gadc_lld.c +++ b/drivers/gadc/AT91SAM7/gadc_lld.c @@ -16,13 +16,12 @@ #include "src/gadc/driver.h" -static GDataBuffer *pData; -static size_t bytesperconversion; +static uint32_t nextfreq; +// Forward references to ISR routines static void ISR_CompleteI(ADCDriver *adcp, adcsample_t *buffer, size_t n); static void ISR_ErrorI(ADCDriver *adcp, adcerror_t err); - static ADCConversionGroup acg = { FALSE, // circular 1, // num_channels @@ -37,82 +36,53 @@ static void ISR_CompleteI(ADCDriver *adcp, adcsample_t *buffer, size_t n) { (void) adcp; (void) buffer; - if (pData) { - // A set of timer base conversions is complete - pData->len += n * bytesperconversion; - - // Are we finished yet? - // In ChibiOS we (may) get a half-buffer complete. In this situation the conversions - // are really not complete and so we just wait for the next lot of data. - if (pData->len + bytesperconversion > pData->size) - gadcDataReadyI(); - - } else { - // A single non-timer conversion is complete - gadcDataReadyI(); - } + gadcGotDataI(n); } static void ISR_ErrorI(ADCDriver *adcp, adcerror_t err) { (void) adcp; (void) err; - gadcDataFailI(); + gadcGotDataI(0); } void gadc_lld_init(void) { adcStart(&ADCD1, 0); } -void gadc_lld_start_timer(GadcLldTimerData *pgtd) { - int phys; +size_t gadc_lld_samplesperconversion(uint32_t physdev) { + size_t samples; - /* Calculate the bytes per conversion from physdev */ - /* The AT91SAM7 has AD0..7 - physdev is a bitmap of those channels */ - phys = pgtd->physdev; - for(bytesperconversion = 0; phys; phys >>= 1) - if (phys & 0x01) - bytesperconversion++; - bytesperconversion *= (gfxSampleFormatBits(GADC_SAMPLE_FORMAT)+7)/8; - - /** - * The AT91SAM7 ADC driver supports triggering the ADC using a timer without having to implement - * an interrupt handler for the timer. The driver also initialises the timer correctly for us. - * Because we aren't trapping the interrupt ourselves we can't increment GADC_Timer_Missed if an - * interrupt is missed. - */ - acg.frequency = pgtd->frequency; + for(samples = 0; physdev; physdev >>= 1) + if (physdev & 0x01) + samples++; + return samples; } -void gadc_lld_stop_timer(GadcLldTimerData *pgtd) { - (void) pgtd; - if ((acg.trigger & ~ADC_TRIGGER_SOFTWARE) == ADC_TRIGGER_TIMER) - adcStop(&ADCD1); +void gadc_lld_start_timerI(uint32_t frequency) { + // Nothing to do here - the AT91SAM7 adc driver uses an internal timer + // which is set up when the job is started. We save this here just to + // indicate the timer should be re-initialised on the next timer job + nextfreq = frequency; } -void gadc_lld_adc_timerI(GadcLldTimerData *pgtd) { - /** - * We don't need to calculate num_channels because the AT91SAM7 ADC does this for us. - */ - acg.channelselects = pgtd->physdev; - acg.trigger = pgtd->now ? (ADC_TRIGGER_TIMER|ADC_TRIGGER_SOFTWARE) : ADC_TRIGGER_TIMER; - - pData = pgtd->pdata; - adcStartConversionI(&ADCD1, &acg, (adcsample_t *)(pgtd->pdata+1), pData->size/bytesperconversion); - - /* Next time assume the same (still running) timer */ - acg.frequency = 0; +void gadc_lld_stop_timerI(void) { + // Nothing to do here. The AT91SAM7 adc driver automatically turns off timer interrupts + // on completion of the job } -void gadc_lld_adc_nontimerI(GadcLldNonTimerData *pgntd) { - /** - * We don't need to calculate num_channels because the AT91SAM7 ADC does this for us. - */ - acg.channelselects = pgntd->physdev; +void gadc_lld_timerjobI(GadcTimerJob *pj) { + acg.channelselects = pj->physdev; + acg.trigger = ADC_TRIGGER_TIMER; + acg.frequency = nextfreq; + nextfreq = 0; // Next job use the same timer + adcStartConversionI(&ADCD1, &acg, pj->buffer, pj->todo); +} + +void gadc_lld_nontimerjobI(GadcNonTimerJob *pj) { + acg.channelselects = pj->physdev; acg.trigger = ADC_TRIGGER_SOFTWARE; - - pData = 0; - adcStartConversionI(&ADCD1, &acg, pgntd->buffer, 1); + adcStartConversionI(&ADCD1, &acg, pj->buffer, 1); } #endif /* GFX_USE_GADC */ diff --git a/src/gadc/driver.h b/src/gadc/driver.h index 6e935576..4145bc4a 100644 --- a/src/gadc/driver.h +++ b/src/gadc/driver.h @@ -27,38 +27,27 @@ /** * @brief The structure passed to start a timer conversion - * @note We use the structure instead of parameters purely to save - * interrupt stack space which is very limited in some platforms. * @{ */ -typedef struct GadcLldTimerData_t { - uint32_t physdev; /* @< Which physical ADC devices/channels to use. Filled in by High Level Code */ - uint32_t frequency; /* @< The conversion frequency. Filled in by High Level Code */ - GDataBuffer *pdata; /* @< The buffer to put the ADC samples into. */ - bool_t now; /* @< Trigger the first conversion now rather than waiting for the first timer interrupt (if possible) */ - } GadcLldTimerData; +typedef struct GadcTimerJob_t { + uint32_t physdev; // @< The physical device/s. The exact meaning of physdev is hardware dependent. + uint32_t frequency; // @< The frequency to sample + adcsample_t *buffer; // @< Where to put the samples + size_t todo; // @< How many conversions to do + size_t done; // @< How many conversions have already been done +} GadcTimerJob; /* @} */ /** - * @brief The structure passed to start a non-timer conversion - * @note We use the structure instead of parameters purely to save - * interrupt stack space which is very limited in some platforms. + * @brief The structure passed to do a single conversion * @{ */ -typedef struct GadcLldNonTimerData_t { - uint32_t physdev; /* @< A value passed to describe which physical ADC devices/channels to use. */ - adcsample_t *buffer; /* @< The static buffer to put the ADC samples into. */ - } GadcLldNonTimerData; +typedef struct GadcNonTimerJob_t { + uint32_t physdev; // @< The physical device/s. The exact meaning of physdev is hardware dependent. + adcsample_t *buffer; // @< Where to put the samples. + } GadcNonTimerJob; /* @} */ -/** - * @brief This can be incremented by the low level driver if a timer interrupt is missed. - * @details Defined in the high level GADC code. - * - * @notapi - */ -extern volatile bool_t GADC_Timer_Missed; - /*===========================================================================*/ /* External declarations. */ /*===========================================================================*/ @@ -75,14 +64,15 @@ extern "C" { * @{ */ /** - * @brief The last conversion requested is now complete + * @brief Indicate that some data has been placed into the buffer for the current job + * + * @param[in] n The number of samples placed in the buffer + * + * @note Calling this with n = 0 causes the current job to be terminated early or aborted. + * It can be called in this mode on an ADC conversion error. Any job will then be + * restarted by the high level code as appropriate. */ - void gadcDataReadyI(void); - - /** - * @brief The last conversion requested failed - */ - void gadcDataFailI(void); + void gadcGotDataI(size_t n); /** * @} */ @@ -95,71 +85,57 @@ extern "C" { void gadc_lld_init(void); /** - * @brief Start a periodic timer for high frequency conversions. + * @brief Return the number of samples per conversion * - * @param[in] pgtd The structure containing the sample frequency and physical device to use. - * - * @note The exact meaning of physdev is hardware dependent. It describes the channels - * the will be used later on when a "timer" conversion is actually scheduled. - * @note It is assumed that the timer is capable of free-running even when the ADC - * is stopped or doing something else. - * @details When a timer interrupt occurs a conversion should start if there is a "timer" conversion - * active. - * @note Timer interrupts occurring before @p gadc_lld_adc_timerI() has been called, - * if @p gadc_lld_adc_timerI() has been called quick enough, or while - * a non-timer conversion is active should be ignored other than (optionally) incrementing - * the GADC_Timer_Missed variable. - * @note The pdata and now members of the pgtd structure are now yet valid. + * @param[in] physdev The hardware dependent physical device descriptor * * @api */ -void gadc_lld_start_timer(GadcLldTimerData *pgtd); +size_t gadc_lld_samplesperconversion(uint32_t physdev); + +/** + * @brief Start a periodic timer for high frequency conversions. + * + * @param[in] freq The frequency for the timer + * + * @note This will only be called if the timer is currently stopped. + * + * @api + * @iclass + */ +void gadc_lld_start_timerI(uint32_t freq); /** * @brief Stop the periodic timer for high frequency conversions. - * @details Also stops any current "timer" conversion (but not a current "non-timer" conversion). * - * @param[in] pgtd The structure containing the sample frequency and physical device to use. + * @note This will only be called if the timer is currently running and all timer jobs + * have been completed/aborted. * - * @note After this function returns there should be no more calls to @p gadcDataReadyI() - * or @p gadcDataFailI() in relation to timer conversions. * @api - */ -void gadc_lld_stop_timer(GadcLldTimerData *pgtd); - -/** - * @brief Start a set of "timer" conversions. - * @details Starts a series of conversions triggered by the timer. - * - * @param[in] pgtd Contains the parameters for the timer conversion. - * - * @note The exact meaning of physdev is hardware dependent. It is likely described in the - * drivers gadc_lld_config.h - * @note The driver should call @p gadcDataReadyI() when it completes the operation - * or @p gadcDataFailI() on an error. - * @note The high level code ensures that this is not called while a non-timer conversion is in - * progress - * * @iclass */ -void gadc_lld_adc_timerI(GadcLldTimerData *pgtd); +void gadc_lld_stop_timerI(void); /** - * @brief Start a "non-timer" conversion. - * @details Starts a single conversion now. + * @brief Start a set of high frequency conversions. * - * @param[in] pgntd Contains the parameters for the non-timer conversion. - * - * @note The exact meaning of physdev is hardware dependent. It is likely described in the - * drivers gadc_lld_config.h - * @note The driver should call @p gadcDataReadyI() when it completes the operation - * or @p gadcDataFailI() on an error. - * @note The high level code ensures that this is not called while a timer conversion is in - * progress + * @note This will only be called if the timer is currently running and the ADC should be ready for + * a new job. * + * @api * @iclass */ -void gadc_lld_adc_nontimerI(GadcLldNonTimerData *pgntd); +void gadc_lld_timerjobI(GadcTimerJob *pjob); + +/** + * @brief Start a non-timer conversion. + * + * @note This will only be called if the ADC should be ready for a new job. + * + * @api + * @iclass + */ +void gadc_lld_nontimerjobI(GadcNonTimerJob *pjob); #ifdef __cplusplus } diff --git a/src/gadc/gadc.c b/src/gadc/gadc.c index e2d2d461..d307519b 100644 --- a/src/gadc/gadc.c +++ b/src/gadc/gadc.c @@ -23,206 +23,151 @@ #error "GADC: GADC_MAX_HIGH_SPEED_SAMPLERATE has been set too high. It must be less than half the maximum CPU rate" #endif -#define GADC_MAX_LOWSPEED_DEVICES ((GADC_MAX_SAMPLE_FREQUENCY/GADC_MAX_HIGH_SPEED_SAMPLERATE)-1) -#if GADC_MAX_LOWSPEED_DEVICES > 4 - #undef GADC_MAX_LOWSPEED_DEVICES - #define GADC_MAX_LOWSPEED_DEVICES 4 -#endif +#define GADC_HSADC_GTIMER 0x8000 +#define GADC_ADC_RUNNING 0x4000 +#define GADC_HSADC_CONVERTION 0x2000 -volatile bool_t GADC_Timer_Missed; - -static bool_t gadcRunning; -static gfxSem LowSpeedSlotSem; -static gfxMutex LowSpeedMutex; -static GTimer LowSpeedGTimer; -static gfxQueueGSync HighSpeedBuffers; +typedef struct NonTimerData_t { + gfxQueueGSyncItem next; + GADCCallbackFunction callback; + union { + void *param; + gfxSem sigdone; + }; + GadcNonTimerJob job; + } NonTimerData; +static volatile uint16_t hsFlags; +static size_t hsBytesPerConv; +static GadcTimerJob hsJob; +static GDataBuffer *hsData; +static gfxQueueGSync hsListDone; +static GADCISRCallbackFunction hsISRcallback; #if GFX_USE_GEVENT - static GTimer HighSpeedGTimer; + static GTimer hsGTimer; #endif +static GTimer lsGTimer; +static gfxQueueGSync lsListToDo; +static gfxQueueGSync lsListDone; +static NonTimerData *lsData; -#define GADC_FLG_ISACTIVE 0x0001 -#define GADC_FLG_ISDONE 0x0002 -#define GADC_FLG_ERROR 0x0004 -#define GADC_FLG_GTIMER 0x0008 -#define GADC_FLG_STALLED 0x0010 +void gadcGotDataI(size_t n) { + if ((hsFlags & GADC_HSADC_CONVERTION)) { -static struct hsdev { - // Our status flags - uint16_t flags; + // A set of timer conversions is done - add them + hsJob.done += n; - // Other stuff we need to track progress and for signaling - GadcLldTimerData lld; - uint16_t eventflags; - GADCISRCallbackFunction isrfn; - } hs; - -static struct lsdev { - // Our status flags - uint16_t flags; - - // What we started with - GadcLldNonTimerData lld; - GADCCallbackFunction fn; - void *param; - } ls[GADC_MAX_LOWSPEED_DEVICES]; - -static struct lsdev *curlsdev; - -/* Find the next conversion to activate */ -static inline void FindNextConversionI(void) { - if (curlsdev) { - /** - * Now we have done a low speed conversion - start looking for the next conversion - * We only look forward to ensure we get a high speed conversion at least once - * every GADC_MAX_LOWSPEED_DEVICES conversions. - */ - curlsdev++; - - } else { - - /* Now we have done a high speed conversion - start looking for low speed conversions */ - curlsdev = ls; - } - - /** - * Look for the next thing to do. - */ - gadcRunning = TRUE; - for(; curlsdev < &ls[GADC_MAX_LOWSPEED_DEVICES]; curlsdev++) { - if ((curlsdev->flags & (GADC_FLG_ISACTIVE|GADC_FLG_ISDONE)) == GADC_FLG_ISACTIVE) { - gadc_lld_adc_nontimerI(&curlsdev->lld); - return; - } - } - curlsdev = 0; - - /* No more low speed devices - do a high speed conversion */ - if (hs.flags & GADC_FLG_ISACTIVE) { - hs.lld.pdata = gfxBufferGetI(); - if (hs.lld.pdata) { - hs.lld.now = GADC_Timer_Missed || (hs.flags & GADC_FLG_STALLED); - hs.flags &= ~GADC_FLG_STALLED; - GADC_Timer_Missed = 0; - gadc_lld_adc_timerI(&hs.lld); - return; - } - - // Oops - no free buffers - mark stalled and go back to low speed devices - hs.flags |= GADC_FLG_STALLED; - hs.eventflags &= ~GADC_HSADC_RUNNING; - for(curlsdev = ls; curlsdev < &ls[GADC_MAX_LOWSPEED_DEVICES]; curlsdev++) { - if ((curlsdev->flags & (GADC_FLG_ISACTIVE|GADC_FLG_ISDONE)) == GADC_FLG_ISACTIVE) { - gadc_lld_adc_nontimerI(&curlsdev->lld); - return; - } - } - curlsdev = 0; - } - - /* Nothing more to do */ - gadcRunning = FALSE; -} - -void gadcDataReadyI(void) { - - if (curlsdev) { - /* This interrupt must be in relation to the low speed device */ - - if (curlsdev->flags & GADC_FLG_ISACTIVE) { - curlsdev->flags |= GADC_FLG_ISDONE; - gtimerJabI(&LowSpeedGTimer); - } - - #if GFX_USE_OS_CHIBIOS && CHIBIOS_ADC_ISR_FULL_CODE_BUG - /** - * Oops - We have just finished a low speed conversion but a bug prevents us - * restarting the ADC here. Other code will restart it in the thread based - * ADC handler. - */ - gadcRunning = FALSE; + // Are we finished yet? (or driver signalled complete now) + if (n && hsJob.done < hsJob.todo) return; + // Clear event flags we might set + hsFlags &= ~(GADC_HSADC_GOTBUFFER|GADC_HSADC_STALL); + + // Is there any data in it + if (!hsJob.done) { + + // Oops - no data in this buffer. Just return it to the free-list + gfxBufferReleaseI(hsData); + goto starttimerjob; // Restart the timer job + } + + // Save the buffer on the hsListDone list + hsData->len = hsJob.done * hsBytesPerConv; + gfxQueueGSyncPutI(&hsListDone, (gfxQueueGSyncItem *)hsData); + hsFlags |= GADC_HSADC_GOTBUFFER; + + /* Signal a buffer completion */ + if (hsISRcallback) + hsISRcallback(); + #if GFX_USE_GEVENT + if (hsFlags & GADC_HSADC_GTIMER) + gtimerJabI(&hsGTimer); #endif - } else { - /* This interrupt must be in relation to the high speed device */ + // Stop if we have been told to + if (!(hsFlags & GADC_HSADC_RUNNING)) { + gadc_lld_stop_timerI(); - if (hs.flags & GADC_FLG_ISACTIVE) { - if (hs.lld.pdata->len) { - /* Save the current buffer on the HighSpeedBuffers */ - gfxQueueGSyncPutI(&HighSpeedBuffers, (gfxQueueGSyncItem *)hs.lld.pdata); - hs.lld.pdata = 0; + // Get the next free buffer + } else if (!(hsData = gfxBufferGetI())) { - /* Save the details */ - hs.eventflags = GADC_HSADC_RUNNING|GADC_HSADC_GOTBUFFER; - if (GADC_Timer_Missed) - hs.eventflags |= GADC_HSADC_LOSTEVENT; - if (hs.flags & GADC_FLG_STALLED) - hs.eventflags |= GADC_HSADC_STALL; + // Oops - no free buffers. Stall + hsFlags &= ~GADC_HSADC_RUNNING; + hsFlags |= GADC_HSADC_STALL; + gadc_lld_stop_timerI(); - /* Our signalling mechanisms */ - if (hs.isrfn) - hs.isrfn(); + // Prepare the next job + } else { - #if GFX_USE_GEVENT - if (hs.flags & GADC_FLG_GTIMER) - gtimerJabI(&HighSpeedGTimer); - #endif - } else { - // Oops - no data in this buffer. Just return it to the free-list - gfxBufferRelease(hs.lld.pdata); - hs.lld.pdata = 0; - } + // Return this new job + #if GFX_USE_OS_CHIBIOS + // ChibiOS api bug - samples must be even + hsJob.todo = (hsData->size / hsBytesPerConv) & ~1; + #else + hsJob.todo = hsData->size / hsBytesPerConv; + #endif + hsJob.done = 0; + hsJob.buffer = (adcsample_t *)(hsData+1); } - } - /** - * Look for the next thing to do. - */ - FindNextConversionI(); -} - -void gadcDataFailI(void) { - if (curlsdev) { - if ((curlsdev->flags & (GADC_FLG_ISACTIVE|GADC_FLG_ISDONE)) == GADC_FLG_ISACTIVE) - /* Mark the error then try to repeat it */ - curlsdev->flags |= GADC_FLG_ERROR; - - #if GFX_USE_OS_CHIBIOS && CHIBIOS_ADC_ISR_FULL_CODE_BUG - /** - * Oops - We have just finished a low speed conversion but a bug prevents us - * restarting the ADC here. Other code will restart it in the thread based - * ADC handler. - */ - gadcRunning = FALSE; - gtimerJabI(&LowSpeedGTimer); - return; - - #endif + // Start a job preferring a non-timer job + if ((lsData = (NonTimerData *)gfxQueueGSyncGetI(&lsListToDo))) { + hsFlags &= ~GADC_HSADC_CONVERTION; + gadc_lld_nontimerjobI(&lsData->job); + } else if ((hsFlags & GADC_HSADC_RUNNING)) { + hsFlags |= GADC_HSADC_CONVERTION; + gadc_lld_timerjobI(&hsJob); + } else + hsFlags &= ~GADC_ADC_RUNNING; } else { - if (hs.flags & GADC_FLG_ISACTIVE) - /* Mark the error and then try to repeat it */ - hs.flags |= GADC_FLG_ERROR; - } - /* Start the next conversion */ - FindNextConversionI(); + // Did it fail + if (!n) { + // Push it back on the head of the queue - it didn't actually get done + gfxQueueGSyncPushI(&lsListToDo, (gfxQueueGSyncItem *)lsData); + lsData = 0; + goto starttimerjob; + } + + // A non-timer job completed - signal + if (lsData->callback) { + // Put it on the completed list and signal the timer to do the call-backs + gfxQueueGSyncPutI(&lsListDone, (gfxQueueGSyncItem *)lsData); + gtimerJabI(&lsGTimer); + } else { + // Signal the thread directly + gfxSemSignalI(&lsData->sigdone); + } + lsData = 0; + + // Start a job preferring a timer job +starttimerjob: + if ((hsFlags & GADC_HSADC_RUNNING)) { + hsFlags |= GADC_HSADC_CONVERTION; + gadc_lld_timerjobI(&hsJob); + } else if ((lsData = (NonTimerData *)gfxQueueGSyncGetI(&lsListToDo))) { + hsFlags &= ~GADC_HSADC_CONVERTION; + gadc_lld_nontimerjobI(&lsData->job); + } else + hsFlags &= ~GADC_ADC_RUNNING; + } } /* Our module initialiser */ void _gadcInit(void) { gadc_lld_init(); - gfxQueueGSyncInit(&HighSpeedBuffers); - gfxSemInit(&LowSpeedSlotSem, GADC_MAX_LOWSPEED_DEVICES, GADC_MAX_LOWSPEED_DEVICES); - gfxMutexInit(&LowSpeedMutex); - gtimerInit(&LowSpeedGTimer); + + gfxQueueGSyncInit(&hsListDone); #if GFX_USE_GEVENT - gtimerInit(&HighSpeedGTimer); + gtimerInit(&hsGTimer); #endif + gtimerInit(&lsGTimer); + gfxQueueGSyncInit(&lsListToDo); + gfxQueueGSyncInit(&lsListDone); } void _gadcDeinit(void) @@ -230,27 +175,13 @@ void _gadcDeinit(void) /* commented stuff is ToDo */ // gadc_lld_deinit(); - gfxQueueGSyncDeinit(&HighSpeedBuffers); - gfxSemDestroy(&LowSpeedSlotSem); - gfxMutexDestroy(&LowSpeedMutex); - gtimerDeinit(&LowSpeedGTimer); + gfxQueueGSyncDeinit(&hsListDone); #if GFX_USE_GEVENT - gtimerDeinit(&HighSpeedGTimer); + gtimerDeinit(&hsGTimer); #endif -} - -static inline void StartADC(bool_t onNoHS) { - gfxSystemLock(); - if (!gadcRunning || (onNoHS && !curlsdev)) - FindNextConversionI(); - gfxSystemUnlock(); -} - -static void BSemSignalCallback(adcsample_t *buffer, void *param) { - (void) buffer; - - /* Signal the BinarySemaphore parameter */ - gfxSemSignal((gfxSem *)param); + gtimerDeinit(&lsGTimer); + gfxQueueGSyncDeinit(&lsListToDo); + gfxQueueGSyncDeinit(&lsListDone); } #if GFX_USE_GEVENT @@ -260,7 +191,7 @@ static void BSemSignalCallback(adcsample_t *buffer, void *param) { GEventADC *pe; psl = 0; - while ((psl = geventGetSourceListener((GSourceHandle)(&HighSpeedGTimer), psl))) { + while ((psl = geventGetSourceListener((GSourceHandle)(&hsGTimer), psl))) { if (!(pe = (GEventADC *)geventGetEventBuffer(psl))) { // This listener is missing - save this. psl->srcflags |= GADC_HSADC_LOSTEVENT; @@ -268,175 +199,162 @@ static void BSemSignalCallback(adcsample_t *buffer, void *param) { } pe->type = GEVENT_ADC; - pe->flags = hs.eventflags | psl->srcflags; + pe->flags = (hsFlags & (GADC_HSADC_RUNNING|GADC_HSADC_GOTBUFFER|GADC_HSADC_STALL)) | psl->srcflags; psl->srcflags = 0; geventSendEvent(psl); } } #endif -static void LowSpeedGTimerCallback(void *param) { - (void) param; - GADCCallbackFunction fn; - void *prm; - adcsample_t *buffer; - struct lsdev *p; - - #if GFX_USE_OS_CHIBIOS && CHIBIOS_ADC_ISR_FULL_CODE_BUG - /* Ensure the ADC is running if it needs to be - Bugfix HACK */ - StartADC(FALSE); - #endif - - /** - * Look for completed low speed timers. - * We don't need to take the mutex as we are the only place that things are freed and we - * do that atomically. - */ - for(p=ls; p < &ls[GADC_MAX_LOWSPEED_DEVICES]; p++) { - if ((p->flags & (GADC_FLG_ISACTIVE|GADC_FLG_ISDONE)) == (GADC_FLG_ISACTIVE|GADC_FLG_ISDONE)) { - /* This item is done - perform its callback */ - fn = p->fn; // Save the callback details - prm = p->param; - buffer = p->lld.buffer; - p->fn = 0; // Needed to prevent the compiler removing the local variables - p->param = 0; // Needed to prevent the compiler removing the local variables - p->lld.buffer = 0; // Needed to prevent the compiler removing the local variables - p->flags = 0; // The slot is available (indivisible operation) - gfxSemSignal(&LowSpeedSlotSem); // Tell everyone - fn(buffer, prm); // Perform the callback - } - } - -} - void gadcHighSpeedInit(uint32_t physdev, uint32_t frequency) { - gadcHighSpeedStop(); /* This does the init for us */ + if ((hsFlags & GADC_HSADC_RUNNING)) + gadcHighSpeedStop(); /* Just save the details and reset everything for now */ - hs.lld.physdev = physdev; - hs.lld.frequency = frequency; - hs.lld.pdata = 0; - hs.lld.now = FALSE; - hs.isrfn = 0; + hsJob.physdev = physdev; + hsJob.frequency = frequency; + hsISRcallback = 0; + hsBytesPerConv = gadc_lld_samplesperconversion(physdev) * sizeof(adcsample_t); } #if GFX_USE_GEVENT GSourceHandle gadcHighSpeedGetSource(void) { - if (!gtimerIsActive(&HighSpeedGTimer)) - gtimerStart(&HighSpeedGTimer, HighSpeedGTimerCallback, 0, TRUE, TIME_INFINITE); - hs.flags |= GADC_FLG_GTIMER; - return (GSourceHandle)&HighSpeedGTimer; + if (!gtimerIsActive(&hsGTimer)) + gtimerStart(&hsGTimer, HighSpeedGTimerCallback, 0, TRUE, TIME_INFINITE); + hsFlags |= GADC_HSADC_GTIMER; + return (GSourceHandle)&hsGTimer; } #endif void gadcHighSpeedSetISRCallback(GADCISRCallbackFunction isrfn) { - hs.isrfn = isrfn; + hsISRcallback = isrfn; } GDataBuffer *gadcHighSpeedGetData(delaytime_t ms) { - return (GDataBuffer *)gfxQueueGSyncGet(&HighSpeedBuffers, ms); + return (GDataBuffer *)gfxQueueGSyncGet(&hsListDone, ms); } GDataBuffer *gadcHighSpeedGetDataI(void) { - return (GDataBuffer *)gfxQueueGSyncGetI(&HighSpeedBuffers); + return (GDataBuffer *)gfxQueueGSyncGetI(&hsListDone); } void gadcHighSpeedStart(void) { - /* If its already going we don't need to do anything */ - if (hs.flags & GADC_FLG_ISACTIVE) + // Safety first + if (!hsJob.frequency || !hsBytesPerConv) return; - hs.flags = GADC_FLG_ISACTIVE; - gadc_lld_start_timer(&hs.lld); - StartADC(FALSE); + gfxSystemLock(); + if (!(hsFlags & GADC_HSADC_RUNNING)) { + if (!(hsData = gfxBufferGetI())) { + // Oops - no free buffers. Stall + hsFlags |= GADC_HSADC_STALL; + #if GFX_USE_GEVENT + if (hsFlags & GADC_HSADC_GTIMER) + gtimerJabI(&hsGTimer); + #endif + + // Prepare the next job + } else { + + #if GFX_USE_OS_CHIBIOS + // ChibiOS api bug - samples must be even + hsJob.todo = (hsData->size / hsBytesPerConv) & ~1; + #else + hsJob.todo = hsData->size / hsBytesPerConv; + #endif + hsJob.done = 0; + hsJob.buffer = (adcsample_t *)(hsData+1); + hsFlags |= GADC_HSADC_RUNNING; + + // Start the timer + gadc_lld_start_timerI(hsJob.frequency); + + // If nothing is running start the job + if (!(hsFlags & GADC_ADC_RUNNING)) { + hsFlags |= (GADC_HSADC_CONVERTION|GADC_ADC_RUNNING); + gadc_lld_timerjobI(&hsJob); + } + } + } + gfxSystemUnlock(); } void gadcHighSpeedStop(void) { - if (hs.flags & GADC_FLG_ISACTIVE) { - /* No more from us */ - hs.flags = 0; - gadc_lld_stop_timer(&hs.lld); - /* - * There might be a buffer still locked up by the driver - if so release it. - */ - if (hs.lld.pdata) { - gfxBufferRelease(hs.lld.pdata); - hs.lld.pdata = 0; - } + // Stop it and wait for completion + hsFlags &= ~GADC_HSADC_RUNNING; + while ((hsFlags & GADC_HSADC_CONVERTION)) + gfxYield(); +} - /* - * We have to pass TRUE to StartADC() as we might have the ADC marked as active when it isn't - * due to stopping the timer while it was converting. - */ - StartADC(TRUE); +static void LowSpeedGTimerCallback(void *param) { + (void) param; + NonTimerData *pdata; + + // Look for completed non-timer jobs and call the call-backs for each + while ((pdata = (NonTimerData *)gfxQueueGSyncGet(&lsListDone, TIME_IMMEDIATE))) { + pdata->callback(pdata->job.buffer, pdata->param); + gfxFree(pdata); } } void gadcLowSpeedGet(uint32_t physdev, adcsample_t *buffer) { - struct lsdev *p; - gfxSem mysem; + NonTimerData ndata; - /* Start the Low Speed Timer */ - gfxSemInit(&mysem, 1, 1); - gfxMutexEnter(&LowSpeedMutex); - if (!gtimerIsActive(&LowSpeedGTimer)) - gtimerStart(&LowSpeedGTimer, LowSpeedGTimerCallback, 0, TRUE, TIME_INFINITE); - gfxMutexExit(&LowSpeedMutex); + // Prepare the job + gfxSemInit(&ndata.sigdone, 0, 1); + ndata.job.physdev = physdev; + ndata.job.buffer = buffer; + ndata.callback = 0; - while(1) { - /* Wait for an available slot */ - gfxSemWait(&LowSpeedSlotSem, TIME_INFINITE); - - /* Find a slot */ - gfxMutexEnter(&LowSpeedMutex); - for(p = ls; p < &ls[GADC_MAX_LOWSPEED_DEVICES]; p++) { - if (!(p->flags & GADC_FLG_ISACTIVE)) { - p->lld.physdev = physdev; - p->lld.buffer = buffer; - p->fn = BSemSignalCallback; - p->param = &mysem; - p->flags = GADC_FLG_ISACTIVE; - gfxMutexExit(&LowSpeedMutex); - StartADC(FALSE); - gfxSemWait(&mysem, TIME_INFINITE); - return; - } - } - gfxMutexExit(&LowSpeedMutex); - - /** - * We should never get here - the count semaphore must be wrong. - * Decrement it and try again. - */ + // Activate it + gfxSystemLock(); + if (!(hsFlags & GADC_ADC_RUNNING)) { + // Nothing is running - start the job + lsData = &ndata; + hsFlags |= GADC_ADC_RUNNING; + hsFlags &= ~GADC_HSADC_CONVERTION; + gadc_lld_nontimerjobI(&ndata.job); + } else { + // Just put it on the queue + gfxQueueGSyncPutI(&lsListToDo, (gfxQueueGSyncItem *)&ndata); } + gfxSystemUnlock(); + + // Wait for it to complete + gfxSemWait(&ndata.sigdone, TIME_INFINITE); + gfxSemDestroy(&ndata.sigdone); } bool_t gadcLowSpeedStart(uint32_t physdev, adcsample_t *buffer, GADCCallbackFunction fn, void *param) { - struct lsdev *p; + NonTimerData *pdata; /* Start the Low Speed Timer */ - gfxMutexEnter(&LowSpeedMutex); - if (!gtimerIsActive(&LowSpeedGTimer)) - gtimerStart(&LowSpeedGTimer, LowSpeedGTimerCallback, 0, TRUE, TIME_INFINITE); + if (!gtimerIsActive(&lsGTimer)) + gtimerStart(&lsGTimer, LowSpeedGTimerCallback, 0, TRUE, TIME_INFINITE); - /* Find a slot */ - for(p = ls; p < &ls[GADC_MAX_LOWSPEED_DEVICES]; p++) { - if (!(p->flags & GADC_FLG_ISACTIVE)) { - /* We know we have a slot - this should never wait anyway */ - gfxSemWait(&LowSpeedSlotSem, TIME_IMMEDIATE); - p->lld.physdev = physdev; - p->lld.buffer = buffer; - p->fn = fn; - p->param = param; - p->flags = GADC_FLG_ISACTIVE; - gfxMutexExit(&LowSpeedMutex); - StartADC(FALSE); - return TRUE; - } + // Prepare the job + if (!(pdata = gfxAlloc(sizeof(NonTimerData)))) + return FALSE; + pdata->job.physdev = physdev; + pdata->job.buffer = buffer; + pdata->callback = fn; + pdata->param = param; + + // Activate it + gfxSystemLock(); + if (!(hsFlags & GADC_ADC_RUNNING)) { + // Nothing is running - start the job + lsData = pdata; + hsFlags |= GADC_ADC_RUNNING; + hsFlags &= ~GADC_HSADC_CONVERTION; + gadc_lld_nontimerjobI(&pdata->job); + } else { + // Just put it on the queue + gfxQueueGSyncPutI(&lsListToDo, (gfxQueueGSyncItem *)pdata); } - gfxMutexExit(&LowSpeedMutex); - return FALSE; + gfxSystemUnlock(); + return TRUE; } #endif /* GFX_USE_GADC */ diff --git a/src/gadc/sys_defs.h b/src/gadc/sys_defs.h index 21e81fb6..035fa9ad 100644 --- a/src/gadc/sys_defs.h +++ b/src/gadc/sys_defs.h @@ -209,12 +209,9 @@ void gadcHighSpeedStop(void); * completion. * @note The result buffer must be large enough to store one sample per device * described by the 'physdev' parameter. - * @note If calling this routine would exceed @p GADC_MAX_LOWSPEED_DEVICES simultaneous low - * speed devices, the routine will wait for an available slot to complete the - * conversion. * @note Specifying more than one device in physdev is possible but discouraged as the * calculations to ensure the high speed ADC correctness will be incorrect. Symptoms - * from over-running the high speed ADC include high speed samples being lost. + * from over-running the high speed ADC include high speed device stalling or samples being lost. * * @api */ @@ -222,7 +219,7 @@ void gadcLowSpeedGet(uint32_t physdev, adcsample_t *buffer); /** * @brief Perform a low speed ADC conversion with callback (in a thread context) - * @details Returns FALSE if there are no free low speed ADC slots. See @p GADC_MAX_LOWSPEED_DEVICES for details. + * @details Returns FALSE if internal memory allocation fails * * @param[in] physdev A value passed to describe which physical ADC devices/channels to use. * @param[in] buffer The static buffer to put the ADC samples into. @@ -237,8 +234,6 @@ void gadcLowSpeedGet(uint32_t physdev, adcsample_t *buffer); * completion. * @note The result buffer must be large enough to store one sample per device * described by the 'physdev' parameter. - * @note As this routine uses a low speed ADC, it asserts if you try to run more than @p GADC_MAX_LOWSPEED_DEVICES - * at the same time. * @note Specifying more than one device in physdev is possible but discouraged as the * calculations to ensure the high speed ADC correctness will be incorrect. Symptoms * from over-running the high speed ADC include high speed samples being lost. @@ -255,4 +250,3 @@ bool_t gadcLowSpeedStart(uint32_t physdev, adcsample_t *buffer, GADCCallbackFunc #endif /* _GADC_H */ /** @} */ -